如果我处于这种情况:
* da6a750 (A) Further in A, okay for merging back into master
* bf27b58 Merge branch 'master' into A
|\
| * 86294d1 (HEAD -> master) Development on master
* | abe6b8a Welcome to branch A
|/
* 589517c First commit
在master
分支上,有三个文件:
./development
:
development on master
initial
./specific
:
master branch
./.gitattributes
:
specific merge=ours
在A
分支上,还有三个文件:
./development
:
development on master
initial
development in A
further in A, okay for merging into master
./specific
:
Welcome to branch A
./.gitattributes
:
specific merge=ours
当我将master
合并到A
以生成提交bf27b58
时,我很高兴./specific
文件未被更改。因为我需要将它保留在分支A
上。
但是,我现在想要进一步将A
更改合并到master
中,以便在新生成的提交中,./specific
文件与提交中的文件相同86294d1
。
我的猜测是specific merge=ours
会保证这一点,但似乎并非如此。我试过跑:
$ git merge A
Updating 86294d1..da6a750
Fast-forward
development | 2 ++
specific | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
..没有成功。已触发快速合并,A
和master
都指向da6a750
。换句话说,我在master
文件中显示Welcome to branch A
提交./specific
:
* da6a750 (HEAD -> master, A) Further in A, okay for merging into master
* bf27b58 Merge branch 'master' into A
|\
| * 86294d1 Development on master
* | abe6b8a Welcome to branch A
|/
* 589517c First commit
..这不是我想要的。
相反,我尝试过运行:
$ git merge --no-ff A
Merge made by the 'recursive' strategy.
development | 2 ++
specific | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
..也没有成功:新的提交02c9c10
产生了同样的问题:
* 02c9c10 (HEAD -> master) Merge branch 'A'
|\
| * da6a750 (A) Further in A, okay for merging into master
| * bf27b58 Merge branch 'master' into A
| |\
| |/
|/|
* | 86294d1 Development on master
| * abe6b8a Welcome to branch A
|/
* 589517c First commit
..这不是我想要的,因为02c9c10(master):./specific
显示Welcome to branch A
。
(所以02c9c10
和da6a750
严格相同,因为提交消息,它们只是哈希值不同,我猜。)
在这种情况下,specific merge=ours
怎么没有被考虑在内?
如何在不需要手动git merge --no-ff --no-commit && git checkout master specific && git commit
的情况下使其工作?
答案 0 :(得分:3)
您的问题归结为:"为什么Git不遵守我的自定义合并方向?"实际上,任何合并以及任何自定义合并驱动程序都可能出现此问题。事实上,这个合并可以作为快进操作完成保证你(在你的特定情况下)会遇到问题。
原因归结为当Git认为有任何自定义.gitattributes
合并驱动程序(包括merge=ours
)时,只调用 这一事实"合并的东西"。在你意识到Git有这样一种信念需要什么之后,这似乎并不那么糟糕。
这里值得一提的是,作为-s strategy
的{{1}} git merge
的{{1}}论证。这些策略接管整个过程,包括"找到合并基础"步骤加上之后的所有内容 - 因此可以做自己的事情,其中包括完全忽略 .gitattributes
。显然,如果策略忽略了您的.gitattributes
,那么设置自定义合并驱动程序或模式就不会有帮助。
因此,我们只关注做使用合并基础的-s
策略以及Git称之为 head 的两个(我们和#39; ll标签"我们的"和"他们的"),做使用.gitattributes
。 Git中有三个内置 - recursive
,resolve
和subtree
- 但它们在这里的工作方式相同,关于合并的内容以及自定义合并驱动程序会发生什么。 (另外两个内置合并策略,ours
和octopus
,要么不打扰合并基础,要么根本不打扰"或者为... {1}} - 有两个以上的头,因此没有明确的概念"我们的"和#34;他们的"。)
所以,既然我们已经确定了内置的合并有一个合并基础提交和两个 head 提交,我们可以看看它对Git意味着什么我认为,以其微小的预先编程的Gitty方式,有要合并的东西。
两个头更容易定义。其中一个,我们称之为"我们的#34;,本身只是octopus
。另一个是我们传递给HEAD
的任何论据:
git merge
意味着"我们的"是git merge A
和"他们的"是由HEAD
标识的提交。
以下是您的A
输出(顺便说一句,感谢包含对大多数合并至关重要的内容!):
git log --all --decorate --oneline --graph
所以我们可以说两个头是提交* da6a750 (A) Further in A, okay for merging back into master
* bf27b58 Merge branch 'master' into A
|\
| * 86294d1 (HEAD -> master) Development on master
* | abe6b8a Welcome to branch A
|/
* 589517c First commit
(86294d1
或HEAD
或只是"我们的")并提交master
({ {1}}或者只是"他们的")。
合并基础是他们在图表历史记录中首先分享的任何提交,即从两个头开始,如果需要,在历史记录中向后工作,直到找到他们共同的提交,您可以从两个头部到达。因此,我们从da6a750
开始,向后工作一步到A
,然后再向后工作一步da6a750
和bf27b58
。同时,我们从86294d1
开始......哦,看看我们已经达成了一个共同的提交! : - )
由于合并基础是两个头之一,通常我们要么快进,要么投诉没有任何合并。由于合并基础是"我们的"在这两个选项中,Git会选择快进操作。使用abe6b8a
告诉Git:不要选择它,然后继续进行完全合并。
现在,合并基础 "我们的"提交保证我们会遇到你的问题,但实际上,即使合并基础不是"我们的"我们也会遇到你的问题。承诺。让我们来看一下提交内部的内容,在Git需要的下一个级别以及它在86294d1
和--no-ff
上工作时的作用 - 但首先,让& #39; s考虑git diff
假设要做什么。
作为一般规则,运行git merge
时的想法是,我们希望在 分支中执行我们所做的两组工作 - < em>我们的提交,以及&#34;他们&#34;无论他们是谁,他们在他们的提交他们的分支中做了 - 并且产生了一个新的承诺这两个世界中最好的:我们做了任何好事,加上他们所做的任何好事。
如果我们水平而不是垂直绘制图形,左边是较旧的提交,右边是较新的提交,我们可以画出:
git merge
每个git merge
都是提交, o--o--o--...--H <-- ours
/
...--o--B
\
o-----...-----T <-- theirs
,o
和B
也是如此。提交H
是合并基础,此图中的两个分支重新加入&#34;过去&#34; (向左)方向。 T
是我们的(HEAD)提交,B
是他们的分支的头/提交。那么,我们如何将我们的工作与他们的工作结合起来呢?
Git的答案是运行两个H
:
T
然后它可以组合这两个差异:
无论我们在某些文件中添加了某些文本行,Git都应该在最终结果中添加这些文件中添加的行。无论我们在某些文件中删除某些文本行,都应该使最终结果删除这些行。
因为git diff
表示差异为&#34;删除它并添加&#34; (即使是将更改的差异),也涵盖git diff B H # find out what we did
git diff B T # find out what they did
所说的所有内容。
同样,无论他们在哪里添加行,Git都应该使最终结果具有添加的行。无论他们删除哪些行,Git都应该使最终结果具有相同的删除。
要处理一个非常常见的情况,如果我们和他们使完全相同更改 - 删除相同的原始行,和/或添加相同的替换 - Git只接受< em>一份。
当然,如果有一个地方我们都触及相同的路线,但是以不同的方式,Git只是抛出它的隐喻之手,感叹"Oy vey!",并声明合并冲突
(这些合并冲突给我们带来了最大的麻烦,因此大多数twisty knobs Git给我们的目的是以某种方式处理这些冲突。这大部分是真的git diff
合并属性 - 虽然这与我们的问题没有直接关系。)
现在,所有这些结合都是很多工作,所以为了让Git快速快速,这是一个捷径。
git diff
到.gitattributes
我们可以使用git merge
查看任何提交对象,或任何Git对象:
git diff
(我在这里剪掉其余部分)。
这里更有趣的部分实际上是git cat-file -p
,所以让我们看一下:
$ git cat-file -p HEAD
tree 5bc304073b94505cd3f6716829c4cec5a7474762
parent 29257c2c82dca881c4cc65765392a32e46264fbe
author Chris Torek <chris.torek@gmail.com> 1490287144 -0700
committer Chris Torek <chris.torek@gmail.com> 1490297185 -0700
insert early footnote on Git branch creation
In the "about version control" chapter section that introduces
(我再次在这里剪掉了一些东西)。
每个tree
对象的每个原始哈希ID(即存储的文件版本)告诉Git 哪个版本与此提交一起使用。 (更准确地说,这是此$ git cat-file -p 5bc304073b94505cd3f6716829c4cec5a7474762
100644 blob 8d1519c435c4da5a65228785fa7ba7033fe011ff .gitignore
100644 blob 66c9d22a735ee9d8da7f7ed49599583aa642842f Makefile
100644 blob c9c824fa6668e45976c4fe8a10e4d5c25e272f0c about.tex
100644 blob 1757109f5aa921ecf9a8051180c25f09e1496c07 aboutvc.tex
对象的文件版本,但此blob
与此提交一致,因此它相同。)
Git可以,实际上已经为三个提交中的每个提交提取这些blob哈希ID - 合并基础,&#34;我们的&#34;和&#34;他们的& #34 ;.哈希ID是能够分散文件的新旧版本的方式,例如tree
(在我的情况下)或tree
(在你的)中。但是这些哈希ID有一个有趣的事情:它们完全基于对象的内容。 1 如果两个不同提交中的两个文件完全是100%bit-for位相同,它们具有相同的散列并且只存储在存储库中一次。这意味着无论有多少提交都有该文件的特定版本的副本,因此只有一个副本存储在数据库中。
1 实际上,它们是对象内容的加密哈希,包括每个对象前面的小型大小标题Git。该标题是为什么 now-famous SHA-1 hash collision不是Git的直接问题。
这种快速哈希比较 - 相同的哈希意味着&#34;该文件的相同版本&#34; - 意味着aboutvc.tex
和specific
可以立即并且很容易告诉我们某些文件没有变化,从基础到我们,或者基础到他们......这正是git diff
出错的地方。 Git关注的是基础与我们的对比,以及与他们的对比。一对具有相同的哈希。一对有不同的哈希。
此时,Git只是假设无论git merge
中的合并策略或turney-knob设置如何,正确的答案是从任何一个头部带有不同的哈希。对于大多数文件,在大多数情况下,正确答案。但是,如果我们定义了自定义合并驱动程序,或者设置了merge=ours
,则可能是错误的答案。
如果一个人的不同之处在于他们的#34;以及自定义合并方向是&#34;保持我们的&#34;,那就是错误的答案。无论选择什么提交作为合并基础,但是当合并基础是.gitattributes
时,这是真的 - 我们的提交 - 然后所有哈希,在基础的差异中对于我们的,是相同的,结果是总是&#34;他们的文件版本&#34;。
事实上,为什么首先可以实现快进:最终合并树始终只是他们的树。 Git实际上忽略 merge=ours
中的所有自定义路线。即使您强制进行真正的合并而不是快进 - 非合并&#34;合并&#34;这仍然是正确的。
也许Git应检查自定义合并驱动程序或HEAD
指令,并禁用此快捷方式,至少对于实际(非快进)合并。但它没有,因此你会遇到这个问题。对于其他情况,你也会遇到这个问题,那里有一个真正的合并要做,但是文件只在基地到他们的比较中被修改。
人们通常希望使用此.gitattributes
来确保存储在分支上的配置文件保持在该分支上的方式。这几乎总是错误的整体策略:相反,配置文件应该完全从版本控制中省略,或者至少从该特定存储库的版本控制中省略。不是提交,例如merge=ours
或merge=ours
,而是提交config.ini
或config.php
或其他类似内容。将此配置复制到&#34; real config&#34;,或者如果&#34; real&#34;将其作为辅助策略读取。配置缺失或不完整。
这为您提供了一种通用版本配置(样本和/或默认版本)的方法,无需使用此存储库对某人的特定运行时配置进行版本控制他们运行软件/应用程序本身的地方。如果用户希望对其特定配置进行版本控制,她可以将其存储在单独的存储库中,并将config.ini.sample
替换为(例如)到config.default.php
的符号链接,是她的配置版本。
(类似的技巧是从config.ini
或../myconfigs/fooapp.ini
获取配置。也就是说,首先单独存储配置。再次,如果您想要或需要某种默认配置,您可以使用该软件保留该版本,但用户自己的配置是独立的,而不是您自己的版本控制。)