在合并冲突时,如何让Git选择特定文件的本地版本?该问题涉及IT技术。

116

假设我正在通过git仓库与他人协作,有一个特定的文件我不希望接受任何外部更改。

有没有办法设置我的本地仓库,在每次git pull时不会出现冲突合并的提示?我希望在合并此文件时始终选择我的本地版本。


1
刚刚通过.gitattributes添加了一个简单的解决方案和一个非常基本的“合并驱动程序”。 - VonC
15
TD;LR: echo 'path/to/file merge=ours' >> .gitattributes && git config --global merge.ours.driver true简化翻译:在.gitattributes文件中添加path/to/file merge=ours,然后执行git config --global merge.ours.driver true - Ciro Santilli OurBigBook.com
1
@CiroSantilli:在Linux上运行得非常好。这个驱动程序足够简单,可以构建到Git中... - krlmlr
您是否希望将更改推送到文件中?或者例如一个配置文件,其中默认设置存储在 git 中。 - Ian Ringrose
3个回答

152

关于配置文件的特定实例,我同意Ron's answer
该配置应该是您工作区域的“私有”(因此“被忽略”,如在“.gitignore”文件中声明)。
您可以拥有一个带有令牌化值的配置文件模板,以及一个将config.template文件转换为私有(并被忽略)配置文件的脚本。


然而,这个具体的评论并没有回答一个更广泛、更一般的问题,也就是你的问题:

如何告诉git在特定文件上进行冲突合并时始终选择我的本地版本?(对于任何文件或文件组)

这种合并被称为“复制合并”,它意味着在发生冲突时,你将始终复制“我们”的或者“他们”的版本。

(如Brian Vandenberg评论中所指出的,'我们的'和'他们的'在此处用于合并。它们在变基时被颠倒使用:参见 "为什么在git-svn中“ours”和“theirs”的含义被颠倒了",其中采用了变基, "git rebase,跟踪'本地'和'远程'")
针对“文件”(不是指“config”文件,因为那是个糟糕的例子),你可以通过调用 merges 的自定义脚本来实现。Git 会调用该脚本,因为你需要定义一个 gitattributes value,该值定义了一个自定义合并驱动程序
在这种情况下,“自定义合并驱动程序”是一个非常简单的脚本,它基本上将保持当前版本不变,因此允许您始终选择本地版本。

例如,正如Ciro Santilli所指出的那样:

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

让我们在一个简单的场景中进行测试,使用Windows上的msysgit 1.6.3,在一个简单的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/

现在,让我们创建两个文件,它们都会有冲突,但会被不同地合并。
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

我们将在两个不同的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

现在让我们尝试使用以下方式将“hisBranch”合并到“myBranch”中:

  • 手动解决冲突合并
  • 除了,我始终希望保留我的版本

由于合并发生在'MyBranch'中,因此我们将切换回它,并添加 'gitattributes'指令,以自定义合并行为。

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

我们在“dirWithCopyMerge”目录中定义了一个“.gitattributes”文件(仅在合并发生的分支“myBranch”中定义),而且我们现在有一个“.git\config”文件,其中包含了一个合并驱动程序。
[merge "keepMine"]
        name = always keep mine during merge
        driver = keepMine.sh %O %A %B

如果您尚未定义keepMine.sh并且仍然启动合并,则会得到以下结果。
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

没问题:

  • a.txt 已准备好进行合并,其中存在冲突
  • b.txt 仍然没有更改,因为合并驱动程序应该处理它(由于其目录中的 .gitattributes 文件中的指令)。

在您的 %PATH%(对于我们的Unix朋友,是 $PATH)中的任何位置定义一个 keepMine.sh。我当然两者都有:我有一个Ubuntu会话在VirtualBox会话中。

正如lrkwz评论,并在自定义Git - Git属性的“合并策略”部分中描述的那样,您可以使用shell命令true替换shell脚本。

git config merge.keepMine.driver true

但是在一般情况下,您可以定义一个脚本文件:

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

(那是一个简单的合并驱动程序 ;) (在这种情况下更简单,使用true
(如果您想保留其他版本,请在exit 0行之前添加:
cp -f $3 $2
就这样。您的合并驱动程序将始终保留来自其他分支的版本,覆盖任何本地更改)

现在,让我们从头开始重试合并:

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.txt
编辑a.txt并保留来自'hisBranch'的行,然后:
git add -A
git commit -m "resolve a.txt by accepting hisBranch version"
[myBranch 77bc81f] resolve a.txt by accepting hisBranch version

让我们检查一下在这次合并中 b.txt 是否被保留了

type dirWithCopyMerge\b.txt
b
myLineForB

最后一次提交确实代表了完整的合并:

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

(以Merge开头的那行证明了这一点)


考虑到您可以定义、组合和/或覆盖合并驱动程序,因为Git将会:

  • 检查<dir>/.gitattributes(与所讨论的路径在同一目录中):将优先于其他目录中的.gitattributes
  • 然后它会检查.gitattributes(在父目录中),并且只有在没有设置指令时才会设置指令
  • 最后,它会检查$GIT_DIR/info/attributes。该文件用于覆盖树内设置。它将覆盖<dir>/.gitattributes指令。

通过“合并”,我指的是“聚合”多个合并驱动程序。
Nick Green评论中尝试实际组合合并驱动程序:请参见“通过Python git驱动程序合并pom”。
但是,正如他的另一个问题中所提到的那样,它仅适用于冲突情况(两个分支中的并发修改)。


1
感谢您提供详细的答案!我明白版本控制配置文件没有意义,但我想要一个简单明了的激励性例子。实际上,更广泛的问题才是我感兴趣的。我以前从未听说过git合并驱动程序,所以感谢您让我受益匪浅。 - saffsd
6
建议对 cp -f $3 $2 添加引号,即改为 cp -f "$3" "$2"。此举有助于避免由于参数中包含空格或其他特殊字符而导致命令执行错误的问题。 - Arc
1
@VonC 感谢您详细的回答!我对它的问题在于它依赖于人们在他们的.git/config文件中设置驱动程序。我想将驱动程序信息添加到项目本身中,这样就可以自动化,并且需要做更少的设置工作。有什么建议吗? - Juan Delgado
2
@ulmangt:你完全可以将那个脚本存储在git仓库中,只要找到一种方法将其父目录添加到PATH(Unix或Windows PATH)中即可。由于该脚本将通过Unix bash shell或MingWin bash MsysGit Windows shell进行解释,因此它是可移植的。 - VonC
5
@VonC 谢谢。还有一个问题。在某些情况下(如果本地分支没有任何更改被合并),似乎合并驱动程序甚至都没有被调用,这导致本地文件被修改(应该使用自定义合并驱动程序来防止它们在合并期间发生更改)。有没有办法强制git始终使用合并驱动程序? - ulmangt
显示剩余20条评论

11
如@ciro-santilli所评论的那样,简单的方法是使用.gitattributes文件并进行相应的设置。
path/to/file merge=ours

并使用以下方法启用这个策略:
git config --global merge.ours.driver true

2
以下是关于编程的内容,请翻译成中文。只返回已翻译的文本: - halfer

0
我们有多个配置文件,永远不希望被覆盖。然而.gitignore和.gitattributes在我们的情况下没有起作用。我们的解决方案是将配置文件存储在configs分支中。然后,在允许文件在git合并期间进行更改后,立即在每次合并后使用“git checkout branch --。”从configs分支复制我们的配置文件。 详细的stackoverflow答案在这里

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接