Jak zgnieść wszystkie zobowiązania git w jeden?


479

Jak zredukować całe repozytorium do pierwszego zatwierdzenia?

Mogę zmienić podstawę do pierwszego zatwierdzenia, ale to dałoby mi 2 zatwierdzenia. Czy istnieje sposób na odniesienie do zatwierdzenia przed pierwszym?


8
„zatwierdzenie przed pierwszym”?
— innaM

31
@innaM - To pierwotne zatwierdzenie zrodziło git . (Mam nadzieję, że humor przenika wystarczająco dobrze przez sieć).
— ripper234

11
Dla tych, którzy przyjdą później na to pytanie, skorzystaj z bardziej nowoczesnej odpowiedzi .
— Droogans

1
Powiązane, ale nie duplikat ( --roottak naprawdę nie jest najlepszym rozwiązaniem do zgniatania wszystkich zatwierdzeń, jeśli jest ich wiele): Czy połączyć pierwsze dwa zatwierdzenia z repozytorium Git? .

2
IMO: jest to najlepszy z @MrTux: stackoverflow.com/questions/30236694/... .
— J0hnG4lt

Odpowiedzi:


130

Być może najłatwiejszym sposobem jest po prostu utworzenie nowego repozytorium z bieżącym stanem kopii roboczej. Jeśli chcesz zachować wszystkie komunikaty zatwierdzeń, które możesz zrobić, git log > original.loga następnie edytować je dla początkowego komunikatu zatwierdzenia w nowym repozytorium:

rm -rf .git
git init
git add .
git commit

lub

git log > original.log
# edit original.log as desired
rm -rf .git
git init
git add .
git commit -F original.log

62
ale tracisz gałęzie tą metodą
— Olivier Refalo

150
Git ewoluował od czasu udzielenia tej odpowiedzi. Nie, tam jest prostszy i lepszy sposób: git rebase -i --root. Patrz: stackoverflow.com/a/9254257/109618
— David J.

5
Może to działać w niektórych przypadkach, ale zasadniczo nie jest to odpowiedź na pytanie. Dzięki temu przepisowi stracisz całą konfigurację i wszystkie inne gałęzie.
— iwein

3
To okropne rozwiązanie, które jest niepotrzebnie destrukcyjne. Proszę, nie używaj tego.
— Daniel Kamil Kozar

5
również złamie submoduły. -1
— Krum

693

Od wersji 1.6.2 możesz używać git rebase --root -i.

Dla każdego zatwierdzenia oprócz pierwszego zmień pickna squash.


49
Dodaj kompletny, działający przykład polecenia, który odpowiada na pierwotne pytanie.
— Jake,

29
Chciałbym przeczytać to przed wysadzeniem całego mojego repozytorium, tak jak zaakceptowana odpowiedź mówi: /
— Mike Chamberlain,

38
Ta odpowiedź jest w porządku , ale jeśli interaktywnie zmieniasz więcej niż, powiedzmy, 20 zatwierdzeń, interaktywny rebase będzie prawdopodobnie zbyt wolny i niewygodny. Prawdopodobnie będzie ci ciężko próbować zgnieść setki lub tysiące zmian. W takim przypadku wybrałbym miękki lub mieszany reset do głównego zatwierdzenia, a następnie ponownie polecam.

20
@Pred Nie używaj squashdo wszystkich zatwierdzeń. Najpierw musi być pick.
— Geert

14
Jeśli masz wiele zatwierdzeń, trudno jest ręcznie zmienić opcję „wybierz” na „squash”. Użyj :% s / pick / squash / gw wierszu poleceń VIM, aby zrobić to szybciej.
— eilas

314

Aktualizacja

Zrobiłem alias git squash-all.
Przykładowe zastosowania : git squash-all "a brand new start".

[alias]
  squash-all = "!f(){ git reset $(git commit-tree HEAD^{tree} -m \"${1:-A new start}\");};f"

Zastrzeżenie : pamiętaj, aby podać komentarz, w przeciwnym razie użyty zostanie domyślny komunikat zatwierdzenia „Nowy początek”.

Lub możesz utworzyć alias za pomocą następującego polecenia:

git config --global alias.squash-all '!f(){ git reset $(git commit-tree HEAD^{tree} -m "${1:-A new start}");};f'

Jedna wkładka

git reset $(git commit-tree HEAD^{tree} -m "A new start")

Uwaga : tutaj „ A new start” to tylko przykład, możesz swobodnie używać własnego języka.

TL; DR

Nie musisz zgniatać, użyj, git commit-treeaby stworzyć sierocą zmianę i idź z nią.

Wyjaśnić

  1. utwórz pojedynczy zatwierdzenie przez git commit-tree

    Co git commit-tree HEAD^{tree} -m "A new start"to jest:

    Tworzy nowy obiekt zatwierdzenia na podstawie podanego obiektu drzewa i emituje nowy identyfikator obiektu zatwierdzenia na standardowym wyjściu. Komunikat dziennika jest odczytywany ze standardowego wejścia, chyba że podano opcje -m lub -F.

    Wyrażenie HEAD^{tree}oznacza obiekt drzewa odpowiadający HEAD, a mianowicie czubek bieżącej gałęzi. zobacz drzewa obiektów i popełnić-obiektów .

  2. zresetuj bieżącą gałąź do nowego zatwierdzenia

    Następnie git resetpo prostu zresetuj bieżącą gałąź do nowo utworzonego obiektu zatwierdzenia.

W ten sposób nic nie jest dotykane w obszarze roboczym, ani nie ma potrzeby korzystania z bazy / squash, co czyni go naprawdę szybkim. Potrzebny czas nie ma znaczenia dla wielkości repozytorium ani głębokości historii.

Odmiana: nowe repozytorium z szablonu projektu

Jest to przydatne do utworzenia „początkowego zatwierdzenia” w nowym projekcie przy użyciu innego repozytorium jako szablonu / archetype / seed / skeleton. Na przykład:

cd my-new-project
git init
git fetch --depth=1 -n https://github.com/toolbear/panda.git
git reset --hard $(git commit-tree FETCH_HEAD^{tree} -m "initial commit")

Pozwala to uniknąć dodawania repozytorium szablonu jako zdalnego ( originlub w inny sposób) i zwija historię repozytorium szablonu do początkowego zatwierdzenia.


6
Wyjaśniono tutaj składnię wersji git (HEAD ^ {drzewo}) na wypadek, gdyby ktoś się zastanawiał: jk.gs/gitrevisions.html
— Colin Bowern

1
Czy to resetuje zarówno lokalne, jak i zdalne repozytorium, czy tylko jedno z nich?
— aleclarson

4
@aleclarson, to resetuje tylko bieżącą gałąź w lokalnym repozytorium, użyj git push -fdo propagacji.
— ryenus

2
Znalazłem tę odpowiedź, szukając sposobu na rozpoczęcie nowego projektu z repozytorium szablonów projektów, które nie wymagało udziału git clone. Jeśli dodać --harddo git reseti przełącznik HEADze FETCH_HEADw git commit-treemożna utworzyć początkowy popełnić po pobraniu repo szablonu. Zredagowałem odpowiedź z sekcją na końcu demonstrującą to.
— toolbear

4
Można się tego pozbyć, ale używając${1?Please enter a message}
— Elliot Cameron

172

Jeśli wszystko, co chcesz zrobić, to zmiażdżyć wszystkie swoje commity do głównego zatwierdzenia, a potem

git rebase --interactive --root

może działać, jest to niepraktyczne dla dużej liczby commits (na przykład setek commits), ponieważ operacja rebase prawdopodobnie będzie działać bardzo wolno, aby wygenerować listę zatwierdzeń interaktywnego edytora rebase, a także uruchomić sam rebase.

Oto dwa szybsze i bardziej wydajne rozwiązania, gdy zgniatasz dużą liczbę zatwierdzeń:

Alternatywne rozwiązanie nr 1: gałęzie osierocone

Możesz po prostu utworzyć nową gałąź osieroconą na końcu (tj. Najnowszym zatwierdzeniu) twojej aktualnej gałęzi. Ta gałąź sieroca tworzy początkowe zatwierdzenie katalogu głównego całkowicie nowego i osobnego drzewa historii zatwierdzeń, co w rzeczywistości jest równoważne zmiażdżeniu wszystkich twoich zatwierdzeń:

git checkout --orphan new-master master
git commit -m "Enter commit message for your new initial commit"

# Overwrite the old master branch reference with the new one
git branch -M new-master master

Dokumentacja:

Alternatywne rozwiązanie # 2: miękki reset

Innym skutecznym rozwiązaniem jest po prostu użycie mieszanego lub miękkiego resetu do głównego zatwierdzenia <root>:

git branch beforeReset

git reset --soft <root>
git commit --amend

# Verify that the new amended root is no different
# from the previous branch state
git diff beforeReset

Dokumentacja:


22
Alternatywne rozwiązanie # 1: sieroce gałęzie - skały!
— Thomas

8
Alternatywne rozwiązanie nr 1 FTW. Wystarczy dodać, jeśli chcesz przekazać zmiany do pilota, zrób git push origin master --force.
— Eddy Verbruggen,

1
nie należy zapominaćgit push --force
— NecipAllef

Nie rób sierocej gałęzi na kodzie (tj. Nie rób alternatywnego rozwiązania nr 1 powyżej), jeśli masz zamiar przeforsować otwarte żądanie ściągnięcia Github !!! Github zamknie twój PR, ponieważ bieżąca głowa nie jest potomkiem przechowywanej głowy sha .
— Andrew Mackie

Alternatywne rozwiązanie nr 1 pozwala także uniknąć konfliktów scalania, które mogą wystąpić podczas zgniatania
— popełnionych błędów

52
echo "message" | git commit-tree HEAD^{tree}

Spowoduje to utworzenie osieroconego zatwierdzenia z drzewem HEAD i wyprowadzenie jego nazwy (SHA-1) na standardowym wyjściu. Następnie zresetuj tam swój oddział.

git reset SHA-1

27
git reset $(git commit-tree HEAD^{tree} -m "commit message")ułatwiłoby to.
— ryenus

4
^ TO! - powinna być odpowiedzią. Nie do końca pewny, czy to był intencja autora, ale był mój (potrzebowałem nieskazitelnego repo z jednym zatwierdzeniem, a to wykona zadanie).
— chesterbr

@ryenus, twoje rozwiązanie zrobiło dokładnie to, czego szukałem. Jeśli dodasz swój komentarz jako odpowiedź, zaakceptuję go.
— tldr

2
Powodem, dla którego sam nie zasugerowałem wariantu podpowłoki jest to, że nie będzie on działać na cmd.exe w systemie Windows.
— kusma

W monitach systemu Windows może być konieczne podanie ostatniego parametru: echo "message" | git commit-tree "HEAD^{tree}"
— Bernard

41

Oto, jak to zrobiłem, na wypadek, gdyby zadziałało to dla kogoś innego:

Pamiętaj, że zawsze istnieje ryzyko zrobienia czegoś takiego i nigdy nie jest to zły pomysł, aby utworzyć gałąź składowania przed rozpoczęciem.

Zacznij od zalogowania

git log --oneline

Przewiń do pierwszego zatwierdzenia, skopiuj SHA

git reset --soft <#sha#>

Zamień <#sha#>w / SHA skopiowane z dziennika

git status

Upewnij się, że wszystko jest zielone, w przeciwnym razie uruchom git add -A

git commit --amend

Zmień wszystkie bieżące zmiany w bieżącym pierwszym zatwierdzeniu

Teraz wymusz push tę gałąź, a ona zastąpi to, co tam jest.


1
Doskonała opcja! Naprawdę proste.
— twicejr

1
To więcej niż fantastyczne! Dzięki!
— Matt Komarnicki

1
Zauważ, że to faktycznie wydaje się pozostawić historię. Osierocasz go, ale wciąż tam jest.
— Brad

Bardzo przydatna odpowiedź ... ale należy pamiętać, że po poleceniu zmiany znajdziesz się w edytorze vim ze specjalną składnią. ESC, ENTER,: x jest twoim przyjacielem.
— Erich Kuester

Doskonała opcja!
— danivicario,

36

Czytałem coś o stosowaniu przeszczepów, ale nigdy nie analizowałem tego zbyt wiele.

W każdym razie możesz ręcznie zgnieść ostatnie 2 zatwierdzenia za pomocą czegoś takiego:

git reset HEAD~1
git add -A
git commit --amend

4
To jest właściwie odpowiedź, której szukałem, szkoda, że ​​nie została zaakceptowana!
— Jay

To niesamowita odpowiedź
— Mistrz Yoda

36

Najprościej jest użyć polecenia „hydraulika”, update-refaby usunąć bieżącą gałąź.

Nie można użyć, git branch -Dponieważ ma zawór bezpieczeństwa, aby zatrzymać usuwanie aktualnego odgałęzienia.

Powoduje to powrót do stanu „początkowego zatwierdzenia”, w którym możesz zacząć od nowego początkowego zatwierdzenia.

git update-ref -d refs/heads/master
git commit -m "New initial commit"


15

W jednym wierszu z 6 słów

git checkout --orphan new_root_branch  &&  git commit

@AlexanderMills, powinieneś przeczytać git help checkouto--orphan
— Kyb

czy możesz podać link do tego dokumentu, aby każdy, kto to czyta, nie musiał ręcznie go szukać?
— Alexander Mills,

1
łatwy. oto jestgit help checkout --orphan
— Kyb

8

utwórz kopię zapasową

git branch backup

zresetować do określonego zatwierdzenia

git reset --soft <#root>

następnie dodaj wszystkie pliki do inscenizacji

git add .

zatwierdzanie bez aktualizacji wiadomości

git commit --amend --no-edit

pchnij nowy oddział zgniecionymi zobowiązaniami do repo

git push -f

Czy to zachowa poprzednie komunikaty zatwierdzania?
— not2qubit

1
@ not2qubit nie to nie zachowa poprzednich komunikatów zatwierdzenia, zamiast zatwierdzenia nr 1, zatwierdzenia nr 2, zatwierdzenia nr 3 wszystkie zmiany w tych zatwierdzeniach zostaną spakowane w jednym zatwierdzeniu nr 1. Zatwierdzenie nr 1 to <root>zatwierdzenie, do którego zresetowano ponownie. git commit --amend --no-editzatwierdzi wszystkie zmiany w bieżącym zatwierdzeniu, które <root>nie wymaga edycji komunikatu zatwierdzenia.
— David Morton

5

Do zgniatania za pomocą przeszczepów

Dodaj plik .git/info/grafts, umieść tam skrót zatwierdzenia, który chcesz stać się Twoim rootem

git log zacznie teraz od tego zatwierdzenia

Aby działało to „prawdziwie” git filter-branch


1

Ta odpowiedź poprawia się w przypadku kilku powyżej (proszę głosować na nie), zakładając, że oprócz utworzenia jednego zatwierdzenia (brak historii bez rodziców), chcesz również zachować wszystkie dane dotyczące tego zatwierdzenia:

  • Autor (imię i adres e-mail)
  • Data własna
  • Commiter (imię i adres e-mail)
  • Data popełnienia
  • Zatwierdź komunikat w dzienniku

Oczywiście zatwierdzenie SHA nowego / pojedynczego zatwierdzenia ulegnie zmianie, ponieważ reprezentuje nową (nie) historię, stając się rodzicielską / zatwierdzeniem root.

Można to zrobić, czytając git logi ustawiając niektóre zmienne dla git commit-tree. Zakładając, że chcesz utworzyć pojedyncze zatwierdzenie masterw nowym oddziale one-commit, zachowując powyższe dane zatwierdzenia:

git checkout -b one-commit master ## create new branch to reset
git reset --hard \
$(eval "$(git log master -n1 --format='\
COMMIT_MESSAGE="%B" \
GIT_AUTHOR_NAME="%an" \
GIT_AUTHOR_EMAIL="%ae" \
GIT_AUTHOR_DATE="%ad" \
GIT_COMMITTER_NAME="%cn" \
GIT_COMMITTER_EMAIL="%ce" \
GIT_COMMITTER_DATE="%cd"')" 'git commit-tree master^{tree} <<COMMITMESSAGE
$COMMIT_MESSAGE
COMMITMESSAGE
')

1

Aby to zrobić, możesz zresetować swoje lokalne repozytorium git do pierwszego hashtagu zatwierdzenia, więc wszystkie zmiany po tym zatwierdzeniu zostaną wycofane ze sceny, a następnie możesz zatwierdzić za pomocą opcji --amend.

git reset your-first-commit-hashtag
git add .
git commit --amend

A jeśli to konieczne, edytuj pierwsze zatwierdzenie i zapisz plik.


1

Dla mnie działało to tak: miałem w sumie 4 zatwierdzenia i użyłem interaktywnego rebase:

git rebase -i HEAD~3

Pozostało pierwsze zatwierdzenie i wziąłem 3 ostatnie zatwierdzenia.

Jeśli utkniesz w edytorze, który pojawi się obok, zobaczysz coś takiego:

pick fda59df commit 1
pick x536897 commit 2
pick c01a668 commit 3

Musisz wziąć pierwsze zatwierdzenie i zgnieść innych. Co powinieneś mieć to:

pick fda59df commit 1
squash x536897 commit 2
squash c01a668 commit 3

W tym celu użyj przycisku INSERT, aby zmienić tryb „wstaw” i „edytuj”.

Aby zapisać i wyjść z edytora, użyj :wq. Jeśli kursor znajduje się między tymi wierszami zatwierdzenia lub gdzieś indziej, wciśnij ESC i spróbuj ponownie.

W rezultacie miałem dwa zatwierdzenia: pierwsze, które pozostało, a drugie z komunikatem „To jest kombinacja 3 zatwierdzeń”.

Sprawdź szczegóły tutaj: https://makandracards.com/makandra/527-squash-several-git-commits-into-a-single-commit


0

Zwykle robię to w ten sposób:

  • Upewnij się, że wszystko zostało zatwierdzone, i zapisz najnowszy identyfikator zatwierdzenia na wypadek, gdyby coś poszło nie tak, lub utwórz oddzielną gałąź jako kopię zapasową

  • Uruchom, git reset --soft `git rev-list --max-parents=0 --abbrev-commit HEAD`aby zresetować głowę do pierwszego zatwierdzenia, ale pozostaw indeks bez zmian. Wszystkie zmiany od pierwszego zatwierdzenia będą teraz wydawać się gotowe do zatwierdzenia.

  • Uruchom, git commit --amend -m "initial commit"aby zmienić swoje zatwierdzenie do pierwszego zatwierdzenia i zmienić komunikat zatwierdzenia, lub jeśli chcesz zachować istniejący komunikat zatwierdzenia, możesz uruchomićgit commit --amend --no-edit

  • Uruchom, git push -faby wymusić wprowadzenie zmian

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.