Obsługa nazw plików w git

440

Przeczytałem, że zmieniając nazwę plików w git , powinieneś zatwierdzić wszelkie zmiany, zmienić nazwę, a następnie wprowadzić zmiany w pliku o zmienionej nazwie. Git rozpozna plik na podstawie zawartości, zamiast widzieć go jako nowy nieśledzony plik i zachowa historię zmian.

Jednak robiąc to właśnie tej nocy, w końcu powróciłem do git mv.

> $ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   index.html
#

Zmień nazwę mojego arkusza stylów w Finderze z iphone.cssnamobile.css

> $ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   index.html
#
# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   deleted:    css/iphone.css
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   css/mobile.css

Więc git teraz myśli, że usunąłem jeden plik CSS i dodałem nowy. Nie to, czego chcę, cofnijmy zmianę nazwy i pozwól git wykonać pracę.

> $ git reset HEAD .
Unstaged changes after reset:
M   css/iphone.css
M   index.html

Z powrotem tam, gdzie zacząłem.

> $ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   index.html
#

Pozwala git mvzamiast tego użyć .

> $ git mv css/iphone.css css/mobile.css
> $ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   renamed:    css/iphone.css -> css/mobile.css
#
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   index.html
#

Wygląda na to, że jesteśmy dobrzy. Dlaczego więc nie rozpoznałem nazwy po raz pierwszy, kiedy korzystałem z Findera?

Greg K.
źródło
29
Git śledzi zawartość, a nie pliki, więc nie ma znaczenia, w jaki sposób ustawić indeks we właściwym stanie - add+rmlub mv- daje ten sam wynik. Następnie Git wykrywa zmianę nazwy / kopii, aby poinformować cię, że była to zmiana nazwy. Źródło, które zacytowałeś, jest również niedokładne. Naprawdę nie ma znaczenia, czy modyfikujesz + zmieniasz nazwę w tym samym zatwierdzeniu, czy nie. Gdy zmienisz zarówno modyfikację, jak i zmianę nazwy, wykrywanie zmiany nazwy spowoduje, że będzie to zmiana nazwy + modyfikacja, lub jeśli modyfikacja jest całkowitym przepisem, będzie wyświetlana jako dodana i usunięta - nadal nie ma znaczenia, jak wykonałeś to.
Cascabel
6
Jeśli to prawda, dlaczego nie wykrył go przy mojej zmianie nazwy za pomocą Findera?
Greg K
26
git mv old newautomatycznie aktualizuje indeks. Kiedy zmienisz nazwę poza Git, będziesz musiał zrobić git add newi git rm oldwprowadzić zmiany w indeksie. Po wykonaniu tej czynności git statusbędzie działać zgodnie z oczekiwaniami.
Chris Johnsen
4
Właśnie przeniosłem kilka plików do katalogu public_html, które są śledzone w git. Po wykonaniu git add .i git commitnadal wyświetlał kilka „usuniętych” plików w git status. Wykonałem git commit -aa, usunięcie zostało zatwierdzone, ale teraz nie mam historii plików, które są public_htmlteraz w użyciu. Ten przepływ pracy nie jest tak płynny, jak bym chciał.
Greg K,

Odpowiedzi:

352

Na git mvstronie podręcznika mówi

Indeks jest aktualizowany po pomyślnym zakończeniu, […]

Tak więc najpierw musisz zaktualizować indeks samodzielnie (za pomocą git add mobile.css). Jednak
git status nadal będą wyświetlać dwa różne pliki

$ git status
# On branch master
warning: LF will be replaced by CRLF in index.html
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   index.html
#       new file:   mobile.css
#
# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       deleted:    iphone.css
#

Możesz uzyskać inną wydajność, uruchamiając git commit --dry-run -a, co powoduje, że oczekujesz:

Tanascius@H181 /d/temp/blo (master)
$ git commit --dry-run -a
# On branch master
warning: LF will be replaced by CRLF in index.html
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   index.html
#       renamed:    iphone.css -> mobile.css
#

Nie mogę powiedzieć dokładnie, dlaczego widzimy te różnice git statusi
git commit --dry-run -a, ale o to wskazówka od Linusa :

git tak naprawdę nie przejmuje się wewnętrznie całym „wykrywaniem zmiany nazwy”, a wszelkie zatwierdzenia dokonane przy zmianie nazw są całkowicie niezależne od heurystyk, których używamy do wyświetlania nazw.

A dry-runużywa prawdziwych mechanizmów zmiany nazwy, podczas gdy git statusprawdopodobnie nie.

tanascius
źródło
1
Nie wspomniałeś o kroku, w którym zrobiłeś git add mobile.css. Bez niego git status -a„zobaczyłby” jedynie usunięcie uprzednio śledzonego iphone.csspliku, ale nie dotknąłby nowego, nieśledzonego mobile.csspliku. Ponadto git status -ajest nieprawidłowy w Git 1.7.0 i nowszych. „„ Git status ”nie jest już„ git commit --dry-run ”.” w kernel.org/pub/software/scm/git/docs/RelNotes-1.7.0.txt . Użyj, git commit --dry-run -ajeśli chcesz tę funkcję. Jak powiedzieli inni, po prostu zaktualizuj indeks i git statusbędzie działał zgodnie z oczekiwaniami PO.
Chris Johnsen
3
jeśli zrobisz normalny git commit, plik nie zostanie zmieniony, a działające drzewo pozostanie takie samo. git commit -apokonuje prawie każdy aspekt modelu przepływu / myślenia git - każda zmiana jest zatwierdzona. co jeśli chcesz tylko zmienić nazwę pliku, ale zatwierdzić zmiany index.htmlw innym zatwierdzeniu?
knittl 15.04.2010
@Chris: tak, oczywiście, że dodałem, o mobile.cssczym powinienem wspomnieć. Ale to jest sens mojej odpowiedzi: strona podręcznika mówi, że the index is updatedkiedy używasz git-mv. Dzięki za status -awyjaśnienie, użyłem git 1.6.4
tanascius
4
Świetna odpowiedź! Waliłem głową w ścianę, próbując dowiedzieć się, dlaczego git statusnie wykryłem zmiany nazwy. Uruchomienie git commit -a --dry-runpo dodaniu moich „nowych” plików pokazało nazwy i wreszcie dało mi pewność, że dokonam zmian!
stephen.hanson
1
W git 1.9.1 git statuszachowuje się teraz jak git commit.
Jacques René Mesrine,
77

Musisz dodać dwa zmodyfikowane pliki do indeksu, zanim git rozpozna go jako ruch.

Jedyna różnica między mv old newi git mv old newpolega na tym, że git mv dodaje również pliki do indeksu.

mv old newwtedy też git add -Aby działało.

Pamiętaj, że nie możesz po prostu użyć, git add .ponieważ to nie dodaje usunięć do indeksu.

Zobacz Różnica między „git add -A” i „git add”.

nieprostokątny
źródło
3
Dzięki za git add -Alink, bardzo przydatny, ponieważ szukałem takiego skrótu!
PhiLho,
5
Należy zauważyć, że z git 2, git add . ma dodać przeprowadzki do indeksu.
Nick McCurdy,
19

Najlepiej jest spróbować samemu.

mkdir test
cd test
git init
touch aaa.txt
git add .
git commit -a -m "New file"
mv aaa.txt bbb.txt
git add .
git status
git commit --dry-run -a

Teraz git status i git commit --dry-run -a pokazuje dwa różne wyniki, w których status git pokazuje bbb.txt jako nowy plik / aaa.txt jest usuwany, a polecenia --dry-run pokazują rzeczywistą zmianę nazwy.

~/test$ git status

# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   new file:   bbb.txt
#
# 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:    aaa.txt
#


/test$ git commit --dry-run -a

# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   renamed:    aaa.txt -> bbb.txt
#

Teraz idź i zrób odprawę.

git commit -a -m "Rename"

Teraz możesz zobaczyć, że nazwa pliku faktycznie została zmieniona, a to, co jest wyświetlane w statusie git, jest nieprawidłowe.

Morał tej historii: jeśli nie masz pewności, czy nazwa pliku została zmieniona, wydaj „git commit --dry-run -a”. Jeśli pokazuje, że nazwa pliku została zmieniona, możesz zacząć.

dimuthu
źródło
3
Oba są ważne dla Gita . To ostatnie jest jednak bliższe temu, jak zapewne komentator to widzi. Prawdziwa różnica pomiędzy zmiana nazwy i usuwanie + tworzyć tylko na poziomie OS / systemu plików (np samo-węzeł # vs. nowego węzła #), który Git nie troszczą się bardzo dużo o.
Alois Mahdal
17

W przypadku git 1.7.x działały dla mnie następujące polecenia:

git mv css/iphone.css css/mobile.css
git commit -m 'Rename folder.' 

Nie było potrzeby dodawania git, ponieważ oryginalny plik (tj. Css / mobile.css) był już wcześniej w zatwierdzonych plikach.

GrigorisG
źródło
6
To. Wszystkie pozostałe odpowiedzi są absurdalnie i niepotrzebnie złożone. Zachowuje to historię plików między zatwierdzeniami, aby scalenia przed / po zmianie nazwy pliku nie były uszkodzone.
Phlucious
10

musisz przejść do git add css/mobile.cssnowego pliku git rm css/iphone.css, więc git o tym wie. wtedy pokaże to samo wyjście wgit status

widać to wyraźnie na wyjściu statusu (nowa nazwa pliku):

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

i (stara nazwa):

# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)

myślę, że za kulisami git mvjest tylko skrypt otoki, który robi dokładnie to: usuń plik z indeksu i dodaj go pod inną nazwą

knittl
źródło
Nie myślałem, że muszę, git rm css/iphone.cssbo myślałem, że to usunie istniejącą historię. Może nie rozumiem przepływu pracy w git.
Greg K
4
@Greg K: git rmnie usunie historii. Usuwa tylko pozycję z indeksu, aby następne zatwierdzenie nie miało pozycji. Jednak nadal będzie istniał w starożytnych zobowiązaniach. Być może mylisz się, że (na przykład) git log -- newzatrzymasz się w miejscu, w którym popełniłeś git mv old new. Jeśli chcesz śledzić zmiany nazw, użyj git log --follow -- new.
Chris Johnsen
9

Pomyślmy o twoich plikach z perspektywy git.

Pamiętaj, że git nie śledzi żadnych metadanych dotyczących twoich plików

Twoje repozytorium ma (między innymi)

$ cd repo
$ ls
...
iphone.css
...

i jest pod kontrolą Git:

$ git ls-files --error-unmatch iphone.css &>/dev/null && echo file is tracked
file is tracked

Przetestuj to za pomocą:

$ touch newfile
$ git ls-files --error-unmatch newfile &>/dev/null && echo file is tracked
(no output, it is not tracked)
$ rm newfile

Kiedy to zrobisz

$ mv iphone.css mobile.css

Z perspektywy Git

  • nie ma iphone.css (jest usunięty -git ostrzega o tym-).
  • jest nowy plik mobile.css .
  • Te pliki są całkowicie niezwiązane.

Git informuje więc o plikach, które już zna ( iphone.css ) i nowych plikach, które wykrywa ( mobile.css ), ale tylko wtedy, gdy pliki są w indeksie lub HEAD git zaczyna sprawdzać ich zawartość.

W tym momencie, ale ani „iphone.css usunięcie” ani mobile.css są na indeksie.

Dodaj usunięcie iphone.css do indeksu

$ git rm iphone.css

git mówi dokładnie, co się stało: ( iphone.css jest usuwany. Nic więcej się nie wydarzyło)

następnie dodaj nowy plik mobile.css

$ git add mobile.css

Tym razem zarówno usunięcie, jak i nowy plik znajdują się w indeksie. Teraz git wykrywa, że ​​kontekst jest taki sam i ujawnia go jako zmianę nazwy. W rzeczywistości, jeśli pliki są w 50% podobne, wykryje to jako zmianę nazwy, co pozwoli ci nieco zmienić mobile.css , zachowując operację jako zmianę nazwy.

Zobacz, że jest to powtarzalne w dniu git diff. Teraz, gdy Twoje pliki są w indeksie, musisz użyć --cached. Edytuj trochę mobile.css , dodaj to do indeksu i zobacz różnicę między:

$ git diff --cached 

i

$ git diff --cached -M

-Mto opcja „wykryj nazwy” dla git diff. -Moznacza -M50%(50% lub więcej podobieństwa sprawi, że git wyrazi to jako zmianę nazwy), ale możesz zmniejszyć to do -M20%(20%), jeśli dużo edytujesz mobile.css.

albfan
źródło
8

Krok 1: Zmień nazwę pliku ze starego pliku na nowy plik

git mv #oldfile #newfile

Krok 2: Zatwierdź i dodaj komentarze

git commit -m "rename oldfile to newfile"

Krok 3: przekaż tę zmianę zdalnemu serwerowi

git push origin #localbranch:#remotebranch
Haimei
źródło
1
Dodaj komentarz, aby był pomocny dla OP
Devrath
Krok 2 jest niepotrzebny. Następnie git mvnowy plik jest już w indeksie.
Alois Mahdal
7

Git rozpozna plik na podstawie zawartości, a nie zobaczy go jako nowy plik bez śledzenia

Tam popełniłeś błąd.

Dopiero po dodaniu pliku git rozpozna go na podstawie zawartości.

hasen
źródło
Dokładnie. Po przejściu git poprawnie pokaże nazwę.
Max MacLeod
3

Nie wystawiłeś wyników swojego ruchu w wyszukiwarce. Wierzę, że jeśli wykonałeś ruch za pomocą Findera, a następnie zrobiłeś to git add css/mobile.css ; git rm css/iphone.css, git obliczy skrót nowego pliku i dopiero wtedy zda sobie sprawę, że skróty plików pasują (a zatem jest to zmiana nazwy).

Mike Seplowitz
źródło
2

W przypadkach, gdy naprawdę musisz ręcznie zmienić nazwę plików, na przykład. za pomocą skryptu, aby wsadowo zmienić nazwę wiązki plików, a następnie przy użyciu git add -A .pracował dla mnie.

pckben
źródło
2

Dla użytkowników Xcode: jeśli zmienisz nazwę pliku w Xcode, ikona odznaki zmieni się na dołączoną. Jeśli wykonasz zatwierdzenie za pomocą XCode, faktycznie utworzysz nowy plik i stracisz historię.

Obejście jest łatwe, ale musisz to zrobić przed zatwierdzeniem przy użyciu Xcode:

  1. Wykonaj status git w swoim folderze. Powinieneś zobaczyć, że zmiany etapowe są poprawne:

przemianowano: Project / OldName.h -> Project / NewName.h przemianowano: Project / OldName.m -> Project / NewName.m

  1. wykonaj -m „zmień nazwę”

Następnie wróć do XCode, a zobaczysz, że znaczek zmienił się z A na M i zapisz się, aby zatwierdzić zmiany przy użyciu xcode teraz.

doozMen
źródło