Jak sprawić, aby git oznaczył usunięty i nowy plik jako przeniesienie pliku?

505

Przeniesiłem plik ręcznie, a następnie zmodyfikowałem go. Według Git jest to nowy plik i plik usunięty. Czy jest jakiś sposób, aby zmusić Gita do traktowania go jako przeniesienia pliku?

pupeno
źródło
3
Dla danego pliku old_file.txt, a następnie git mv old_file.txt new_file.txtodpowiada git rm --cached old_file.txt, mv old_file.txt new_file.txt, git add new_file.txt.
Jarl
3
Jarl: nie, nie jest. Jeśli w pliku są również zmiany, git mvnie doda ich do pamięci podręcznej, ale git addzrobi to. Wolę przenieść plik z powrotem, aby móc go użyć git mv, a następnie git add -pprzejrzeć mój zestaw zmian.
opóźniony
proszę sprawdź mój skrypt github.com/Deathangel908/python-tricks/blob/master/…
deathangel908
Git poprawił się w ciągu ostatnich 8 lat, jeśli jest to tylko jeden plik, najlepsza odpowiedź stackoverflow.com/a/433142/459 nic nie zrobiła… ale możesz śledzić stackoverflow.com/a/1541072/459, aby uzyskać aktualizację / dodać do statusu mv / modyfikuj.
dlamblin,

Odpowiedzi:

435

Git automatycznie wykryje przeniesienie / zmianę nazwy, jeśli modyfikacja nie jest zbyt poważna. Tylko git addnowy plik i git rmstary plik. git statuspokaże wtedy, czy wykrył zmianę nazwy.

dodatkowo w przypadku przemieszczania się po katalogach może być konieczne:

  1. cd na początek tej struktury katalogów.
  2. Biegać git add -A .
  3. Uruchom, git statusaby sprawdzić, czy „nowy plik” jest teraz plikiem o zmienionej nazwie

Jeśli status git nadal pokazuje „nowy plik”, a nie „przemianowano”, musisz postępować zgodnie z radą Hanka Gaya, wykonać ruch i zmodyfikować w dwóch osobnych zatwierdzeniach.

Bombe
źródło
75
Wyjaśnienie: „niezbyt poważny” oznacza, że ​​nowy plik i stary plik są w 50% „podobne” na podstawie niektórych indeksów podobieństwa używanych przez git.
pjz
151
Coś, co rozłączyło mnie na kilka minut: jeśli pliki o zmienionej nazwie i pliki usunięte nie zostaną ustawione do zatwierdzenia, pojawią się one jako usunięte i nowy plik. Po dodaniu ich do indeksu pomostowego rozpozna to jako zmianę nazwy.
marczych
19
Warto wspomnieć, że mówiąc o „ Git automatycznie wykryje ruch / zmianę nazwy ”. Czyni to w momencie wykorzystać git status, git logalbo git diff, nie w momencie robisz git add, git mvalbo git rm. Mówiąc dalej o wykrywaniu zmiany nazwy, ma to sens tylko w przypadku plików przemieszczanych. Zatem git mvzmiana pliku może wyglądać git statustak, jakby uważała go za plik rename, ale kiedy użyjesz git stage(tak jak git add) pliku, staje się jasne, że zmiana jest zbyt duża, aby można go było wykryć jako zmianę nazwy.
Jarl
5
Prawdziwym kluczem wydaje się być dodanie zarówno nowej, jak i starej lokalizacji w tym samym miejscu git add, co, jak sugeruje @ jrhorn424, powinno prawdopodobnie stanowić po prostu całe repo.
brianary
4
Nie jest to jednak nieomylne, szczególnie jeśli plik się zmienił i jest mały. Czy istnieje metoda, aby konkretnie powiedzieć gitowi o ruchu?
Rebs
112

Wykonaj ruch i modyfikuj w osobnych zatwierdzeniach.

Hank Gay
źródło
12
Rozumiem, że Git potrafi jednocześnie obsługiwać ruchy i modyfikacje. Podczas programowania w Javie i używania IDE zmiana nazwy klasy jest zarówno modyfikacją, jak i posunięciem. Rozumiem, że Git powinien nawet być w stanie automatycznie dowiedzieć się, kiedy nastąpi ruch (z usunięcia i stworzenia).
pupeno
1
Perl też tego wymaga i nigdy nie miałem, aby Git wykrył ruch / zmianę nazwy.
jrockway
10
@jrockway, miałem. Zdarza się łatwo z małymi plikami, wydaje mi się, że „stają się one” zbyt różne, aby oznaczać ruch ”.
ANeves
2
Czy można to zrobić automatycznie? Mam wiele plików w indeksie, chcę najpierw zatwierdzić przeniesienie, a następnie zmianę, ale trudno to zrobić ręcznie
ReDetection
5
Należy zauważyć, że jeśli git diffnie rozpozna zmiany nazwy w jednym zatwierdzeniu, nie rozpozna go w dwóch zatwierdzeniach. Aby to zrobić, musisz użyć -Maka --find-renames. Więc jeśli motywacją, która doprowadziła cię do tego pytania, jest zobaczenie zmiany nazwy w żądaniu ściągnięcia (mówienie z doświadczenia), podzielenie jej na dwie zmiany nie doprowadzi cię do tego celu.
mattliu
44

To wszystko jest kwestią percepcyjną. Git jest ogólnie dość dobry w rozpoznawaniu ruchów, ponieważ GIT jest narzędziem do śledzenia zawartości

Wszystko, co tak naprawdę zależy, to sposób, w jaki wyświetla się „stat”. Jedyną różnicą jest tutaj flaga -M.

git log --stat -M

commit 9c034a76d394352134ee2f4ede8a209ebec96288
Author: Kent Fredric
Date:   Fri Jan 9 22:13:51 2009 +1300


        Category Restructure

     lib/Gentoo/Repository.pm                |   10 +++++-----
     lib/Gentoo/{ => Repository}/Base.pm     |    2 +-
     lib/Gentoo/{ => Repository}/Category.pm |   12 ++++++------
     lib/Gentoo/{ => Repository}/Package.pm  |   10 +++++-----
     lib/Gentoo/{ => Repository}/Types.pm    |   10 +++++-----
     5 files changed, 22 insertions(+), 22 deletions(-)

git log --stat

commit 9c034a76d394352134ee2f4ede8a209ebec96288
Author: Kent Fredric
Date:   Fri Jan 9 22:13:51 2009 +1300

    Category Restructure

 lib/Gentoo/Base.pm                |   36 ------------------------
 lib/Gentoo/Category.pm            |   51 ----------------------------------
 lib/Gentoo/Package.pm             |   41 ---------------------------
 lib/Gentoo/Repository.pm          |   10 +++---
 lib/Gentoo/Repository/Base.pm     |   36 ++++++++++++++++++++++++
 lib/Gentoo/Repository/Category.pm |   51 ++++++++++++++++++++++++++++++++++
 lib/Gentoo/Repository/Package.pm  |   41 +++++++++++++++++++++++++++
 lib/Gentoo/Repository/Types.pm    |   55 +++++++++++++++++++++++++++++++++++++
 lib/Gentoo/Types.pm               |   55 -------------------------------------
 9 files changed, 188 insertions(+), 188 deletions(-)

git help log

   -M
       Detect renames.

   -C
       Detect copies as well as renames. See also --find-copies-harder.
Kent Fredric
źródło
76
Przepraszam, jeśli wydaje się to trochę pedantyczne, ale „Git jest ogólnie dość dobry w rozpoznawaniu ruchów, ponieważ GIT jest narzędziem do śledzenia zawartości” wydaje mi się, że nie jest sekwencją. Tak, jest to moduł do śledzenia zawartości i być może jest dobry w wykrywaniu ruchów, ale jedno zdanie tak naprawdę nie wynika z drugiego. Tylko dlatego, że jest to moduł do śledzenia zawartości, wykrywanie ruchu niekoniecznie jest dobre. W rzeczywistości moduł do śledzenia zawartości może w ogóle nie wykrywać ruchu.
Laurence Gonsalves,
1
@WarrenSeine po zmianie nazwy katalogu SHA1 plików w tym katalogu nie zmieniają się. Wszystko, co masz, to nowy obiekt DRZEWO z tymi samymi SHA1. Nie ma powodu, dla którego zmiana nazwy katalogu spowodowałaby znaczne zmiany danych.
Kent Fredric
1
@KentFredric Pokazałeś, że używając -M z „git logiem” możemy sprawdzić, czy nazwa pliku została zmieniona, czy też nie, wypróbowałem go i działa dobrze, ale kiedy opublikuję mój kod do sprawdzenia i zobaczę ten plik w gerrit ( gerritcodereview.com ) tam pokazuje, że plik został nowo dodany, a poprzedni został usunięty. Czy istnieje opcja w „git commit”, za pomocą której wykonuję i gerrit pokazuje to poprawnie.
Patrick,
1
Nie. W ogóle tego nie pokazałem. Pokazałem, że Git może udawać, że add + delete to zmiana nazwy, ponieważ treść jest taka sama. Nie ma sposobu, aby wiedzieć, co się stało, i to nie obchodzi. Wszystko, co pamięta git, jest „dodane” i „usunięte”. „Zmiana nazwy” nigdy nie jest nagrywana.
Kent Fredric,
1
Na przykład ostatnio zrobiłem dużo „Kopiuj + Edytuj” na repozytorium. Większość sposobów przeglądania to tylko „nowy plik”, ale jeśli przejdziesz git log -M1 -C1 -B1 -D --find-copies-harder, git może „odkryć”, że nowy plik mógł zostać skopiowany jako pierwszy. Czasami robi to dobrze, innym razem znajduje całkowicie niepowiązane pliki, które miały identyczną zawartość.
Kent Fredric,
36

git diff -Mlub git log -Mpowinien automatycznie wykryć takie zmiany jako zmianę nazwy z niewielkimi zmianami, o ile rzeczywiście są. Jeśli Twoje drobne zmiany nie są niewielkie, możesz zmniejszyć trzykrotne podobieństwo, np

$ git log -M20 -p --stat

zmniejszyć go z domyślnych 50% do 20%.

Simon East
źródło
13
Czy można zdefiniować próg w konfiguracji?
ReDetection
34

Oto szybkie i brudne rozwiązanie dla jednego lub kilku plików o zmienionej nazwie i zmodyfikowanych, które nie zostały zatwierdzone.

Powiedzmy, że plik został nazwany, fooa teraz nazywa się bar:

  1. Zmień nazwę barna tymczasową nazwę:

    mv bar side
    
  2. Zamówienie foo:

    git checkout HEAD foo
    
  3. Zmień nazwę foona barGit:

    git mv foo bar
    
  4. Teraz zmień nazwę pliku tymczasowego z powrotem na bar.

    mv side bar
    

Ten ostatni krok przywraca zmienioną zawartość do pliku.

Chociaż może to zadziałać, jeśli przeniesiony plik ma zbyt inną zawartość niż oryginalny git, uzna, że ​​skuteczniej jest zdecydować, że jest to nowy obiekt. Pokażę:

$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    renamed:    README -> README.md

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   README.md
    modified:   work.js

$ git add README.md work.js # why are the changes unstaged, let's add them.
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    deleted:    README
    new file:   README.md
    modified:   work.js

$ git stash # what? let's go back a bit
Saved working directory and index state WIP on dir: f7a8685 update
HEAD is now at f7a8685 update
$ git status
On branch workit
Untracked files:
  (use "git add <file>..." to include in what will be committed)

    .idea/

nothing added to commit but untracked files present (use "git add" to track)
$ git stash pop
Removing README
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    new file:   README.md

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    deleted:    README
    modified:   work.js

Dropped refs/stash@{0} (1ebca3b02e454a400b9fb834ed473c912a00cd2f)
$ git add work.js
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    new file:   README.md
    modified:   work.js

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    deleted:    README

$ git add README # hang on, I want it removed
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    deleted:    README
    new file:   README.md
    modified:   work.js

$ mv README.md Rmd # Still? Try the answer I found.
$ git checkout README
error: pathspec 'README' did not match any file(s) known to git.
$ git checkout HEAD README # Ok the answer needed fixing.
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    new file:   README.md
    modified:   work.js

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    deleted:    README.md
    modified:   work.js

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    Rmd

$ git mv README README.md
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    renamed:    README -> README.md
    modified:   work.js

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   work.js

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    Rmd

$ mv Rmd README.md
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    renamed:    README -> README.md
    modified:   work.js

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   README.md
    modified:   work.js

$ # actually that's half of what I wanted; \
  # and the js being modified twice? Git prefers it in this case.
dlamblin
źródło
27
Ten proces jest bezcelowy. git nie dodaje żadnych metadanych dla nazw, git mvjest po prostu wygodą dla pary git rm/ git addpary. Jeśli już zrobiłeś 'mv bar foo', wszystko, co musisz zrobić, to upewnić się, że dokonałeś zatwierdzenia git add fooi git rm barprzed nim. Można to zrobić tak samo, jak pojedyncze git add -Apolecenie lub git add foo; git commit -asekwencję.
CB Bailey,
17
Wiem tylko, że zanim to zrobiłem, Git nie rozpoznał tego jako ruchu. Po tym, jak to zrobiłem, Git rozpoznał to jako ruch.
8
Rozpoznaje to jako ruch. Ale teraz ma też tę zmianę, że nie jest wystawiana. Po addponownym utworzeniu pliku git przerywa przenoszenie / modyfikację do usuniętego / dodanego ponownie.
Michael Piefel
4
Ten proces będzie działał, jeśli wykonasz git commitkrok 3, w przeciwnym razie będzie niepoprawny. Ponadto @CharlesBailey ma rację, możesz równie łatwo wykonać normalną mv blah fooczynność w kroku 3, a następnie zatwierdzić i uzyskać te same wyniki.
DaFlame
5
„Ten proces jest bezcelowy”. - Służy to celowi, gdy git się pomylił i myśli, że plik został usunięty, a dodano inny plik. To usuwa zamieszanie Gita i oznacza plik jako jawnie przeniesiony bez konieczności wykonywania pośrednich zatwierdzeń.
Danack
20

Jeśli mówisz o git statusnie pokazywaniu nazw, spróbuj git commit --dry-run -azamiast tego

BoD
źródło
11

Jeśli używasz TortoiseGit, ważne jest, aby pamiętać, że automatyczne wykrywanie zmiany nazwy Gita ma miejsce podczas zatwierdzania, ale fakt, że tak się stanie, nie zawsze jest wcześniej wyświetlany przez oprogramowanie. Przeniosłem dwa pliki do innego katalogu i dokonałem niewielkich zmian. Używam TortoiseGit jako mojego narzędzia do zatwierdzania, a lista wprowadzonych zmian pokazała, że ​​pliki są usuwane i dodawane, a nie przenoszone. Uruchomienie statusu git z wiersza poleceń pokazało podobną sytuację. Jednak po zatwierdzeniu plików pojawiły się w dzienniku jako przemianowane. Tak więc odpowiedź na twoje pytanie jest taka, o ile nie zrobiłeś nic zbyt drastycznego, Git powinien automatycznie zmienić nazwę.

Edycja: Najwyraźniej jeśli dodasz nowe pliki, a następnie wykonasz status git z wiersza poleceń, zmiana nazwy powinna pojawić się przed zatwierdzeniem.

Edycja 2: Ponadto, w TortoiseGit, dodaj nowe pliki w oknie dialogowym zatwierdzenia, ale ich nie zatwierdzaj. Następnie, jeśli przejdziesz do polecenia Pokaż dziennik i spojrzysz na katalog roboczy, zobaczysz, czy Git wykrył zmianę nazwy przed zatwierdzeniem.

To samo pytanie zostało zadane tutaj: https://tortoisegit.org/issue/1389 i zostało zarejestrowane jako błąd do naprawy tutaj: https://tortoisegit.org/issue/1440 Okazuje się, że jest to problem z wyświetlaniem w zatwierdzeniu TortoiseGit dialog, a także istnieje w stanie git, jeśli nie dodałeś nowych plików.

Giles Roberts
źródło
2
Masz rację, nawet jeśli TortoiseGit pokazuje usuń + dodaj, nawet jeśli status git pokazuje usuń + dodaj, nawet jeśli git commit - suche uruchomienie pokazuje usuń + dodaj, po git commit widzę zmianę nazwy, a nie usuwanie + dodawanie.
Tomas Kubes
1
Jestem prawie pewien, że automatyczne wykrywanie zmiany nazwy ma miejsce podczas wyszukiwania historii ; w zatwierdzeniu jest to zawsze dodawanie + usuwanie. To wyjaśnia również opisywane przez ciebie schizofreniczne zachowania. Stąd opcje tutaj: stackoverflow.com/a/434078/155892
Mark Sowul
10

Albo możesz spróbować odpowiedzieć na to pytanie tutaj przez Amber ! Cytując to jeszcze raz:

Najpierw anuluj dodawanie etapowe dla ręcznie przeniesionego pliku:

$ git reset path/to/newfile
$ mv path/to/newfile path/to/oldfile

Następnie użyj Git, aby przenieść plik:

$ git mv path/to/oldfile path/to/newfile

Oczywiście, jeśli już dokonałeś ręcznego przenoszenia, możesz zamiast tego zresetować wersję przed przeniesieniem, a następnie po prostu pobrać stamtąd mv.

Zingam
źródło
7

Użyj git mvpolecenia, aby przenieść pliki, zamiast poleceń przenoszenia systemu operacyjnego: https://git-scm.com/docs/git-mv

Pamiętaj, że git mvpolecenie istnieje tylko w Git w wersji 1.8.5 i nowszych. Może być konieczne zaktualizowanie Gita, aby użyć tego polecenia.

mostafa.elhoushi
źródło
3

Ostatnio miałem ten problem podczas przenoszenia (ale nie modyfikowania) niektórych plików.

Problem polega na tym, że Git zmienił niektóre zakończenia linii, gdy przenosiłem pliki, a następnie nie był w stanie stwierdzić, że pliki były takie same.

Korzystanie git mvz rozwiązania problemu, ale działa tylko na pojedyncze pliki / katalogi, a ja miałem wiele plików w katalogu głównym repozytorium do zrobienia.

Jednym ze sposobów naprawienia tego byłoby użycie magii typu bash / batch.

Inny sposób jest następujący

  • Przenieś pliki i git commit. To aktualizuje zakończenia linii.
  • Przenieś pliki z powrotem do ich oryginalnej lokalizacji, teraz, gdy mają nowe zakończenia linii i git commit --amend
  • Przenieś pliki ponownie i git commit --amend. Tym razem nie ma zmian w zakończeniach linii, więc Git jest szczęśliwy
cedd
źródło
1

Dla mnie zadziałało przechowywanie wszystkich zmian przed zatwierdzeniem i wyskakiwanie ich ponownie. To spowodowało, że git ponownie przeanalizował dodane / usunięte pliki i poprawnie oznaczył je jako przeniesione.

Alex Ilie
źródło
0

Jest na to prawdopodobnie lepszy sposób „wiersza poleceń” i wiem, że to hack, ale nigdy nie byłem w stanie znaleźć dobrego rozwiązania.

Korzystanie z TortoiseGIT: Jeśli masz zatwierdzenie GIT, w którym niektóre operacje przenoszenia plików są wyświetlane jako ładunek dodawania / usuwania zamiast zmiany nazw, nawet jeśli pliki mają tylko niewielkie zmiany, wykonaj następujące czynności:

  1. Sprawdź, co zrobiłeś lokalnie
  2. Sprawdź zmianę mini-liniową w drugim zatwierdzeniu
  3. Idź do GIT zaloguj się żółw git
  4. Wybierz dwa zatwierdzenia, kliknij prawym przyciskiem myszy i wybierz „scal w jeden zatwierdzenie”

Nowe zatwierdzenie będzie teraz poprawnie wyświetlało nazwy plików… co pomoże utrzymać właściwą historię plików.

Jon Rea
źródło
0

Kiedy jednocześnie edytuję, zmieniam nazwę i przenoszę plik, żadne z tych rozwiązań nie działa. Rozwiązaniem jest zrobienie tego w dwóch zatwierdzeniach (edycja i zmiana nazwy / przeniesienie osobno), a następnie fixupw drugim zatwierdzeniu za pomocą git rebase -ijednego zatwierdzenia.

Hativ
źródło
0

Rozumiem to pytanie: „Jak sprawić, aby git rozpoznał usunięcie starego pliku i utworzenie nowego pliku jako przeniesienie pliku”.

Tak w katalogu roboczym po usunięciu starego pliku i wstawieniu starego pliku, git statuspowie „ deleted: old_file” i „ Untracked files: ... new_file

Ale w indeksie pomostowym / poziomie po dodaniu i usunięciu pliku za pomocą git zostanie rozpoznany jako przeniesienie pliku. Aby to zrobić, zakładając, że wykonałeś usuwanie i tworzenie za pomocą swojego systemu operacyjnego, podaj następujące polecenia:

git add new_file
git rm old_file

Jeśli zawartość pliku jest w 50% lub więcej podobna, uruchomiona git statuskomenda powinna dać ci:

renamed: old_file -> new_file
harshainfo
źródło
1
git statuspowie „ deleted: old_file” i „ Untracked files: ... new_file”: Nie od Gita 2.18: stackoverflow.com/a/50573107/6309
VonC