Como mesclar ou escolher seletivamente alterações de outra filial no Git?
-
19-08-2019 - |
Pergunta
Estou usando o GIT em um novo projeto que tem dois paralelos - mas atualmente experimentais - ramos de desenvolvimento:
master
: importar a base de código existente mais alguns mods que geralmente tenho certezaexp1
: ramo experimental #1exp2
: ramo experimental #2
exp1
e exp2
representam duas abordagens arquitetônicas muito diferentes. Até que eu me aproxime, não tenho como saber qual (se qualquer um) funcionará. Ao progredir em um ramo, às vezes tenho edições que seriam úteis no outro ramo e gostariam de mesclar apenas essas.
Qual é a melhor maneira de mesclar mudanças seletivas de um ramo de desenvolvimento para outro, deixando para trás todo o resto?
Abordagens que eu considerei:
git merge --no-commit
Seguido por soltar manual de um grande número de edições que não quero tornar comum entre os galhos.Cópia manual de arquivos comuns em um diretório temporário seguido de
git checkout
passar para o outro ramo e, em seguida, mais cópias manuais para fora do diretório temporário para a árvore de trabalho.Uma variação no acima. Abandonar o
exp
Ramificações por enquanto e use dois repositórios locais adicionais para experimentação. Isso torna a cópia manual de arquivos muito mais direta.
Todas essas três abordagens parecem tediosas e propensas a erros. Espero que haja uma abordagem melhor; algo semelhante a um parâmetro de caminho do filtro que faria git-merge
mais seletivo.
Solução
Você usa o colher cerejas comando para obter compromissos individuais de uma filial.
Se as alterações que você deseja não estão em compromissos individuais, use o método mostrado aqui dividir o compromisso em compromissos individuais. Grosso falando, você usa git rebase -i
Para obter o compromisso original de editar, então git reset HEAD^
Para reverter seletivamente as alterações, então git commit
para comprometer isso como um novo compromisso na história.
Há outro bom método aqui na revista Red Hat, onde eles usam git add --patch
ou possivelmente git add --interactive
O que permite adicionar apenas partes de um pedaço, se você deseja dividir alterações diferentes em um arquivo individual (pesquise nessa página por "divisão").
Tendo dividido as mudanças, agora você pode escolher apenas as que deseja.
Outras dicas
Eu tive exatamente o mesmo problema mencionado por você acima. Mas eu encontrei isto mais claro em explicar a resposta.
Resumo:
Confira os caminhos (s) do ramo que você deseja mesclar,
$ git checkout source_branch -- <paths>...
Dica: também funciona sem
--
Como visto na postagem vinculada.ou para mesclar seletivamente pedaços
$ git checkout -p source_branch -- <paths>...
Como alternativa, use redefinir e adicione com a opção
-p
,$ git reset <paths>... $ git add -p <paths>...
Finalmente comet
$ git commit -m "'Merge' these changes"
Para mesclar seletivamente arquivos de uma ramificação em outra ramificação, execute
git merge --no-ff --no-commit branchX
Onde branchX
é o ramo que você deseja se fundir para o ramo atual.
o --no-commit
A opção encenará os arquivos que foram mesclados pelo Git sem realmente cometê -los. Isso dará a você a oportunidade de modificar os arquivos mesclados como desejar e depois cometê -los você mesmo.
Dependendo de como você deseja mesclar arquivos, existem quatro casos:
1) Você quer uma mesclagem verdadeira.
Nesse caso, você aceita os arquivos mesclados da maneira que o Git os fundiu automaticamente e os compromete.
2) Existem alguns arquivos que você não deseja mesclar.
Por exemplo, você deseja reter a versão na ramificação atual e ignorar a versão na filial da qual está se fundindo.
Para selecionar a versão na filial atual, execute:
git checkout HEAD file1
Isso recuperará a versão de file1
no ramo atual e substituem o file1
Automerged by Git.
3) Se você deseja a versão em Branchx (e não uma mesclagem verdadeira).
Corre:
git checkout branchX file1
Isso recuperará a versão de file1
dentro branchX
e substitua file1
Mercada automaticamente por Git.
4) O último caso é se você deseja selecionar apenas mesclas específicas em file1
.
Nesse caso, você pode editar o modificado file1
diretamente, atualize -o para o que você deseja a versão de file1
tornar -se e depois se comprometer.
Se o Git não puder mesclar um arquivo automaticamente, ele relatará o arquivo como "não -siderado"E produza uma cópia na qual você precisará resolver os conflitos manualmente.
Para explicar mais com um exemplo, digamos que você queira se fundir branchX
no ramo atual:
git merge --no-ff --no-commit branchX
Você então executa o git status
comando para visualizar o status de arquivos modificados.
Por exemplo:
git status
# On branch master
# Changes to be committed:
#
# modified: file1
# modified: file2
# modified: file3
# Unmerged paths:
# (use "git add/rm <file>..." as appropriate to mark resolution)
#
# both modified: file4
#
Onde file1
, file2
, e file3
Os arquivos são o GIT com sucesso automaticamente.
O que isso significa é que muda no master
e branchX
Para todos esses três arquivos, foram combinados sem conflitos.
Você pode inspecionar como a fusão foi feita executando o git diff --cached
;
git diff --cached file1
git diff --cached file2
git diff --cached file3
Se você encontrar alguma mesclagem indesejável, então você pode
- Edite o arquivo diretamente
- Salve
git commit
Se você não quiser se fundir file1
e deseja manter a versão na filial atual
Corre
git checkout HEAD file1
Se você não quiser se fundir file2
e só quero a versão em branchX
Corre
git checkout branchX file2
Se você quiser file3
Para ser mesclado automaticamente, não faça nada.
Git já fundiu neste momento.
file4
Acima está uma falha por falha pelo git. Isso significa que há alterações nas duas ramificações que ocorrem na mesma linha. É aqui que você precisará resolver os conflitos manualmente. Você pode descartar o mescla file4
tornar-se.
Finalmente, não se esqueça de git commit
.
Eu não gosto das abordagens acima. Usar a escolha de cereja é ótimo para escolher uma única mudança, mas é uma dor se você quiser trazer todas as mudanças, exceto algumas ruins. Aqui está minha abordagem.
Não há --interactive
Argumento que você pode passar para a fusão do git.
Aqui está a alternativa:
Você tem algumas mudanças no ramo 'recurso' e deseja trazer alguns, mas nem todos eles para 'master' de uma maneira não desleixada (ou seja, você não quer escolher e cometer cada um)
git checkout feature
git checkout -b temp
git rebase -i master
# Above will drop you in an editor and pick the changes you want ala:
pick 7266df7 First change
pick 1b3f7df Another change
pick 5bbf56f Last change
# Rebase b44c147..5bbf56f onto b44c147
#
# Commands:
# pick = use commit
# edit = use commit, but stop for amending
# squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#
git checkout master
git pull . temp
git branch -d temp
Então, envolva isso em um script de shell, transforme o mestre em $ para e altere o recurso para $ de e você está pronto para ir:
#!/bin/bash
# git-interactive-merge
from=$1
to=$2
git checkout $from
git checkout -b ${from}_tmp
git rebase -i $to
# Above will drop you in an editor and pick the changes you want
git checkout $to
git pull . ${from}_tmp
git branch -d ${from}_tmp
Há outro caminho ir:
git checkout -p
É uma mistura entre git checkout
e git add -p
e pode ser exatamente o que você está procurando:
-p, --patch
Interactively select hunks in the difference between the <tree-ish>
(or the index, if unspecified) and the working tree. The chosen
hunks are then applied in reverse to the working tree (and if a
<tree-ish> was specified, the index).
This means that you can use git checkout -p to selectively discard
edits from your current working tree. See the “Interactive Mode”
section of git-add(1) to learn how to operate the --patch mode.
Embora algumas dessas respostas sejam muito boas, sinto que ninguém realmente respondeu à restrição original da OP: selecionando arquivos específicos de ramificações específicas. Esta solução faz isso, mas pode ser tediosa se houver muitos arquivos.
Vamos dizer que você tem o master
, exp1
, e exp2
galhos. Você deseja mesclar um arquivo de cada uma das filiais experimentais em mestre. Eu faria algo assim:
git checkout master
git checkout exp1 path/to/file_a
git checkout exp2 path/to/file_b
# save these files as a stash
git stash
# merge stash with master
git merge stash
Isso lhe dará diffs no arquivo para cada um dos arquivos que você deseja. Nada mais. Nada menos. É útil que você tenha alterações de arquivo radicalmente diferentes entre as versões-no meu caso, alterando um aplicativo de Rails 2 para Rails 3.
EDITAR: Isso mesclará arquivos, mas faz uma mesclagem inteligente. Não consegui descobrir como usar esse método para obter informações diff no arquivo (talvez ainda o fará para diferenças extremas. Irritas pequenas coisas como o espaço em branco se fundem de volta, a menos que você use o -s recursive -X ignore-all-space
opção)
A resposta da informação de 1800 está completamente correta. Como um git noob, porém, "Use Git Cherry-Pick" não foi suficiente para eu descobrir isso sem um pouco mais de escavação na Internet, então pensei em postar um guia mais detalhado, caso qualquer outra pessoa esteja em um barco semelhante.
Meu caso de uso estava querendo extrair seletivamente as alterações da ramificação do Github de outra pessoa. Se você já possui uma filial local com as alterações, só precisa fazer as etapas 2 e 5-7.
Crie (se não for criado) uma filial local com as alterações que você deseja trazer.
$ git branch mybranch <base branch>
Mude para ele.
$ git checkout mybranch
Puxe as mudanças que você deseja da conta da outra pessoa. Se você ainda não o fez, você deseja adicioná -los como controle remoto.
$ git remote add repos-w-changes <git url>
Puxe tudo do ramo deles.
$ git pull repos-w-changes branch-i-want
Veja os logs de confirmação para ver quais mudanças você deseja:
$ git log
Volte para o ramo no qual deseja puxar as alterações.
$ git checkout originalbranch
Cherry Escolha seus compromissos, um por um, com os hashes.
$ git cherry-pick -x hash-of-commit
Gorjeta de chapéu: http://www.sourcemage.org/git_guide
Aqui está como você pode substituir Myclass.java
arquivo em master
ramo com Myclass.java
dentro feature1
ramo. Vai funcionar mesmo se Myclass.java
não existe em master
.
git checkout master
git checkout feature1 Myclass.java
Observe que isso substituirá - não se fundirá - e ignorará as alterações locais na filial mestre.
A maneira simples, para realmente mesclar Arquivos específicos de duas ramificações, não apenas substituindo arquivos específicos por outros de outra ramificação.
Etapa um: Diff os ramos
git diff branch_b > my_patch_file.patch
Cria um arquivo de patch da diferença entre o ramo atual e o ramo_b
Etapa dois: aplique o patch nos arquivos que correspondem a um padrão
git apply -p1 --include=pattern/matching/the/path/to/file/or/folder my_patch_file.patch
Notas úteis sobre as opções
Você pode usar *
Como curinga no padrão de inclusão.
As barras não precisam ser escapadas.
Além disso, você pode usar -EXCLUSE e aplicá -lo a tudo, exceto os arquivos que correspondem ao padrão, ou reverter o patch com -r
A opção -p1 é uma espetáculo do comando *unix patch e o fato de que o conteúdo do arquivo do patch preende cada nome a/
ou b/
(ou mais, dependendo de como o arquivo de patch foi gerado), que você precisa tirar para que ele possa descobrir o arquivo real para o caminho para o arquivo ao qual o patch precisa ser aplicado.
Confira a página do homem para obter mais opções para obter mais opções.
Etapa três: não há etapa três
Obviamente, você gostaria de cometer suas mudanças, mas quem deve dizer que não tem outros ajustes relacionados que deseja fazer antes de fazer sua confirmação.
Veja como você pode fazer com que a história siga apenas alguns arquivos de outra filial com um mínimo de confusão, mesmo que uma mesclagem mais "simples" teria trazido muito mais mudanças que você não deseja.
Primeiro, você dará a etapa incomum de declarar com antecedência que o que você está prestes a cometer é uma mesclagem, sem que o Git faça qualquer coisa com os arquivos em seu diretório de trabalho:
git merge --no-ff --no-commit -s ours branchname1
. . . Onde "BranchName" é o que você alega estar se fundindo. Se você se comprometesse imediatamente, isso não faria alterações, mas ainda mostraria ancestralidade do outro ramo. Você pode adicionar mais ramificações/tags/etc. para a linha de comando, se você precisar, também. Neste ponto, porém, não há alterações a serem confirmadas; portanto, obtenha os arquivos das outras revisões, a seguir.
git checkout branchname1 -- file1 file2 etc
Se você estava se fundindo de mais de um outro ramo, repita conforme necessário.
git checkout branchname2 -- file3 file4 etc
Agora, os arquivos da outra filial estão no índice, prontos para serem comprometidos, com o histórico.
git commit
E você terá muita explicação para fazer nessa mensagem de confirmação.
Observe que, no entanto, caso não esteja claro, que isso é uma coisa a se fazer. Não está no espírito do que é um "ramo", e a pique de cereja é uma maneira mais honesta de fazer o que você estaria fazendo aqui. Se você quiser fazer outra "mesclagem" para outros arquivos na mesma ramificação que você não trouxe da última vez, ele o impedirá com uma mensagem "já atualizada". É um sintoma de não se ramificar quando deveríamos ter, no ramo "do", deve ser mais de um ramo diferente.
Eu sei que estou um pouco tarde, mas esse é o meu fluxo de trabalho para mesclar arquivos seletivos.
#make a new branch ( this will be temporary)
git checkout -b newbranch
# grab the changes
git merge --no-commit featurebranch
# unstage those changes
git reset HEAD
(you can now see the files from the merge are unstaged)
# now you can chose which files are to be merged.
git add -p
# remember to "git add" any new files you wish to keep
git commit
eu encontrei esta postagem para conter a resposta mais simples. Apenas fazer:
$ #git checkout <branch from which you want files> <file paths>
Exemplo:
$ #pulling .gitignore file from branchB into current branch
$ git checkout branchB .gitignore
Veja a postagem para obter mais informações.
A maneira mais fácil é definir seu repositório para o ramo com o qual você deseja se fundir e depois correr,
git checkout [branch with file] [path to file you would like to merge]
Se você correr
git status
Você verá o arquivo já encenado ...
Então corra
git commit -m "Merge changes on '[branch]' to [file]"
Simples.
É estranho que o Git ainda não tenha uma ferramenta tão conveniente "fora da caixa". Eu o uso fortemente quando atualiza algum ramo de versão antiga (que ainda tem muitos usuários de software) por apenas algum Bugfixes da ramificação da versão atual. Nesse caso, muitas vezes é necessário para obter rapidamente apenas algum Linhas de código do arquivo no tronco, ignorando muitas outras mudanças (que não devem entrar na versão antiga) ... e, claro, Três vias interativas a mesclagem é necessária neste caso, git checkout --patch <branch> <file path>
não é utilizável para esse objetivo seletivo de mesclagem.
Você pode fazer isso facilmente:
Basta adicionar esta linha a [alias]
Seção em seu global .gitconfig
ou local .git/config
Arquivo:
[alias]
mergetool-file = "!sh -c 'git show $1:$2 > $2.theirs; git show $(git merge-base $1 $(git rev-parse HEAD)):$2 > $2.base; /C/BCompare3/BCompare.exe $2.theirs $2 $2.base $2; rm -f $2.theirs; rm -f $2.base;' -"
Isso implica que você usa além da comparação. Basta mudar para o software de sua escolha, se necessário. Ou você pode alterá-lo para a mérge de três vias se não precisar da fusão seletiva interativa:
[alias]
mergetool-file = "!sh -c 'git show $1:$2 > $2.theirs; git show $(git merge-base $1 $(git rev-parse HEAD)):$2 > $2.base; git merge-file $2 $2.base $2.theirs; rm -f $2.theirs; rm -f $2.base;' -"
Em seguida, use assim:
git mergetool-file <source branch> <file path>
Isso lhe dará o verdadeiro seletivo Way de árvore mesclar a oportunidade de qualquer arquivo em outra filial.
Não é exatamente o que você estava procurando, mas foi útil para mim:
git checkout -p <branch> -- <paths> ...
É uma mistura de algumas respostas.
Eu tive exatamente o mesmo problema mencionado por você acima. Mas eu encontrei Este blog git mais claro em explicar a resposta.
Comando do link acima:
#You are in the branch you want to merge to
git checkout <branch_you_want_to_merge_from> <file_paths...>
Eu faria um
git diffmits1..Commit2 filepattern | Git-Apply-Index && Git Commit
Dessa forma, você pode limitar o intervalo de commits para um FilePattern de uma filial.
Roubado de: http://www.gelato.unsw.edu.au/archives/git/0701/37964.html
Eu gosto da resposta 'Git-Interactive-Merge', acima, mas há uma mais fácil. Deixe o Git fazer isso por você usando uma combinação de rebase de interativo e para:
A---C1---o---C2---o---o feature
/
----o---o---o---o master
Portanto, o caso é que você deseja que C1 e C2 da ramificação 'Recurso' (Ponto de Branch 'A'), mas nenhum dos demais por enquanto.
# git branch temp feature
# git checkout master
# git rebase -i --onto HEAD A temp
O que, como acima, o deixa para o editor interativo, onde você seleciona as linhas 'Pick' para C1 e C2 (como acima). Salve e desista, e então ele prosseguirá com a rebase e lhe dará 'temp' e também vá em Master + C1 + C2:
A---C1---o---C2---o---o feature
/
----o---o---o---o-master--C1---C2 [HEAD, temp]
Então você pode apenas atualizar o mestre para liderar e excluir a filial temp e pronto:
# git branch -f master HEAD
# git branch -d temp
Sei que essa pergunta é antiga e há muitas outras respostas, mas escrevi meu próprio script chamado 'pmerge' para mesclar diretórios parcialmente. É um trabalho em andamento e ainda estou aprendendo scripts Git e Bash.
Este comando usa git merge --no-commit
E, em seguida, não se aplica alterações que não correspondam ao caminho fornecido.
Uso: git pmerge branch path
Exemplo: git merge develop src/
Eu não testei extensivamente. O diretório de trabalho deve estar livre de alterações não comprometidas e arquivos não rastreados.
#!/bin/bash
E_BADARGS=65
if [ $# -ne 2 ]
then
echo "Usage: `basename $0` branch path"
exit $E_BADARGS
fi
git merge $1 --no-commit
IFS=$'\n'
# list of changes due to merge | replace nulls w newlines | strip lines to just filenames | ensure lines are unique
for f in $(git status --porcelain -z -uno | tr '\000' '\n' | sed -e 's/^[[:graph:]][[:space:]]\{1,\}//' | uniq); do
[[ $f == $2* ]] && continue
if git reset $f >/dev/null 2>&1; then
# reset failed... file was previously unversioned
echo Deleting $f
rm $f
else
echo Reverting $f
git checkout -- $f >/dev/null 2>&1
fi
done
unset IFS
A respeito git reset --soft branch
? Estou surpreso que ninguém tenha mencionado isso ainda.
Para mim, é a maneira mais fácil de escolher seletivamente as alterações de outro ramo, pois esse comando coloca na minha árvore de trabalho, todas as mudanças diff e posso escolher ou reverter facilmente de que eu preciso. Dessa forma, tenho controle total sobre os arquivos comprometidos.
Você pode usar read-tree
Para ler ou mesclar a árvore remota dada no índice atual, por exemplo:
git remote add foo git@example.com/foo.git
git fetch foo
git read-tree --prefix=my-folder/ -u foo/master:trunk/their-folder
Para realizar a mesclagem, use -m
em vez de.
Veja também: Como faço para mesclar um sub -diretório no Git?
Uma abordagem simples para fusão/compromisso seletivo por arquivo:
git checkout dstBranch
git merge srcBranch
// make changes, including resolving conflicts to single files
git add singleFile1 singleFile2
git commit -m "message specific to a few files"
git reset --hard # blow away uncommitted changes
Se você não tiver muitos arquivos que mudaram, isso deixará você sem compromissos extras.
1. Filial duplicada temporariamente
$ git checkout -b temp_branch
2. Redefinir para o último comércio procurado
$ git reset --hard HEAD~n
, Onde n
é o número de commits que você precisa para voltar
3. Feche cada arquivo da filial original
$ git checkout origin/original_branch filename.ext
Agora você pode comprometer e forçar o push (para substituir o controle remoto), se necessário.
Quando apenas alguns arquivos foram alterados entre os compromissos atuais das duas ramificações, mescho manualmente as alterações passando pelos diferentes arquivos.
git difftoll <branch-1>..<branch-2>
Se você só precisar mesclar um diretório específico e deixar tudo o mais intacto e ainda preservar a história, você pode tentar isso ... criar um novo target-branch
fora do master
antes de experimentar.
As etapas abaixo assumem que você tem dois ramos target-branch
e source-branch
, e o diretório dir-to-merge
que você quer se fundir está no source-branch
. Assume também que você tem outros diretórios como dir-to-retain
No alvo que você não deseja mudar e manter a história. Além disso, pressupõe que há conflitos de mesclagem no dir-to-merge
.
git checkout target-branch
git merge --no-ff --no-commit -X theirs source-branch
# the option "-X theirs", will pick theirs when there is a conflict.
# the options "--no--ff --no-commit" prevent a commit after a merge, and give you an opportunity to fix other directories you want to retain, before you commit this merge.
# the above, would have messed up the other directories that you want to retain.
# so you need to reset them for every directory that you want to retain.
git reset HEAD dir-to-retain
# verify everything and commit.