Como escolher uma série de commits e mesclar em outro branch?

Eu tenho o seguinte layout de repository:

  • ramo mestre (produção)
  • integração
  • trabalhando

O que eu quero alcançar é selecionar uma série de commits do branch de trabalho e mesclá-lo no branch de integração. Eu sou muito novo para o git e não consigo descobrir exatamente como fazer isso (a seleção de intervalos de confirmação em uma operação e não a mesclagem) sem atrapalhar o repository. Quaisquer sugestões ou pensamentos sobre isso? Obrigado!

Quando se trata de uma série de commits, cherry picking é não era prático.

Como mencionado abaixo por Keith Kim , o Git 1.7.2+ introduziu a habilidade de selecionar uma série de commits (mas você ainda precisa estar ciente das conseqüências do cherry-picking para futuras mesclagens )

git cherry-pick “aprendeu a escolher uma gama de commits
(por exemplo, ” cherry-pick A..B ” e ” cherry-pick --stdin “), assim como ” git revert “; no entanto, eles não suportam o melhor controle de seqüenciamento ” rebase [-i] “.

Damian comenta e nos adverte:

Na forma ” cherry-pick A..B “, A deve ser mais antigo que B
Se eles estiverem na ordem errada, o comando falhará silenciosamente .

Se você quiser escolher o intervalo de B a D (inclusive), isso seria B^..D
Veja ” Git create branch from range of previous commits ” como uma ilustração.

Como Jubobs menciona nos comentários :

Isto assume que B não é um commit root; você receberá um erro de ” unknown revision ” caso contrário.

Nota: a partir do Git 2.9.x / 2.10 (Q3 2016), você pode selecionar um intervalo de commit diretamente em um branch órfão (empty head): veja ” Como tornar um branch órfão no git “.


Resposta original (janeiro de 2010)

Um rebase --onto seria melhor, onde você reproduziria o intervalo de commit fornecido em cima de sua ramificação de integração, como Charles Bailey descreveu aqui .
(também, procure por “Aqui está como você iria transplantar um ramo de tópico baseado em um ramo para outro” na página man git rebase , para ver um exemplo prático de git rebase --onto )

Se sua ramificação atual é integração:

 # 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 

Isso irá reproduzir tudo entre:

  • após o pai de first_SHA-1_of_working_branch_range (daí o ~1 ): o primeiro commit que você deseja reproduzir
  • até ” integration ” (que aponta para o último commit que você deseja reproduzir, do ramo de working )

para ” tmp ” (que aponta para onde a integration estava apontando antes)

Se houver algum conflito quando um desses commits for repetido:

  • ou resolva e execute ” git rebase --continue “.
  • ou pule este patch, e execute ” git rebase --skip
  • ou cancelar tudo com um ” git rebase --abort ” (e colocar de volta o ramo de integration no branch tmp )

Depois disso rebase --onto , a integration estará de volta no último commit do branch de integração (que é ” tmp ” branch + todos os commits repetidos)

Com cherry picking ou rebase --onto , não se esqueça que tem consequências em fusões subsequentes, como descrito aqui .


Uma solução ” cherry-pick ” pura é discutida aqui e envolveria algo como:

Se você quiser usar uma abordagem de patch, então “git format-patch | git am” e “git cherry” são suas opções.
Atualmente, o git cherry-pick aceita apenas um único commit, mas se você quiser escolher o intervalo de B a D que seria B^..D no git lingo, então

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

Mas de qualquer maneira, quando você precisa “repetir” uma série de commits, a palavra “replay” deve obrigar você a usar o recurso ” rebase ” do Git.

A partir de git v1.7.2 cherry pick pode aceitar uma série de commits:

git cherry-pick aprendeu a escolher uma série de commits (por exemplo, cherry-pick A..B e cherry-pick --stdin ), assim como git revert ; no entanto, eles não suportam o melhor rebase [-i] controle de seqüenciamento rebase [-i] .

Tem certeza de que não quer mesclar os twigs? Se o ramo de trabalho tem alguns commits recentes que você não quer, você pode simplesmente criar um novo branch com um HEAD no ponto que você quiser.

Agora, se você realmente quiser selecionar uma série de commits, por qualquer motivo, uma maneira elegante de fazer isso é simplesmente puxar um patchset e aplicá-lo ao seu novo ramo de integração:

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

Isso é essencialmente o que o git-rebase está fazendo de qualquer maneira, mas sem a necessidade de jogar. Você pode adicionar --3way ao git-am se você precisar mesclar. Certifique-se de que não há outros arquivos * .patch no diretório onde você faz isso, se você seguir as instruções na íntegra …

Suponha que você tenha 2 filiais,

“branchA”: inclui confirmações que você deseja copiar (de “commitA” para “commitB”

“branchB”: o ramo que você quer que os commits sejam transferidos de “branchA”

1)

  git checkout  

2) obter os IDs de “commitA” e “commitB”

3)

 git checkout  

4)

 git cherry-pick ^.. 

5) Em caso de conflito, resolva e digite

 git cherry-pick --continue 

para continuar o processo de escolha de cereja.

Eu envolvi o código do VonC em um script curto, git-multi-cherry-pick , para facilitar a execução:

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

Atualmente estou usando isso como reconstruir o histórico de um projeto que tinha código de terceiros e personalizações misturadas no mesmo tronco svn. Agora estou separando códigos de terceiros, módulos de terceiros e personalizações em suas próprias ramificações para melhor entender as personalizações daqui para frente. git-cherry-pick é útil nessa situação, pois tenho duas trees no mesmo repository, mas sem um ancestral compartilhado.

Todas as opções acima solicitarão que você resolva conflitos de mesclagem. Se você estiver mesclando alterações confirmadas para uma equipe, será difícil resolver os conflitos de mesclagem dos desenvolvedores e prosseguir. No entanto, “git merge” fará a mesclagem de uma só vez, mas você não pode passar um intervalo de revisões como argumento. temos que usar os comandos “git diff” e “git apply” para fazer o intervalo de mesclagem de revs. Eu observei que “git apply” irá falhar se o arquivo de correção tiver diff para muitos arquivos, então temos que criar um patch por arquivo e depois aplicar. Observe que o script não poderá excluir os arquivos excluídos na ramificação de origem. Este é um caso raro, você pode excluir manualmente esses arquivos do ramo de destino. O status de saída de “git apply” não é zero se não for possível aplicar o patch, no entanto, se você usar a opção -3way, ele voltará para a mesclagem de 3 vias e você não precisa se preocupar com essa falha.

Abaixo está o roteiro.

 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" 

Outra opção pode ser mesclar com a nossa estratégia para o commit antes do intervalo e, em seguida, um ‘normal’ mesclar com o último commit desse intervalo (ou branch quando for o último). Então suponha que apenas 2345 e 3456 commits de master sejam mesclados em branch de resources:

 mestre:
 1234
 2345
 3456
 4567

no ramo de recurso:

 git merge -s nosso 4567
 git merge 2345