Come scegliere una serie di commit e unirli in un altro ramo?
-
22-09-2019 - |
Domanda
Ho il seguente layout del repository:
- ramo principale (produzione)
- integrazione
- lavorando
Ciò che voglio ottenere è selezionare una serie di commit dal ramo di lavoro e unirli al ramo di integrazione.Sono abbastanza nuovo in Git e non riesco a capire come farlo esattamente (la scelta degli intervalli di commit in un'unica operazione, non la fusione) senza incasinare il repository.Qualche indicazione o pensiero su questo?Grazie!
Soluzione
Quando si tratta di una serie di commit, la scelta migliore È era non pratico.
COME menzionato sotto di Keith Kim, Git 1.7.2+ ha introdotto la possibilità di selezionare una serie di commit (ma è comunque necessario essere consapevoli delle conseguenza della scelta selettiva per future fusioni)
git cherry-pick" ha imparato a scegliere una serie di commit
(per esempio."cherry-pick A..B
" E "cherry-pick --stdin
"), così feci "git revert
";questi non supportano il controllo sequenziale più bello "rebase [-i]
"ha, però.
Damiano Commenti e ci avverte:
Nel "
cherry-pick A..B
" modulo,A
dovrebbe essere più vecchio diB
.
Se sono nell'ordine sbagliato, il comando fallirà silenziosamente.
Se vuoi scegliere il allineare B
Attraverso D
(compreso) sarebbe B^..D
.
Vedere "Git crea un ramo dalla gamma di commit precedenti?" Come un'illustrazione.
COME Jubob menziona nei commenti:
Ciò presuppone questo
B
non è un commit root;riceverai un "unknown revision
" errore altrimenti.
Nota:a partire da Git 2.9.x/2.10 (terzo trimestre 2016), puoi scegliere una serie di commit direttamente su un ramo orfano (testa vuota):Vedere "Come rendere orfano un ramo esistente in Git".
Risposta originale (gennaio 2010)
UN rebase --onto
sarebbe meglio, dove riproduci l'intervallo dato di commit sopra il tuo ramo di integrazione, come Charles Bailey descritto qui.
(inoltre, cerca "Ecco come trapiantare un ramo di un argomento basato su un ramo su un altro" nel file 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 desideri riprodurre - fino a "
integration
" (che punta all'ultimo commit che desideri riprodurre, dal fileworking
ramo)
A "tmp
" (che indica dove integration
indicavo prima)
Se si verifica un conflitto quando uno di questi commit viene riprodotto:
- o risolvilo ed esegui "
git rebase --continue
". - oppure salta questa patch ed esegui invece "
git rebase --skip
" - oppure annulla tutto con un "
git rebase --abort
" (e rimettere a posto ilintegration
ramo sultmp
ramo)
Dopo di che rebase --onto
, integration
tornerà 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.
Un puro"cherry-pick
"la soluzione è discusso qui, e implicherebbe qualcosa del tipo:
Se vuoi utilizzare un approccio 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 scegliere l'intervalloB
AttraversoD
sarebbeB^..D
nel gergo di Git, quindi
git rev-list --reverse --topo-order B^..D | while read rev
do
git cherry-pick $rev || break
done
Ma in ogni caso, quando hai bisogno di "rigiocare" una serie di commit, la parola "rigiocare" dovrebbe spingerti a usare il comando "rebase
" caratteristica di Git.
Altri suggerimenti
A partire dal git prelievo v1.7.2 ciliegia può accettare una serie di commit:
git cherry-pick
imparato a prendere una serie di commit (ad esempiocherry-pick A..B
echerry-pick --stdin
), così ha fattogit revert
; questi non supportare il più bellorebase [-i]
controllo di sequenziamento ha, però.
Supponiamo di avere 2 rami,
"branchA": comprende impegna da copiare (da "commitA" a "commitB"
"branchB": il ramo si desidera che le impegna a essere trasferiti 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) Nel caso in cui si dispone di un conflitto, risolverlo e il tipo
git cherry-pick --continue
per continuare il processo di cherry-pick.
Sei sicuro che non vuoi fondere in realtà i rami? Se il ramo di lavoro ha alcuni commit recenti che non si desidera, si può semplicemente creare un nuovo ramo con una testa in corrispondenza del punto che si desidera.
Ora, se davvero si vuole cherry-pick una serie di commit, per qualsiasi motivo, un modo elegante per farlo è quello di tirare solo di un patchset e applicarlo al nuovo ramo di integrazione:
git format-patch A..B
git checkout integration
git am *.patch
Questo è essenzialmente ciò che git-rebase sta facendo in ogni caso, ma senza la necessità di giocare. È possibile aggiungere --3way
a git-am
se è necessario unire. Assicurarsi che non vi siano altri file .patch * già nella directory in cui si esegue questa operazione, se si seguono le istruzioni alla lettera ...
ho avvolto di VonC codice in uno script bash breve, 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 come ricostruisco la storia di un progetto che ha avuto sia il codice 3rd-party e le personalizzazioni mescolati insieme nello stesso tronco svn. Ora sto dividendo a parte codice di base 3a parte, i moduli 3rd party, e le personalizzazioni sui loro propri rami Git per una migliore comprensione di personalizzazioni per il futuro. git-cherry-pick
è utile in questa situazione dal momento che ho due alberi nello stesso repository, ma senza un antenato comune.
Tutte le opzioni di cui sopra vi chiederà di risolvere i conflitti di fusione. Se si uniscono i cambiamenti registrati per una squadra, è difficile avere risolto i conflitti di unione da parte degli sviluppatori e procedere. Tuttavia, "git merge" farà la fusione in un solo colpo, ma non si può passare una serie di revisioni come argomento. dobbiamo usare "git diff" e "git apply" comandi per fare la gamma fusione di giri. Ho osservato che "git apply" avrà esito negativo se il file patch è diff per troppi di 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 fonte. Questo è un caso raro, è possibile eliminare manualmente questi file dal ramo di destinazione. Lo stato di uscita di "git apply" non è zero se non è in grado di applicare la patch, se si utilizza l'opzione -3way cadrà di nuovo a 3 vie di unione e non devono preoccuparsi di questo fallimento.
Di seguito è riportato lo script.
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 fondersi con la nostra strategia per il commit prima del range e poi un 'normale' fondersi con l'ultimo commit di tale intervallo (o ramo quando è l'ultima). Quindi supponiamo solo 2345 e 3456 commit del maestro da unire in ramo della funzione:
master: 1234 2345 3456 4567
nel ramo di caratteristica:
git merge -s ours 4567 git merge 2345