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?

Verhogen
źródło
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
Pat Notz
źródło
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.

Jordan Lewis
źródło
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.

ryenus
źródło
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:


źródło
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
kusma
źródło
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.

Logan
źródło
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
R. Martinho Fernandes
źródło
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"
CB Bailey
źródło
15

W jednym wierszu z 6 słów

git checkout --orphan new_root_branch  &&  git commit
kyb
źródło
@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
David Morton
źródło
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

dimus
źródło
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
')
javabrett
źródło
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.

T.EL MOUSSAOUI
źródło
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

Vlad
źródło
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

Kent Munthe Caspersen
źródło