Come scegliere una serie di commit e fondersi in un altro ramo?


640

Ho il seguente layout di repository:

  • filiale principale (produzione)
  • integrazione
  • Lavorando

Quello che voglio ottenere è scegliere una serie di commit dal ramo di lavoro e unirli nel ramo di integrazione. Sono abbastanza nuovo con Git e non riesco a capire come fare esattamente questo (la raccolta ciliegia degli intervalli di commit in un'operazione non l'unione) senza incasinare il repository. Qualche suggerimento o pensiero su questo? Grazie!


Risposte:


808

Quando si tratta di una serie di impegni, la raccolta delle ciliegie è non era pratico.

Come menzionato di seguito da Keith Kim , Git 1.7.2+ ha introdotto la possibilità di selezionare una serie di commit (ma è comunque necessario essere consapevoli delle conseguenze della raccolta delle ciliegie per future fusioni )

git cherry-pick "ha imparato a scegliere una serie di commit
(ad es." cherry-pick A..B"e" cherry-pick --stdin"), così ha fatto" git revert", ma questi non supportano il controllo di sequenziamento più bello che ha" rebase [-i]".

Damian commenta e ci avverte:

Nella cherry-pick A..Bforma " ", Adovrebbe essere più vecchio diB .
Se sono nell'ordine sbagliato, il comando fallirà silenziosamente .

Se si vuole scegliere la gamma Battraverso D(compreso) che sarebbe B^..D.
Vedi " Git creare un ramo dall'intervallo di precedenti commit? " Come illustrazione.

Come menziona Jubobs nei commenti :

Questo presuppone che Bnon sia un commit root; in caso unknown revisioncontrario verrà visualizzato un " " errore.

Nota: a partire da Git 2.9.x / 2.10 (3 ° trimestre 2016), è possibile selezionare una gamma di commit direttamente su un ramo orfano (testa vuota): vedere " Come rendere un ramo esistente un orfano in git ".


Risposta originale (gennaio 2010)

A rebase --ontosarebbe meglio, dove si riproduce l'intervallo dato di commit in cima al ramo di integrazione, come Charles Bailey ha descritto qui .
(cerca anche "Ecco come trapiantare un ramo di argomento basato su un ramo all'altro" nella pagina man di git rebase , per vedere un esempio pratico di git rebase --onto)

Se la tua filiale attuale è l'integrazione:

# 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

Ciò riprodurrà tutto tra:

  • dopo il genitore di first_SHA-1_of_working_branch_range(quindi il ~1): il primo commit che si desidera riprodurre
  • fino a " integration" (che indica l'ultimo commit che si desidera riprodurre, dal workingramo)

a " tmp" (che indica dove integrationpuntava prima)

In caso di conflitto durante la riproduzione di uno di questi commit:

  • o risolverlo ed eseguire " git rebase --continue".
  • o salta questa patch ed esegui invece " git rebase --skip"
  • o annulla il tutto con un " git rebase --abort" (e rimetti il integrationramo sul tmpramo)

Successivamente rebase --onto, integrationtornerà all'ultimo commit del ramo di integrazione (ovvero " tmp" ramo + tutti i commit riprodotti)

Con la raccolta delle ciliegie o rebase --onto, non dimenticare che ha conseguenze sulle fusioni successive, come descritto qui .


Una " cherry-pick" soluzione pura è discussa qui e implicherebbe qualcosa di simile:

Se vuoi usare un approccio patch allora "git format-patch | git am" e "git cherry" sono le tue opzioni.
Attualmente, git cherry-pickaccetta solo un singolo commit, ma se si vuole scegliere la gamma Battraverso Dche sarebbe B^..Din git gergo, in modo

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

Ma comunque, quando devi "ripetere" una serie di commit, la parola "replay" dovrebbe spingerti ad usare la " rebase" caratteristica di Git.


1
Se hai dei commit che hanno genitori che richiedono l' -mopzione, come gestirli? O c'è un modo per filtrare questi commit?
agosto

@aug -mdovrebbe gestirli per te, selezionando la linea principale a cui fa riferimento il -mparametro che hai scelto per questo cherry-picking.
VonC,

Il fatto è che se stai raccogliendo una serie di commit, sceglierà il commit corretto del genitore ma poi quando raggiunge un commit normale, fallisce e dice che il commit non è una fusione. Immagino che la mia domanda sia formulata meglio come far passare l' -mopzione solo quando colpisce un commit genitore quando si seleziona una gamma di commit? In questo momento se passo -mcome git cherry-pick a87afaaf..asfa789 -m 1si applica a tutti i commit all'interno dell'intervallo.
agosto

@aug Strange, non ho riprodotto il problema. Qual è la tua versione git e cosa è esattamente il messaggio di errore che vedi?
VonC,

Ah, sto eseguendo git versione 2.6.4 (Apple Git-63). L'errore che vedo sarebbe qualcosa di simile error: Commit 8fcaf3b61823c14674c841ea88c6067dfda3af48 is a merge but no -m option was given.in realtà ho capito si può solo git cherry-pick --continuee che sarebbe stato bene (ma non includerebbe il genitore commit)
Agosto

136

A partire da git v1.7.2 cherry pick può accettare una serie di commit:

git cherry-pickimparato a scegliere una serie di commit (es. cherry-pick A..Be cherry-pick --stdin), così ha fatto git revert; questi, tuttavia, non supportano il migliore controllo del sequenziamento rebase [-i].


103
Nota che cherry-pick A..Bnon verrà eseguito il commit A (ne avresti bisogno A~1..B), e se ci sono conflitti git non continuerà automaticamente come fa rebase (almeno dall'1.7.3.1)
Gabe Moothart

3
È anche bello notare che git cherry-pick A..B Cnon funziona come ti aspetteresti, ingenuamente. Non sceglierà tutto nella gamma A..Be si impegnerà C! Per fare questo, è necessario dividere in due righe, prima git cherry-pick A..Be poi git cherry-pick C. Quindi, ogni volta che hai un intervallo, devi eseguirlo separatamente.
MicroVirus,

42

Supponi di avere 2 rami,

"branchA": include i commit che vuoi copiare (da "commitA" a "commitB"

"branchB": il ramo in cui desideri vengano trasferiti i commit da "branchA"

1)

 git checkout <branchA>

2) ottenere gli ID di "commitA" e "commitB"

3)

git checkout <branchB>

4)

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

5) In caso di conflitto, risolvilo e digita

git cherry-pick --continue

per continuare il processo di selezione delle ciliegie.


Ha funzionato come un incanto! Molte grazie!
Snukone

28

Sei sicuro di non voler davvero unire i rami? Se il ramo di lavoro ha alcuni commit recenti che non vuoi, puoi semplicemente creare un nuovo ramo con un HEAD nel punto desiderato.

Ora, se vuoi davvero scegliere una serie di commit, per qualsiasi motivo, un modo elegante per farlo è semplicemente estrarre un patchset e applicarlo al tuo nuovo ramo di integrazione:

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

Questo è essenzialmente ciò che git-rebase sta facendo comunque, ma senza la necessità di giocare. È possibile aggiungere --3waya git-amse avete bisogno di unire. Assicurati che non ci siano altri file * .patch nella directory in cui lo fai, se segui le istruzioni alla lettera ...


1
Si noti che, come con altri intervalli di revisione, deve essere A^incluso A.
Gino Mempin,

9

Ho racchiuso il codice di VonC in un breve script bash git-multi-cherry-pick, per una facile esecuzione:

#!/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 

Attualmente sto usando questo mentre ricostruisco la storia di un progetto che aveva sia codice di terze parti che personalizzazioni mescolate insieme nello stesso trunk svn. Ora sto dividendo il codice di terze parti core, i moduli di terze parti e le personalizzazioni sui loro rami git per una migliore comprensione delle personalizzazioni future. git-cherry-pickè utile in questa situazione poiché ho due alberi nello stesso repository, ma senza un antenato condiviso.


3

Tutte le opzioni sopra ti chiederanno di risolvere i conflitti di unione. Se stai unendo le modifiche impegnate per un team, è difficile risolvere i conflitti di unione degli sviluppatori e procedere. Tuttavia, "git merge" farà l'unione in un colpo solo, ma non è possibile passare una serie di revisioni come argomento. dobbiamo usare i comandi "git diff" e "git apply" per unire l'intervallo di giri. Ho osservato che "git apply" fallirà se il file patch ha diff per troppi file, quindi dobbiamo creare una patch per file e quindi applicare. Si noti che lo script non sarà in grado di eliminare i file eliminati nel ramo di origine. Questo è un caso raro, è possibile eliminare manualmente tali file dal ramo di destinazione. Lo stato di uscita di "git apply" non è zero se non è in grado di applicare la patch,

Di seguito è la sceneggiatura.

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

L'ho provato alcuni giorni fa, dopo aver letto la spiegazione molto chiara di Vonc.

I miei passi

Inizio

  • Filiale dev: ABCDEFGHIJ
  • Filiale target: ABCD
  • Non voglio EH

Passaggi per copiare le funzioni senza i passaggi E e H nel ramo dev_feature_wo_E_H

  • git checkout dev
  • git checkout -b dev_feature_wo_E_H
  • git rebase --interactive --rebase-merges --no-ff Ddove ho messo in dropprimo piano Ee Hnell'editor rebase
  • risolvere i conflitti, continuare e commit

I passaggi per copiare il ramo dev_feature_wo_E_Hsulla destinazione.

  • git checkout target
  • git merge --no-ff --no-commit dev_feature_wo_E_H
  • risolvere i conflitti, continuare e commit

Alcune osservazioni

  • L'ho fatto a causa di troppo cherry-picknei giorni precedenti
  • git cherry-pick è potente e semplice ma

    • crea commit duplicati
    • e quando voglio mergedevo risolvere i conflitti dei commit iniziali e dei duplicati, quindi per uno o due cherry-pick, va bene la "raccolta delle ciliegie" ma per di più è troppo prolisso e il ramo diventerà troppo complesso
  • Nella mia mente i passaggi che ho fatto sono più chiari di git rebase --onto

1
Buon post. Upvoted. Mi ricorda stackoverflow.com/a/38418941/6309 . Ho ripreso gli svantaggi della raccolta delle ciliegie nel 2012: stackoverflow.com/a/13524494/6309 .
VonC

1

Un'altra opzione potrebbe essere quella di unire con la nostra strategia il commit prima dell'intervallo e quindi un'unione 'normale' con l'ultimo commit di quell'intervallo (o ramo quando è l'ultimo). Supponiamo quindi che solo 2345 e 3456 eseguano il commit del master da unire nel ramo feature:

maestro:
1234
2345
3456
4567

nel ramo delle caratteristiche:

git merge -s our 4567
git merge 2345
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.