Praktyczne zastosowania git reset --soft?

144

Współpracuję z git nieco ponad miesiąc. Rzeczywiście, dopiero wczoraj po raz pierwszy użyłem resetu, ale miękki reset nadal nie ma dla mnie większego sensu.

Rozumiem, że mogę użyć miękkiego resetowania do edycji zatwierdzenia bez zmiany indeksu lub katalogu roboczego, tak jak zrobiłbym to z git commit --amend.

Czy te dwa polecenia są rzeczywiście takie same ( reset --softvs commit --amend)? Czy jest jakiś powód, aby używać jednego lub drugiego w praktyce? A co ważniejsze, czy reset --softoprócz zmiany zatwierdzenia są jakieś inne zastosowania ?

AJJ
źródło

Odpowiedzi:

114

git resetprzede wszystkim o przeniesienie HEAD, i ogólnie oddział ref .
Pytanie: co z drzewem roboczym i indeksem?
W przypadku korzystania z --soft, przenosi HEAD, najczęściej aktualizuje referencję gałęzi i tylkoHEAD .
To różni się od commit --amend:

  • nie tworzy nowego zatwierdzenia.
  • może faktycznie przenieść HEAD do dowolnego zatwierdzenia (tak jak commit --amendtylko o nie przenoszeniu HEAD, jednocześnie pozwalając na ponowne wykonanie bieżącego zatwierdzenia)

Właśnie znalazłem ten przykład połączenia:

  • klasyczne połączenie
  • scalenie poddrzewa

wszystko w jeden (ośmiornica, ponieważ jest więcej niż dwie połączone gałęzie) zatwierdzić scalić.

Tomas „wereHamster” Carnecky wyjaśnia w swoim artykule „Scalanie ośmiornic poddrzewa” :

  • Strategii scalania poddrzewa można użyć, jeśli chcesz scalić jeden projekt z podkatalogiem innego projektu, a następnie zachować aktualność podprojektu. Jest to alternatywa dla podmodułów git.
  • Strategii łączenia ośmiornicy można użyć do scalenia trzech lub więcej gałęzi. Normalna strategia może łączyć tylko dwie gałęzie, a jeśli spróbujesz scalić więcej niż to, git automatycznie wróci do strategii ośmiornicy.

Problem w tym, że możesz wybrać tylko jedną strategię. Chciałem jednak połączyć te dwa elementy, aby uzyskać czystą historię, w której całe repozytorium jest atomowo aktualizowane do nowej wersji.

Mam superprojekt, nazwijmy go projectA, i podprojekt projectB, który połączyłem w podkatalogu projectA.

(to jest część scalania poddrzewa)

Utrzymuję też kilka lokalnych zmian.
ProjectAjest regularnie aktualizowany, projectBma nową wersję co kilka dni lub tygodni i zwykle zależy od konkretnej wersji projectA.

Decydując się na aktualizację obu projektów, po prostu nie wyciągam z nich, projectAa projectB to spowodowałoby utworzenie dwóch zatwierdzeń dla czegoś, co powinno być atomową aktualizacją całego projektu .
Zamiast tworzyć jeden scalającej który łączy projectA, projectBa moim lokalnym zobowiązuje .
Problem polega na tym, że jest to scalanie ośmiornic (trzy głowy), ale projectBmusi zostać połączone ze strategią poddrzewa . Więc oto co robię:

# Merge projectA with the default strategy:
git merge projectA/master

# Merge projectB with the subtree strategy:
git merge -s subtree projectB/master

Tutaj autor użył a reset --hard, a następnie, read-treeaby przywrócić to, co zrobiły pierwsze dwa scalenia w drzewie roboczym i indeksie, ale to reset --softmoże pomóc:
Jak powtórzyć te dwa scalenia , które zadziałały, tj. Moje drzewo robocze i indeks są dobrze, ale bez konieczności nagrywania tych dwóch zatwierdzeń?

# Move the HEAD, and just the HEAD, two commits back!
git reset --soft HEAD@{2}

Teraz możemy wznowić rozwiązanie Tomasza:

# Pretend that we just did an octopus merge with three heads:
echo $(git rev-parse projectA/master) > .git/MERGE_HEAD
echo $(git rev-parse projectB/master) >> .git/MERGE_HEAD

# And finally do the commit:
git commit

Tak więc za każdym razem:

  • jesteś zadowolony z tego, z czym skończysz (pod względem drzewa roboczego i indeksu)
  • jesteś nie zadowolony z wszystkich zatwierdzeń, które miały ty się tam dostać:

git reset --soft jest odpowiedzią.

VonC
źródło
8
Notatka dla siebie: prosty przykład zgniatania za pomocą git reset --soft: stackoverflow.com/questions/6869705/ ...
VonC
3
jest również przydatne, jeśli zdecydowałeś się na niewłaściwą gałąź. wszystkie zmiany wracają do strefy przejściowej i poruszają się razem z tobą, podczas gdy ty płacisz za prawą gałąź.
smoebody
48

Przykład użycia - połącz serię lokalnych zatwierdzeń

„Ups. Te trzy zatwierdzenia mogą być tylko jednym”.

Więc cofnij ostatnie 3 (lub cokolwiek) zatwierdzenia (bez wpływu na indeks ani katalog roboczy). Następnie zatwierdź wszystkie zmiany jako jedną.

Na przykład

> git add -A; git commit -m "Start here."
> git add -A; git commit -m "One"
> git add -A; git commit -m "Two"
> git add -A' git commit -m "Three"
> git log --oneline --graph -4 --decorate

> * da883dc (HEAD, master) Three
> * 92d3eb7 Two
> * c6e82d3 One
> * e1e8042 Start here.

> git reset --soft HEAD~3
> git log --oneline --graph -1 --decorate

> * e1e8042 Start here.

Teraz wszystkie zmiany są zachowane i gotowe do zatwierdzenia jako jedna.

Krótkie odpowiedzi na Twoje pytania

Czy te dwa polecenia są rzeczywiście takie same ( reset --softvs commit --amend)?

  • Nie.

Czy jest jakiś powód, aby używać jednego lub drugiego w praktyce?

  • commit --amend aby dodać / rm pliki z ostatniego zatwierdzenia lub zmienić jego komunikat.
  • reset --soft <commit> aby połączyć kilka kolejnych zatwierdzeń w nowe.

A co ważniejsze, czy reset --softoprócz zmiany zatwierdzenia są jakieś inne zastosowania ?

  • Zobacz inne odpowiedzi :)
Shaun Luttin
źródło
2
Zobacz inne odpowiedzi dla „czy są jakieś inne zastosowania reset --softoprócz zmiany zatwierdzenia - Nie”
Antony Hatchkins
W odpowiedzi na ostatni komentarz - częściowo się zgodził, ale powiedziałbym, że zmiana pojedynczego zatwierdzenia jest zbyt szczegółowa. Na przykład możesz chcieć, po napisaniu funkcji, zmiażdżyć wszystko i stworzyć nowe zatwierdzenia, które są bardziej przydatne dla recenzentów. Powiedziałbym, że miękkie resety (w tym zwykłe git reset) są dobre, gdy (a) chcesz przepisać historię, (b) nie przejmujesz się starymi zatwierdzeniami (dzięki czemu możesz uniknąć kłopotów związanych z interaktywną rebase) i (c) mieć więcej niż jedno zobowiązanie do zmiany (w przeciwnym razie commit --amendjest prostsze).
johncip
18

Używam go do poprawiania czegoś więcej niż tylko ostatniego zatwierdzenia.

Powiedzmy, że popełniłem błąd w zatwierdzeniu A, a następnie zatwierdziłem B. Teraz mogę tylko poprawić B. Więc robię git reset --soft HEAD^^, poprawiam i ponownie zatwierdzam A, a następnie ponownie zatwierdzam B.

Oczywiście nie jest to zbyt wygodne w przypadku dużych zatwierdzeń… ale i tak nie powinieneś robić dużych zatwierdzeń ;-)

Szymon
źródło
3
git commit --fixup HEAD^^ git rebase --autosquash HEAD~Xdziała też dobrze.
Niklas
3
git rebase --interactive HEAD^^gdzie wybierasz edycję zarówno zatwierdzeń A i B. W ten sposób zachowuje się komunikaty zmian A i B, w razie potrzeby nadal możesz je modyfikować.
Paul Pladijs
2
Jak ponownie zatwierdzić B po zresetowaniu do A?
northben
1
Inny sposób to prawdopodobnie mniej pracy: sprawdź swój dziennik git, uzyskać zatwierdzenie mieszania B, a następnie git reset A, należy dodać zmian git commit --amend, git cherry-pick <B-commit-hash>.
naught101
15

Innym potencjalnym zastosowaniem jest alternatywa dla przechowywania (co niektórym się nie podoba, np. Https://codingkilledthecat.wordpress.com/2012/04/27/git-stash-pop-considered-harmful/ ).

Na przykład, jeśli pracuję nad gałęzią i muszę pilnie naprawić coś na master, mogę po prostu zrobić:

git commit -am "In progress."

następnie mistrz kasy i napraw. Kiedy skończę, wracam do swojego oddziału i robię

git reset --soft HEAD~1

kontynuować pracę od miejsca, w którym skończyłem.

deltacrux
źródło
3
Aktualizacja (teraz, gdy lepiej rozumiem git): --softjest tutaj właściwie niepotrzebna, chyba że naprawdę zależy ci na natychmiastowym przygotowaniu zmian. Teraz po prostu używam, git reset HEAD~kiedy to robię. Jeśli mam jakieś zmiany w fazie, gdy muszę zmienić gałęzie i chcę to tak zachować, to robię git commit -m "staged changes", a git commit -am "unstaged changes"potem później git reset HEAD~, git reset --soft HEAD~aby całkowicie przywrócić stan roboczy. Chociaż, szczerze mówiąc, teraz robię obie te rzeczy o wiele rzadziej git-worktree:)
deltacrux
8

Jednym praktycznym zastosowaniem jest to, że jeśli już zdecydowałeś się na lokalne repozytorium (np. Git commit -m), możesz cofnąć to ostatnie zatwierdzenie wykonując git reset --soft HEAD ~ 1

Również dla twojej wiedzy, jeśli już przygotowałeś swoje zmiany (np. Za pomocą git add.), Możesz odwrócić staging, wykonując reset git - mieszany HEAD lub często również po prostu użyłemgit reset

na koniec git reset --hard usuwa wszystko, łącznie z lokalnymi zmianami. Nagłówek ~ po nagłówku informuje, do ilu zatwierdzeń przejść od góry.

j2emanue
źródło
1
git reset --soft HEAD ~1daje mi fatal: Cannot do soft reset with paths., myślę, że musimy usunąć spację po HEAD, aby byłogit reset --soft HEAD~1
Andrew Lohr
7

Możesz użyć, git reset --softaby zmienić wersję, którą chcesz mieć jako nadrzędną dla zmian wprowadzonych w indeksie i drzewie roboczym. Przypadki, w których jest to przydatne, są rzadkie. Czasami możesz zdecydować, że zmiany, które wprowadziłeś w swoim drzewie roboczym, powinny dotyczyć innej gałęzi. Możesz też użyć tego jako prostego sposobu na zwinięcie kilku zatwierdzeń w jedno (podobnie do squash / fold).

Zobacz odpowiedź VonC, aby uzyskać praktyczny przykład: Zgniatać pierwsze dwa zatwierdzenia w Git?

Johannes Rudolph
źródło
To dobre pytanie, którego wcześniej nie znalazłem. Ale nie jestem pewien, czy mogę zobaczyć, jak wprowadzić zmiany w innej gałęzi za pomocą programu resetowania. Więc mam Oddział do kasy, potem używam git reset --soft anotherBranchi zatwierdzam tam? Ale tak naprawdę nie zmieniasz gałęzi ckeckout, więc czy zdecydujesz się przejść do Branch czy do innego Branch ?
AJJ
Robiąc to, ważne jest, aby nie używać git checkout, ponieważ zmieni to twoje drzewo. Pomyśl o git reset --soft jako o sposobie manipulowania wersją, na którą wskazuje HEAD.
Johannes Rudolph
7

Jednym z możliwych zastosowań byłoby kontynuowanie pracy na innym komputerze. To działałoby tak:

  1. Kup nowy oddział z nazwą podobną do skrytki,

    git checkout -b <branchname>_stash
    
  2. Podnieś swoją gałąź skrytki

    git push -u origin <branchname>_stash
    
  3. Przełącz się na inną maszynę.

  4. Zdejmij zarówno swój zapas, jak i istniejące gałęzie,

    git checkout <branchname>_stash; git checkout <branchname>
    
  5. Powinieneś być teraz w swoim istniejącym oddziale. Wklej zmiany z gałęzi skrytki,

    git merge <branchname>_stash
    
  6. Miękki reset istniejącej gałęzi do 1 przed połączeniem,

    git reset --soft HEAD^
    
  7. Usuń swoją gałąź skrytki,

    git branch -d <branchname>_stash
    
  8. Usuń także swoją gałąź skrytki od początku,

    git push origin :<branchname>_stash
    
  9. Kontynuuj pracę ze zmianami tak, jakbyś je normalnie schował.

Myślę, że w przyszłości GitHub i spółka. powinien oferować tę funkcję „zdalnego przechowywania” w mniejszej liczbie kroków.

llaughlin
źródło
2
Chcę zaznaczyć, że pierwszy stash i pop na pierwszym komputerze są zupełnie niepotrzebne, możesz po prostu utworzyć nową gałąź bezpośrednio z brudnej kopii roboczej, zatwierdzić, a następnie przesłać zmiany do pilota.
6

Świetnym powodem użycia „ git reset --soft <sha1>” jest ruchHEAD do samego repozytorium.

Jeśli spróbujesz użyć opcji --mixedlub --hard, pojawi się błąd, ponieważ próbujesz zmodyfikować i robocze drzewo i / lub indeks, które nie istnieją.

Uwaga: musisz to zrobić bezpośrednio z samego repozytorium.

Uwaga: Musisz upewnić się, że gałąź, którą chcesz zresetować w czystym repozytorium, jest gałęzią aktywną. Jeśli nie, postępuj zgodnie z odpowiedzią VonC, jak zaktualizować aktywną gałąź w gołym repozytorium, gdy masz bezpośredni dostęp do repozytorium.

Hazok
źródło
1
Jak wspomniano w Twojej poprzedniej odpowiedzi ( stackoverflow.com/questions/4624881/… ), jest to prawdą, gdy masz bezpośredni dostęp do samego repozytorium (o którym wspominasz tutaj). Jednak +1.
VonC
@VonC Tak, absolutnie poprawne i dziękuję za dodanie notatki! Ciągle zapominam o dodaniu tego, ponieważ zakładam, że resety są wykonywane bezpośrednio z repozytorium. Zakładam również, że gałąź, którą osoba chce zresetować w gołym repozytorium, jest jej aktywną gałęzią. Jeśli gałąź nie jest ich aktywną gałęzią, musisz zaktualizować zgodnie z twoją odpowiedzią ( stackoverflow.com/questions/3301956/… ), jak zaktualizować aktywną gałąź dla gołych repozytoriów. Zaktualizuję odpowiedź również o informacje o aktywnym oddziale. Dzięki jeszcze raz!!!
Hazok
2

SourceTree to GUI git, które ma całkiem wygodny interfejs do umieszczania tylko tych bitów, które chcesz. Nie ma niczego podobnego do poprawiania poprawnej wersji.

git reset --soft HEAD~1Jest więc znacznie bardziej przydatne niż commit --amendw tym scenariuszu. Mogę cofnąć zatwierdzenie, przenieść wszystkie zmiany z powrotem do obszaru przejściowego i wznowić dostosowywanie etapowych bitów za pomocą SourceTree.

Naprawdę wydaje mi się, że commit --amendjest to bardziej zbędne polecenie z tych dwóch, ale git to git i nie stroni od podobnych poleceń, które robią nieco inne rzeczy.

Roman Starkov
źródło
1

Chociaż bardzo podobają mi się odpowiedzi w tym wątku, używam git reset --softnieco innego, ale bardzo praktycznego scenariusza.

Używam IDE do programowania, które ma dobre narzędzie porównywania do pokazywania zmian (wystawionych i niestacjonarnych) po moim ostatnim zatwierdzeniu. Obecnie większość moich zadań obejmuje wiele zatwierdzeń. Na przykład, powiedzmy, że wykonuję 5 zatwierdzeń, aby wykonać określone zadanie. Używam narzędzia porównywania w IDE podczas każdego zatwierdzenia przyrostowego od 1-5, aby przyjrzeć się moim zmianom od ostatniego zatwierdzenia. Uważam, że jest to bardzo pomocny sposób przeglądania zmian przed ich zatwierdzeniem.

Ale pod koniec mojego zadania, gdy chcę zobaczyć wszystkie moje zmiany razem (przed pierwszym zatwierdzeniem), aby dokonać przeglądu własnego kodu przed wykonaniem żądania ściągnięcia, zobaczyłem tylko zmiany z mojego poprzedniego zatwierdzenia (po zatwierdzeniu 4) i nie zmienia się ze wszystkich zatwierdzeń mojego obecnego zadania.

Więc używam, git reset --soft HEAD~4aby cofnąć się o 4 zatwierdzenia. To pozwala mi zobaczyć wszystkie zmiany razem. Kiedy jestem pewny swoich zmian, mogę to zrobić git reset HEAD@{1}i pewnie przenieść to na odległość.

Swanky Coder
źródło
1
... następnie zrób git add --patchi git commitwielokrotnie, aby zbudować serię zatwierdzeń, którą skonstruowałbyś, gdybyś wiedział, co robisz przez cały czas. Twoje pierwsze zatwierdzenia są jak notatki na twoim biurku lub pierwszy szkic notatki, służą do organizowania twojego myślenia, a nie do publikacji.
jthill
Hej, nie do końca rozumiem, co sugerujesz.
Swanky Coder
1
Po prostu zamiast git reset @{1}przywracać pierwszą wersję roboczą serii, możesz zamiast tego utworzyć serię do publikacji z git add -pi git commit.
jthill
Tak, poprawnie. Innym sposobem (tym, za którym zwykle się kieruję) jest zmiana bazy na nadrzędnym / głównym i zmiażdżenie zatwierdzeń do jednego.
Swanky Coder
1

Innym przypadkiem użycia jest sytuacja, gdy chcesz zastąpić inną gałąź swoją w żądaniu ściągnięcia, na przykład powiedzmy, że masz oprogramowanie z funkcjami A, B, C.

Rozwijasz się z następną wersją i:

  • Usunięta funkcja B

  • Dodano funkcję D

W trakcie opracuj właśnie dodane poprawki dla funkcji B.

Możesz scalić program develop w next, ale czasami może to być kłopotliwe, ale możesz także użyć git reset --soft origin/developi utworzyć zatwierdzenie ze swoimi zmianami, a gałąź można scalić bez konfliktów i zachować zmiany.

Okazuje się, że git reset --softto poręczna komenda. Osobiście używam go często do zgniatania zatwierdzeń, które nie mają „ukończonej pracy”, jak „WIP”, więc kiedy otwieram żądanie ściągnięcia, wszystkie moje zatwierdzenia są zrozumiałe.

cnexans
źródło