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

Ho il seguente layout del repository:

  • ramo principale (produzione)
  • integrazione
  • lavoro

Quello che voglio ottenere è selezionare una serie di commit dal ramo di lavoro e fonderlo nel ramo di integrazione. Sono abbastanza nuovo da git e non riesco a capire come fare esattamente questo (il cherry picking degli intervalli di commit in una sola operazione, non l’unione) senza rovinare il repository. Qualche suggerimento o idea su questo? Grazie!

Quando si tratta di una serie di commit, cherry-picking è non era pratico

Come accennato in seguito da Keith Kim , Git 1.7.2+ ha introdotto la capacità di selezionare una serie di commit (ma è comunque necessario essere consapevoli delle conseguenze del cherry-picking per future fusioni )

git cherry-pick “ha imparato a scegliere una serie di commit
(ad esempio ” cherry-pick A..B ” e ” cherry-pick --stdin “), così ha fatto ” git revert “; questi non supportano il controllo di sequenziamento più gradevole ” rebase [-i] ” ha, però.

commenti damiani e ci avverte:

Nel modulo ” cherry-pick A..B “, A dovrebbe essere più vecchio di B
Se sono nell’ordine sbagliato, il comando fallirà silenziosamente .

Se si desidera selezionare l’ intervallo da B a D (compreso) che sarebbe B^..D
Vedi ” Git crea il ramo dal range dei precedenti commit? ” Come un’illustrazione.

Come dice Jubobs nei commenti :

Ciò presuppone che B non sia un commit di root; altrimenti otterrai un errore di ” unknown revision “.

Nota: a partire da Git 2.9.x / 2.10 (3 ° trim. 2016), è ansible selezionare un intervallo di commit direttamente su un ramo orfano (testa vuota): consultare ” Come rendere esistente un ramo orfano in git “.


Risposta originale (gennaio 2010)

Un rebase --onto sarebbe meglio, in cui si ripete l’intervallo di commit dato sopra il proprio ramo di integrazione, come descritto da Charles Bailey .
(inoltre, cerca “Ecco come trapianterai un ramo argomento basato su un ramo in un altro” nella pagina man di rebase git , per vedere un esempio pratico di git rebase --onto )

Se il tuo attuale ramo è 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 (da cui il ~1 ): il primo commit che si desidera riprodurre
  • fino a ” integration ” (che punta all’ultimo commit che si desidera riprodurre, dal ramo di working )

a ” tmp ” (che indica dove l’ integration stava puntando prima)

Se c’è un conflitto quando uno di questi viene ripetuto:

  • o risolverlo ed eseguire ” git rebase --continue “.
  • o salta questa patch, ed esegui invece ” git rebase --skip
  • oppure cancella il tutto con un ” git rebase --abort ” (e git rebase --abort il ramo di integration sul ramo tmp )

Dopo tale rebase --onto , l’ integration tornerà all’ultima commit del ramo di integrazione (cioè il ramo ” tmp ” + tutti i commit ripetuti)

Con cherry-picking o rebase --onto , non dimenticare che ha conseguenze sulle successive fusioni, come descritto qui .


Una pura soluzione ” cherry-pick ” è discussa qui e coinvolgerebbe qualcosa come:

Se vuoi usare un approccio di patch allora “git format-patch | git am” e “git cherry” sono le tue opzioni.
Attualmente, git cherry-pick accetta solo un singolo commit, ma se vuoi selezionare l’intervallo da B a D che sarebbe B^..D in git gergo, quindi

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

Ma comunque, quando hai bisogno di “ripetere” un intervallo di commit, la parola “replay” dovrebbe spingerti ad usare la funzione ” rebase ” di Git.

A partire da git v1.7.2 cherry pick può accettare un range di commit:

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

Sei sicuro di non voler effettivamente 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 che desideri.

Ora, se vuoi davvero selezionare 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 sta facendo git-rebase, ma senza la necessità di giocare. È ansible aggiungere --3way a git-am se è necessario unire. Assicurarsi che non ci siano altri file * .patch già nella directory in cui si esegue questa operazione, se si seguono le istruzioni alla lettera …

Supponiamo che tu abbia 2 rami,

“branchA”: include commit che vuoi copiare (da “commitA” a “commitB”

“branchB”: il ramo in cui si desidera che i commit vengano trasferiti da “branchA”

1)

  git checkout  

2) ottenere gli ID di “commitA” e “commitB”

3)

 git checkout  

4)

 git cherry-pick ^.. 

5) Nel caso tu abbia un conflitto, risolvilo e digita

 git cherry-pick --continue 

per continuare il processo di selezione delle ciliegie.

Ho avvolto 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 cronologia di un progetto in cui sia il codice di terze parti che le personalizzazioni si sono mescolati nello stesso trunk svn. Ora sto dividendo il codice di terze parti di base, i moduli di terze parti e le personalizzazioni sui propri rami git per una migliore comprensione delle personalizzazioni in futuro. git-cherry-pick è utile in questa situazione poiché ho due alberi nello stesso repository, ma senza un antenato condiviso.

Tutte le opzioni sopra indicate ti chiederanno di risolvere i conflitti di fusione. Se si uniscono le modifiche apportate per un team, è difficile risolvere i conflitti di fusione degli sviluppatori e procedere. Tuttavia, “git merge” farà l’unione in un colpo solo, ma non puoi passare un intervallo di revisioni come argomento. dobbiamo usare i comandi “git diff” e “git apply” per fare l’intervallo di unioni di rev. Ho notato che “git apply” fallirà se il file di 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 che vengono eliminati nel ramo sorgente. Questo è un caso raro, è ansible 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, tuttavia se si utilizza l’opzione -3way ricadrà all’unione a 3 vie e non ci si deve preoccupare di questo errore.

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" 

Un’altra opzione potrebbe essere quella di unire con la strategia nostra al 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 i 2345 e 3456 commit del master vengano uniti in feature branch:

 maestro:
 1234
 2345
 3456
 4567

in feature branch:

 git merge -s ours 4567
 git si fondono 2345