Was genau macht Git's "Rebase - Preserve-Merges" (und warum?)

355

Gits Dokumentation für den rebaseBefehl ist ziemlich kurz:

--preserve-merges
    Instead of ignoring merges, try to recreate them.

This uses the --interactive machinery internally, but combining it
with the --interactive option explicitly is generally not a good idea
unless you know what you are doing (see BUGS below).

Was passiert also eigentlich, wenn Sie verwenden --preserve-merges? Wie unterscheidet es sich vom Standardverhalten (ohne dieses Flag)? Was bedeutet es, eine Zusammenführung usw. "neu zu erstellen"?

Chris
quelle
20
Warnung: Ab Git 2.18 (Q2 2018, 5 Jahre später) git --rebase-mergeswird letztendlich das alte ersetzt git --preserve-merges. Siehe meine Antwort unten
VonC

Antworten:

464

Wie bei einer normalen Git-Rebase --preserve-mergesidentifiziert Git mit zuerst eine Liste von Commits, die in einem Teil des Commit-Diagramms vorgenommen wurden, und spielt diese Commits dann über einem anderen Teil ab. Die Unterschiede --preserve-mergesbetreffen, welche Commits für die Wiedergabe ausgewählt werden und wie diese Wiedergabe für Zusammenführungs-Commits funktioniert.

Um die Hauptunterschiede zwischen normaler und zusammenführungserhaltender Rebase genauer zu erläutern:

  • Zusammenführungserhaltende Rebase ist bereit, (einige) Merge-Commits erneut abzuspielen, während normale Rebase Merge-Commits vollständig ignoriert.
  • Da es bereit ist, Zusammenführungs-Commits erneut abzuspielen, muss die Zusammenführungserhaltung definieren, was es bedeutet , ein Zusammenführungs-Commit erneut abzuspielen und einige zusätzliche Falten zu beseitigen
    • Der konzeptionell interessanteste Teil ist vielleicht die Auswahl der Zusammenführungseltern des neuen Commits.
    • Das Wiederholen von Merge-Commits erfordert auch das explizite Auschecken bestimmter Commits ( git checkout <desired first parent>), während sich die normale Rebase darüber keine Gedanken machen muss.
  • Bei der Zusammenführung, bei der die Zusammenführung erhalten bleibt, werden flachere Commits für die Wiederholung berücksichtigt:
    • Insbesondere werden nur Wiederholungs-Commits berücksichtigt, die seit der letzten Zusammenführungsbasis (en) - dh dem letzten Zeitpunkt, zu dem die beiden Zweige auseinander gingen - getätigt wurden, wohingegen normale Wiederholungs-Commits möglicherweise Wiederholungs-Commits wiederholen, die bis zum ersten Mal zurückgingen, als die beiden Zweige auseinander gingen.
    • Um vorläufig und unklar zu sein, glaube ich, dass dies letztendlich ein Mittel ist, um die Wiederholung von "alten Commits", die bereits in ein Merge-Commit "integriert" wurden, herauszufiltern.

Zuerst werde ich versuchen, "ausreichend genau" zu beschreiben, was Rebase --preserve-mergestut, und dann wird es einige Beispiele geben. Man kann natürlich mit den Beispielen beginnen, wenn das sinnvoller erscheint.

Der Algorithmus in "Brief"

Wenn Sie wirklich in das Unkraut eindringen möchten, laden Sie die Git-Quelle herunter und erkunden Sie die Datei git-rebase--interactive.sh. (Rebase ist nicht Teil des C-Kerns von Git, sondern in Bash geschrieben. Und hinter den Kulissen teilt es Code mit "Interactive Rebase".)

Aber hier werde ich skizzieren, was ich für das Wesentliche halte. Um die Anzahl der Dinge zu verringern, über die man nachdenken muss, habe ich mir ein paar Freiheiten genommen. (zB versuche ich nicht, die genaue Reihenfolge, in der die Berechnungen stattfinden, mit 100% iger Genauigkeit zu erfassen, und ignoriere einige weniger zentral erscheinende Themen, z. B. was mit Commits zu tun ist, die bereits zwischen Zweigen ausgewählt wurden).

Beachten Sie zunächst, dass eine nicht zusammenführungserhaltende Rebase ziemlich einfach ist. Es ist mehr oder weniger:

Find all commits on B but not on A ("git log A..B")
Reset B to A ("git reset --hard A") 
Replay all those commits onto B one at a time in order.

Rebase --preserve-mergesist vergleichsweise kompliziert. Hier ist so einfach, wie ich es geschafft habe, ohne Dinge zu verlieren, die ziemlich wichtig erscheinen:

Find the commits to replay:
  First find the merge-base(s) of A and B (i.e. the most recent common ancestor(s))
    This (these) merge base(s) will serve as a root/boundary for the rebase.
    In particular, we'll take its (their) descendants and replay them on top of new parents
  Now we can define C, the set of commits to replay. In particular, it's those commits:
    1) reachable from B but not A (as in a normal rebase), and ALSO
    2) descendants of the merge base(s)
  If we ignore cherry-picks and other cleverness preserve-merges does, it's more or less:
    git log A..B --not $(git merge-base --all A B)
Replay the commits:
  Create a branch B_new, on which to replay our commits.
  Switch to B_new (i.e. "git checkout B_new")
  Proceeding parents-before-children (--topo-order), replay each commit c in C on top of B_new:
    If it's a non-merge commit, cherry-pick as usual (i.e. "git cherry-pick c")
    Otherwise it's a merge commit, and we'll construct an "equivalent" merge commit c':
      To create a merge commit, its parents must exist and we must know what they are.
      So first, figure out which parents to use for c', by reference to the parents of c:
        For each parent p_i in parents_of(c):
          If p_i is one of the merge bases mentioned above:
            # p_i is one of the "boundary commits" that we no longer want to use as parents
            For the new commit's ith parent (p_i'), use the HEAD of B_new.
          Else if p_i is one of the commits being rewritten (i.e. if p_i is in R):
            # Note: Because we're moving parents-before-children, a rewritten version
            # of p_i must already exist. So reuse it:
            For the new commit's ith parent (p_i'), use the rewritten version of p_i.
          Otherwise:
            # p_i is one of the commits that's *not* slated for rewrite. So don't rewrite it
            For the new commit's ith parent (p_i'), use p_i, i.e. the old commit's ith parent.
      Second, actually create the new commit c':
        Go to p_1'. (i.e. "git checkout p_1'", p_1' being the "first parent" we want for our new commit)
        Merge in the other parent(s):
          For a typical two-parent merge, it's just "git merge p_2'".
          For an octopus merge, it's "git merge p_2' p_3' p_4' ...".
        Switch (i.e. "git reset") B_new to the current commit (i.e. HEAD), if it's not already there
  Change the label B to apply to this new branch, rather than the old one. (i.e. "git reset --hard B")

Rebase mit einem --onto CArgument sollte sehr ähnlich sein. Anstatt die Festschreibungswiedergabe am KOPF von B zu starten, starten Sie stattdessen die Festschreibungswiedergabe am KOPF von C. (Und verwenden Sie C_new anstelle von B_new.)

Beispiel 1

Nehmen Sie zum Beispiel ein Commit-Diagramm

  B---C <-- master
 /                     
A-------D------E----m----H <-- topic
         \         /
          F-------G

m ist eine Zusammenführung mit den Eltern E und G.

Angenommen, wir haben das Thema (H) über Master (C) mit einer normalen, nicht zusammenführungserhaltenden Rebase neu basiert. (Beispiel: Checkout-Thema; Rebase-Master .) In diesem Fall würde git die folgenden Commits für die Wiedergabe auswählen:

  • wähle D.
  • wähle E.
  • wähle F.
  • wähle G.
  • wähle H.

und aktualisieren Sie dann das Festschreibungsdiagramm wie folgt:

  B---C <-- master
 /     \                
A       D'---E'---F'---G'---H' <-- topic

(D 'ist das wiedergegebene Äquivalent von D usw.)

Beachten Sie, dass Merge Commit m nicht für die Wiedergabe ausgewählt ist.

Wenn wir stattdessen eine --preserve-mergesRebase von H über C durchführen würden (z. B. Checkout-Thema; Rebase --preserve-merges master ). In diesem neuen Fall würde git die folgenden Commits für die Wiedergabe auswählen:

  • wähle D.
  • wähle E.
  • wähle F (auf D 'im Zweig' Unterthema ')
  • wähle G (auf F 'im Zweig' Unterthema ')
  • Wählen Sie Zweig 'Unterthema' zum Thema zusammenführen
  • wähle H.

Jetzt m wurde für die Wiedergabe ausgewählt. Beachten Sie auch, dass die Zusammenführungseltern E und G vor dem Zusammenführungs-Commit m zur Aufnahme ausgewählt wurden.

Hier ist das resultierende Commit-Diagramm:

 B---C <-- master
/     \                
A      D'-----E'----m'----H' <-- topic
        \          / 
         F'-------G'

Wieder ist D 'eine von Kirschen gepflückte (dh neu erstellte) Version von D. Gleiches gilt für E' usw. Jedes Commit, das nicht auf dem Master ausgeführt wurde, wurde wiederholt. Sowohl E als auch G (die zusammengeführten Eltern von m) wurden als E 'und G' neu erstellt, um als Eltern von m 'zu dienen (nach dem Rebase bleibt die Baumhistorie immer noch dieselbe).

Beispiel 2

Anders als bei einer normalen Rebase kann eine zusammenführungserhaltende Rebase mehrere untergeordnete Elemente des Upstream-Kopfes erstellen.

Betrachten Sie zum Beispiel:

  B---C <-- master
 /                     
A-------D------E---m----H <-- topic
 \                 |
  ------- F-----G--/ 

Wenn wir H (Thema) über C (Master) neu gründen, werden folgende Commits für die Basis neu ausgewählt:

  • wähle D.
  • wähle E.
  • wähle F.
  • wähle G.
  • wähle m
  • wähle H.

Und das Ergebnis ist wie folgt:

  B---C  <-- master
 /    | \                
A     |  D'----E'---m'----H' <-- topic
       \            |
         F'----G'---/

Beispiel 3

In den obigen Beispielen sind sowohl das Zusammenführungs-Commit als auch seine beiden übergeordneten Elemente wiedergegebene Commits und nicht die ursprünglichen Eltern, die das ursprüngliche Zusammenführungs-Commit hat. Bei anderen Rebases kann ein wiedergegebenes Zusammenführungs-Commit jedoch zu Eltern führen, die sich vor dem Zusammenführen bereits im Commit-Diagramm befanden.

Betrachten Sie zum Beispiel:

  B--C---D <-- master
 /    \                
A---E--m------F <-- topic

Wenn wir das Thema auf den Master zurücksetzen (Zusammenführungen beibehalten), werden die Commits für die Wiedergabe ausgeführt

  • Wählen Sie Merge Commit m
  • wähle F.

Das neu geschriebene Commit-Diagramm sieht folgendermaßen aus:

                     B--C--D <-- master
                    /       \             
                   A-----E---m'--F'; <-- topic

Hier erhält das wiedergegebene Zusammenführungs-Commit m 'Eltern, die im Commit-Diagramm bereits vorhanden waren, nämlich D (der KOPF des Masters) und E (eines der Eltern des ursprünglichen Merge-Commits m).

Beispiel 4

Die Zusammenführung, bei der die Zusammenführung erhalten bleibt, kann in bestimmten Fällen "leeres Festschreiben" verwirrt werden. Zumindest trifft dies nur auf einige ältere Versionen von git zu (zB 1.7.8.)

Nehmen Sie dieses Commit-Diagramm:

                   A--------B-----C-----m2---D <-- master
                    \        \         /
                      E--- F--\--G----/
                            \  \
                             ---m1--H <--topic

Beachten Sie, dass sowohl Commit m1 als auch m2 alle Änderungen von B und F enthalten sollten.

Wenn wir versuchen, git rebase --preserve-mergesH (Thema) auf D (Master) zu übertragen, werden die folgenden Commits für die Wiedergabe ausgewählt:

  • wähle m1
  • wähle H.

Beachten Sie, dass die in m1 vereinigten Änderungen (B, F) bereits in D enthalten sein sollten. (Diese Änderungen sollten bereits in m2 integriert sein, da m2 die untergeordneten Elemente von B und F zusammenführt.) D sollte wahrscheinlich entweder ein No-Op sein oder ein leeres Commit erstellen (dh eines, bei dem der Unterschied zwischen aufeinanderfolgenden Revisionen leer ist).

Stattdessen kann git jedoch den Versuch ablehnen, m1 über D wiederzugeben. Sie können folgende Fehlermeldung erhalten:

error: Commit 90caf85 is a merge but no -m option was given.
fatal: cherry-pick failed

Es sieht so aus, als hätte man vergessen, ein Flag an git zu übergeben, aber das zugrunde liegende Problem ist, dass git es nicht mag, leere Commits zu erstellen.

Chris
quelle
6
Mir ist aufgefallen, dass git rebase --preserve-mergesdas viel langsamer ist als rebaseohne --preserve-merges. Ist das ein Nebeneffekt beim Finden der richtigen Commits? Kann man etwas tun, um es zu beschleunigen? (Übrigens ... danke für die sehr detaillierte Antwort!)
David Alan Hjelle
7
Es klingt so, als ob Sie immer --preserve-merges verwenden sollten. Andernfalls besteht die Möglichkeit, dass die Historie verloren geht, dh die Zusammenführung wird festgeschrieben.
DarVar
19
@DarVar Sie verlieren bei einer Rebase immer den Verlauf, weil Sie behaupten, dass Änderungen an einer anderen Codebasis vorgenommen wurden als tatsächlich.
Chronial
5
Ist das noch eine "vorläufige Antwort"?
Andrew Grimm
5
@Chronial Natürlich haben Sie Recht, dass das Umbasieren immer das Verlieren des Verlaufs beinhaltet, aber vielleicht hat DarVar darauf hingewiesen, dass Sie nicht nur den Verlauf verlieren, sondern auch Änderungen an der Codebasis vornehmen. Die Konfliktlösung enthält Informationen, die auf alle möglichen Arten verloren gehen, wie eine Rebase durchgeführt werden kann. Sie müssen es immer wiederholen. Gibt es wirklich keine Möglichkeit, Git Ihre Konfliktlösung wiederholen zu lassen? Warum kann Git das Merge-Commit nicht auswählen?
Nils_M
94

Git 2.18 (Q2 2018) wird die --preserve-mergeOption durch Hinzufügen einer neuen Option erheblich verbessern .

" git rebase" gelernt " --rebase-merges", die gesamte Topologie des Commit-Graphen an eine andere Stelle zu übertragen .

(Hinweis: Git 2.22, Q2 2019, ist tatsächlich veraltet --preserve-merge , und Git 2.25, Q1 2020, macht keine Werbung mehr in der " git rebase --help" Ausgabe. )

Sehen Sie verpflichten 25cff9f , begehen 7543f6f , begehen 1131ec9 , begehen 7ccdf65 , begehen 537e7d6 , begehen a9be29c , begehen 8f6aed7 , begehen 1644c73 , begehen d1e8b01 , begehen 4c68e7d , begehen 9055e40 , begehen cb5206e , begehen a01c2a5 , begehen 2f6b1d1 , begehen bf5c057 (25. April 2018) von Johannes Schindelin ( dscho) .
Siehe Commit f431d73 (25. April 2018) von Stefan Beller ( stefanbeller) .
Siehe Commit 2429335 (25. April 2018) von Phillip Wood ( phillipwood) .
(Zusammengeführt von Junio ​​C Hamano - gitster- in Commit 2c18e6a , 23. Mai 2018)

pull: Accept --rebase-merges, um die Branchentopologie neu zu erstellen

Ähnlich wie im preserveModus, in dem die --preserve-merges Option einfach an den rebaseBefehl übergeben wird, wird im mergesModus die --rebase-mergesOption einfach übergeben .

Auf diese Weise können Benutzer nicht triviale Festschreibungstopologien beim Abrufen neuer Festschreibungen bequem neu definieren, ohne sie zu reduzieren.


git rebaseDie Manpage enthält jetzt einen vollständigen Abschnitt, der sich der Neugestaltung des Verlaufs durch Zusammenführungen widmet .

Extrakt:

Es gibt legitime Gründe, warum ein Entwickler Merge-Commits neu erstellen möchte: um die Zweigstellenstruktur (oder "Commit-Topologie") beizubehalten, wenn er an mehreren miteinander verbundenen Zweigen arbeitet.

Im folgenden Beispiel arbeitet der Entwickler an einem Themenzweig, der die Definition von Schaltflächen umgestaltet, und an einem anderen Themenzweig, der dieses Refactoring verwendet, um eine Schaltfläche "Fehler melden" zu implementieren.
Die Ausgabe von git log --graph --format=%s -5kann folgendermaßen aussehen:

*   Merge branch 'report-a-bug'
|\
| * Add the feedback button
* | Merge branch 'refactor-button'
|\ \
| |/
| * Use the Button class for all buttons
| * Extract a generic Button class from the DownloadButton one

Der Entwickler möchte diese Commits möglicherweise auf eine neuere Basis zurücksetzen, master während die Zweigstellentopologie beibehalten wird , z. B. wenn erwartet wird, dass der erste Themenzweig masterviel früher als der zweite integriert wird, um beispielsweise Zusammenführungskonflikte mit Änderungen an der vorgenommenen DownloadButtonKlasse zu lösen es in master.

Diese Rebase kann mit der --rebase-mergesOption durchgeführt werden.


Siehe 1644c73 verpflichten für ein kleines Beispiel:

rebase-helper --make-script: Führen Sie ein Flag ein, um Zusammenführungen neu zu starten

Der Sequenzer hat gerade neue Befehle gelernt, mit denen die Zweigstruktur wiederhergestellt werden soll ( ähnlich --preserve-merges, aber mit einem wesentlich weniger fehlerhaften Design ).

Lassen wir das zu rebase--helper Aufgabenlisten mithilfe dieser Befehle generieren, die durch die neue --rebase-mergesOption ausgelöst werden .
Für eine Commit-Topologie wie diese (wobei der HEAD auf C zeigt):

- A - B - C (HEAD)
    \   /
      D

Die generierte Aufgabenliste würde folgendermaßen aussehen:

# branch D
pick 0123 A
label branch-point
pick 1234 D
label D

reset branch-point
pick 2345 B
merge -C 3456 D # C

Was ist der Unterschied zu --preserve-merge ?
Commit 8f6aed7 erklärt:

Es war einmal, hier dachte der Entwickler: Wäre es nicht schön, wenn beispielsweise die Patches von Git für Windows auf dem Kern-Git als Dickicht von Zweigen dargestellt und auf dem Kern-Git neu basiert würden, um dies zu tun eine Reihe von Patch-Serien pflegen?

Der ursprüngliche Versuch, dies zu beantworten, war : git rebase --preserve-merges.

Dieses Experiment war jedoch nie als interaktive Option gedacht und wurde nur als Huckepack verwendet, git rebase --interactiveda die Implementierung dieses Befehls bereits sehr, sehr vertraut aussah: Es wurde von derselben Person entworfen, die es entworfen hat--preserve-merges : wirklich von Ihnen.

Und mit "Mit freundlichen Grüßen" bezieht sich der Autor auf sich selbst: Johannes Schindelin ( dscho) , der der Hauptgrund (mit einigen anderen Helden - Hannes, Steffen, Sebastian, ...) ist, dass wir Git For Windows haben (obwohl damals - 2009 - war das nicht einfach ).
Er arbeitet seit September 2015 bei Microsoft. Dies ist sinnvoll, da Microsoft Git jetzt stark nutzt und seine Dienste benötigt.
Dieser Trend begann 2013 mit TFS . Seitdem verwaltet Microsoft das größte Git-Repository der Welt ! Und, seit Oktober 2018 hat Microsoft GitHub übernommen .

Sie können Johannes in diesem Video sprechen sehen für Git Merge 2018 im April 2018 .

Einige Zeit später entschied ein anderer Entwickler (ich sehe dich an, Andreas! ;-)), dass es eine gute Idee wäre, die --preserve-mergesKombination mit --interactive(mit Vorbehalten!) Und dem Git-Betreuer (nun ja, dem vorläufigen Git-Betreuer) zuzulassen während Junios Abwesenheit war das vereinbart, und dann begann der Glamour des --preserve-mergesDesigns ziemlich schnell und unscheinbar auseinanderzufallen.

Hier spricht Jonathan über Andreas Schwab von Suse.
Sie können einige ihrer Diskussionen im Jahr 2012 sehen .

Der Grund? Im --preserve-mergesModus wurden die übergeordneten Elemente eines Zusammenführungs-Commits (oder eines Commits) nicht explizit angegeben, sondern durch den an den Befehl übergebenen Commit-Namen impliziertpick .

Dies machte es beispielsweise unmöglich, Commits neu zu ordnen .
Ganz zu schweigen davon, Commits zwischen Zweigen zu verschieben oder, Gott sei Dank, Themenzweige in zwei Teile zu teilen.

Leider haben diese Mängel auch verhindert, dass dieser Modus (dessen ursprünglicher Zweck darin bestand, die Anforderungen von Git für Windows zu erfüllen, mit der zusätzlichen Hoffnung, dass er auch für andere nützlich sein kann) die Anforderungen von Git für Windows erfüllt.

Fünf Jahre später, als es wirklich unhaltbar wurde, eine unhandliche, große Hodge-Podge-Patch-Serie von teilweise verwandten, teilweise nicht verwandten Patches in Git für Windows zu haben, die von Zeit zu Zeit auf die Kern-Tags von Git zurückgesetzt wurde (was den unverdienten Zorn des Entwicklers verdiente) Von der unglückseligen git-remote-hgSerie, die zuerst den konkurrierenden Ansatz von Git für Windows überholte, um später ohne Betreuer aufgegeben zu werden, war der " Git garden shearswurden geboren : ein Skript, huckepack auf das interaktiven Fütterungsmaterial, Das würde zuerst die Verzweigungstopologie der neu zu basierenden Patches bestimmen, eine Pseudo-ToDo-Liste zur weiteren Bearbeitung erstellen und das Ergebnis in eine echte ToDo-Liste umwandeln (wobei dieexec Befehl zum "Implementieren" der fehlenden ToDo-Listenbefehle) und zum erneuten Erstellen der Patch-Serie über dem neuen Basis-Commit.

(Auf das Git Garden Shears-Skript wird in diesem Patch in Commit 9055e40 verwiesen. )

Das war im Jahr 2013.
Und es dauerte ungefähr drei Wochen, um das Design zu entwickeln und es als Out-of-Tree-Skript zu implementieren. Es ist unnötig zu erwähnen, dass die Implementierung einige Jahre brauchte, um sich zu stabilisieren, während sich das Design selbst als solide erwies.

Mit diesem Patch kommt die Güte der Git Gartenschere an git rebase -isich .
Wenn Sie die --rebase-mergesOption übergeben, wird eine Aufgabenliste erstellt, die leicht verständlich ist und bei der es offensichtlich ist, wie Commits neu angeordnet werden .
Neue Zweige können durch Einfügen von labelBefehlen und Aufrufen eingeführt werden merge <label>.
Und sobald dieser Modus stabil und allgemein anerkannt ist, können wir den Designfehler, der aufgetreten ist, ablehnen--preserve-merges .


Git 2.19 (Q3 2018) verbessert die neue --rebase-mergesOption, indem sie funktioniert --exec.

Die --execOption " " to " git rebase --rebase-merges" platzierte die exec-Befehle an falschen Stellen, was korrigiert wurde.

Siehe Commit 1ace63b (09. August 2018) und Commit f0880f7 (06. August 2018) von Johannes Schindelin ( dscho) .
(Zusammengeführt von Junio ​​C Hamano - gitster- in Commit 750eb11 , 20. August 2018)

rebase --exec: lass es funktionieren mit --rebase-merges

Die Idee von --execist, execnach jedem einen Anruf anzuhängen pick.

Seit der Einführung des fixup!/ s quash!Festschreibungen, wurde diese Idee anzuwenden erweitert „pick, gegebenenfalls gefolgt von einer Fixup / Squash - Kette“, also wäre ein exec nicht zwischen a eingefügt werden pickund jedes seiner entsprechenden fixupoder squashLinien.

Die aktuelle Implementierung verwendet einen schmutzigen Trick , dies zu erreichen: es wird davon ausgegangen , dass es nur sind Pick / fixup / Squash - Befehle, und dann fügt die execZeilen vor jedem pickaber den ersten und fügt einen endgültigen.

Mit den ToDo - Listen , die durch git rebase --rebase-mergesdiese einfache Implementierung zeigt seine Probleme: es produziert die genauen falsche Sache , wenn es label, resetund mergeBefehle.

Lassen Sie uns die Implementierung ändern, um genau das zu tun, was wir wollen: Suchen Sie nach pickZeilen, überspringen Sie alle Fixup- / Squash-Ketten und fügen Sie die exec Zeile ein . Aufschäumen, ausspülen, wiederholen.

Hinweis: Wir bemühen uns, wann immer möglich vor Kommentarzeilen einzufügen , da leere Commits durch auskommentierte Auswahlzeilen dargestellt werden (und wir möchten die Ausführungszeile einer vorhergehenden Auswahl vorher einfügen solchen Zeile , nicht danach).

Fügen Sie dabei auch execZeilen nach mergeBefehlen hinzu, da diese den Befehlen ähneln pick: Sie fügen neue Festschreibungen hinzu.


Git 2.22 (Q2 2019) korrigiert die Verwendung von refs / rewritten / hierarchy zum Speichern von Rebase-Zwischenzuständen, wodurch die Hierarchie von Natur aus pro Arbeitsbaum erstellt wird.

Siehe Commit b9317d5 , Commit 90d31ff , Commit 09e6564 (07. März 2019) von Nguyễn Thái Ngọc Duy ( pclouds) .
(Zusammengeführt von Junio ​​C Hamano - gitster- in Commit 917f2cd , 9. April 2019)

Stellen Sie sicher, dass refs / rewritten / pro Arbeitsbaum ist

a9be29c (Sequenzer: Refs labelerstellen, die mit dem Befehl worktree-local, 2018-04-25, Git 2.19 generiert wurden) wird refs/rewritten/als Referenzraum pro Arbeitsbaum hinzugefügt.
Leider (mein schlechtes) gibt es einige Stellen, die aktualisiert werden müssen, um sicherzustellen, dass es wirklich pro Arbeitsbaum ist.

- add_per_worktree_entries_to_dir()wird aktualisiert, um sicherzustellen, dass sich die Ref-Liste auf den Arbeitsbaum refs/rewritten/anstatt auf den pro Repo bezieht .

  • common_list[]wird aktualisiert, sodass git_path()der richtige Speicherort zurückgegeben wird. Dies beinhaltet " rev-parse --git-path".

Dieses Durcheinander wird von mir erstellt.
Ich fing an, es mit der Einführung zu beheben, refs/worktree,wo alle Refs ohne spezielle Behandlungen pro Arbeitsbaum sein werden.
Unglückliche refs / umgeschrieben kamen vor refs / worktree, also ist dies alles, was wir tun können.


Mit Git 2.24 (Q4 2019) lernte " git rebase --rebase-merges", verschiedene Zusammenführungsstrategien voranzutreiben und strategiespezifische Optionen an sie weiterzugeben.

Siehe Commit 476998d (04. September 2019) von Elijah Newren (newren ) .
Siehe e1fac53 begehen , begehen a63f990 , begehen 5dcdd74 , begehen e145d99 , begehen 4e6023b , begehen f67336d , begehen a9c7107 , begehen b8c6f24 , begehen d51b771 , begehen c248d32 , begehen 8c1e240 , begehen 5efed0e , begehen 68b54f6 , begehen 2e7bbac , begehen 6180b20 , begehen d5b581f (31 Jul 2019) vonJohannes Schindelin ( dscho).
(Zusammengeführt von Junio ​​C Hamano - gitster- in Commit 917a319 , 18. September 2019)


Mit Git 2.25 (Q1 2020) ist die Logik, die verwendet wird, um die lokalen und globalen Refs des Arbeitsbaums voneinander zu unterscheiden, festgelegt, um die Zusammenführung zu erleichtern.

Siehe Commit f45f88b , Commit c72fc40 , Commit 8a64881 , Commit 7cb8c92 , Commit e536b1f (21. Oktober 2019) von SZEDER Gábor ( szeder) .
(Zusammengeführt von Junio ​​C Hamano - gitster- in Commit db806d7 , 10. November 2019)

path.c: Rufen Sie die matchFunktion nicht ohne Wert in auftrie_find()

Unterzeichnet von: SZEDER Gábor

'logs / refs' ist kein baumspezifischer Arbeitspfad, aber seit dem Festschreiben von b9317d55a3 ( Stellen Sie sicher, dass refs / rewritten / per-worktree, 2019-03-07, v2.22.0-rc0) ' git rev-parse --git-path' einen falschen Pfad zurückgegeben hat wenn ein nachfolgendes ' /' vorhanden ist:

$ git -C WT/ rev-parse --git-path logs/refs --git-path logs/refs/
/home/szeder/src/git/.git/logs/refs
/home/szeder/src/git/.git/worktrees/WT/logs/refs/

Wir verwenden eine trieDatenstruktur, um effizient zu entscheiden, ob ein Pfad zum allgemeinen Verzeichnis gehört oder baumspezifisch arbeitet.

Zufällig löste b9317d55a3 einen Fehler aus, der so alt ist wie die trieImplementierung selbst, der in 4e09cf2acf hinzugefügt wurde (" path: Gemeinsame Verzeichnisprüfung optimieren", 31.08.2015, Git v2.7.0-rc0 - Zusammenführung in Stapel 2 aufgeführt ).

  • Gemäß dem beschreibenden Kommentar trie_find()sollte die angegebene Übereinstimmungsfunktion 'fn' nur für ein "/ -oder- \ 0-terminiertes Präfix des Schlüssels aufgerufen werden, für den der Versuch einen Wert enthält".
    Dies ist nicht wahr: Es gibt drei Stellen, an denen trie_find () die Übereinstimmungsfunktion aufruft, aber einer von ihnen fehlt die Prüfung auf Wertexistenz.

  • b9317d55a3 fügte zwei neue Schlüssel hinzu trie:

    • ' logs/refs/rewritten' und
    • ' logs/refs/worktree', neben dem bereits vorhandenen ' logs/refs/bisect'.
      Dies führte zu einem trieKnoten mit dem Pfad ' logs/refs/', der zuvor nicht vorhanden war und an den kein Wert angehängt ist.
      Eine Abfrage nach ' logs/refs/' findet diesen Knoten und trifft dann auf die eine Aufrufseite der matchFunktion, die nicht auf die Existenz des Werts prüft, und ruft daher die matchFunktion mit NULLals Wert auf.
  • Wenn die matchFunktion check_common()mit einem NULLWert aufgerufen wird, wird 0 zurückgegeben, was darauf hinweist, dass der abgefragte Pfad nicht zum allgemeinen Verzeichnis gehört, was letztendlich zu dem oben gezeigten falschen Pfad führt.

Fügen Sie die fehlende Bedingung hinzu, trie_find()damit die Übereinstimmungsfunktion niemals mit einem nicht vorhandenen Wert aufgerufen wird.

check_common() muss dann nicht mehr überprüfen, ob es einen Nicht-NULL-Wert hat, also entfernen Sie diese Bedingung.

Ich glaube, dass es keine anderen Pfade gibt, die eine ähnliche Scheinausgabe verursachen könnten.

AFAICT Die einzige andere Taste, die dazu führt, dass die Übereinstimmungsfunktion mit einem NULLWert aufgerufen wird, ist ' co' (wegen der Tasten ' common' und ' config').

Da sie sich jedoch nicht in einem Verzeichnis befinden, das zum allgemeinen Verzeichnis gehört, wird der resultierende baumspezifische Pfad erwartet.

VonC
quelle
3
Ich denke, dies sollte die beste Antwort sein, --preserve-merges"bewahrt" die Zusammenführungen nicht so, wie Sie es möchten, es ist sehr naiv. Auf diese Weise können Sie die Zusammenführungs-Commits und die übergeordneten Commit-Beziehungen beibehalten und gleichzeitig die Flexibilität einer interaktiven Rebase erhalten. Diese neue Funktion ist fantastisch und ohne diese gut geschriebene SO-Antwort hätte ich es nicht gewusst!
egucciar
@egucciar Danke. Und es ist nicht die einzige Funktion von Git 2.18 ( stackoverflow.com/search?q=user%3A6309+%22git+2.18%22 ) und Git 2.19 ( stackoverflow.com/search?q=user%3A6309+%22git+2.19% 22 )
VonC
1
Sehr hilfreich, wenn Sie versuchen, viele
verschieben
1
Oh, das ist wirklich das, wonach ich seit einer Weile gesucht habe! Ich hatte eine manuelle Problemumgehung für Fälle wie den, bei denen ein fiktives Commit erstellt werden sollte, das alle Zusammenführungen verbindet.
Carnicer
Typischer Git. Wenn Sie es wagen, eine einfache Frage zu stellen, müssen Sie höchstwahrscheinlich die Geschichte von Git, die internen Algorithmen und alle unübersichtlichen Implementierungsdetails lernen. Außerdem benötigen Sie ein Hauptfach in Graphentheorie, um zu verstehen, was vor sich geht.
Dimitris