Squash

Squash: Commits zusammenführen

Was ist squash1?

Ein Git Squash ermöglicht es, mehrere Commits zu einem einzelnen Commit zusammenzufassen. Dies ist besonders nützlich, um die Commit-Historie sauber zu halten. Dabei kan man squash sowohl mit rebase (aktueller Branch) als auch mit merge nutzen.

Anwendung mit rebase

Ein rebase kann in einen interaktiven Modus durchgeführt werden (Parameter -i oder --interactive). Dabei werden alle betroffenen Commits, die “wiedergegeben” werden, in dem Texteditor angezeigt und können angepasst werden.

Für squash mit rebase geben wir die Anzahl der Commits, die zusammengefasst werden sollen mit HEAD-<n> an (die selbe Syntax, die wir bereits mit diff gesehen haben, um Commits relativ anzusprechen). Wir wollen nun die letzten Beiden Commits unserer Historie zusammenfassen.

repo_1$ git pl
* 874e90c (HEAD -> main) Neue Geschichte
* 71c03a0 Start mit Kapitel 7
* cf10f7f Besser
*   4a8d01e (origin/main) Merge branch 'feat_kap_5'
...

Dazu führen wir das interaktive rebase für die letzten 2 Commits durch.

repo_1$ git rebase -i HEAD~2

Darauf erscheint im Texteditor die Auflistung der betroffenen Commits (und ein Wenig Dokumentation dazu - mit # auskommentiert):

pick 71c03a0 Start mit Kapitel 7
pick 874e90c Neue Geschichte

# Rebase cf10f7f..874e90c onto cf10f7f (2 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
#                    commit's log message, unless -C is used, in which case
#                    keep only this commit's message; -c is same as -C but
#                    opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
#         create a merge commit using the original merge commit's
#         message (or the oneline, if no original merge commit was
#         specified); use -c <commit> to reword the commit message
# u, update-ref <ref> = track a placeholder for the <ref> to be updated
#                       to this position in the new commits. The <ref> is

Relevant sind nur die ersten beiden Zeilen. Jede Zeile (Commit), die mit pick beginnt, wird im Original übernommen - wird also wieder zu einem Commit. Ändern Sie die Commits, die Zusammengefasst werden sollen von pick zu squash (in unserem Beispiel also den zweiten Commit).

pick 71c03a0 Start mit Kapitel 7
squash 874e90c Neue Geschichte

Unter Linux/macOS können Sie den “vi(m)"-Editor mit Esc-Taste und dann mit :wq (write and quit) verlassen.

Nun können Sie auch das finale Commit bearbeiten, wenn Sie es möchten:

# This is a combination of 2 commits.
# This is the 1st commit message:

Start mit Kapitel 7

# This is the commit message #2:

Neue Geschichte

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Mon Apr 7 08:01:30 2025 +0200
#
# interactive rebase in progress; onto cf10f7f
# Last commands done (2 commands done):
#    pick 71c03a0 Start mit Kapitel 7
#    squash 874e90c Neue Geschichte
# No commands remaining.
# You are currently rebasing branch 'main' on 'cf10f7f'.
#
# Changes to be committed:
#       new file:   kapitel_7.txt
#

Mit dem Verlassen des Commit-Message Editors, wird die Historie umgeschrieben.

repo_1$ git rebase -i HEAD~2   
[detached HEAD a93f4f8] Start mit Kapitel 7
 Date: Mon Apr 7 08:01:30 2025 +0200
 1 file changed, 2 insertions(+)
 create mode 100644 kapitel_7.txt
Successfully rebased and updated refs/heads/main.
repo_1$ git pl
* a93f4f8 (HEAD -> main) Start mit Kapitel 7
* cf10f7f Besser
*   4a8d01e (origin/main) Merge branch 'feat_kap_5'

Letzten Commit anpassen

Sollten nur die letzten Änderungen (bereits mit git add in das Staging-Bereich hinzugefügt, aber noch kein Commit erstellt) in das letzte Commit reinfließen, geht es noch ein wenig einfacher. Dazu kann das --amend2 Parameter vom commit-Command genutzt werden.

Machen Sie folgende Änderung in kapitel_7.txt.

# Kapitel 7
Der Ausgang der Geschichte wird angepasst ...
Und manchmal zusammengeführt...

Fügen Sie die Änderung zum Staging Bereich mit git add.

repo_1$ git add kapitel_7.txt 

Nun können wir mit --amend diese Änderung zum letzten Commit hinzufügen. Wenn wir die Message des letzten Commits nicht ändern wollen, können wird den Parameter --no-edit anhängen, oder mit -m eine neue Message angeben.

repo_1$ git commit --amend -m "Kapitel 7 fängt an"
[main 28087f7] Kapitel 7 fängt an
 Date: Mon Apr 7 08:01:30 2025 +0200
 1 file changed, 3 insertions(+)
 create mode 100644 kapitel_7.txt

Mit dem Status sehen wir nun, dass keine Änderungen mehr vorliegen und and der Historie den geänderten letzten Commit (anderer Hash-Wert und Message).

repo_1$ git status
On branch main
Your branch is ahead of 'origin/main' by 2 commits.
  (use "git push" to publish your local commits)

nothing to commit, working tree clean
repo_1$ git pl
* 28087f7 (HEAD -> main) Kapitel 7 fängt an
* cf10f7f Besser
*   4a8d01e (origin/main) Merge branch 'feat_kap_5'
...
Warnung

Da sowohl rebase -i als auch commit --amend die Historie verändert, sollte dieser nur auf den Commits durchgeführt werden, die noch nicht zu einem Zentralen git-Server synchronisiert wurden (oder nur auf Branches auf denen man allein arbeitet).

docs