Parcialmente escolhendo um commit com Git

501

Estou trabalhando em 2 ramos diferentes: lançamento e desenvolvimento .

Percebi que ainda preciso integrar algumas alterações que foram confirmadas no ramo de lançamento no ramo de desenvolvimento .

O problema é que não preciso de todo o commit, apenas alguns trechos em determinados arquivos, portanto, um simples

git cherry-pick bc66559

não faz o truque.

Quando eu faço um

git show bc66559

Eu posso ver o diff, mas realmente não conheço uma boa maneira de aplicar isso parcialmente à minha árvore de trabalho atual.

oliver
fonte

Respostas:

792

A principal coisa que você vai querer aqui é git add -p( -pé um sinônimo --patch). Isso fornece uma maneira interativa de fazer check-in de conteúdo, permitindo que você decida se cada pedaço deve entrar e até mesmo editando manualmente o patch, se necessário.

Para usá-lo em combinação com a seleção de cereja:

git cherry-pick -n <commit> # get your patch, but don't commit (-n = --no-commit)
git reset                   # unstage the changes from the cherry-picked commit
git add -p                  # make all your choices (add the changes you do want)
git commit                  # make the commit!

(Obrigado a Tim Henigan por me lembrar que o git-cherry-pick tem uma opção --no-commit, e obrigado a Felix Rabe por apontar que você precisa redefinir! Se você quiser apenas deixar algumas coisas de fora do commit , você pode usar git reset <path>...para desfazer apenas esses arquivos.)

Obviamente, você pode fornecer caminhos específicos para, add -pse necessário. Se você estiver começando com um patch, poderá substituí-lo cherry-pickpor apply.


Se você realmente deseja um git cherry-pick -p <commit>(essa opção não existe), pode usar

git checkout -p <commit>

Isso diferenciará o commit atual em relação ao commit que você especificar e permitirá que você aplique pedaços desse diff individualmente. Essa opção pode ser mais útil se o commit que você está recebendo tiver conflitos de mesclagem em parte do commit que você não está interessado. (Observe, no entanto, que checkoutdifere de cherry-pick: checkouttenta aplicar <commit>completamente o conteúdo cherry-pickde a confirmação especificada do pai. Isso significa que checkoutpode aplicar mais do que apenas essa confirmação, que pode ser mais do que você deseja.)

Cascabel
fonte
Na verdade, se eu seguir o conselho do git 1.7.5.4, 'git add -p' diz 'Sem alterações' porque já está tudo no índice. Eu preciso fazer um 'git reset HEAD' antes de 'git add' - alguma maneira de evitar esse reset com alguma opção?
Felix Rabe
@FelixRabe: Estou realmente surpreso que, em algum momento, cherry-pick -naparentemente não tenha deixado as mudanças organizadas - a convenção é definitivamente que as --no-commitopções param logo antes do commit, ou seja, com todas as mudanças organizadas. Vou adicionar a redefinição na resposta.
Cascabel
1
@Blixt Ou seja, se os commits que estão no outro ramo, mas não o seu ramo atual são ABCDEF, git checkout -p <F>que não apenas te as mudanças de F, você fica ABCDEF tudo purê juntos e permite classificar para fora o que parte do que você quer . Reduzir isso a apenas algumas das mudanças de F é uma dor. Por outro lado, git cherry-pick -n <F>você obtém apenas as alterações de F - e se algumas dessas alterações entrarem em conflito, será útil informar para que você possa descobrir como mesclar corretamente.
Cascabel
Eu tive problemas com isso quando a alteração foi uma adição de arquivo. O git resetremoveria os arquivos temporários e add -papenas diria 'nada a acrescentar'.
Ian Grainger
36

Supondo que as alterações desejadas estejam na cabeça do ramo em que você deseja as alterações, use git checkout

para um único arquivo:

git checkout branch_that_has_the_changes_you_want path/to/file.rb

para vários arquivos apenas em cadeia:

git checkout branch_that_has_the_changes_you_want path/to/file.rb path/to/other_file.rb
Jay Swain
fonte
5
Isso copia o arquivo inteiro: não apenas as alterações.
Lista Jeremy
1
Essa resposta, assim como qualquer outra resposta aqui até agora, tem uma grande desvantagem: ela não preserva o autor original da alteração, e sim compromete seu nome. Se uma mudança é boa, você está roubando o crédito de alguém; se uma mudança é ruim, você está se incendiando. Parece que não há como evitar git cherry-pick -p, e é uma pena que ainda não esteja lá.
Pfalcon # 29/15
15

Com base na resposta de Mike Monkiewicz, você também pode especificar um ou mais arquivos a serem retirados do sha1 / branch fornecido.

git checkout -p bc66559 -- path/to/file.java 

Isso permitirá que você escolha interativamente as alterações que deseja aplicar à sua versão atual do arquivo.

Christian.D
fonte
5
Isso é problemático se a versão atual do arquivo for significativamente diferente daquela versão. Você será solicitado com várias alterações irrelevantes (que não aparecem no commit). Além disso, as alterações que você realmente deseja podem aparecer em uma "forma disfarçada" como uma diferença para a atual, não para a original. É possível que a diferença original que você deseja tenha conflito e precise ser adequadamente mesclada; você não terá essa oportunidade aqui.
Kaz
1

Se você deseja especificar uma lista de arquivos na linha de comando e executar tudo em um único comando atômico, tente:

git apply --3way <(git show -- list-of-files)

--3way: Se um patch não se aplicar corretamente, o Git criará um conflito de mesclagem para que você possa executar git mergetool. Omitir --3wayfará o Git desistir de patches que não se aplicam de maneira limpa.

nyanpasu64
fonte
0

Se "selecionar parcialmente" significa "dentro dos arquivos, escolhendo algumas alterações, mas descartando outras", isso pode ser feito trazendo git stash:

  1. Faça a escolha completa.
  2. git reset HEAD^ para converter todo o commit escolhido em mudanças de trabalho sem etapas.
  3. Agora git stash save --patch: selecione interativamente o material indesejado a ser guardado.
  4. O Git reverte as alterações ocultas da sua cópia de trabalho.
  5. git commit
  6. Jogue fora o estoque de alterações indesejadas: git stash drop.

Dica: se você der um nome ao stash de alterações indesejadas: git stash save --patch junkse você esquecer de fazer (6) agora, mais tarde reconhecerá o stash pelo que é.

Kaz
fonte
Se você apenas escolher a cereja, em seguida, redefinir ... você não vai esconder nada. Como dito acima, você deve fazer uma escolha de cereja com --no-commit ou uma redefinição --hard HEAD ~. Observe que você está procedendo negativamente (selecionando o que não deseja). A solução aceita acima permite uma abordagem positiva ou negativa.
Pierre-Olivier Vares
0

Use git format-patchpara cortar a parte do commit que você gosta e git amaplicá-lo a outro ramo

git format-patch <sha> -- path/to/file
git checkout other-branch
git am *.patch
adrock20
fonte