Wie kann man eine Reihe von Commits auswählen und in einem anderen Zweig zusammenführen?


639

Ich habe das folgende Repository-Layout:

  • Hauptniederlassung (Produktion)
  • Integration
  • Arbeiten

Was ich erreichen möchte, ist, eine Reihe von Commits aus dem Arbeitszweig auszuwählen und in den Integrationszweig zusammenzuführen. Ich bin ziemlich neu in Git und ich kann nicht herausfinden, wie ich das genau machen soll (das Kirschpflücken von Festschreibungsbereichen in einem Vorgang, nicht das Zusammenführen), ohne das Repository durcheinander zu bringen. Irgendwelche Hinweise oder Gedanken dazu? Vielen Dank!


Antworten:


805

Wenn es um eine Reihe von Commits geht, Kirschernte ist war nicht praktisch.

Wie unten von Keith Kim erwähnt , hat Git 1.7.2+ die Möglichkeit eingeführt, eine Reihe von Commits auszuwählen (aber Sie müssen sich immer noch der Konsequenzen der Auswahl von Kirschen für zukünftige Zusammenführungen bewusst sein ).

git cherry-pick "hat gelernt, eine Reihe von Commits auszuwählen
(z. B." cherry-pick A..B"und" cherry-pick --stdin"), ebenso" git revert"; diese unterstützen jedoch nicht die schönere Sequenzierungssteuerung" rebase [-i]".

damian kommentiert und warnt uns:

In der " cherry-pick A..B" Form Asollte älter sein alsB .
Wenn sie in der falschen Reihenfolge sind, schlägt der Befehl stillschweigend fehl .

Wenn Sie den Bereich Bdurch D(einschließlich) auswählen möchten , wäre dies B^..D.
Siehe " Git Zweig aus Bereich früherer Commits erstellen? " Zur Veranschaulichung.

Wie Jubobs erwähnt in den Kommentaren :

Dies setzt voraus, dass Bes sich nicht um ein Root-Commit handelt. unknown revisionAndernfalls wird ein " " Fehler angezeigt.

Hinweis: Ab Git 2.9.x / 2.10 (Q3 2016) können Sie einen Bereich von Commits direkt für einen verwaisten Zweig (leerer Kopf) auswählen: siehe " So machen Sie einen vorhandenen Zweig zu einem verwaisten Zweig in Git ".


Ursprüngliche Antwort (Januar 2010)

A rebase --ontowäre besser, wenn Sie den angegebenen Commit-Bereich zusätzlich zu Ihrem Integrationszweig wiedergeben, wie Charles Bailey hier beschrieben hat .
( Suchen Sie auch in der git rebase-Manpage nach "So würden Sie einen Themenzweig basierend auf einem Zweig in einen anderen transplantieren" , um ein praktisches Beispiel dafür zu sehen. git rebase --onto)

Wenn Ihre aktuelle Niederlassung Integration ist:

# Checkout a new temporary branch at the current location
git checkout -b tmp

# Move the integration branch to the head of the new patchset
git branch -f integration last_SHA-1_of_working_branch_range

# Rebase the patchset onto tmp, the old location of integration
git rebase --onto tmp first_SHA-1_of_working_branch_range~1 integration

Das wird alles zwischen:

  • nach dem übergeordneten Element von first_SHA-1_of_working_branch_range(daher das ~1): das erste Commit, das Sie wiederholen möchten
  • bis zu " integration" (was auf das letzte Commit zeigt, das Sie aus dem workingZweig wiedergeben möchten )

zu " tmp" (was zeigt, wohin integrationvorher gezeigt wurde)

Wenn bei der Wiedergabe eines dieser Commits ein Konflikt auftritt:

  • entweder lösen und " git rebase --continue" ausführen .
  • oder überspringen Sie diesen Patch und führen Sie stattdessen " git rebase --skip"
  • oder stornieren Sie alles mit einem " git rebase --abort" (und setzen Sie den integrationZweig wieder auf den tmpZweig)

Danach rebase --onto, integrationwird in der letzten wieder commit der Integration Zweig (das „ist tmp“ branch + alle wieder Commits)

rebase --ontoVergessen Sie nicht, beim Kirschpflücken oder , wie hier beschrieben, Konsequenzen für spätere Zusammenführungen zu haben .


Eine reine " cherry-pick" Lösung wird hier diskutiert und würde Folgendes beinhalten:

Wenn Sie einen Patch-Ansatz verwenden möchten, stehen Ihnen "git format-patch | git am" und "git cherry" zur Verfügung.
Derzeit git cherry-pickakzeptiert nur ein einziges Commit, aber wenn Sie den Bereich Bdurch auswählen möchten D, wäre dies B^..Din Git-Jargon, also

git rev-list --reverse --topo-order B^..D | while read rev 
do 
  git cherry-pick $rev || break 
done 

Wenn Sie jedoch eine Reihe von Commits "wiedergeben" müssen, sollte das Wort "Wiedergabe" Sie dazu bringen, die " rebase" -Funktion von Git zu verwenden.


1
-mWie gehen Sie mit diesen Commits um, wenn Sie Commits haben, deren Eltern diese Option benötigen ? Oder gibt es eine Möglichkeit, diese Commits herauszufiltern?
August

@aug -msoll sie für Sie erledigen, indem Sie die Hauptzeile auswählen, auf die sich der -mParameter bezieht, den Sie für diese Kirschernte ausgewählt haben.
VonC

Die Sache ist, wenn Sie eine Reihe von Commits auswählen, werden die übergeordneten Commits korrekt ausgewählt, aber wenn es auf ein normales Commit trifft, schlägt es fehl und sagt, dass Commit keine Zusammenführung ist. Ich denke, meine Frage ist besser formuliert, wie sie die -mOption nur dann bestehen lässt, wenn sie ein übergeordnetes Commit trifft, wenn eine Reihe von Commits ausgewählt wird. Gerade jetzt , wenn ich gehe , -mwie git cherry-pick a87afaaf..asfa789 -m 1es gilt für alle Commits im Bereich.
August

@aug Seltsam, ich habe das Problem nicht reproduziert. Was ist deine Git-Version und was ist die genaue Fehlermeldung, die du siehst?
VonC

Ah, ich verwende die Git-Version 2.6.4 (Apple Git-63). Der Fehler sehe ich wäre so etwas wie error: Commit 8fcaf3b61823c14674c841ea88c6067dfda3af48 is a merge but no -m option was given.ich Sie gerade tatsächlich realisiert könnte git cherry-pick --continueund es wäre schön (aber es würde auch nicht die Eltern begehen)
August

136

Ab Git v1.7.2 kann Cherry Pick eine Reihe von Commits akzeptieren:

git cherry-picklernte, eine Reihe von Commits (z. B. cherry-pick A..Bund cherry-pick --stdin) auszuwählen, und tat dies auch git revert; Diese unterstützen jedoch nicht die schönere Sequenzierungskontrolle rebase [-i].


103
Beachten Sie, dass cherry-pick A..BCommit A nicht erhalten wird (das würden Sie dafür benötigen A~1..B), und wenn es Konflikte gibt, wird Git nicht automatisch fortgesetzt, wie es Rebase tut (zumindest ab 1.7.3.1)
Gabe Moothart

3
Es ist auch gut zu bemerken, dass git cherry-pick A..B Ces nicht so funktioniert, wie man es naiv erwarten würde. Es wird nicht alles im Bereich auswählen A..Bund festschreiben C! Dazu müssen Sie zuerst git cherry-pick A..Bund dann in zwei Zeilen aufteilen git cherry-pick C. Wenn Sie also einen Bereich haben, müssen Sie ihn separat ausführen.
MicroVirus

42

Angenommen, Sie haben 2 Zweige,

"branchA": Enthält Commits, die Sie kopieren möchten (von "commitA" nach "commitB").

"branchB": Der Zweig, in den die Commits von "branchA" übertragen werden sollen.

1)

 git checkout <branchA>

2) Holen Sie sich die IDs von "commitA" und "commitB"

3)

git checkout <branchB>

4)

git cherry-pick <commitA>^..<commitB>

5) Wenn Sie einen Konflikt haben, lösen Sie ihn und geben Sie ein

git cherry-pick --continue

um den Kirschpflückprozess fortzusetzen.


Arbeitete wie Charme! Vielen Dank!
Snukone

28

Sind Sie sicher, dass Sie die Zweige nicht zusammenführen möchten? Wenn der Arbeitszweig einige kürzlich festgeschriebene Commits enthält, die Sie nicht möchten, können Sie einfach an der gewünschten Stelle einen neuen Zweig mit einem HEAD erstellen.

Wenn Sie aus irgendeinem Grund wirklich eine Reihe von Commits auswählen möchten, können Sie dies elegant tun, indem Sie einfach ein Patchset abrufen und es auf Ihren neuen Integrationszweig anwenden:

git format-patch A..B
git checkout integration
git am *.patch

Dies ist im Wesentlichen das, was git-rebase sowieso tut, aber ohne dass Spiele gespielt werden müssen. Sie können hinzufügen --3way, git-amwenn Sie zusammenführen müssen. Stellen Sie sicher, dass sich noch keine anderen * .patch-Dateien in dem Verzeichnis befinden, in dem Sie dies tun, wenn Sie die Anweisungen wörtlich befolgen ...


1
Beachten Sie, dass es wie bei anderen Revisionsbereichen auch A^einschließen muss A.
Gino Mempin

9

Ich habe den Code von VonC in ein kurzes Bash-Skript eingewickelt git-multi-cherry-pick, damit es einfach ausgeführt werden kann:

#!/bin/bash

if [ -z $1 ]; then
    echo "Equivalent to running git-cherry-pick on each of the commits in the range specified.";
    echo "";
    echo "Usage:  $0 start^..end";
    echo "";
    exit 1;
fi

git rev-list --reverse --topo-order $1 | while read rev 
do 
  git cherry-pick $rev || break 
done 

Ich verwende dies derzeit, um den Verlauf eines Projekts neu zu erstellen, bei dem sowohl Code von Drittanbietern als auch Anpassungen im selben SVN-Trunk gemischt wurden. Ich teile jetzt den Kerncode von Drittanbietern, Module von Drittanbietern und Anpassungen in ihre eigenen Git-Zweige auf, um die zukünftigen Anpassungen besser zu verstehen. git-cherry-pickist in dieser Situation hilfreich, da ich zwei Bäume im selben Repository habe, aber ohne einen gemeinsamen Vorfahren.


3

Alle oben genannten Optionen fordern Sie auf, Zusammenführungskonflikte zu lösen. Wenn Sie für ein Team festgeschriebene Änderungen zusammenführen, ist es schwierig, die Zusammenführungskonflikte von Entwicklern zu lösen und fortzufahren. "Git Merge" führt die Zusammenführung jedoch in einem Schuss durch, aber Sie können eine Reihe von Revisionen nicht als Argument übergeben. Wir müssen die Befehle "git diff" und "git apply" verwenden, um den Zusammenführungsbereich der Umdrehungen durchzuführen. Ich habe festgestellt, dass "git apply" fehlschlägt, wenn die Patch-Datei für zu viele Dateien unterschiedlich ist. Daher müssen wir einen Patch pro Datei erstellen und dann anwenden. Beachten Sie, dass das Skript die im Quellzweig gelöschten Dateien nicht löschen kann. Dies ist ein seltener Fall. Sie können solche Dateien manuell aus dem Zielzweig löschen. Der Exit-Status von "git apply" ist nicht Null, wenn der Patch nicht angewendet werden kann.

Unten ist das Skript.

enter code here



  #!/bin/bash

    # This script will merge the diff between two git revisions to checked out branch
    # Make sure to cd to git source area and checkout the target branch
    # Make sure that checked out branch is clean run "git reset --hard HEAD"


    START=$1
    END=$2

    echo Start version: $START
    echo End version: $END

    mkdir -p ~/temp
    echo > /tmp/status
    #get files
    git --no-pager  diff  --name-only ${START}..${END} > ~/temp/files
    echo > ~/temp/error.log
    # merge every file
    for file in `cat  ~/temp/files`
    do
      git --no-pager diff --binary ${START}..${END} $file > ~/temp/git-diff
      if [ $? -ne 0 ]
      then
#      Diff usually fail if the file got deleted 
        echo Skipping the merge: git diff command failed for $file >> ~/temp/error.log
        echo Skipping the merge: git diff command failed for $file
        echo "STATUS: FAILED $file" >>  /tmp/status
        echo "STATUS: FAILED $file"
    # skip the merge for this file and continue the merge for others
        rm -f ~/temp/git-diff
        continue
      fi

      git apply  --ignore-space-change --ignore-whitespace  --3way --allow-binary-replacement ~/temp/git-diff

      if [ $? -ne 0 ]
       then
#  apply failed, but it will fall back to 3-way merge, you can ignore this failure
         echo "git apply command filed for $file"
       fi
       echo
       STATUS=`git status -s $file`


       if [ ! "$STATUS" ]
       then
#   status is null if the merged diffs are already present in the target file
         echo "STATUS:NOT_MERGED $file"
         echo "STATUS: NOT_MERGED $file$"  >>  /tmp/status
       else
#     3 way merge is successful
         echo STATUS: $STATUS
         echo "STATUS: $STATUS"  >>  /tmp/status
       fi
    done

    echo GIT merge failed for below listed files

    cat ~/temp/error.log

    echo "Git merge status per file is available in /tmp/status"

2

Ich habe das vor einigen Tagen getestet, nachdem ich die sehr klare Erklärung von Vonc gelesen hatte.

Meine Schritte

Start

  • Niederlassung dev: ABCDEFGHIJ
  • Zweig target: ABCD
  • Ich will Enoch nichtH

Schritte zum Kopieren von Features ohne die Schritte E und H in der Verzweigung dev_feature_wo_E_H

  • git checkout dev
  • git checkout -b dev_feature_wo_E_H
  • git rebase --interactive --rebase-merges --no-ff Dwo ich dropvor Eund Hin den Rebase-Editor stelle
  • Konflikte lösen, fortfahren und commit

Schritte zum Kopieren des Zweigs dev_feature_wo_E_Hauf das Ziel.

  • git checkout target
  • git merge --no-ff --no-commit dev_feature_wo_E_H
  • Konflikte lösen, fortfahren und commit

Einige Bemerkungen

  • Ich habe das wegen zu viel cherry-pickin den Tagen zuvor getan
  • git cherry-pick ist mächtig und einfach aber

    • Es werden doppelte Commits erstellt
    • und wenn ich möchte, muss mergeich Konflikte der anfänglichen Commits lösen und Commits duplizieren. Für ein oder zwei cherry-pickist es also in Ordnung, "Kirschen zu pflücken", aber für mehr ist es zu ausführlich und der Zweig wird zu komplex
  • In meinen Augen sind die Schritte, die ich getan habe, klarer als git rebase --onto

1
Guter Post. Upvoted. Es erinnert mich an stackoverflow.com/a/38418941/6309 . Ich habe die Nachteile der Kirschernte im Jahr 2012 zusammengefasst: stackoverflow.com/a/13524494/6309 .
VonC

1

Eine andere Möglichkeit könnte darin bestehen, mit unserer Strategie zum Commit vor dem Bereich zusammenzuführen und dann eine "normale" Zusammenführung mit dem letzten Commit dieses Bereichs (oder einem Zweig, wenn es der letzte ist). Angenommen, nur 2345 und 3456 Commits des Masters werden in den Feature-Zweig eingefügt:

Meister:
1234
2345
3456
4567

im Feature-Zweig:

Git Merge -s unsere 4567
Git Merge 2345
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.