Wie verschmilzt Git nach der Arbeit mit Kirschen?

190

Stellen wir uns vor, wir haben eine masterNiederlassung.

Dann erstellen wir eine newbranch

git checkout -b newbranch

und machen Sie zwei neue Commits für newbranch: commit1 und commit2

Dann wechseln wir zu Master und machen cherry-pick

git checkout master
git cherry-pick hash_of_commit1

Wenn gitkwir uns das ansehen, sehen wir, dass commit1 und seine von Kirschen ausgewählte Version unterschiedliche Hashes haben. Technisch gesehen handelt es sich also um zwei verschiedene Commits.

Schließlich verschmelzen wir newbranchzu master:

git merge newbranch

und sehen Sie, dass diese beiden Commits mit unterschiedlichen Hashes ohne Probleme zusammengeführt wurden, obwohl sie implizieren, dass dieselben Änderungen zweimal angewendet werden sollten, sodass einer von ihnen fehlschlagen sollte.

Führt git beim Zusammenführen wirklich eine intelligente Analyse des Inhalts von Commits durch und entscheidet, dass Änderungen nicht zweimal angewendet werden sollten, oder werden diese Commits intern als miteinander verknüpft markiert?

Paul
quelle

Antworten:

131

Kurze Antwort

Keine Sorge, Git wird sich darum kümmern.

Lange Antwort

Im Gegensatz zu z. B. SVN 1 speichert Git Commits nicht im Delta-Format, sondern basiert auf Snapshots 2,3 . Während SVN naiv versuchen würde, jedes zusammengeführte Commit als Patch anzuwenden (und aus genau dem von Ihnen beschriebenen Grund fehlschlagen würde), ist Git im Allgemeinen in der Lage, dieses Szenario zu handhaben.

Beim Zusammenführen versucht Git, die Snapshots beider HEAD-Commits zu einem neuen Snapshot zu kombinieren. Wenn ein Teil des Codes oder einer Datei in beiden Snapshots identisch ist (dh weil ein Commit bereits ausgewählt wurde), wird Git ihn nicht berühren.

Quellen

1 Skip-Deltas in Subversion
2 Git-Grundlagen
3 Das Git-Objektmodell

Helmbert
quelle
40
Eigentlich würde ich sagen, Sie sollten sich Gedanken über das Zusammenführen machen und "Git wird damit umgehen" ist keine gute Faustregel.
Cregox
4
Tatsächlich kann die Zusammenführung in einigen Fällen zu doppeltem Inhalt führen. Git geht manchmal damit um, aber manchmal nicht.
Donquijote
Das ist schrecklich falsch. Got speichert die Dateidateien so ziemlich in allen möglichen Formen. Und wenn ich mich richtig erinnere, hat SVN Schnappschüsse gespeichert.
he_the_great
2
@he_the_great, nein. Das Skip-Delta-Speicherformat von SVN (! = Snapshots) ist im Handbuch gut dokumentiert . Und ich verstehe wirklich nicht, was du mit condevable meinst . Ich bin kein Muttersprachler, aber ich bin mir ziemlich sicher, dass das kein richtiges Wort ist.
Helmbert
2
@he_the_great Aber selbst als Packdateien führt jeder Hash für eine Datei zur vollständigen Datei. Ja, es wird mithilfe von Deltas komprimiert, aber es ist kein Delta für die Änderungen in einem Commit, sondern ein Delta zwischen Hashes für eine Datei. Was das Festschreibungsobjekt betrifft, verweist es auf einen Baum, der auf den Hash für eine vollständige Datei verweist. Dass die Daten unter der Haube komprimiert werden, hat keinen Einfluss auf die Funktionsweise von Git. Git speichert vollständige Dateien, was ein Commit betrifft, SVN speichert Deltas für Commits, soweit ich weiß.
Peter Olson
43

Nach einer solchen Zusammenführung haben Sie möglicherweise zweimal Commits in der Geschichte ausgewählt.

Lösung, um dies zu verhindern Ich zitiere aus einem Artikel, der für Zweige mit doppelten (von Kirschen gepflückten) Commits empfiehlt, Rebase vor dem Zusammenführen zu verwenden:

Git-Merge nach Git-Cherry-Pick: Vermeidung doppelter Commits

Stellen Sie sich vor, wir haben den Hauptzweig und einen Zweig b:

   o---X   <-- master
    \
     b1---b2---b3---b4   <-- b

Jetzt brauchen wir dringend die Commits b1 und b3 im Master, aber nicht die verbleibenden Commits in b. Wir checken also die Hauptniederlassung und die Cherry-Pick-Commits b1 und b3 aus:

$ git checkout master
$ git cherry-pick "b1's SHA"
$ git cherry-pick "b3's SHA"

Das Ergebnis wäre:

   o---X---b1'---b3'   <-- master
    \
     b1---b2---b3---b4   <-- b

Nehmen wir an, wir machen ein weiteres Commit für den Master und wir bekommen:

   o---X---b1'---b3'---Y   <-- master
    \
     b1---b2---b3---b4   <-- b

Wenn wir jetzt Zweig b in Master zusammenführen würden:

$ git merge b

Wir würden folgendes bekommen:

   o---X---b1'---b3'---Y--- M  <-- master
     \                     /
      b1----b2----b3----b4   <-- b

Das bedeutet, dass die durch b1 und b3 eingeführten Änderungen zweimal in der Historie erscheinen würden. Um dies zu vermeiden, können wir neu aufbauen anstatt zusammenzuführen:

$ git rebase master b

Welches würde ergeben:

   o---X---b1'---b3'---Y   <-- master
                        \
                         b2'---b4'   <-- b

Schließlich:

$ git checkout master
$ git merge b

gibt uns:

   o---X---b1'---b3'---Y---b2'---b4'   <-- master, b

EDIT Korrekturen von David Lemons Kommentar angenommen

Ephemerr
quelle
1
toller Hinweis zu Rebase! Alle von Kirschen gepflückten Commits werden automatisch übersprungen.
iTake
2
Ehrlich gesagt klingt es zu schön um wahr zu sein, ich muss es mit meinen Augen sehen. Auch Rebase ändert Commits, Ihre letzte Timeline sollte sein---Y---b2'---b4'
David Lemon
Funktioniert perfekt. Sehr hilfreich, wenn Sie nicht möchten, dass die von Kirschen gepflückten Commits zweimal in der Geschichte ausgeführt werden.
user2350644
1
Sollte nicht beachtet werden, dass Rebase zwar schön ist, die Gefahr bei der Verwendung jedoch darin besteht, dass alle Gabeln oder Zweige, die aus dem alten b erstellt wurden, nicht synchron sind und Benutzer möglicherweise auf Dinge wie git reset --hard und git zurückgreifen müssen push -f?
JHH
1
@JHH das ist, warum wir lokale Niederlassung hier neu
gründen