.gitattributes:merge =我们的策略与快速合并 [英] .gitattributes: merge=ours strategy vs. fast-forward merging

查看:162
本文介绍了.gitattributes:merge =我们的策略与快速合并的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如果我处于这种git情况:

If I am in such a git situation:

* 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分支上,三个文件:


On the master branch, three files:

./development:

development on master
initial

./specific:

master branch

./.gitattributes:

specific merge=ours


A分支上,还有三个文件:


On A branch, three files as well:

./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上.


When I merged master into A to produce commit bf27b58, I was happy that the ./specific file was not changed. Because I need it to be kept as it is on branch A.

但是,我现在想将A的更多更改合并到master中,以使得在新生成的提交中,./specific文件与提交86294d1中的文件相同.

However, I would like now to merge further A changes into master in such a way that, in the newly produced commit, the ./specific file would be the same as in commit 86294d1.

我的猜测是specific merge=ours可以保证这一点,但事实并非如此.我试过跑步:

My guess was that specific merge=ours would guarantee this, but it seems not to be the case. I have tried running:

$ git merge A
Updating 86294d1..da6a750
Fast-forward
 development | 2 ++
 specific    | 2 +-
 2 files changed, 3 insertions(+), 1 deletion(-)

..没有成功.触发了快进合并,我发现Amaster都指向da6a750.换句话说,我有一个master提交,在./specific文件中显示Welcome to branch A:

.. with no success. A fast-forward merging has been triggered and I get myself with A and master both pointing to da6a750. In other words, I have a master commit displaying Welcome to branch A in the ./specific file:

* 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

..这不是我想要的.

.. which is not what I want.

相反,我尝试运行:

$ git merge --no-ff A
Merge made by the 'recursive' strategy.
 development | 2 ++
 specific    | 2 +-
 2 files changed, 3 insertions(+), 1 deletion(-)

..也没有成功:产生新的提交02c9c10,但存在相同的问题:

.. with no success either : a new commit 02c9c10 is produced with the same problem:

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

.. which is not what I want because 02c9c10(master):./specific displays Welcome to branch A.

(所以02c9c10da6a750严格相同,我猜它们只是因为提交消息而散列不同).

(so 02c9c10 and da6a750 are strictly identical, they just hash differently because of the commit message, I guess).

在这种情况下,为什么不考虑specific merge=ours?
我如何使其工作而无需手动git merge --no-ff --no-commit && git checkout master specific && git commit?

How come specific merge=ours seems not taken into account in this case?
How do I make it work without needing to manually git merge --no-ff --no-commit && git checkout master specific && git commit?

推荐答案

TL; DR:这实际上并不是快进本身

您的问题归结为:为什么Git不遵循我的自定义合并方向?"实际上,使用 any 合并和 any 自定义合并驱动程序可能会发生此问题.可以将合并 作为快进操作来完成这一事实,只是保证,您(根据您的具体情况)将解决此问题.

TL;DR: it's not actually the fast forward itself

Your question comes down to: "why isn't Git obeying my custom merge direction?" In fact, this problem can occur with any merge, and any custom merge driver. The fact that this merge can be done as a fast forward operation merely guarantees that you (with your particular case) will hit the problem.

原因归结为以下事实:当Git认为存在某些要合并的东西"时,任何定制的.gitattributes合并驱动程序,包括merge=ours,仅被 调用. ".直到您意识到Git拥有这样的信念之前,这似乎并不那么糟糕.

The reason boils down to the fact that any custom .gitattributes merge driver, including merge=ours, is invoked only when Git believes there is "something to merge". This does not seem so bad until you realize what it takes for Git to have such a belief.

在这里值得一提的是,Git的-s strategy自变量git merge作为侧边栏.这些策略接管了整个过程,包括查找合并基础"步骤以及此后的所有操作,因此可以做自己的事情,包括完全忽略 .gitattributes.显然,如果某个策略忽略了您的.gitattributes,则在其中设置自定义合并驱动程序或模式将无济于事.

It's worth mentioning here, as a side-bar, Git's -s strategy argument to git merge. These strategies take over the whole process, including the "find the merge base" step—plus everything after that—and hence can do their own thing, which includes ignoring .gitattributes entirely. Obviously if a strategy ignores your .gitattributes, setting a custom merge driver or mode there won't help.

因此,我们只研究使用合并基础的-s策略和Git称为 heads 的两个(我们将其标记为我们的"和他们的")以及 do 使用.gitattributes. Git内建了三类,分别是recursiveresolvesubtree,但是对于合并内容和自定义合并驱动程序发生的操作,它们在这里都相同. (其他两个内置的合并策略oursoctopus要么根本不用合并基础和他们的",要么-对于octopus-具有两个以上的头,因此没有我们的"和他们的"的明确概念.)

Therefore, we're looking only at the -s strategies that do use a merge base and two of what Git calls heads (which we'll label "ours" and "theirs"), and do use .gitattributes. There are three of those built in to Git—recursive, resolve, and subtree—but they all work the same here, with respect to what gets merged and what happens with custom merge drivers. (The other two built-in merge strategies, ours and octopus, either don't bother with a merge base and a "theirs" at all, or—for octopus—have more than two heads, so that there is no clear notion of "ours" and "theirs".)

因此,既然我们已经确定了具有一个合并基础提交和两个 head 提交的内置合并,那么我们可以看看Git的含义以一种很小的预先编程的Gitty方式认为,有要合并的东西.

So, now that we have settled on the built in merges that have one merge base commit and two head commits, we can look at what it means for Git to think, in its tiny little pre-programmed Gitty way, that there is something to merge.

两个 heads 更容易定义.其中之一,我们称为我们的",本身就是HEAD.另一个是我们传递给git merge的任何参数:

The two heads are easier to define. One of them, the one we call "ours", is just HEAD itself. The other is whatever argument we pass to git merge:

git merge A

表示我们的"是HEAD,他们的"是由A标识的提交.

means "ours" is HEAD and "theirs" is the commit identified by A.

这又是您的git log --all --decorate --oneline --graph输出(顺便说一下,谢谢您的加入,这对大多数合并至关重要!):

Here is your git log --all --decorate --oneline --graph output again (thanks, by the way, for including that—it's critical for most merges!):

* 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(HEADmaster或只是我们的")和提交da6a750(A或只是他们的").

so we can say that the two heads are commit 86294d1 (HEAD or master or just "ours") and commit da6a750 (A or just "theirs").

合并基础是他们根据图形历史记录首次共享的提交,即从两个头开始,根据需要在历史记录中进行倒排,直到找到他们共同的提交为止,您可以从两个方向达到.因此,我们从da6a750开始,向后退一步到bf27b58,然后再向后退一步到86294d1abe6b8a.同时,我们从86294d1开始并...哦,看来我们已经完成了一个常见的提交! :-)

The merge base is whatever commit they first share in terms of their graph history, i.e., starting from both heads, work backwards in history if needed until you find a commit that they have in common, that you can reach from both heads. So we start from da6a750, work backwards one step to bf27b58, then work backwards one more step to both 86294d1 and abe6b8a. Meanwhile, we start from 86294d1 and ... oh look we've hit a common commit already! :-)

由于合并基础 是两个负责人之一,所以通常我们要么快速前进,要么抱怨没有可合并的内容.由于合并基础是这两个选项的我们的"头,因此Git将选择快进操作.使用--no-ff告诉Git:不要选择,继续进行完全合并.

Since the merge base is one of the two heads, normally we'd either get a fast forward, or a complaint that there is nothing to merge. Since the merge base is the "ours" head, of those two options, Git would pick the fast forward operation. Using --no-ff tells Git: don't pick that, go ahead and do a full blown merge after all.

现在,合并基础是我们的"提交保证的事实,我们将遇到您的问题,但是实际上,即使合并基础不是我们的"提交.让我们看一下提交中的内容,以及Git在git diffgit merge上都能工作时需要做什么的下一层内容-但首先,让我们考虑应假定的git merge 去做.

Now, the fact that the merge base is the "ours" commit guarantees we will have your problem, but in fact, we could have your problem even if the merge base were not the "ours" commit. Let's take a look at what's inside a commit, at the next level down of what Git needs and does when it works on both git diff and git merge—but first, let's think about what git merge is supposed to do.

作为一般规则,运行git merge时的想法是我们要承担两组工作-我们在 our 分支上所做的事情 我们的提交,无论他们是谁,事情他们"都是在他们的提交中的分支上进行的,并产生了新的提交世界:这需要我们做的任何好事,加上他们所做的任何好事.

As a general rule, the idea when running git merge is that we want to take two sets of work—things we did on our branch in our commits, and things "they", whoever they are, did on their branch in their commits—and produce a new commit that is the best of both worlds: that takes any good stuff we did, plus any good stuff they did.

如果我们水平而不是垂直地绘制图形,左边的是较旧的提交,右边的是较新的提交,我们可以这样绘制:

If we draw the graph horizontally instead of vertically, with older commits at the left and newer ones at the right, we can draw this:

          o--o--o--...--H   <-- ours
         /
...--o--B
         \
          o-----...-----T   <-- theirs

其中每个o是一个提交,BHT也是.提交B合并基础,此图中的两个分支在过去"(向左)方向上重新结合. H是我们的(HEAD)提交,而T是其分支的head/tip提交.那么,我们如何才能将我们的工作与他们的工作结合起来呢?

where each o is a commit, and so are B, H, and T. Commit B is the merge base, where the two forks in this graph rejoin in the "past" (leftward) direction. H is our (HEAD) commit and T is the head / tip commit of their branch. How, then, can we combine our work with their work?

Git的答案是运行两个git diff:

Git's answer is to run two git diffs:

git diff B H     # find out what we did
git diff B T     # find out what they did

然后它可以组合这两个差异:

Then it can combine these two diffs:

  • 无论我们在某些文件中添加一些内容(一些文本行),Git都应使最终结果在这些文件中添加这些行.无论我们在某些文件中删除了几行文本的位置,都应使最终结果被删除.

  • Wherever we added something—some lines of text—to some files, Git should make the final result have those added lines in those files. Wherever we deleted some lines of text in some files, it should make the final result have those lines deleted.

因为git diff将差异表示为删除并添加该内容"(即使对于更改为该内容的差异),也涵盖了git diff所说的所有内容.

Because git diff expresses the differences as "delete this and add that" (even for differences that change this to that), that covers everything git diff says.

同样,无论他们在何处添加行,Git都应使最终结果具有已添加的行.无论他们在哪里删除行,Git都应使最终结果具有相同的删除.

Likewise, wherever they added lines, Git should make the final result have the added lines. Wherever they deleted lines, Git should make the final result have those same deletions.

为了处理一个非常常见的情况,如果我们和他们进行了完全相同的更改,即删除相同的原始行和/或添加相同的替换,Git只需要一份.

To take care of a very common case, if we and they made the exact same change—deleting the same original lines, and/or adding the same replacements—Git takes only one copy of this.

当然,如果有一个地方我们都碰到了相同的话,但是以不同的方式,Git只是举起了隐喻的手,,并声明合并冲突.

And of course, if there's a place where we both touched the same lines, but in different ways, Git just throws up its metaphorical hands, exclaims "Oy vey!", and declares a merge conflict.

(正是这些合并冲突使我们最头疼,所以大多数扭曲旋钮 Git为我们提供了旨在以某种方式处理这些冲突的方法..gitattributes合并属性也是如此,尽管这与我们这里的问题没有直接关系.)

(It's these merge conflicts that give us the most headaches, so most of the twisty knobs Git gives us are designed for dealing with those conflicts in some way. That's mostly true of .gitattributes merge attributes, too—though that's not directly relevant to our problem here.)

现在,所有这些结合工作量很大,因此要使Git快速运行,有一条捷径.

Now, all this combining is a lot of work, so to make Git go fast, there's a short-cut.

我们可以使用git cat-file -p来查看任何提交对象,甚至可以查看任何Git对象:

We can look at any commit object, or indeed any Git object at all, with 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

(我在这里把其余的都剪掉了.)

(I snipped the rest off here).

这里更有趣的部分实际上是tree,所以让我们来看一些:

The more interesting part here is actually the tree, so let's view some of that:

$ git cat-file -p 5bc304073b94505cd3f6716829c4cec5a7474762
100644 blob 8d1519c435c4da5a65228785fa7ba7033fe011ff    .gitignore
100644 blob 66c9d22a735ee9d8da7f7ed49599583aa642842f    Makefile
100644 blob c9c824fa6668e45976c4fe8a10e4d5c25e272f0c    about.tex
100644 blob 1757109f5aa921ecf9a8051180c25f09e1496c07    aboutvc.tex

(再次我在这里剪掉了东西).

(again I snipped things off here).

每个blob对象的每个原始哈希ID(即存储的文件版本)告诉Git 哪个版本与该提交一起进行. (更确切地说,这是此tree对象的文件版本,但是此tree与此提交一起使用,因此它具有相同的含义.)

Each of those raw hash IDs for each blob object—i.e., stored file version—tells Git which version goes with this commit. (More precisely, that's the file version for this tree object, but this tree goes with this commit, so it amounts to the same thing.)

Git可以并且实际上已经可以为三个提交(合并基础,我们的"和他们的")中的每个提交提取这些blob哈希ID.哈希ID是 用来区分旧版本和新版本的文件的方式,例如aboutvc.tex(在我的情况下)或specific(在您的情况下).但是这些哈希ID有一个有趣的事情:它们完全基于对象的内容. 1 如果两个不同提交中的两个文件完全相同,则完全是100%相同,它们具有相同的哈希值,并且仅存储在存储库中一次.这意味着无论有多少提交都拥有该文件特定版本的副本,数据库中仅存储一个副本.

Git can, and in fact has to, extract these blob hash IDs for each of the three commits—the merge base, "ours", and "theirs". The hash IDs are how it will be able to diff the old and new versions of files like aboutvc.tex (in my case) or specific (in yours). But there is an interesting thing about these hash IDs: they're based entirely on the contents of the object.1 If two files in two different commits are exactly, completely, 100% bit-for-bit identical, they have the same hash and are stored in the repository just once. This means that no matter how many commits have a copy of that particular version of that file, there's only one copy stored in the database.

1 实际上,它们是对象内容的加密哈希,包括在每个对象前面粘贴的小字号和大小的标头Git.该标头是为什么 著名的SHA-1哈希冲突不是一个直接的问题.用于Git.

1In fact, they are cryptographic hashes of the object contents, including the little type-and-size header Git sticks on the front of each object. That header is why the now-famous SHA-1 hash collision is not an immediate problem for Git.

这种快速的哈希比较-相同的 哈希表示该文件的相同版本"这一事实-表示git diffgit merge可以立即轻松地知道没有更改到某个文件,从基本文件到我们的文件,或者从基本文件到他们的文件...这正是merge=ours出错的地方. Git考察了我们的基础对比他们的基础.一对具有 same 哈希.一对具有不同哈希.

This fast hash comparison—the fact that the same hash means "same version of that file"—means that git diff and git merge can immediately and easily tell that there's no change to some file, from base to ours, or base to theirs ... and this is precisely where merge=ours goes wrong. Git looks at base-vs-ours, and base-vs-theirs. One pair has the same hash. One pair has a different hash.

在这一点上,Git只是假设,正确的答案是,无论.gitattributes中的合并策略或Turney-knob设置如何,都将从具有不同头的文件中获取文件哈希.对于大多数文件,在大多数情况下,这是正确答案.但是,如果我们定义了自定义合并驱动程序或设置了merge=ours,则可能是错误的答案.

At this point, Git simply assumes that the right answer, regardless of merge strategy or turney-knob setting in .gitattributes, is to take the file from whichever head has a different hash. For most files, in most cases, that's the right answer. But if we have defined a custom merge driver, or set merge=ours, it might be the wrong answer.

当一个不同的头是他们的",而自定义合并方向是保持我们的头"时,这是错误的答案.无论选择哪种提交作为合并基础,这都是事实,但是当合并基础为HEAD时,即为我们的提交,则从基础到我们的差异中的 all 散列是相同的,结果始终是 文件的版本".

When the one head that's different is "theirs", and the custom merge direction is "keep ours", it's the wrong answer. That's true no matter what commit is chosen as the merge base, but when the merge base is HEAD—is our commit—then all the hashes, in the diff from base to ours, are the same, and the result is always "their version of the file".

实际上,这是为什么为什么首先可以实现快进:最终合并的树始终只是它们的树.实际上,Git 忽略 .gitattributes中的所有自定义方向.即使您强制执行真正的合并,而不是快速前进非合并的合并",这一点仍然成立.

That, in fact, is why a fast forward is possible in the first place: the final merged tree is always just their tree. Git, in effect, ignores all the custom directions in .gitattributes. That remains true even if you force a real merge rather than a fast-forward-non-merge "merge".

也许Git应该检查自定义合并驱动程序或merge=ours指令,并至少在实际(非快进)合并中禁用此快捷方式.但事实并非如此,因此您将遇到此问题.在其他情况下,您也会 遇到此问题,在这种情况下,需要进行真正的合并,但仅在基础比较时会修改文件.

Perhaps Git should check for custom merge drivers or merge=ours directives, and disable this short-cut, at least for real (non-fast-forward) merges. But it doesn't, and therefore you will have this problem. You will also have this problem for other cases, where there's a real merge to be done, but the file is modified only in the base-to-theirs comparison.

人们经常想使用此merge=ours来确保存储在分支上的配置文件保持它们在该分支上的方式.这几乎总是错误的整体策略:相反,应该从版本控制中(至少从该特定存储库的版本控制中)完全省略配置文件.而不是提交例如config.iniconfig.php,而是提交config.ini.sampleconfig.default.php或类似的东西.将此配置复制到真实配置",或者如果真实"配置丢失或不完整,则将其作为辅助策略读取.

People often want to use this merge=ours to make sure that configuration files stored on a branch are kept the way they are on that branch. This is nearly always the wrong overall strategy: instead, configuration files should be omitted entirely from version control, or at least from the version control of this particular repository. Instead of committing, e.g., config.ini or config.php, commit a config.ini.sample or config.default.php or some such. Copy this configuration to the "real config", or read it as a secondary strategy if the "real" configuration is missing or incomplete.

这为您提供了一种对版本配置(示例和/或默认配置)进行总体版本控制的方法,而无需对使用 this 存储库作为某人的特定运行时配置进行版本控制他们运行软件/应用程序本身的位置.如果用户希望对自己的 specific 配置进行版本控制,则可以将其存储在单独的存储库中,并将config.ini替换为(例如)到../myconfigs/fooapp.ini的符号链接.她的配置已版本化.

This gives you a way to version configurations (sample and/or default ones) in general, without versioning the specific run-time configuration of someone using this repository as the place from which they run the software / app itself. Should the user wish to version-control her particular configuration, she can store that in a separate repository, and replace config.ini with (e.g.) a symbolic link to ../myconfigs/fooapp.ini, which is where she has her configurations versioned.

(类似的技巧是从$HOME/.gitconfig/usr/local/etc/fooapp.ini获取配置.也就是说,首先将配置单独存储.同样,如果需要或需要某种 default 配置,则可以使用该软件对该版本进行维护,但是用户自己的配置是单独的,完全不受您自己的版本控制.)

(A similar trick is to get the configuration from $HOME/.gitconfig or /usr/local/etc/fooapp.ini. That is, store the configuration separately in the first place. Again, if you want or need some sort of default configuration, you can keep that versioned with the software, but the user's own configuration is separate, and not under your own version control at all.)

这篇关于.gitattributes:merge =我们的策略与快速合并的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆