web-dev-qa-db-de.com

Squash die ersten beiden Commits in Git?

Mit git rebase --interactive <commit> Sie können beliebig viele Commits zu einem einzigen zusammenfassen.

Das ist alles großartig, es sei denn, Sie möchten Commits in das ursprüngliche Commit einbringen. Das scheint unmöglich zu sein.

Gibt es Möglichkeiten, dies zu erreichen?


Mäßig verwandt:

In einer verwandten Frage gelang es mir, eine andere Herangehensweise an die Notwendigkeit zu finden, gegen das erste Commit zu drücken, das heißt, es zum zweiten zu machen.

Wenn Sie interessiert sind: git: Wie füge ich ein Commit als erstes ein und verschiebe alle anderen?

517
kch

Update Juli 2012 ( git 1.7.12 + )

Sie können jetzt alle Commits bis zum Root zurücksetzen und das zweite Commit Y auswählen, das mit dem ersten X gequetscht werden soll.

git rebase -i --root master

pick sha1 X
squash sha1 Y
pick sha1 Z
git rebase [-i] --root $tip

Dieser Befehl kann jetzt verwendet werden, um den gesamten Verlauf neu zu schreiben, der von "$tip "bis zum Root Commit.

Siehe commit df5df20c1308f936ea542c86df1e9c6974168472 auf GitHub von Chris Webb (arachsys) .


Ursprüngliche Antwort (Februar 2009)

Ich glaube, Sie finden verschiedene Rezepte dafür in der Frage "SO question" Wie kombiniere ich die ersten beiden Commits eines Gits? Repository? "

Charles Bailey gab dort die ausführlichste Antwort und erinnerte uns daran, dass ein Commit ein vollständiger Baum ist (der sich nicht nur von einem früheren Zustand unterscheidet) ).
Und hier haben das alte Commit (das "ursprüngliche Commit") und das neue Commit (das Ergebnis des Squashing) keinen gemeinsamen Vorfahren.
Das heißt, Sie können nicht "commit --amend "das erste Commit in ein neues, und dann die Historie des vorherigen ersten Commits auf das neue initiale Commit zurückführen (viele Konflikte)

(Dieser letzte Satz ist nicht mehr wahr mit git rebase -i --root <aBranch>)

Vielmehr (mit A dem ursprünglichen "Initial Commit" und B musste ein nachfolgendes Commit in das erste gequetscht werden):

  1. Gehen Sie zurück zu der letzten Festschreibung, die Sie als erste Festschreibung festlegen möchten (lösen Sie HEAD):

    git checkout <sha1_for_B>
    
  2. Setzen Sie den Verzweigungszeiger auf das ursprüngliche Commit zurück, lassen Sie jedoch den Index und den Arbeitsbaum intakt:

    git reset --soft <sha1_for_A>
    
  3. Ändern Sie den ursprünglichen Baum mit dem Baum von 'B':

    git commit --amend
    
  4. Kennzeichnen Sie diese neue anfängliche Festschreibung vorübergehend (oder Sie können sich die neue Festschreibung sha1 manuell merken):

    git tag tmp
    
  5. Kehren Sie zum ursprünglichen Zweig zurück (nehmen Sie für dieses Beispiel master an):

    git checkout master
    
  6. Wiederholen Sie alle Festschreibungen nach B für die neue anfängliche Festschreibung:

    git rebase --onto tmp <sha1_for_B>
    
  7. Entfernen Sie das temporäre Tag:

    git tag -d tmp
    

Auf diese Weise wird die "rebase --onto "führt während des Zusammenführens nicht zu Konflikten, da der Verlauf zurückgesetzt wird made after der letzte Commit (B), der in den ersten (der A) bis tmp (repräsentiert das gequetschte neue initiale Commit): Nur triviale Schnellvorlauf-Merges.

Das funktioniert für "A-B", aber auch "A-...-...-...-B "(auf diese Weise kann eine beliebige Anzahl von Commits in die erste gequetscht werden)

683
VonC

Ich habe VonCs Skript überarbeitet, um alles automatisch zu erledigen und mich um nichts zu bitten. Du gibst ihm zwei Commit-SHA1s und es wird alles zwischen ihnen zu einem Commit mit dem Namen "Squashed History" zusammengefasst:

#!/bin/sh
# Go back to the last commit that we want
# to form the initial commit (detach HEAD)
git checkout $2

# reset the branch pointer to the initial commit (= $1),
# but leaving the index and working tree intact.
git reset --soft $1

# amend the initial tree using the tree from $2
git commit --amend -m "squashed history"

# remember the new commit sha1
TARGET=`git rev-list HEAD --max-count=1`

# go back to the original branch (assume master for this example)
git checkout master

# Replay all the commits after $2 onto the new initial commit
git rebase --onto $TARGET $2
30
fonsinchen

Ich vermeide dieses Problem, indem ich immer ein "no-op" -Erst-Commit erstelle, bei dem das einzige im Repository ein leerer .gitignore ist:

https://github.com/DarwinAwardWinner/git-custom-commands/blob/master/bin/git-myinit

Auf diese Weise gibt es keinen Grund, sich mit dem ersten Commit herumzuschlagen.

23
Ryan Thompson

Wenn Sie einfach alle Commits zu einem einzigen Commit zusammenfassen möchten, setzen Sie einfach das Repository zurück und ändern Sie das erste Commit:

git reset hash-of-first-commit
git add -A
git commit --amend

Git-Reset lässt den Arbeitsbaum intakt, so dass alles noch da ist. Fügen Sie also einfach die Dateien mit den Befehlen git add hinzu und ändern Sie das erste Commit mit diesen Änderungen. Im Vergleich zu rebase -i verlieren Sie jedoch die Möglichkeit, die Git-Kommentare zusammenzuführen.

18
Mike Looijmans

Dadurch wird das zweite Commit in das erste eingepreßt:

A-B-C-... -> AB-C-...

git filter-branch --commit-filter '
    if [ "$GIT_COMMIT" = <sha1ofA> ];
    then
        skip_commit "[email protected]";
    else
        git commit-tree "[email protected]";
    fi
' HEAD

Die Commit-Nachricht für AB wird von B übernommen (obwohl ich es von A vorziehen würde).

Hat den gleichen Effekt wie die Antwort von Uwe Kleine-König, funktioniert aber auch für nicht initiales A.

5

Wenn Sie das erste und das zweite Commit quetschen, wird das erste Commit neu geschrieben. Wenn Sie mehr als einen Zweig haben, der auf dem ersten Festschreiben basiert, würden Sie diesen Zweig abschneiden.

Betrachten Sie das folgende Beispiel:

a---b---HEAD
 \
  \
   '---d

Das Zusammendrücken von a und b in ein neues Festschreiben "ab" würde zu zwei unterschiedlichen Bäumen führen, was in den meisten Fällen nicht wünschenswert ist, da Git-Merge und Git-Rebase nicht mehr funktionieren über die beiden Zweige.

ab---HEAD

a---d

Wenn Sie das wirklich wollen, können Sie es tun. Schauen Sie sich Git-Filter-Zweig an, um ein mächtiges (und gefährliches) Werkzeug für das Umschreiben der Geschichte zu finden.

3
hillu

Du kannst dazu git filter-branch verwenden. z.B.

git filter-branch --parent-filter \
'if test $GIT_COMMIT != <sha1ofB>; then cat; fi'

Dies führt dazu, dass AB-C das Commit-Protokoll von A verwirft.

3

Sie können rebase interactive verwenden, um die letzten beiden Festschreibungen zu ändern, bevor sie auf eine Fernbedienung übertragen werden

git rebase HEAD^^ -i
1
todd