Wie kann man alle Git-Commits zu einem zusammenfassen?

480

Wie quetschen Sie Ihr gesamtes Repository bis zum ersten Commit?

Ich kann auf das erste Commit zurückgreifen, aber das würde mir 2 Commits lassen. Gibt es eine Möglichkeit, das Commit vor dem ersten zu referenzieren?

Verhogen
quelle
8
"das Commit vor dem ersten"?
InnaM
31
@innaM - Es ist das Ur-Commit, das den Idioten gezeugt hat . (Hoffnungen Humor geht gut genug durch das Internet).
Ripper234
11
Verwenden Sie für diejenigen, die später zu dieser Frage kommen, unbedingt die modernere Antwort .
Droogans
1
Verwandte, aber kein Duplikat ( --rootist eigentlich nicht die beste Lösung, um alle Commits zu quetschen, wenn viele davon zu quetschen sind): Kombinieren Sie die ersten beiden Commits eines Git-Repositorys? .
2
Imo: Dies ist das Beste von @MrTux: stackoverflow.com/questions/30236694/… .
J0hnG4lt

Antworten:

130

Am einfachsten ist es vielleicht, einfach ein neues Repository mit dem aktuellen Status der Arbeitskopie zu erstellen. Wenn Sie alle Festschreibungsnachrichten behalten möchten, können Sie dies zuerst tun git log > original.logund dann für Ihre erste Festschreibungsnachricht im neuen Repository bearbeiten:

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

oder

git log > original.log
# edit original.log as desired
rm -rf .git
git init
git add .
git commit -F original.log
Pat Notz
quelle
62
aber Sie verlieren Zweige mit dieser Methode
Olivier Refalo
150
Git hat sich weiterentwickelt, seit diese Antwort gegeben wurde. Nein, es gibt einen einfacheren und besseren Weg : git rebase -i --root. Siehe: stackoverflow.com/a/9254257/109618
David J.
5
Dies mag in einigen Fällen funktionieren, ist jedoch im Wesentlichen nicht die Antwort auf die Frage. Mit diesem Rezept verlieren Sie auch Ihre gesamte Konfiguration und alle anderen Zweige.
iwein
3
Dies ist eine schreckliche Lösung, die unnötig destruktiv ist. Bitte benutze es nicht.
Daniel Kamil Kozar
5
wird auch Submodule brechen. -1
Krum
694

Ab Git 1.6.2 können Sie verwenden git rebase --root -i.

Wechseln Sie für jedes Commit mit Ausnahme des ersten pickzu squash.

Jordan Lewis
quelle
49
Bitte fügen Sie ein vollständiges Beispiel für einen funktionierenden Befehl hinzu, das die ursprüngliche Frage beantwortet.
Jake
29
Ich wünschte, ich würde dies lesen, bevor ich mein gesamtes Repository wegblase, wie die akzeptierte Antwort sagt: /
Mike Chamberlain
38
Diese Antwort ist in Ordnung , aber wenn Sie interaktiv mehr als Rebasing, sagen wir, 20 Commits, wird die interaktive rebase wahrscheinlich sein Weg langsam und unhandlich. Es wird Ihnen wahrscheinlich schwer fallen, Hunderte oder Tausende von Commits zu unterdrücken. Ich würde mit einem weichen oder gemischten Reset zum Root-Commit gehen und dann in diesem Fall erneut festlegen.
20
@Pred Nicht squashfür alle Commits verwenden. Das allererste muss sein pick.
Geert
14
Wenn Sie viele Commits haben, ist es schwierig, 'pick' manuell in 'squash' zu ändern. Verwenden Sie :% s / pick / squash / g in der VIM-Befehlszeile, um dies schneller zu erledigen.
Eilas
314

Aktualisieren

Ich habe einen Alias ​​gemacht git squash-all.
Anwendungsbeispiel : git squash-all "a brand new start".

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

Vorsichtsmaßnahme : Denken Sie daran, einen Kommentar abzugeben, da sonst die Standard-Commit-Meldung "Ein neuer Start" verwendet wird.

Oder Sie können den Alias ​​mit dem folgenden Befehl erstellen:

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

Einzeiler

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

Hinweis : Hier ist " A new start" nur ein Beispiel. Sie können auch Ihre eigene Sprache verwenden.

TL; DR

Keine Notwendigkeit zu quetschen, verwenden git commit-tree, um ein Orphan Commit zu erstellen und damit zu gehen.

Erklären

  1. Erstellen Sie ein einzelnes Commit über git commit-tree

    Was git commit-tree HEAD^{tree} -m "A new start"tut ist:

    Erstellt ein neues Festschreibungsobjekt basierend auf dem bereitgestellten Baumobjekt und gibt die neue Festschreibungsobjekt-ID auf stdout aus. Die Protokollnachricht wird von der Standardeingabe gelesen, sofern nicht die Optionen -m oder -F angegeben sind.

    Der Ausdruck HEAD^{tree}bedeutet das Baumobjekt HEAD, das der Spitze Ihres aktuellen Zweigs entspricht. siehe Tree-Objects und Commit-Objects .

  2. Setzen Sie den aktuellen Zweig auf das neue Commit zurück

    git resetSetzen Sie dann einfach den aktuellen Zweig auf das neu erstellte Commit-Objekt zurück.

Auf diese Weise wird nichts im Arbeitsbereich berührt, und es besteht keine Notwendigkeit für Rebase / Squash, was es sehr schnell macht. Die benötigte Zeit spielt für die Größe des Repositorys oder die Verlaufstiefe keine Rolle.

Variation: Neues Repo aus einer Projektvorlage

Dies ist nützlich, um das "anfängliche Festschreiben" in einem neuen Projekt unter Verwendung eines anderen Repositorys als Vorlage / Archetyp / Startwert / Skelett zu erstellen. Zum Beispiel:

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")

Dadurch wird vermieden, dass das Vorlagen-Repo als Remote ( originoder auf andere Weise) hinzugefügt wird, und der Verlauf des Vorlagen-Repos wird in Ihrem anfänglichen Commit zusammengefasst.

Ryenus
quelle
6
Die Syntax der Git-Revisionssyntax (HEAD ^ {tree}) wird hier erklärt, falls sich jemand anderes wundert: jk.gs/gitrevisions.html
Colin Bowern
1
Setzt dies sowohl das lokale als auch das Remote-Repository zurück oder nur eines davon?
Aleclarson
4
@aleclarson, dies setzt nur den aktuellen Zweig im lokalen Repository zurück, der git push -ffür die Weitergabe verwendet wird.
Ryenus
2
Ich habe diese Antwort gefunden, als ich nach einer Möglichkeit gesucht habe, ein neues Projekt aus einem Projektvorlagen-Repository zu starten, an dem es nicht beteiligt war git clone. Wenn Sie hinzufügen --hardzu git resetund Schalter HEADmit FETCH_HEADin der git commit-treeSie eine erste begehen erstellen , nachdem die Vorlage Repo holen. Ich habe die Antwort mit einem Abschnitt am Ende bearbeitet, der dies demonstriert.
Toolbear
4
Sie könnten diese "Einschränkung" loswerden, aber nur mit${1?Please enter a message}
Elliot Cameron
172

Wenn Sie nur alle Ihre Commits auf das Root-Commit reduzieren möchten, dann während

git rebase --interactive --root

kann funktionieren, ist für eine große Anzahl von Commits (z. B. Hunderte von Commits) unpraktisch, da der Rebase-Vorgang wahrscheinlich sehr langsam ausgeführt wird, um die Commit-Liste des interaktiven Rebase-Editors zu generieren und die Rebase selbst auszuführen.

Hier sind zwei schnellere und effizientere Lösungen, wenn Sie eine große Anzahl von Commits quetschen:

Alternative Lösung Nr. 1: Waisenzweige

Sie können einfach einen neuen verwaisten Zweig an der Spitze (dh dem letzten Commit) Ihres aktuellen Zweigs erstellen. Dieser verwaiste Zweig bildet das anfängliche Root-Commit eines völlig neuen und separaten Commit-Verlaufsbaums, der effektiv dem Squashing aller Ihrer Commits entspricht:

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

Dokumentation:

Alternative Lösung Nr. 2: Soft-Reset

Eine andere effiziente Lösung besteht darin, einfach einen gemischten oder Soft-Reset für das Root-Commit zu verwenden <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

Dokumentation:


quelle
22
Alternative Lösung Nr. 1: Waisenzweige - Felsen!
Thomas
8
Alternative Lösung # 1 FTW. Nur um hinzuzufügen, wenn Sie Ihre Änderungen auf die Fernbedienung übertragen möchten, tun Sie dies git push origin master --force.
Eddy Verbruggen
1
sollte nicht vergessengit push --force
NecipAllef
Machen Sie keinen verwaisten Zweig für Code (dh machen Sie keine alternative Lösung Nr. 1 oben), wenn Sie im Begriff sind, auf eine offene Github-Pull-Anfrage zu pushen !!! Github wird Ihre PR schließen, da der aktuelle Kopf kein Nachkomme des gespeicherten Kopfes ist .
Andrew Mackie
Die alternative Lösung Nr. 1 vermeidet auch die Zusammenführungskonflikte , die beim Squashing von Commits auftreten können
FernAndr,
52
echo "message" | git commit-tree HEAD^{tree}

Dadurch wird ein verwaistes Commit mit dem HEAD-Baum erstellt und dessen Name (SHA-1) auf stdout ausgegeben. Dann setzen Sie einfach Ihren Zweig dort zurück.

git reset SHA-1
Kusma
quelle
27
git reset $(git commit-tree HEAD^{tree} -m "commit message")würde es einfacher machen.
Ryenus
4
^ DAS! - sollte eine Antwort sein. Ich bin mir nicht ganz sicher, ob es die Absicht des Autors war, aber meine (brauchte ein makelloses Repo mit einem einzigen Commit, und das erledigt den Job).
Chesterbr
@ryenus, deine Lösung hat genau das getan, wonach ich gesucht habe. Wenn Sie Ihren Kommentar als Antwort hinzufügen, werde ich ihn akzeptieren.
tldr
2
Der Grund, warum ich die Subshell-Variante selbst nicht vorgeschlagen habe, ist, dass sie unter cmd.exe unter Windows nicht funktioniert.
Kusma
In Windows-Eingabeaufforderungen müssen Sie möglicherweise den letzten Parameter echo "message" | git commit-tree "HEAD^{tree}"
Bernard
41

So habe ich das gemacht, nur für den Fall, dass es für jemand anderen funktioniert:

Denken Sie daran, dass es immer ein Risiko gibt, solche Dinge zu tun, und es ist nie eine schlechte Idee, vor dem Start einen sicheren Zweig zu erstellen.

Beginnen Sie mit der Protokollierung

git log --oneline

Scrollen Sie zum ersten Commit und kopieren Sie SHA

git reset --soft <#sha#>

Ersetzen Sie <#sha#>mit dem aus dem Protokoll kopierten SHA

git status

Stellen Sie sicher, dass alles grün ist, sonst laufen git add -A

git commit --amend

Ändern Sie alle aktuellen Änderungen am aktuellen ersten Commit

Drücken Sie nun diesen Zweig mit Gewalt und er überschreibt, was sich dort befindet.

Logan
quelle
1
Ausgezeichnete Option! Wirklich einfach.
zweimal am
1
Das ist mehr als fantastisch! Vielen Dank!
Matt Komarnicki
1
Beachten Sie, dass dies tatsächlich die Geschichte zu verlassen scheint. Du verwaist es, aber es ist immer noch da.
Brad
Sehr nützliche Antwort ... aber Sie sollten sich bewusst sein, dass Sie sich nach dem Änderungsbefehl im vim-Editor mit seiner speziellen Syntax befinden. ESC, ENTER ,: x ist dein Freund.
Erich Kuester
Ausgezeichnete Option!
Danivicario
36

Ich habe etwas über die Verwendung von Transplantaten gelesen, aber nie viel untersucht.

Wie auch immer, Sie können die letzten 2 Commits manuell mit so etwas quetschen:

git reset HEAD~1
git add -A
git commit --amend
R. Martinho Fernandes
quelle
4
Dies ist eigentlich die Antwort, nach der ich gesucht habe. Ich wünschte, es wäre die akzeptierte!
Jay
Dies ist eine erstaunliche Antwort
Meister Yoda
36

Am einfachsten ist es, update-refden aktuellen Zweig mit dem Befehl 'plumbing' zu löschen.

Sie können es nicht verwenden, git branch -Dda es ein Sicherheitsventil hat, das Sie daran hindert, den aktuellen Zweig zu löschen.

Dies versetzt Sie wieder in den Status "Erstes Festschreiben", in dem Sie mit einem neuen anfänglichen Festschreiben beginnen können.

git update-ref -d refs/heads/master
git commit -m "New initial commit"
CB Bailey
quelle
15

In einer Zeile mit 6 Wörtern

git checkout --orphan new_root_branch  &&  git commit
kyb
quelle
@ AlexanderMills, sollten Sie git help checkoutüber--orphan
Kyb
Können Sie dafür einen Link zu den Dokumenten erstellen, damit jeder, der dies liest, nicht manuell danach suchen muss?
Alexander Mills
1
einfach. hier ist esgit help checkout --orphan
kyb
8

Erstellen Sie ein Backup

git branch backup

auf das angegebene Commit zurücksetzen

git reset --soft <#root>

Fügen Sie dann alle Dateien zum Staging hinzu

git add .

Festschreiben, ohne die Nachricht zu aktualisieren

git commit --amend --no-edit

Schieben Sie einen neuen Zweig mit gequetschten Commits zum Repo

git push -f
David Morton
quelle
Erhält dies die vorherigen Festschreibungsnachrichten?
not2qubit
1
@ not2qubit nein, dadurch werden die vorherigen Festschreibungsnachrichten nicht beibehalten. Anstelle von Festschreiben Nr. 1, Festschreiben Nr. 2, Festschreiben Nr. 3 werden alle Änderungen in diesen Festschreibungen in einem einzigen Festschreiben Nr. 1 zusammengefasst. Commit Nr. 1 ist das <root>Commit, auf das Sie zurückgesetzt haben. git commit --amend --no-editÜbernimmt alle Änderungen an der aktuellen Festschreibung, <root>ohne dass die Festschreibungsnachricht bearbeitet werden muss.
David Morton
5

Mit Transplantaten quetschen

Fügen Sie eine Datei hinzu .git/info/graftsund legen Sie dort den Commit-Hash ab, den Sie als Root verwenden möchten

git log wird nun von diesem Commit ausgehen

Damit es "echt" läuft git filter-branch

dimus
quelle
1

Diese Antwort verbessert sich gegenüber den oben genannten Punkten (bitte stimmen Sie ab), vorausgesetzt, Sie möchten nicht nur das eine Commit erstellen (keine Eltern, keine Historie), sondern auch alle Commit-Daten dieses Commits beibehalten:

  • Autor (Name und E-Mail)
  • Erstellungsdatum
  • Commiter (Name und E-Mail)
  • Festgeschriebenes Datum
  • Protokollnachricht senden

Natürlich wird sich die Commit-SHA des neuen / einzelnen Commits ändern, da sie eine neue (Nicht-) Historie darstellt und zu einem übergeordneten / Root-Commit wird.

Dies kann durch Lesen git logund Einstellen einiger Variablen für erfolgen git commit-tree. Angenommen, Sie möchten ein einzelnes Commit masterin einem neuen Zweig erstellen one-commit, wobei die oben genannten Commit-Daten beibehalten werden:

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
quelle
1

Zu diesem Zweck können Sie Ihr lokales Git-Repository auf das erste Commit-Hashtag zurücksetzen, sodass alle Änderungen nach diesem Commit nicht mehr bereitgestellt werden. Anschließend können Sie das Commit mit der Option --amend durchführen.

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

Bearbeiten Sie dann bei Bedarf den ersten Commit-Namen und speichern Sie die Datei.

T.EL MOUSSAOUI
quelle
1

Bei mir hat es so funktioniert: Ich hatte insgesamt 4 Commits und habe die interaktive Rebase verwendet:

git rebase -i HEAD~3

Das allererste Commit bleibt und ich habe 3 letzte Commits gemacht.

Falls Sie im Editor stecken bleiben, der als nächstes angezeigt wird, sehen Sie Folgendes:

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

Sie müssen zuerst ein Commit durchführen und andere darauf drücken. Was Sie haben sollten, ist:

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

Verwenden Sie dazu die INSERT-Taste, um den Einfüge- und den Bearbeitungsmodus zu ändern.

Verwenden Sie zum Speichern und Beenden des Editors :wq. Wenn sich Ihr Cursor zwischen diesen Festschreibungszeilen oder an einer anderen Stelle befindet, drücken Sie ESC und versuchen Sie es erneut.

Als Ergebnis hatte ich zwei Commits: das allererste, das übrig blieb, und das zweite mit der Meldung "Dies ist eine Kombination aus 3 Commits."

Weitere Informationen finden Sie hier: https://makandracards.com/makandra/527-squash-several-git-commits-into-a-single-commit

Vlad
quelle
0

Normalerweise mache ich das so:

  • Stellen Sie sicher, dass alles festgeschrieben ist, und notieren Sie sich die neueste Festschreibungs-ID, falls etwas schief geht, oder erstellen Sie einen separaten Zweig als Sicherung

  • Lauf git reset --soft `git rev-list --max-parents=0 --abbrev-commit HEAD` , um Ihren Kopf auf das erste Commit zurückzusetzen, aber lassen Sie Ihren Index unverändert. Alle Änderungen seit dem ersten Festschreiben werden nun zum Festschreiben bereit angezeigt.

  • Ausführen git commit --amend -m "initial commit", um Ihr Commit auf das erste Commit zu ändern und die Commit-Nachricht zu ändern. Wenn Sie die vorhandene Commit-Nachricht beibehalten möchten, können Sie sie ausführengit commit --amend --no-edit

  • Führen Sie aus git push -f, um Ihre Änderungen zu erzwingen

Kent Munthe Caspersen
quelle