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 certeza
  • exp1: ramo experimental #1
  • exp2: 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:

  1. 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.

  2. 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.

  3. 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.

Foi útil?

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

  1. Edite o arquivo diretamente
  2. Salve 
  3. 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.

  1. Crie (se não for criado) uma filial local com as alterações que você deseja trazer.

    $ git branch mybranch <base branch>

  2. Mude para ele.

    $ git checkout mybranch

  3. 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>

  4. Puxe tudo do ramo deles.

    $ git pull repos-w-changes branch-i-want

  5. Veja os logs de confirmação para ver quais mudanças você deseja:

    $ git log

  6. Volte para o ramo no qual deseja puxar as alterações.

    $ git checkout originalbranch

  7. 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.
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top