Jak usunąć konkretną wersję w historii git?

213

Załóżmy, że twoja historia gitów wygląda następująco:

1 2 3 4 5

1–5 to osobne wersje. Musisz usunąć 3, jednocześnie zachowując 1, 2, 4 i 5. Jak to zrobić?

Czy istnieje skuteczna metoda, jeśli po usunięciu istnieją setki wersji?

1800 INFORMACJI
źródło
To pytanie jest źle zdefiniowane. Nie mówi wprost, że autor chce 1-2- (3 + 4) -5 lub 1-2-4-5
RandyTek
14
8 lat później nie mogę powiedzieć dokładnie, jaki problem próbowałem rozwiązać. Ale w git zawsze jest wiele sposobów na zrobienie czegoś i jest wiele odpowiedzi, które podobały się różnym ludziom, więc wydaje mi się, że dwuznaczność nie sprawia zbyt wielu ludziom trudności
1800 INFORMACJE
1
git rebase --onto 2 3 HEADwłaściwie oznacza rebase na 2, z zatwierdzeniami między 3 a HEAD (HEAD jest opcjonalny, lub 5 w tym przypadku)
neaumusic

Odpowiedzi:

76

Aby połączyć wersję 3 i 4 w jedną wersję, możesz użyć git rebase. Jeśli chcesz usunąć zmiany w wersji 3, musisz użyć polecenia edycji w interaktywnym trybie bazy. Jeśli chcesz połączyć zmiany w jedną wersję, użyj squasha.

Z powodzeniem zastosowałem tę technikę squasha, ale nigdy wcześniej nie musiałem usuwać wersji. Mam nadzieję, że dokumentacja git-rebase pod „Podziałem zobowiązań” powinna dać ci wystarczająco dużo pomysłu, aby to rozgryźć. (Lub ktoś inny może wiedzieć).

Z dokumentacji git :

Rozpocznij od najstarszego zatwierdzenia, które chcesz zachować bez zmian:

git rebase -i <after-this-commit>

Zostanie uruchomiony edytor ze wszystkimi zatwierdzeniami w bieżącym oddziale (ignorując zatwierdzenia scalania), które pojawiają się po danym zatwierdzeniu. Możesz zmienić kolejność zatwierdzeń na tej liście do zawartości twojego serca i możesz je usunąć. Lista wygląda mniej więcej tak:

wybierz deadbee Oneline tego zatwierdzenia
pick fa1afe1 Oneline następnego zatwierdzenia
...

Opisy online są wyłącznie dla twojej przyjemności; git-rebase nie będzie na nie patrzył, ale na nazwy zatwierdzeń (w tym przykładzie „deadbee” i „fa1afe1”), więc nie usuwaj ani nie edytuj nazw.

Zastępując polecenie „pick” poleceniem „edit”, możesz powiedzieć git-rebase, aby przestał działać po zastosowaniu tego zatwierdzenia, abyś mógł edytować pliki i / lub komunikat zatwierdzenia, zmienić zatwierdzenie i kontynuować bazowanie.

Jeśli chcesz złożyć dwa lub więcej zatwierdzeń w jeden, zamień polecenie „pick” na „squash” dla drugiego i kolejnego zatwierdzenia. Jeśli zatwierdzenia miały różnych autorów, przypisuje zgniecione zatwierdzenie autorowi pierwszego zatwierdzenia.

garethm
źródło
42
-1 Pytanie dobrze zdefiniowane, ale ta odpowiedź nie jest tak jasna. Autor nie mówi, jakie jest dokładne rozwiązanie.
Aleksandr Levchuk
1
To jest zły trop. Sekcja ZGODNOŚCI ROZDZIELCZEJ jest nieprawidłowa. Chcesz przeczytać znacznie więcej w instrukcji - patrz odpowiedź @Rares Vernica.
Aleksandr Levchuk
1
@AleksandrLevchuk Pytanie nie jest dobrze zdefiniowane: nie jest jasne, w jaki sposób pytanie brzmi, czy zestaw zmian w 3 ma zostać zachowany, czy odrzucony. Zgadzam się, że jeśli zmiany mają zostać odrzucone, inne odpowiedzi oferują łatwiejsze podejście. Jeśli zmiany zostaną zachowane, będzie to operacja czysto kosmetyczna. Oba podejścia przepisują historię w sposób niebezpieczny, jeśli inni mogliby oprzeć swoją pracę na błędnej historii; w takim przypadku czyszczenie kosmetyczne nie powinno być wykonane, a usunięcie zmiany lepiej byłoby wykonać za pomocą git revert.
Theodore Murdock
124

Zgodnie z tym komentarzem (i sprawdziłem, że to prawda) odpowiedź rado jest bardzo bliska, ale pozostawia gita w stanie odłączonej głowy. Zamiast tego usuń HEADi użyj tego, aby usunąć <commit-id>z gałęzi, w której jesteś:

git rebase --onto <commit-id>^ <commit-id>
kareem
źródło
1
Gdybyś mógł mi powiedzieć, jak usunąć ten identyfikator z historii na podstawie samego identyfikatora, byłbyś moim bohaterem.
kayleeFrye_onDeck
Czy możesz dołączyć wyjaśnienie, co magiczne polecenie robi w odpowiedzi? Tj. Co oznacza każdy parametr?
alexandroid
to jest niesamowite, ale co jeśli chcę usunąć pierwsze zatwierdzenie w historii? (dlatego tu przybyłem: P)
Sterling Camden
2
Z jakiegoś powodu nic się nie dzieje, kiedy to uruchamiam. Jednak zmiana ^na ~1sprawiła, że ​​działało.
Antimony
Znacznie późno, ale dodam, że jeśli napotkasz konflikty scalania, przerwij tworzenie bazy, a następnie uruchom ją ponownie --strategy-option theirsna końcu.
LastStar007
122

Oto sposób na usunięcie nieinteraktywnie określonego <commit-id>, znając tylko te <commit-id>, które chcesz usunąć:

git rebase --onto <commit-id>^ <commit-id> HEAD
rado
źródło
2
Pracowałem też dla mnie. Przy okazji, co robi operator ^? Czy to oznacza następne zatwierdzenie po określonym?
hopia
3
@hopia oznacza (pierwszego) rodzica określonego zatwierdzenia. Zobacz „wersje pomocy git”
Emil Styrke
12
Zobacz zalecenie @ kareem w jego odpowiedzi, aby pominąć HEAD, aby uniknąć odłączenia głowy.
mklement0
1
Znacznie łatwiejsze niż ustalenie, ile osób zobowiązuje się do wyszukiwania.
Dana Woodman,
1
to jest niesamowite, ale co jeśli chcę usunąć pierwsze zatwierdzenie w historii? (dlatego tu przybyłem: P)
Sterling Camden
76

Jak wspomniano wcześniej, git-rebase (1) jest twoim przyjacielem. Zakładając, że zatwierdzenia znajdują się w twoim masteroddziale, zrobiłbyś:

git rebase --onto master~3 master~2 master

Przed:

1---2---3---4---5  master

Po:

1---2---4'---5' master

Z git-rebase (1):

Zakres zatwierdzeń można również usunąć za pomocą rebase. Jeśli mamy następującą sytuację:

E---F---G---H---I---J  topicA

następnie polecenie

git rebase --onto topicA~5 topicA~3 topicA

spowodowałoby usunięcie zatwierdzeń F i G:

E---H'---I'---J'  topicA

Jest to przydatne, jeśli F i G były w jakiś sposób wadliwe lub nie powinny być częścią tematu A. Zauważ, że argumentem --onto i parametrem może być dowolny poprawny zatwierdzenie.

rvernica
źródło
3
nie powinno tak być --onto master~3 master~1?
Matthias
3
Jeśli chcesz po prostu usunąć ostatnie zatwierdzenie, to --onto master ~ 1 master
MikeHoss
Chciałbym nosić ten zol. ale dostaję MERGE CONFLICTbłąd. Użyłem scenariusza wymienionego w stackoverflow.com/questions/2938301/remove-specific-commit i nie mogę usunąć drugiego zatwierdzenia w tym przykładzie.
maan81
22

Jeśli wszystko, co chcesz zrobić, to usunąć zmiany wprowadzone w wersji 3, możesz użyć git revert.

Git revert tworzy po prostu nową wersję ze zmianami, które cofają wszystkie zmiany w cofniętej wersji.

Oznacza to, że zachowujesz informacje zarówno o niechcianym zatwierdzeniu, jak i zatwierdzeniu, które usuwa te zmiany.

Jest to prawdopodobnie o wiele bardziej przyjazne, jeśli w ogóle możliwe jest, że ktoś wyciągnął z twojego repozytorium w międzyczasie, ponieważ przywracanie jest w zasadzie tylko standardowym zatwierdzeniem.

SpoonMeiser
źródło
4
Niestety, nie jest to dla mnie dobre rozwiązanie, ponieważ ktoś przypadkowo przeznaczył 100 MB bzdur na repozytorium, powiększając rozmiar i spowalniając interfejs sieciowy.
Stephen Smith
18

Wszystkie dotychczasowe odpowiedzi nie uwzględniają problemu końcowego:

Czy istnieje skuteczna metoda, jeśli po usunięciu istnieją setki wersji?

Kroki są następujące, ale w celach informacyjnych załóżmy następującą historię:

[master] -> [hundreds-of-commits-including-merges] -> [C] -> [R] -> [B]

C : zatwierdzenie tuż po zatwierdzeniu do usunięcia (wyczyść)

R : Zatwierdzenie do usunięcia

B : zatwierdzenie tuż przed zatwierdzeniem do usunięcia (podstawa)

Ze względu na ograniczenie „setek wersji” zakładam następujące warunki wstępne:

  1. jest pewne żenujące zobowiązanie, którego nigdy nie chciałbyś istnieć
  2. istnieją kolejne ZERO, które faktycznie zależą od tego żenującego zatwierdzenia (zero konfliktów przy przywróceniu)
  3. nie obchodzi cię, że zostaniesz wymieniony jako „Committer” spośród setek interweniujących zmian („Autor” zostanie zachowany)
  4. nigdy nie udostępniłeś repozytorium
    • lub rzeczywiście masz wystarczający wpływ na wszystkich ludzi, którzy kiedykolwiek sklonowali historię z tym zobowiązaniem, aby przekonać ich do korzystania z nowej historii
    • i nie dbam o przepisywanie historii

Jest to dość restrykcyjny zestaw ograniczeń, ale istnieje interesująca odpowiedź, która faktycznie działa w tym przypadku narożnika.

Oto kroki:

  1. git branch base B
  2. git branch remove-me R
  3. git branch save
  4. git rebase --preserve-merges --onto base remove-me

Jeśli naprawdę nie ma konfliktów, powinno to przebiegać bez dalszych zakłóceń. Jeśli występują konflikty, możesz je rozwiązać i / rebase --continuelub po prostu żyć z zażenowaniem i rebase --abort.

Teraz powinno być na masterktóre nie ma już popełnić R w nim. Te savepunkty odgałęzienia do miejsca, gdzie były wcześniej, w przypadku, gdy chcesz się pogodzić.

To, jak chcesz zorganizować przeniesienie wszystkich osób do nowej historii, zależy od Ciebie. Trzeba będzie zapoznać się z stash, reset --hard, i cherry-pick. I można usunąć base, remove-meoraz saveoddziały

jdsumsion
źródło
jak mogę to lubić 150000 razy?
Gena Moroz
Pracowałem dla mnie, aby usunąć 3 commity po kolei ze środka historii oddziału, w którym ktoś popełnił kilka plików 150 MB.
Marcos,
3

Wylądowałem również w podobnej sytuacji. Użyj interaktywnej bazy za pomocą poniższego polecenia i podczas wybierania upuść trzeci zatwierdzenie.

git rebase -i remote/branch
Sankalp
źródło
2

Oto scenariusz, z którym miałem do czynienia i sposób jego rozwiązania.

[branch-a]

[Hundreds of commits] -> [R] -> [I]

oto Rzatwierdzenie, które musiałem usunąć, i Ijest to jedno zatwierdzenie, które nastąpi późniejR

Zrobiłem rewizję i zmiażdżyłem je razem

git revert [commit id of R]
git rebase -i HEAD~3

Podczas interaktywnego squasha rebase ostatnie 2 zatwierdzenia.

Gautam
źródło
0

Odpowiedzi rado i kareem nic dla mnie nie robią (pojawia się tylko komunikat „Aktualna gałąź jest aktualna.”). Być może dzieje się tak, ponieważ symbol „^” nie działa w konsoli Windows. Jednak zgodnie z tym komentarzem zastąpienie „^” przez „~ 1” rozwiązuje problem.

git rebase --onto <commit-id>^ <commit-id>
fdermishin
źródło