Dlaczego istnieją dwa sposoby na wycofanie pliku z Git?

1166

Czasami git sugeruje, git rm --cachedaby oderwać plik, czasem git reset HEAD file. Kiedy powinienem użyć które?

EDYTOWAĆ:

D:\code\gt2>git init
Initialized empty Git repository in D:/code/gt2/.git/
D:\code\gt2>touch a

D:\code\gt2>git status
# On branch master
#
# Initial commit
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       a
nothing added to commit but untracked files present (use "git add" to track)

D:\code\gt2>git add a

D:\code\gt2>git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#       new file:   a
#
D:\code\gt2>git commit -m a
[master (root-commit) c271e05] a
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 a

D:\code\gt2>touch b

D:\code\gt2>git status
# On branch master
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       b
nothing added to commit but untracked files present (use "git add" to track)

D:\code\gt2>git add b

D:\code\gt2>git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       new file:   b
#
Senthess
źródło
20
Dlaczego? Powiedziałbym, że dzieje się tak, ponieważ interfejs linii poleceń git ewoluował organicznie i nigdy nie został poddany poważnej restrukturyzacji w celu zapewnienia spójności. (Jeśli nie zgadzasz się, uwaga, jak git rmmożna zarówno etap usunięcie , a także unstage się dodatek )
Roman Starkov
3
@romkyns: Zgadzam się, że interfejs Gita ma kilka osobliwości, ponieważ ewoluował organicznie, ale usunięcie jest z pewnością odwrotną funkcją dodawania, więc czy logiczne rmjest cofanie add? Jak myślisz, jak rmpowinien się zachowywać?
Zaz
6
Jedyną faktyczną odpowiedzią na twoje pytanie jest to, że zaraz po git initnie ma HEADmożliwości zresetowania.
Miles Rout,
Najlepsze dokumenty do tego: help.github.com/articles/changing-a-remote-s-url
ScottyBlades
4
@Zaz, dam opinię. rmoznacza usunięcie w kontekście uniksowym. Nie jest odwrotnością dodawania do indeksu. Funkcja usuwania plików nie powinna być przeciążona funkcjami zmieniającymi stan przemieszczania. Jeśli istnieją szczegóły implementacji, które ułatwiają łączenie, oznacza to po prostu brak przemyślanej warstwy abstrakcji w git, co uczyniłoby użyteczność jasną.
Joshua Goldberg

Odpowiedzi:

1891

git rm --cached <filePath> nie usuwa scenografii z pliku, w rzeczywistości dokonuje etapów usuwania pliku (ów) z repozytorium (zakładając, że został już wcześniej popełniony), ale pozostawia plik w twoim drzewie roboczym (pozostawiając plik bez śledzenia).

git reset -- <filePath>będzie unstage wszelkie stopniowe zmiany dla danego pliku (ów).

To powiedziawszy, jeśli używałeś git rm --cachednowego pliku, który został zainscenizowany, w zasadzie wyglądałby tak, jakbyś go po prostu wystawił scenę, ponieważ nigdy wcześniej nie został popełniony.

Zaktualizuj git 2.24
W tej nowszej wersji git możesz używać git restore --stagedzamiast git reset. Zobacz dokumenty git .

Ryan Stewart
źródło
70
Powiedziałbym, że git rm --cachedwycofuje plik, ale nie usuwa go z katalogu roboczego.
Pierre de LESPINAY,
4
Aby usunąć plik przemieszczany do dodania, aby nie był już przemieszczany, z pewnością można go nazwać „wycofaniem pliku przygotowanego do dodania”, prawda? Wynik końcowy nie jest etapowym usunięciem , to na pewno, dlatego myślę, że nieporozumienie jest całkowicie zrozumiałe.
Roman Starkov
4
Tak więc zazwyczaj używa się git rm --cached <filePath>do usunięcia niektórych plików z repozytorium po uświadomieniu sobie, że nigdy nie powinno być w repozytorium: najprawdopodobniej uruchomienie tego polecenia i dodanie odpowiednich plików do gitignore. Mam rację?
Adrien Be
13
Mając tyle głosów na pytanie i odpowiedź, powiedziałbym, że najwyraźniej chcemy mieć unstagerozkaz git.
milosmns
4
„git status” radzi teraz: użyj „git restore --staged <file> ...”, aby się wycofać
yumper
334

git rm --cachedsłuży do usunięcia pliku z indeksu. W przypadku, gdy plik jest już w repozytorium, git rm --cachedusunie plik z indeksu, pozostawiając go w katalogu roboczym, a zatwierdzenie usunie go również z repozytorium. Zasadniczo po zatwierdzeniu plik zostałby cofnięty i zachowana lokalna kopia.

git reset HEAD file(który domyślnie używa --mixedflagi) różni się tym, że w przypadku, gdy plik znajduje się już w repozytorium, zastępuje wersję indeksu pliku wersją z repozytorium (HEAD), skutecznie cofając zmiany w tym pliku .

W przypadku pliku niewersjonowanego, odłączy scenę całego pliku, ponieważ pliku nie było w HEAD. W tym aspekcie git reset HEAD filei git rm --cachedsą takie same, ale nie są takie same (jak wyjaśniono w przypadku plików już w repozytorium)

Na pytanie Why are there 2 ways to unstage a file in git?- nigdy tak naprawdę nie ma tylko jednego sposobu na zrobienie czegokolwiek w git. to jest jego piękno :)

manojlds
źródło
7
Zarówno przyjęta odpowiedź, jak i ta są świetne, i wyjaśniają, dlaczego miałbyś używać jednego kontra drugiego. Ale nie odpowiadają bezpośrednio na ukryte pytanie, dlaczego git sugeruje dwie różne metody. W pierwszym przypadku w przykładzie PO właśnie wykonano git init. W takim przypadku git sugeruje „git rm --cached”, ponieważ w tym momencie w repozytorium nie ma żadnych zatwierdzeń, więc HEAD jest niepoprawny. „git reset HEAD - a” wytwarza: „fatal: Nie można rozpoznać„ HEAD ”jako prawidłowej referencji.”
sootsnoot
5
czy w programie „git checkout” nie stracisz wszystkich zmian, które wprowadziłeś w pliku? To nie to samo, co wycofanie pliku, chyba że się mylę.
John Deighan,
there is never really only one way to do anything in git. that is the beauty of it- Hmm dlaczego ? zawsze jest świetnie, gdy jest tylko jeden oczywisty sposób. oszczędza to wiele naszego czasu i pamięci w mózgu))
Oto Shavadze,
128

Całkiem proste:

  • git rm --cached <file> sprawia, że ​​git przestaje całkowicie śledzić plik (pozostawiając go w systemie plików, w przeciwieństwie do zwykłego git rm*)
  • git reset HEAD <file> wycofuje sceny z modyfikacji dokonanych w pliku od czasu ostatniego zatwierdzenia (ale nie przywraca ich w systemie plików, w przeciwieństwie do tego, co sugeruje nazwa polecenia **). Plik pozostaje pod kontrolą wersji.

Jeśli plik nie był wcześniej w kontroli wersji (tzn. Odtwarzasz plik, który właśnie edytowałeś git addpo raz pierwszy), to te dwa polecenia mają ten sam efekt, stąd ich pojawienie się jest „dwoma sposobami zrobienia czegoś” „.

* Pamiętaj, że zastrzeżenie @DrewT wspomina w swojej odpowiedzi dotyczącej git rm --cachedpliku, który został wcześniej zatwierdzony w repozytorium. W kontekście tego pytania o plik, który właśnie został dodany i jeszcze nie został popełniony, nie ma się o co martwić.

** Bałam się żenująco długiego czasu, aby użyć polecenia git reset ze względu na jego nazwę - i nadal często szukam składni, aby upewnić się, że nie spieprzę. ( aktualizacja : W końcu poświęciłem czas na podsumowanie użycia git resetna stronie tldr , więc teraz mam lepszy mentalny model tego, jak to działa, i szybkie odniesienie, gdy zapomnę o niektórych szczegółach.)

waldyrious
źródło
Togit rm <file> --cached
neonmate
8
Naprawdę nie sądzę, że edycja 4 sierpnia 2015 r. Tej odpowiedzi była ogólną poprawą. Być może miał on poprawioną techniczną poprawność (nie czuję się wykwalifikowany, aby to ocenić), ale obawiam się, że to uczyniło ton odpowiedzi znacznie mniej dostępnym, wprowadzając język taki jak „odznacza konieczność rozpoczęcia śledzenia aktualnie nie śledzonego pliku ”i używając żargonu, takiego jak„ index ”i„ HEAD ”, dokładnie takie rzeczy, które odstraszają początkujących. Jeśli ktoś może, edytuj, aby przywrócić bardziej przyjazny język dla nowych użytkowników.
waldyrious,
5
Zgadzam się z @waldyrious. Oryginalna odpowiedź mogła nie pochodzić wprost z podręcznika git, ale odpowiadała na pytanie na wystarczającym poziomie technicznym. Szczegóły techniczne powinny zostać wyjaśnione w komentarzach, a nie jako edycja, która przesłaniała pierwotne zamiary.
Simon Robb,
Cofnąłem edycję. Uważam, że społeczność dostatecznie potwierdziła (w poprzednich komentarzach i głosach nad nimi), że zmiana była szkodliwa dla jasności odpowiedzi.
waldyrious
Uwaga @DrewT ostrzega, że ​​jeśli użyjesz rm --cachedi pchasz , każdy ciągnący tę samą gałąź będzie miał faktycznie usunięte pliki z drzewa roboczego.
Tom Hale,
53

Ten wątek jest nieco stary, ale nadal chcę dodać małą demonstrację, ponieważ nadal nie jest to intuicyjny problem:

me$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   new file:   to-be-added
#   modified:   to-be-modified
#   deleted:    to-be-removed
#

me$ git reset -q HEAD to-be-added

    # ok

me$ git reset -q HEAD to-be-modified

    # ok

me$ git reset -q HEAD to-be-removed

    # ok

# or alternatively:

me$ git reset -q HEAD to-be-added to-be-removed to-be-modified

    # ok

me$ git status
# On branch master
# 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)
#
#   modified:   to-be-modified
#   deleted:    to-be-removed
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   to-be-added
no changes added to commit (use "git add" and/or "git commit -a")

git reset HEAD(bez -q) wyświetla ostrzeżenie o zmodyfikowanym pliku, a jego kod zakończenia to 1, co zostanie uznane za błąd w skrypcie.

Edycja: git checkout HEAD to-be-modified to-be-removeddziała również w przypadku wycofywania scen, ale całkowicie usuwa zmianę z obszaru roboczego

Zaktualizuj git 2.23.0: od czasu do czasu polecenia zmieniają się. Teraz git statusmówi:

  (use "git restore --staged <file>..." to unstage)

... który działa dla wszystkich trzech rodzajów zmian

Daniel Alder
źródło
Dzięki, nie było do końca jasne z pierwszych dwóch odpowiedzi (prawdopodobnie tylko moja niewiedza na temat terminologii), że git reset pozostawił modyfikacje w pliku lokalnie (w przeciwieństwie do git checkout, który by je cofnął).
soupdog
Na początku powinieneś umieścić ostrzeżenie o wersji, ponieważ stara wersja usuwa pliki w nowych wersjach
Luis Mauricio
@LuisMauricio, które polecenie usuwa pliki?
Daniel Alder
36

jeśli przypadkowo przygotowałeś pliki, których nie chciałbyś zatwierdzić i chcesz mieć pewność, że zachowasz zmiany, możesz również użyć:

git stash
git stash pop

powoduje to zresetowanie HEAD i ponowne zastosowanie zmian, umożliwiając ponowne ustawienie poszczególnych plików do zatwierdzenia. jest to również pomocne, jeśli zapomniałeś utworzyć gałąź funkcji dla żądań ściągania ( git stash ; git checkout -b <feature> ; git stash pop).

ives
źródło
3
To czyste rozwiązanie i znacznie mniej niepokojące niż pisanie „git rm”
Subimage
1
git stashma inne powiązane korzyści, ponieważ tworzy wpisy w dzienniku reflogu, które są następnie dostępne w przyszłości. w razie wątpliwości idź dalej i zrób git stash(np. git stash save -u "WIP notes to self"(„-u” oznacza włączenie nowych / nieśledzonych plików do zatwierdzenia ukrytego) ... następnie spróbuj git reflog show stashzobaczyć listę zatwierdzeń ukrytych i ich sha. Polecam powłokę alias likealias grs="git reflog show stash"
tydzień
15

Te 2 polecenia mają kilka subtelnych różnic, jeśli dany plik znajduje się już w repozytorium i jest pod kontrolą wersji (wcześniej zatwierdzony itp.):

  • git reset HEAD <file> rozpakowuje plik w bieżącym zatwierdzeniu.
  • git rm --cached <file>usunie scenę z pliku dla przyszłych zatwierdzeń. Nie jest wystawiany, dopóki nie zostanie ponownie dodany git add <file>.

I jest jeszcze jedna ważna różnica:

  • Po uruchomieniu git rm --cached <file>i przekazaniu gałęzi do pilota każdy, kto wyciągnie gałąź ze zdalnego, otrzyma AKTUALNIE usunięty plik ze swojego folderu, nawet jeśli w lokalnym zestawie roboczym plik po prostu nie zostanie wyśledzony (tzn. Nie zostanie fizycznie usunięty z folderu).

Ta ostatnia różnica jest ważna w przypadku projektów, które zawierają plik konfiguracyjny, w którym każdy programista w zespole ma inną konfigurację (tj. Inny podstawowy adres URL, adres IP lub ustawienie portu), więc jeśli używasz git rm --cached <file>kogoś, kto pobierze twój oddział, będzie musiał ręcznie ponownie utwórz konfigurację lub możesz wysłać je do Ciebie, a oni mogą ponownie edytować go z powrotem do ustawień IP (itp.), ponieważ usunięcie powoduje tylko efekt ściągania twojej gałęzi z pilota.

DrewT
źródło
10

Załóżmy, że masz stagecały katalog za pośrednictwem git add <folder>, ale chcesz wykluczyć plik z listy etapowej (tj. Listy generowanej podczas działania git status) i zachować zmiany w wykluczonym pliku (pracujesz nad czymś i nie jest on gotowy do zatwierdzenia, ale nie chcesz stracić pracy ...). Możesz po prostu użyć:

git reset <file>

Po uruchomieniu git status, można zauważyć, że niezależnie od pliku (ów), które resetunstagedi resztę plików, które addedsą nadal w stagedliście.

jiminikiz
źródło
10

1.

D:\code\gt2>git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#       new file:   a

(użyj „git rm --cached ...”, aby cofnąć scenę)

  • git to system wskaźników

  • nie masz jeszcze zatwierdzenia, aby zmienić swój wskaźnik na

  • jedynym sposobem na „wyjęcie plików z wskazanego segmentu” jest usunięcie plików, które poleciłeś gitowi, aby obserwował zmiany

2)

D:\code\gt2>git commit -m a
[master (root-commit) c271e05] a
0 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 a

git commit -ma

  • zobowiązałeś się, „ zapisałeś

3)

D:\code\gt2>git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       new file:   b
#

(użyj „git reset HEAD ...”, aby się wycofać)

  • w tym momencie dokonałeś zatwierdzenia w kodzie
  • teraz możesz zresetować wskaźnik do zatwierdzenia „ przywróć do ostatniego zapisu
Timothy LJ Stewart
źródło
1
To właściwie jedyna odpowiedź, która poprawnie odpowiada na pytanie, IMO. W rzeczywistości odpowiada na pytanie, które nie brzmi „jakie są różnice między„ git rm - cached ”a„ git reset HEAD ”, ale„ dlaczego git niekonsekwentnie podaje obie opcje jako opcje? ”, Odpowiedź jest taka, że ​​HEAD nie ma resetowania do kiedy git initpo raz pierwszy.
Miles Rout,
5

Dziwię się, że nikt nie wspominał o dzienniku git ( http://git-scm.com/docs/git-reflog ):

# git reflog
<find the place before your staged anything>
# git reset HEAD@{1}

Reflog jest historią git, która nie tylko śledzi zmiany w repozytorium, ale także śledzi akcje użytkownika (np. Ściąganie, pobieranie do innej gałęzi itp.) I pozwala cofnąć te akcje. Zamiast odstawiać plik, który został omyłkowo zainscenizowany, w którym można powrócić do punktu, w którym pliki nie były etapowane.

Jest to podobne, git reset HEAD <file>ale w niektórych przypadkach może być bardziej szczegółowe.

Przepraszam - tak naprawdę nie odpowiadam na twoje pytanie, ale po prostu wskazuję kolejny sposób na wycofanie plików, których często używam (na przykład lubię odpowiedzi Ryana Stewarta i bardzo waldyrious.);) Mam nadzieję, że to pomoże.

Alex
źródło
5

Po prostu użyj:

git reset HEAD <filename>

Spowoduje to rozproszenie pliku i zachowanie wprowadzonych zmian, dzięki czemu możesz z kolei zmienić gałęzie, jeśli chcesz, a git addte pliki na inną gałąź. Wszystkie zmiany są zachowywane.

Edgar Quintero
źródło
3

Wydaje mi się, że git rm --cached <file>usuwa plik z indeksu bez usuwania go z katalogu, w którym zwykły git rm <file>zrobiłby oba, podobnie jak system operacyjny rm <file>usuwałby plik z katalogu bez usuwania jego wersji.

ernie.cordell
źródło
1

Tylko dla wersji 2.23 i nowszych

Zamiast tych sugestii możesz użyć git restore --staged <file>w celu unstageuzyskania pliku (ów).

Kaan Taha Köken
źródło
Działa zarówno z opcjami, --stagejak i --staged.
dhana1310