Jak zresetować - twarde podkatalog?

197

AKTUALIZACJA² : W Git 2.23 (sierpień 2019) pojawiło się nowe polecenie, git restorektóre to robi, zobacz przyjętą odpowiedź .

AKTUALIZACJA : Będzie działać bardziej intuicyjnie od wersji Git 1.8.3, zobacz moją własną odpowiedź .

Wyobraź sobie następujący przypadek użycia: Chcę pozbyć się wszystkich zmian w określonym podkatalogu mojego drzewa roboczego Git, pozostawiając wszystkie pozostałe podkatalogi nietknięte.

Jakie jest właściwe polecenie Git dla tej operacji?

Poniższy skrypt ilustruje problem. Wstaw właściwe polecenie poniżej How to make fileskomentarza - bieżące polecenie przywróci plik, a/c/acktóry powinien zostać wykluczony przez rzadkie pobieranie. Pamiętaj, że nie chcę jawnie przywracać, a/aa a/bjedynie „wiem” ai chcę przywrócić wszystko poniżej. EDYCJA : I też nie „wiem” b, ani które inne katalogi znajdują się na tym samym poziomie co a.

#!/bin/sh

rm -rf repo; git init repo; cd repo
for f in a b; do
  for g in a b c; do
    mkdir -p $f/$g
    touch $f/$g/$f$g
    git add $f/$g
    git commit -m "added $f/$g"
  done
done
git config core.sparsecheckout true
echo a/a > .git/info/sparse-checkout
echo a/b >> .git/info/sparse-checkout
echo b/a >> .git/info/sparse-checkout
git read-tree -m -u HEAD
echo "After read-tree:"
find * -type f

rm a/a/aa
rm a/b/ab
echo >> b/a/ba
echo "After modifying:"
find * -type f
git status

# How to make files a/* reappear without changing b and without recreating a/c?
git checkout -- a

echo "After checkout:"
git status
find * -type f
krlmlr
źródło
3
co około git stash && git stash drop?
CharlesB
1
co git checkout -- /path/to/subdir/?
iberbeu
3
@CharlesB: git stashnie akceptuje argumentu ścieżki ...
krlmlr
@iberbeu: Nie. Dodane zostaną również pliki wykluczone przez rzadkie pobranie.
krlmlr
1
@CharlesBailey: Dlaczego więc jest przycisk z napisem „Szukasz odpowiedzi pochodzącej z wiarygodnych i / lub oficjalnych źródeł”. w oknie nagród? Sam tego nie napisałem! Spróbuj także google „podkatalog git reset” (bez cudzysłowów) i zobacz, co jest na pierwszych 3 pozycjach. Na pewno trudniej będzie znaleźć wiadomość na listę mailingową kernel.org. - Również dla mnie nie jest jeszcze jasne, czy to zachowanie jest błędem czy cechą.
krlmlr

Odpowiedzi:

163

W Git 2.23 (sierpień 2019 r.) Masz nowe poleceniegit restore

git restore --source=HEAD --staged --worktree -- aDirectory
# or, shorter
git restore -s@ -SW  -- aDirectory

To zastąpiłoby zarówno indeks, jak i drzewo robocze HEADzawartością, tak jak reset --hardby to zrobiło, ale dla określonej ścieżki.


Oryginalna odpowiedź (2013)

Zauważ ( skomentowane przez Dana Fabulicha ), że:

  • git checkout -- <path> nie wykonuje twardego resetu: zastępuje zawartość drzewa roboczego zawartością etapową.
  • git checkout HEAD -- <path>wykonuje twardy reset ścieżki, zastępując zarówno indeks, jak i drzewo robocze wersją z HEADzatwierdzenia.

Jak odpowiedział przez Ajedi32 , obie formy Zamówienie nie usuwać pliki, które zostały usunięte w rewizji docelowego .
Jeśli masz dodatkowe pliki w drzewie roboczym, które nie istnieją w HEAD, a git checkout HEAD -- <path>nie usuwa ich.

Uwaga: W przypadku git checkout --overlay HEAD -- <path>(Git 2.22, I kwartał 2019 r.) Pliki pojawiające się w indeksie i drzewie roboczym, ale nie w, <tree-ish>są usuwane, aby były <tree-ish>dokładnie dopasowane .

Ale ta kasa może respektować git update-index --skip-worktree(dla tych katalogów, które chcesz zignorować), jak wspomniano w „ Dlaczego wykluczone pliki pojawiają się ponownie w mojej git rzadkiej kasie? ”.

VonC
źródło
1
Proszę o wyjaśnienie. Po tym git checkout HEAD -- .ponownie pojawiają się pliki wykluczone przez rzadkie pobieranie. Co git update-index --skip-worktreema robić?
krlmlr
@krlmlr skip-worktree lub assume-niezmienione to dwa sposoby próby wprowadzenia wpisu do indeksu „niewidoczny” dla git: fallengamer.livejournal.com/93321.html , stackoverflow.com/q/13630849/6309 i stackoverflow. com / a / 6139470/6309
VonC
@krlmlr te linki są tylko wskazówkami, abyś mógł sprawdzić, czy kasa nadal przywróci te wpisy, gdy zostaną one oznaczone jako „pomijane-drzewko robocze”.
VonC
Przepraszam, ale to zbyt skomplikowane jak na dane zadanie. Chcę resetu włączającego, a nie wyłącznego. Czy naprawdę nie ma dobrego sposobu na zrobienie tego w Git?
krlmlr
@krlmlr no: najlepiej to zrobić git checkout HEAD -- <path>, a następnie usunąć katalogi, które zostały przywrócone (ale nadal są zadeklarowane w rzadkim kasie).
VonC
126

Według dewelopera Git Duy Nguyen, który uprzejmie wdrożył tę funkcję i przełącznik zgodności , następujące działania działają zgodnie z oczekiwaniami od wersji Git 1.8.3 :

git checkout -- a

(gdzie ajest katalog, który chcesz zresetować na stałe). Oryginalne zachowanie można uzyskać za pośrednictwem

git checkout --ignore-skip-worktree-bits -- a
krlmlr
źródło
5
Dziękujemy za wysiłek związany z kontynuowaniem pracy z zespołem programistów Git, który spowodował zmianę w Git.
Dan Cruz
13
I zauważ, że „a” w tym przypadku oznacza katalog, który chcesz przywrócić, więc jeśli znajdujesz się w katalogu, który chcesz przywrócić, polecenie powinno być git checkout -- .tam, gdzie .oznacza bieżący katalog.
TheWestIsThe ...
5
Jeden komentarz z mojej strony jest taki, że najpierw musisz usunąć scenę z folderu git reset -- a(gdzie jest katalog, który chcesz zresetować)
Boyan
Czy to nie prawda, jeśli chcesz git reset --hardcałe repo?
krlmlr
A jeśli dodałeś nowe pliki do tego katalogu, zrób to rm -rf awcześniej.
Tobias Feil
30

Spróbuj zmienić

git checkout -- a

do

git checkout -- `git ls-files -m -- a`

Od wersji 1.7.0 Git honoruje flagę skip-worktree .ls-files

Uruchomienie skryptu testowego (z kilkoma drobnymi poprawkami zmieniającymi git commit... na git commit -qi git statusna git status --short) wyniki:

Initialized empty Git repository in /home/user/repo/.git/
After read-tree:
a/a/aa
a/b/ab
b/a/ba
After modifying:
b/a/ba
 D a/a/aa
 D a/b/ab
 M b/a/ba
After checkout:
 M b/a/ba
a/a/aa
a/c/ac
a/b/ab
b/a/ba

Uruchamianie skryptu testowego z proponowanymi checkoutwynikami zmian:

Initialized empty Git repository in /home/user/repo/.git/
After read-tree:
a/a/aa
a/b/ab
b/a/ba
After modifying:
b/a/ba
 D a/a/aa
 D a/b/ab
 M b/a/ba
After checkout:
 M b/a/ba
a/a/aa
a/b/ab
b/a/ba
Dan Cruz
źródło
Brzmi dobrze. Ale czy nie powinien git checkoutprzede wszystkim szanować drzewka „pomiń pracę”?
krlmlr
Szybki przegląd checkout.ci tree.cnie ujawnia, że ​​używana jest flaga skip-worktree.
Dan Cruz
Jest to na tyle proste, że może być przydatne w praktyce, nawet jeśli będę musiał ustawić alias bash dla tego polecenia. Duy Nguyen ma odpowiedział na moją wiadomość na liście mailingowej Git, zobaczmy, czy wkrótce pojawi się bardziej przyjazna dla użytkownika alternatywa.
krlmlr
18

W przypadku zwykłego odrzucania zmian polecenia git checkout -- path/lub git checkout HEAD -- path/polecenia sugerowane przez inne odpowiedzi działają świetnie. Jeśli jednak chcesz zresetować katalog do wersji innej niż HEAD, to rozwiązanie ma poważny problem: nie usuwa plików, które zostały usunięte w wersji docelowej.

Zamiast tego zacząłem używać następującego polecenia:

git diff --cached commit -- subdir | git apply -R --index

Działa to poprzez znalezienie różnicy między docelowym zatwierdzeniem a indeksem, a następnie zastosowanie tej różnicy w odwrotnej kolejności do katalogu roboczego i indeksu. Zasadniczo oznacza to, że dopasowuje zawartość indeksu do treści określonej wersji. Fakt, że git diffpobiera argument ścieżki, pozwala ograniczyć ten efekt do określonego pliku lub katalogu.

Ponieważ to polecenie jest dość długie i planuję go często używać, skonfigurowałem dla niego alias, który nazwałem reset-checkout:

git config --global alias.reset-checkout '!f() { git diff --cached "$@" | git apply -R --index; }; f'

Możesz użyć tego w następujący sposób:

git reset-checkout 451a9a4 -- path/to/directory

Lub tylko:

git reset-checkout 451a9a4
Ajedi32
źródło
Widziałem twój komentarz wczoraj i eksperymentowałem go dzisiaj. Twój alias jest pomocny. +1
VonC
Jak ta opcja git checkout --overlay HEAD -- <path>wypada w porównaniu z poleceniem, które @VonC wspomina w swojej odpowiedzi?
Ehtesh Choudhury
1
@EhteshChoudhury Uwaga, że git checkout --overlay HEAD -- <path>nie została jeszcze wydana (Git 2.22 zostanie wydany w drugim kwartale 2019 r.)
VCC
5

Mam tutaj do zaoferowania straszną opcję, ponieważ nie mam pojęcia, jak zrobić cokolwiek z Gitem, add commita pushoto, w jaki sposób „przywróciłem” podkatalog:

Założyłem nowe repozytorium na moim komputerze lokalnym, przywróciłem całą treść do zatwierdzenia, z którego chciałem skopiować kod, a następnie skopiowałem te pliki do mojego katalogu roboczego, add commit pushet voila. Nie nienawidź gracza, nienawidź pana Torvaldsa za to, że jest mądrzejszy od nas wszystkich.

Abraham Brookes
źródło
4

Reset zwykle zmienia wszystko, ale możesz użyć, git stashaby wybrać to, co chcesz zachować. Jak wspomniałeś, stashnie akceptuje ścieżki bezpośrednio, ale nadal można jej użyć do utrzymania określonej ścieżki z --keep-indexflagą. W twoim przykładzie przechowujesz katalog b, a następnie resetujesz wszystko inne.

# How to make files a/* reappear without changing b and without recreating a/c?
git add b               #add the directory you want to keep
git stash --keep-index  #stash anything that isn't added
git reset               #unstage the b directory
git stash drop          #clean up the stash (optional)

To prowadzi cię do punktu, w którym ostatnia część skryptu wyświetli:

After checkout:
# On branch master
# Changes not staged for commit:
#
#   modified:   b/a/ba
#
no changes added to commit (use "git add" and/or "git commit -a")
a/a/aa
a/b/ab
b/a/ba

Myślę, że to był wynik docelowy (b pozostaje zmodyfikowany, pliki a / * powróciły, a / c nie zostało odtworzone).

Takie podejście ma tę dodatkową zaletę, że jest bardzo elastyczne; możesz uzyskać tak szczegółowe, jak chcesz, dodawanie określonych plików, ale nie innych, do katalogu.

Jonathan Wren
źródło
To miło, ale musiałbym mieć git addwszystko oprócz a, prawda? Brzmi trudnie w praktyce.
krlmlr
1
@krlmlr Nie bardzo. git add .Następnie możesz git reset adodać wszystko oprócz a.
Jonathan Wren
1
@krlmlr Warto również zauważyć, że git addnie dodaje usuniętych plików. Tak więc, jeśli odzyskujesz tylko usunięte pliki, git add .dodasz wszystkie zmodyfikowane pliki, ale nie usunięte.
Jonathan Wren
3

Jeśli wielkość podkatalogu nie jest szczególnie duża ORAZ chcesz trzymać się z dala od interfejsu CLI, oto szybkie rozwiązanie, aby ręcznie zresetować podkatalog:

  1. Przejdź do gałęzi master i skopiuj podkatalog, który chcesz zresetować.
  2. Teraz wróć do gałęzi funkcji i zamień podkatalog na kopię utworzoną właśnie w kroku 1.
  3. Zatwierdź zmiany.

Twoje zdrowie. Wystarczy ręcznie zresetować podkatalog w gałęzi funkcji, aby był taki sam jak w gałęzi master !!

Mehul Parmar
źródło
Nie widziałem głosów za tą odpowiedzią, ale czasami jest to najprostsza gwarantowana droga do sukcesu.
Sue Spence
1

Ajedi32 „s odpowiedź jest to, czego szukałem, ale dla niektórych zobowiązuje wpadłem na tego błędu:

error: cannot apply binary patch to 'path/to/directory' without full index line

Być może niektóre pliki katalogu są plikami binarnymi. Naprawiono to, dodając opcję „--binary” do polecenia git diff:

git diff --binary --cached commit -- path/to/directory | git apply -R --index
khelkun
źródło
0

Co powiesz na

subdir=thesubdir
for fn in $(find $subdir); do
  git ls-files --error-unmatch $fn 2>/dev/null >/dev/null;
  if [ "$?" = "1" ]; then
    continue;
  fi
  echo "Restoring $fn";
  git show HEAD:$fn > $fn;
done 
Cochise Ruhulessin
źródło