Como faço para dizer ao git para sempre selecionar minha versão local para mesclagens conflitantes em um arquivo específico?

100

Digamos que eu esteja colaborando com alguém por meio de um repositório git e haja um arquivo específico para o qual nunca quero aceitar nenhuma alteração externa.

Existe alguma maneira de configurar meu repositório local para não reclamar de um conflito de mesclagem toda vez que faço pull? Eu gostaria de sempre selecionar minha versão local ao mesclar este arquivo.

saffsd
fonte
1
Acabei de adicionar uma solução simples por meio de .gitattributes e um "driver de mesclagem" muito básico
VonC
12
TD; LR:echo 'path/to/file merge=ours' >> .gitattributes && git config --global merge.ours.driver true
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
@CiroSantilli: Funciona perfeitamente no Linux. Este driver é simples o suficiente para ser integrado ao Git ...
krlmlr
Você deseja enviar suas alterações para o arquivo? Ou é, por exemplo, um arquivo de configuração onde o padrão é armazenado no git.
Ian Ringrose
O comentário de @CiroSantilli 新疆 改造 中心 六四 事件 法轮功 está correto, mas fará com que esse comportamento ocorra para cada repositório em seu sistema com a --globaltag. Se você deseja esse comportamento apenas para um único repo, --globalecho 'path/to/file merge=ours' >> .gitattributes && git config merge.ours.driver true
omita

Respostas:

140

Sobre a instância específica de um arquivo de configuração, eu concordaria com a resposta de Ron :
uma configuração deve ser "privada" para sua área de trabalho (portanto, "ignorada", como em "declarada em um .gitignorearquivo").
Você pode ter um modelo de arquivo de configuração com valores tokenizados e um script que transforma esse config.templatearquivo em um arquivo de configuração privado (e ignorado).


No entanto, essa observação específica não responde a uma questão mais ampla e geral, ou seja, sua pergunta (!):

Como faço para dizer ao git para sempre selecionar minha versão local para mesclagens conflitantes em um arquivo específico? (para qualquer arquivo ou grupo de arquivos)

Esse tipo de mesclagem é uma "mesclagem de cópia", na qual você sempre copiará a versão 'nossa' ou 'deles' de um arquivo sempre que houver um conflito.

(como Brian Vandenberg observa nos comentários , ' ours' e ' theirs' são usados ​​aqui para uma mesclagem .
Eles são revertidos para um rebase : consulte " Why is the meaning of “ours” and “theirs” reversed with git-svn", que usa um rebase, " git rebase, mantendo o controle de 'local' e 'remoto' " )

Para "um arquivo" (um arquivo em geral, sem falar em um arquivo "config", já que é um mau exemplo), você conseguiria isso com um script personalizado chamado por meio de mesclagens.
O Git chamará esse script porque você terá de definir um valor gitattributes , que define um driver de mesclagem personalizado .

O "driver de mesclagem personalizado" é, neste caso, um script muito simples que basicamente manterá inalterada a versão atual, permitindo que você sempre selecione sua versão local.

IE., Conforme observado por Ciro Santilli :

echo 'path/to/file merge=ours' >> .gitattributes
git config --global merge.ours.driver true

Vamos testar isso em um cenário simples, com um msysgit 1.6.3 no Windows, em uma mera sessão do DOS:

cd f:\prog\git\test
mkdir copyMerge\dirWithConflicts
mkdir copyMerge\dirWithCopyMerge
cd copyMerge
git init
Initialized empty Git repository in F:/prog/git/test/copyMerge/.git/

Agora, vamos fazer dois arquivos, que terão conflitos, mas que serão mesclados de forma diferente.

echo a > dirWithConflicts\a.txt
echo b > dirWithCopyMerge\b.txt
git add -A
git commit -m "first commit with 2 directories and 2 files"
[master (root-commit) 0adaf8e] first commit with 2 directories and 2 files

Vamos introduzir um "conflito" no conteúdo de ambos os arquivos em dois branches diferentes do git:

git checkout -b myBranch
Switched to a new branch 'myBranch'
echo myLineForA >> dirWithConflicts\a.txt
echo myLineForB >> dirWithCopyMerge\b.txt
git add -A
git commit -m "add modification in myBranch"
[myBranch 97eac61] add modification in myBranch

git checkout master
Switched to branch 'master'
git checkout -b hisBranch
Switched to a new branch 'hisBranch'
echo hisLineForA >> dirWithConflicts\a.txt
echo hisLineForB >> dirWithCopyMerge\b.txt
git add -A
git commit -m "add modification in hisBranch"
[hisBranch 658c31c] add modification in hisBranch

Agora, vamos tentar mesclar "hisBranch" em "myBranch", com:

  • resolução manual para mesclagens conflitantes
  • exceto para dirWithCopyMerge\b.txtonde eu sempre quero manter a minha versão do b.txt.

Uma vez que a fusão ocorre em ' MyBranch', voltaremos a ela e adicionaremos as gitattributesdiretivas ' ' que irão personalizar o comportamento da fusão.

git checkout myBranch
Switched to branch 'myBranch'
echo b.txt merge=keepMine > dirWithCopyMerge\.gitattributes
git config merge.keepMine.name "always keep mine during merge"
git config merge.keepMine.driver "keepMine.sh %O %A %B"
git add -A
git commit -m "prepare myBranch with .gitattributes merge strategy"
[myBranch ec202aa] prepare myBranch with .gitattributes merge strategy

Temos um .gitattributesarquivo definido no dirWithCopyMergediretório (definido apenas no branch onde ocorrerá a fusão:) myBranch, e temos um .git\configarquivo que agora contém um driver de fusão.

[merge "keepMine"]
        name = always keep mine during merge
        driver = keepMine.sh %O %A %B

Se você ainda não definiu keepMine.sh e lançou a mesclagem mesmo assim, aqui está o que você obtém.

git merge hisBranch
sh: keepMine.sh: command not found
fatal: Failed to execute internal merge
git st
# On branch myBranch
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       modified:   dirWithConflicts/a.txt
#
no changes added to commit (use "git add" and/or "git commit -a")

type dirWithConflicts\a.txt
a
<<<<<<< HEAD:dirWithConflicts/a.txt
myLineForA
=======
hisLineForA
>>>>>>> hisBranch:dirWithConflicts/a.txt

Está bem:

  • a.txt está pronto para ser mesclado e tem conflito
  • b.txtainda não foi alterado, pois o driver de mesclagem deve cuidar disso (devido à diretiva no .gitattributesarquivo em seu diretório).

Defina um keepMine.shem qualquer lugar no seu %PATH%(ou $PATHpara nosso amigo Unix. Eu faço ambos, é claro: tenho uma sessão do Ubuntu em uma sessão do VirtualBox)

Conforme comentado por lrkwz e descrito na seção " Merge Strategies " de Customizing Git - Git Attributes , você pode substituir o script de shell pelo comando shell true.

git config merge.keepMine.driver true

Mas, no caso geral, você pode definir um arquivo de script:

keepMine.sh

# I want to keep MY version when there is a conflict
# Nothing to do: %A (the second parameter) already contains my version
# Just indicate the merge has been successfully "resolved" with the exit status
exit 0

(que era um simples motorista de fusão;) (Ainda mais simples, nesse caso, o uso true)
(Se você queria manter a outra versão, basta adicionar antes da exit 0linha:
cp -f $3 $2.
É isso Mesclar motorista brindes manter a versão vinda do outro. filial, substituindo qualquer mudança local)

Agora, vamos tentar novamente a mesclagem desde o início:

git reset --hard
HEAD is now at ec202aa prepare myBranch with .gitattributes merge strategy

git merge hisBranch
Auto-merging dirWithConflicts/a.txt
CONFLICT (content): Merge conflict in dirWithConflicts/a.txt
Auto-merging dirWithCopyMerge/b.txt
Automatic merge failed; fix conflicts and then commit the result.

A mesclagem falha ... apenas para a.txt .
Edite a.txt e deixe a linha de 'hisBranch', então:

git add -A
git commit -m "resolve a.txt by accepting hisBranch version"
[myBranch 77bc81f] resolve a.txt by accepting hisBranch version

Vamos verificar se b.txt foi preservado durante esta fusão

type dirWithCopyMerge\b.txt
b
myLineForB

O último commit representa a mesclagem completa :

git show -v 77bc81f5e
commit 77bc81f5ed585f90fc1ca5e2e1ddef24a6913a1d
Merge: ec202aa 658c31c
git merge hisBranch
Already up-to-date.

(A linha que começa com Merge prova isso)


Considere que você pode definir, combinar e / ou substituir o driver de mesclagem, como o Git fará:

  • examine <dir>/.gitattributes(que está no mesmo diretório do caminho em questão): prevalecerá sobre o outro .gitattributesnos diretórios
  • Em seguida, examina .gitattributes(que está no diretório pai), só definirá as diretivas se ainda não estiver definido
  • Finalmente, ele examina $GIT_DIR/info/attributes. Este arquivo é usado para substituir as configurações da árvore. Ele substituirá as <dir>/.gitattributesdiretivas.

Por "combinação", quero dizer "agregar" vários drivers de mesclagem.
Nick Green tenta, nos comentários , realmente combinar os drivers de mesclagem: consulte " Mesclar pom's via driver git python ".
No entanto, como mencionado em sua outra pergunta , ele só funciona em caso de conflitos (modificação simultânea em ambos os ramos).

VonC
fonte
1
Obrigado pela resposta detalhada! Eu entendo que não faz sentido para arquivos de configuração de controle de versão, mas eu estava atrás de um exemplo simples e motivador. Na verdade, é a questão mais ampla que me interessou. Eu nunca tinha ouvido falar de drivers git merge antes, então obrigado por me esclarecer.
saffsd
6
O cp -f $3 $2provavelmente deve ser citado, ou seja cp -f "$3" "$2".
Arco de
1
@VonC obrigado pela resposta detalhada! O problema que tenho com ele é que depende das pessoas definindo o driver em seu arquivo .git / config. Gostaria de adicionar as informações do driver ao próprio projeto, para que fosse automático e menos trabalho de configuração a ser feito. Quaisquer dicas?
Juan Delgado
2
@ulmangt: você pode armazenar esse script no repositório git também, ... contanto que encontre uma maneira de adicionar seu diretório pai ao PATH(Unix ou Windows PATH). Como esse script será interpretado por meio do shell bash do Unix ou do shell do Windows MingWin bash MsysGit, ele será portável.
VonC
5
Obrigado @VonC. Mais um problema. Sob certas circunstâncias (se não houver nenhuma mudança no branch local sendo mesclado), parece que o driver de mesclagem nunca é chamado, o que resulta na modificação dos arquivos locais (que deveriam estar usando o driver de mesclagem personalizado para evitar que sejam alterados durante uma fusão). Existe alguma maneira de forçar o git a sempre usar o driver de mesclagem?
ulmangt
1

Como @ciro-santilli comentou, a maneira simples de fazer isso é usar .gitattributescom as configurações:

path/to/file merge=ours

e habilite essa estratégia com:

git config --global merge.ours.driver true

(Estou adicionando isso como uma resposta para torná-lo mais visível, mas tornando-o um Wiki da comunidade para não tentar obter acima dos créditos do usuário para mim.

Greg Dubicki
fonte
(À parte: se alguém der a resposta como um comentário e não adicionar uma resposta, é perfeitamente normal escrever uma resposta não CW e obter os créditos. Se ele ainda for um membro ativo, você pode enviar um ping para adicionar uma resposta se desejar, mas eles tecnicamente já tiveram sua chance :-)).
Halfer
0

Temos vários arquivos de configuração que nunca queremos sobrescritos. No entanto, .gitignore e .gitattributes não funcionaram em nossa situação. Nossa solução foi armazenar os arquivos de configuração em um branch de configurações. Em seguida, permita que os arquivos sejam alterados durante a fusão git, mas imediatamente após a fusão, use "git checkout branch -." para copiar nossos arquivos de configuração do branch de configurações após cada fusão. Resposta detalhada do stackoverflow aqui

HamletHub
fonte