git checkout --ours 不会从未合并的文件列表中删除文件
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/39438168/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
git checkout --ours does not remove files from unmerged files list
提问by Charlestone
Hi I need to merge two branches like this.
嗨,我需要像这样合并两个分支。
This is just an example what is happening, I work with hundreds of files which need resolution.
这只是正在发生的一个例子,我处理了数百个需要解析的文件。
git merge branch1
...conflicts...
git status
....
# Unmerged paths:
# (use "git add/rm <file>..." as appropriate to mark resolution)
#
# both added: file1
# both added: file2
# both added: file3
# both added: file4
git checkout --ours file1
git chechout --theirs file2
git checkout --ours file3
git chechout --theirs file4
git commit -a -m "this should work"
U file1
fatal: 'commit' is not possible because you have unmerged files.
Please, fix them up in the work tree, and then use 'git add/rm <file>' as
appropriate to mark resolution and make a commit, or use 'git commit -a'.
When I do git merge tool
, there is the correct content just from the 'ours' branch and when I save it, the file disappears from the unmerged list. But since I have hundreds of files like this, this is not an option.
当我这样做时git merge tool
,'ours' 分支中有正确的内容,当我保存它时,该文件从未合并的列表中消失。但是因为我有数百个这样的文件,所以这不是一个选择。
I thought that this approach will bring me where I want to be - easily say which file from which branch I want to keep.
我认为这种方法会将我带到我想去的地方 - 轻松说出我想保留哪个分支的哪个文件。
But I guess I misunderstood the concept of the git checkout --ours/theirs
commands after a merge.
但我想我误解git checkout --ours/theirs
了合并后命令的概念。
Could you please provide me some info, how to handle this situation? I use git 1.7.1
能否请您提供一些信息,如何处理这种情况?我用git 1.7.1
回答by torek
It's mostly a quirk of how git checkout
works internally. The Git folks have a tendency to let implementation dictate interface.
这主要是git checkout
内部运作方式的一个怪癖。Git 人员倾向于让实现决定接口。
The end result is that after git checkout
with --ours
or --theirs
, if you want to resolve the conflict, you must also git add
the same paths:
最终结果是在git checkout
with--ours
或 之后--theirs
,如果要解决冲突,还必须git add
使用相同的路径:
git checkout --ours -- path/to/file
git add path/to/file
But this is notthe case with other forms of git checkout
:
但这不是其他形式的情况git checkout
:
git checkout HEAD -- path/to/file
or:
或者:
git checkout MERGE_HEAD -- path/to/file
(these are subtly different in multiple ways). In some cases this means the fastest way is to use the middle command. (Incidentally, the --
here is to make sure Git can distinguish between a path name and an option or branch name. For instance, if you have a file named --theirs
, it will look like an option, but --
will tell Git that no, it's really a path name.)
(这些在多种方面略有不同)。在某些情况下,这意味着最快的方法是使用中间命令。(顺便说一句,--
这里是为了确保 Git 可以区分路径名和选项或分支名。例如,如果您有一个名为 的文件--theirs
,它看起来像一个选项,但--
会告诉 Git 不,它实际上是一个路径名。)
To see how this all works internally, and why you need the separate git add
except when you don't, read on. :-) First, let's do a quick review of the merge process.
要了解这一切在内部是如何工作的,以及为什么需要单独的git add
除非您不需要,请继续阅读。:-) 首先,让我们快速回顾一下合并过程。
Merge, part 1: how merge begins
合并,第 1 部分:合并如何开始
When you run:
当你运行时:
$ git merge commit-or-branch
$ git merge commit-or-branch
the first thing Git does is find the merge basebetween the named commit and the current (HEAD
) commit. (Note that if you supply a branch name here, as in git merge otherbranch
, Git translates that to a commit-ID, namely the tip of the branch. It saves the branch name argument for the eventual merge log message, but needs the commit ID to find the merge base.)
Git 做的第一件事是找到命名提交和当前 ( ) 提交之间的合并基础HEAD
。(请注意,如果您在此处提供分支名称,如git merge otherbranch
,Git 会将其转换为提交 ID,即分支的尖端。它保存最终合并日志消息的分支名称参数,但需要提交 ID 才能找到合并基础。)
Having found a suitable merge base,1Git then produces two git diff
listings: one from the merge base to HEAD
, and one from the merge base to the commit you identified. This gets "what you changed" and "what they changed", which Git now has to combine.
找到合适的合并基础后,1Git 然后生成两个git diff
列表:一个从合并基础到HEAD
,一个从合并基础到您确定的提交。这会得到“你改变了什么”和“他们改变了什么”,Git 现在必须将它们结合起来。
For files where you made a change and they didn't, Git can just take your version.
对于您进行了更改而他们没有更改的文件,Git 可以采用您的版本。
For files where they made a change and you didn't, Git can just take their version.
对于他们进行了更改而您没有更改的文件,Git 可以采用他们的版本。
For files where you both made changes, Git must do some real merge work. It compares the changes, line by line, to see if it can combine them. If it cancombine them, it does so. If the merges seem—based, again, on purely line-by-line comparisons—to conflict, Git declares a "merge conflict" for that file (and goes ahead and tries to merge anyway, but leaves conflict markers in place).
对于你们都做了更改的文件,Git 必须做一些真正的合并工作。它逐行比较更改,以查看是否可以将它们组合起来。如果它可以组合它们,它就会这样做。如果合并似乎(同样基于纯粹的逐行比较)冲突,Git 会为该文件声明“合并冲突”(并继续尝试合并,但会留下冲突标记)。
Once Git has merged everything it can, it either finishes the merge—because there were no conflicts—or stops with a merge conflict.
一旦 Git 合并了所有可以合并的内容,它要么完成合并(因为没有冲突),要么因合并冲突而停止。
1The merge base is obvious if you draw the commit graph. Without drawing the graph, it's kind of mysterious. This is why I always tell people to draw the graph, or at least, as much of it as needed to make sense.
1如果您绘制提交图,则合并基础是显而易见的。不画图,有点神秘。这就是为什么我总是告诉人们绘制图表,或者至少,尽可能多地绘制图表以使其有意义。
The technical definition is that the merge base is the "lowest common ancestor" (LCA) node in the commit graph. In less technical terms, it's the most recent commit where your current branch joins up with the branch you're merging. That is, by recording each merge's parent commit IDs, Git is able to find the lasttime the two branches were together, and hence figure out both what you did, and what they did. For this to work at all, though, Git has to record each merge. Specifically, it has to write both (or all, for so-called "octopus" merges) parent IDs into the new merge commit.
技术定义是合并基是提交图中的“最低共同祖先”(LCA)节点。用较少的技术术语来说,它是您当前分支与您要合并的分支合并的最新提交。也就是说,通过记录每个合并的父提交 ID,Git 能够找到两个分支最后一次在一起的时间,从而弄清楚你做了什么,他们做了什么。然而,为了让这一切正常工作,Git 必须记录每个合并。具体来说,它必须将两个(或全部,对于所谓的“章鱼”合并)父 ID 写入新的合并提交。
In some cases, there's more than one suitable merge base. The process then depends on your merge strategy. The default recursivestrategy will merge the multiple merge bases to produce a "virtual merge base". This is rare enough that you can ignore it for now.
在某些情况下,有不止一个合适的合并基础。然后该过程取决于您的合并策略。默认递归策略将合并多个合并基以产生“虚拟合并基”。这很少见,您现在可以忽略它。
Merge, part 2: stopping with a conflict, and Git's "index"
合并,第 2 部分:因冲突而停止,以及 Git 的“索引”
When Git does stop this way, it needs to give you a chance to resolvethe conflicts. But this also means that it needs to recordthe conflicts, and this is where Git's "index"—also called "the staging area", and sometimes "the cache"—really earns its existence.
当 Git 以这种方式停止时,它需要给您一个解决冲突的机会。但这也意味着它需要记录冲突,这就是 Git 的“索引”——也称为“暂存区”,有时也称为“缓存”——真正值得存在的地方。
For every staged file in your work-tree, the index has up to fourentries, rather than just one entry. At most three of these are ever actually in use, but there are four slots, which are numbered, 0
through 3
.
对于工作树中的每个暂存文件,索引最多有四个条目,而不仅仅是一个条目。其中最多三个实际使用过,但有四个插槽,编号0
从3
.
Slot zero is used for resolved files. When you're working with Git and not doing merges, onlyslot zero gets used. When you edit a file in the work tree, it has "unstaged changes", and then you git add
the file and the changes are written to the repository, updating slot zero; your changes are now "staged".
插槽零用于解析文件。当您使用 Git 而不进行合并时,只会使用插槽零。当您在工作树中编辑文件时,它具有“未暂存的更改”,然后您git add
将文件和更改写入存储库,更新插槽零;您的更改现在已“暂存”。
Slots 1-3 are used for unresolved files. When git merge
has to stop with a merge conflict, it leaves slot zero empty, and writes everything to slots 1, 2, and 3. The merge baseversion of the file is recorded in slot 1, the --ours
version is recorded in slot 2, and the --theirs
version is recorded in slot 3. These nonzero slot entries are how Git knows that the file is unresolved.2
插槽 1-3 用于未解析的文件。当git merge
必须因合并冲突而停止时,它将插槽 0 留空,并将所有内容写入插槽 1、2 和 3。文件的合并基础版本记录在插槽 1 中,--ours
版本记录在插槽 2 中,并且--theirs
版本记录在槽 3 中。这些非零槽条目是 Git 知道文件未解析的方式。2
As you resolve files, you git add
them, which erases all the slot 1-3 entries and writes a slot-zero, staged-for-commit entry. This is how Git knows the file is resolved and ready for a new commit. (Or, in some cases, you git rm
the file, in which case Git writes a special "removed" value to slot zero, again erasing slots 1-3.)
当您解析文件时,您git add
将删除所有插槽 1-3 条目并写入一个插槽零、暂存提交条目。这就是 Git 知道文件已解析并准备好进行新提交的方式。(或者,在某些情况下,您git rm
是文件,在这种情况下,Git 将一个特殊的“删除”值写入插槽 0,再次擦除插槽 1-3。)
2There are a few cases where one of these three slots is also empty. Suppose file new
does not exist in the merge base and is added in both ours and theirs. Then :1:new
is left empty and :2:new
and :3:new
record the add/add conflict. Or, suppose file f
does exist in the base, is modified in our HEAD branch, and is removed in their branch. Then :1:f
records the base file, :2:f
records our version of the file, and :3:f
is empty, recording the modify/delete conflict.
2在少数情况下,这三个插槽之一也是空的。假设文件new
在合并库中不存在,并且在我们的和他们的中都添加了。然后为空并且和记录的添加/加冲突。或者,假设文件确实存在于 base 中,在我们的 HEAD 分支中被修改,并在它们的分支中被删除。然后记录base文件,记录我们的文件版本,为空,记录修改/删除冲突。:1:new
:2:new
:3:new
f
:1:f
:2:f
:3:f
For modify/modify conflicts, all three slots are occupied; only when one file is missing is one of these slots empty. It's logically impossible to have two empty slots: there's no such thing as a delete/delete conflict, nor a nocreate/add conflict. But there is some weirdness with renameconflicts, which I've omitted here as this answer is long enough! In any case, it's the very existence of some value(s) in slots 1, 2, and/or 3 that mark the file as unresolved.
对于修改/修改冲突,三个槽都被占用;只有当缺少一个文件时,这些插槽之一才为空。逻辑上不可能有两个空槽:没有删除/删除冲突,也没有 nocreate/add 冲突。但是重命名冲突有一些奇怪的地方,我在这里省略了,因为这个答案已经足够长了!在任何情况下,正是插槽 1、2 和/或 3 中某些值的存在将文件标记为未解析。
Merge, part 3: finishing the merge
合并,第 3 部分:完成合并
Once all files are resolved—all entries are only in the zero-numbered slots—you can git commit
the merge result. If git merge
is able to do the merge without assistance, it normally runs git commit
for you, but the actual commit is still done by running git commit
.
一旦所有文件都被解析——所有条目都只在零编号的插槽中——你可以git commit
合并结果。如果git merge
能够在没有帮助的情况下进行合并,它通常会git commit
为您运行,但实际提交仍然是通过运行git commit
.
The commit command works the same way as it always does: it turns the index contents into treeobjects and writes a new commit. The only thing special about a merge commit is that it has more than one parent commit ID.3The extra parents come from a file git merge
leaves behind. The default merge message also comes from a file (a separate file in practice, although in principle they could have been combined).
commit 命令的工作方式与往常相同:它将索引内容转换为树对象并写入新的提交。合并提交的唯一特别之处在于它有多个父提交 ID。3额外的父母来自一个文件git merge
留下。默认的合并消息也来自一个文件(实际上是一个单独的文件,虽然原则上它们可以合并)。
Note that in all cases, the new commit's contents are determined by the index's contents. Moreover, once the new commit is done, the index is still full: it still contains the same contents. By default, git commit
won't make another new commit at this point because it sees that the index matches the HEAD
commit. It calls this "empty" and requires --allow-empty
to make an extra commit, but the index is not emptyat all. It's still quite full—it just is full of the same thingas the HEAD
commit.
请注意,在所有情况下,新提交的内容都由索引的内容决定。此外,一旦新的提交完成,索引仍然是满的:它仍然包含相同的内容。默认情况下,此时git commit
不会进行另一个新提交,因为它看到索引与HEAD
提交匹配。它称之为“空”并需要--allow-empty
进行额外的提交,但索引根本不为空。它仍然是相当全面,它只是充满了同样的事情作为HEAD
承诺。
3This assumes you are making a real merge, not a squash merge. When making a squash merge, git merge
deliberately does notwrite the extra parent ID to the extra file, so that the new merge commit has only a single parent. (For some reason, git merge --squash
also suppresses the automatic commit, as if it included the --no-commit
flag as well. It's not clear why, since you couldjust run git merge --squash --no-commit
if you wantthe automatic commit suppressed.)
3这假设您正在进行真正的合并,而不是壁球合并。在进行squash合并时,git merge
故意不将额外的父ID写入额外的文件,这样新的合并提交只有一个父。(出于某种原因,git merge --squash
还抑制了自动提交,就好像它也包含了--no-commit
标志一样。不清楚为什么,因为如果您想要抑制自动提交,您可以直接运行。)git merge --squash --no-commit
A squash merge does not record its other parent(s). This means that if we go to merge again, some time later, Git won't know where to start the diffs from. This means you should generally only squash-merge if you plan to abandon the other branch. (There are some tricky ways to combine squash merges and real merges but they're well out of the scope of this answer.)
壁球合并不会记录其其他父级。这意味着如果我们再次合并,一段时间后,Git 将不知道从哪里开始差异。这意味着如果你打算放弃另一个分支,你通常应该只进行压缩合并。(有一些棘手的方法可以将壁球合并和真正的合并结合起来,但它们远远超出了本答案的范围。)
How git checkout branch
uses the index
如何使用索引git checkout branch
With all that out of the way, we then have to look at how git checkout
uses Git's index, too. Remember, in normal usage, only slot zero is occupied, and the index has one entry for every staged file. Moreover, that entry matches the current (HEAD
) commitunless you've modified the file and git add
-ed the result. It also matches the file in the work-treeunless you've modified the file.4
完成所有这些之后,我们还必须看看如何git checkout
使用 Git 的索引。请记住,在正常使用中,只有插槽 0 被占用,并且索引对每个暂存文件都有一个条目。此外,该条目与当前 ( HEAD
) 提交匹配,除非您已修改文件并git add
-ed 结果。除非您修改了文件,否则它还会匹配工作树中的文件。4
If you are on some branch and you git checkout
some otherbranch, Git tries to switch to the other branch. For this to succeed, Git has to replace the index entry for each file with the entry that goes with the other branch.
如果你在某个分支上而你git checkout
在另一个分支上,Git 会尝试切换到另一个分支。为此,Git 必须将每个文件的索引条目替换为与另一个分支对应的条目。
Let's say, just for concreteness, that you're on master
and you are doing git checkout branch
. Git will compare each current index entry with the index entry it would need to be on the tip-most commit of branch branch
. That is, for file README.txt
, are the master
contents the sameas those for branch
, or are they different?
让我们说,只是为了具体起见,你正在master
和你正在做git checkout branch
。Git 会将每个当前索引条目与它需要在 branch 的最尖端提交上的索引条目进行比较branch
。也就是说,对于 file README.txt
,master
内容与 for的内容相同branch
,还是不同?
If the contents are the same, Git can take it easy and just move on to the next file. If the contents are different, Git has to do something to the index entry. (It's around this point that Git checks to see if the work-tree file differs from the index entry, too.)
如果内容相同,Git 可以放轻松,直接转到下一个文件。如果内容不同,Git 必须对索引条目做一些事情。(大约在这一点上,Git 检查工作树文件是否也与索引条目不同。)
Specifically, in the case where branch
's file differs from master
's, git checkout
has to replacethe index entry with the version from branch
—or, if README.txt
doesn't existin the tip commit of branch
, Git has to removethe index entry. Moreover, if git checkout
is going to modify or remove the index entry, it also needs to modify or remove the work-tree file. Git makes sure this is a safe thing to do, i.e., that the work-tree file matches the master
commit's file, before it will let you switch branches.
具体来说,在branch
's 文件与 's 不同的情况下master
,git checkout
必须用来自的版本替换索引条目branch
——或者,如果在 的提示提交README.txt
中不存在branch
,Git 必须删除索引条目。此外,如果git checkout
要修改或删除索引条目,还需要修改或删除工作树文件。Git 确保这是一件安全的事情,即工作树文件与master
提交的文件匹配,然后才能让您切换分支。
In other words, this is how (and why) Git finds out whether it's OK to change branches—whether you have modifications that would be clobbered by switching from master
to branch
. If you have modifications in your work-tree, butthe modified files are the same in both branches,Git can just leave the modifications in the index and work-tree. It can and will alert you to these modified files "carried over" into the new branch: easy, since it had to check for this anyway.
换句话说,这就是 Git 如何(以及为什么)找出更改分支是否可以的方式——无论您是否有修改,如果从 切换master
到branch
. 如果您的工作树中有修改,但修改后的文件在两个分支中是相同的,Git 可以将修改保留在索引和工作树中。它可以并且会提醒您这些修改后的文件“转移”到新分支中:很容易,因为无论如何它都必须检查这一点。
Once all the tests have passed and Git has decided that it's OK to switch from master
to branch
—or if you specified --force
—git checkout
actually updates the index with all the changed (or removed) files, and updates the work-tree to match.
一旦所有的测试都通过并且 Git 决定可以从 切换master
到branch
- 或者如果你指定--force
-git checkout
实际上更新所有更改(或删除)文件的索引,并更新工作树以匹配。
Note that all this action has used slot zero. There are no slot 1-3 entries at all, so that git checkout
does not have to remove any such things. You're not in the middle of a conflicted merge, and you ran git checkout branch
to not just check out one file, but rather an entire setof files and switch branches.
请注意,所有此操作都使用了插槽零。根本没有插槽 1-3 条目,因此git checkout
不必删除任何此类内容。您并没有处于合并冲突的中间,您git checkout branch
不仅要检查一个文件,还要检查一整套文件并切换分支。
Note also that you can, instead of checking out a branch, check out a specific commit. For instance, this is how you might look at a previous commit:
另请注意,您可以检出特定的提交,而不是检出分支。例如,您可以通过以下方式查看之前的提交:
$ git log
... peruse log output ...
$ git checkout f17c393 # let's see what's in this commit
The action here is the same as for checking out a branch, except that instead of using the tipcommit of the branch, Git checks out an arbitrary commit. Instead of now being "on" the new branch, you're now on nobranch:5Git gives you a "detached HEAD". To reattach your head, you must git checkout master
or git checkout branch
to get back "on" the branch.
此处的操作与检出分支相同,只是Git 检出任意提交而不是使用分支的提示提交。现在不是在新分支上,而是在没有分支上:5Git 给了你一个“分离的 HEAD”。要重新连接你的头,你必须git checkout master
或git checkout branch
回到“分支”上。
4The index entry may not match the work-tree version if Git is doing special CR-LF ending modifications, or applying smudge filters. This gets pretty advanced and the best thing is to ignore this case for now. :-)
4如果 Git 正在进行特殊的 CR-LF 结尾修改或应用污迹过滤器,则索引条目可能与工作树版本不匹配。这变得非常先进,最好的办法是暂时忽略这种情况。:-)
5More accurately, this puts you on an anonymous(unnamed) branch that will grow from the current commit. You will stay in detached HEAD mode if you make new commits, and as soon as you git checkout
some other commit or branch, you'll switch there and Git will "abandon" the commits you've made. The point of this detached HEAD mode is both to let you look around andto let you make new commits that willjust go away if you don't take special action to save them. For anyone relatively new to Git, though, having commits "just go away" is not so good—so make sure you know that you're in this "detached HEAD" mode, whenever you are in it.
5更准确地说,这会将您置于一个匿名(未命名)分支上,该分支将从当前提交增长。如果你进行新的提交,你将保持在分离的 HEAD 模式,一旦你git checkout
有其他提交或分支,你就会切换到那里,Git 将“放弃”你所做的提交。这种分离的 HEAD 模式的重点是让您环顾四周,并让您进行新的提交,如果您不采取特殊措施来保存它们,这些提交就会消失。但是,对于 Git 相对较新的人来说,让提交“立即消失”并不是那么好——所以确保你知道你处于这种“分离的 HEAD”模式,无论何时你都处于这种模式。
The git status
command will tell you if you're in detached HEAD mode. Use it often.6If your Git is old (the OP's is 1.7.1, which is very old now), git status
is not as helpful as it is in modern versions of Git, but it's still better than nothing.
该git status
命令将告诉您是否处于分离的 HEAD 模式。经常使用它。6如果您的 Git 很旧(OP 是 1.7.1,现在已经很旧了),git status
那么它不如在现代版本的 Git 中那么有用,但总比没有好。
6Some programmers like to have key git status
information encoded into each command-prompt. I personally do not go this far, but can be a good idea.
6有些程序员喜欢将关键git status
信息编码到每个命令提示符中。我个人不会走这么远,但可以是一个好主意。
Checking out specific files, and why it sometimes resolves merge conflicts
检查特定文件,以及为什么它有时会解决合并冲突
The git checkout
command has other modes of operation, though. In particular, you can run git checkout [flags etc] -- path [path ...]
to check out specific files. This is where things get weird. Note that when you use thisform of the command, Git does notcheck to make sure you are not overwriting your files.7
不过,该git checkout
命令还有其他操作模式。特别是,您可以运行git checkout [flags etc] -- path [path ...]
以检出特定文件。这就是事情变得奇怪的地方。请注意,当您使用这种形式的命令时,Git不会检查以确保您没有覆盖文件。7
Now, instead of changing branches, you're telling Git to get some particular file(s) from somewhere, and drop them into the work-tree, overwriting whatever is there, if anything. The tricky question is: just where is Git getting these files?
现在,不是更改分支,而是告诉 Git 从某处获取一些特定文件,并将它们放入工作树中,覆盖那里的任何内容(如果有的话)。棘手的问题是:Git 从哪里获取这些文件?
Generally speaking, there are three places that Git keeps files:
一般来说,Git 保存文件的地方有三个:
- in commits;8
- in the index;
- and in the work-tree.
- 在提交中;8
- 在索引中;
- 并在工作树中。
The checkout command can read from either of the first two places, and always writes the result to the work-tree.
checkout 命令可以从前两个位置中的任何一个读取,并始终将结果写入工作树。
When git checkout
gets a file from a commit, it first copies it to the index. Whenever it does this, it writes the file to slot zero. Writing to slot zero wipes out slots 1-3, if they are occupied. When git checkout
gets a file from the index, it does not have to copy it to the index. (Of course not: it's already there!) This is how git checkout
works when you are notin the middle of a merge: you can git checkout -- path/to/file
to get the index version back.9
当git checkout
从提交中获取文件时,它首先将其复制到 index。每当它这样做时,它都会将文件写入插槽零。写入插槽 0 会清除插槽 1-3(如果它们被占用)。当git checkout
得到从索引文件,它并没有将它复制到索引。(当然不是:它已经在那里了!)git checkout
当您不在合并过程中时,这是如何工作的:您可以git checkout -- path/to/file
恢复索引版本。9
Suppose, though, that you arein the middle of a conflicted merge and are going to git checkout
some path, maybe with --ours
. (If you are not in the middle of a merge, there's nothing in slots 1-3, and --ours
makes no sense.) So you run git checkout --ours -- path/to/file
.
但是,假设您正处于冲突合并的过程中,并且要走git checkout
某条路,也许是--ours
. (如果您不在合并过程中,则插槽 1-3 中--ours
没有任何内容,并且没有任何意义。)所以您运行git checkout --ours -- path/to/file
.
This git checkout
gets the file from the index—in this case, from index slot 2. Since this is already in the index, Git does not write tothe index, just to the work-tree. So the file is notresolved!
这git checkout
得从索引,在这种情况下,从指数插槽2的文件,因为这已经是该指数,Git不会写入到索引,只是为了工作树。所以文件没有解析!
The same goes for git checkout --theirs
: it gets the file from the index (slot 3), and does not resolve anything.
这同样适用于git checkout --theirs
:它从索引(插槽 3)中获取文件,并且不解析任何内容。
But: if you git checkout HEAD -- path/to/file
, you are telling git checkout
to extract from the HEAD
commit. Since this is a commit, Git starts by writing the file contents to the index. This writes slot 0 and erases 1-3. And now the file is resolved!
但是:如果你git checkout HEAD -- path/to/file
,你是在告诉git checkout
从HEAD
提交中提取。由于这是一次提交,Git 首先将文件内容写入索引。这将写入插槽 0 并擦除 1-3。现在文件已解决!
Since, during a conflicted merge, Git records the being-merged commit's ID in MERGE_HEAD
, you can also git checkout MERGE_HEAD -- path/to/file
to get the file from the other commit. This, too, extracts from a commit, so it writes to the index, resolving the file.
由于在冲突合并期间,Git 将正在合并的提交的 ID 记录在 中MERGE_HEAD
,因此您还git checkout MERGE_HEAD -- path/to/file
可以从另一个提交中获取文件。这也从提交中提取,因此它写入索引,解析文件。
7I often wish Git used a different front-end command for this, since we could then say, unequivocally, that git checkout is safe, that it won't overwrite files without --force
. But this kind of git checkout
doesoverwrite files, on purpose!
7我经常希望 Git 为此使用不同的前端命令,因为我们可以明确地说,git checkout 是安全的,它不会在没有--force
. 但是这种git checkout
确实会覆盖文件,这是故意的!
8This is a bit of a lie, or at least a stretch: commits don't contain files directly. Instead, commits contain a (single) pointer to a treeobject. This tree object contains the IDs of additional tree objects and of blobobjects. The blob objects contain the actual file contents.
8这有点撒谎,或者至少是一个延伸:提交不直接包含文件。相反,提交包含一个指向树对象的(单个)指针。此树对象包含附加树对象和Blob对象的 ID 。blob 对象包含实际的文件内容。
The same is, in fact, true of the index as well. Each index slot contains, not the actual file contents, but rather the hash IDs of blob objects in the repository.
事实上,索引也是如此。每个索引槽包含的不是实际的文件内容,而是存储库中 blob 对象的哈希 ID。
For our purposes, though, this doesn't really matter: we just ask Git to retrieve commit:path
and it finds the trees and the blob ID for us. Or, we ask Git to retrieve :n:path
and it finds the blob ID in the index entry for path
for slot n
. Then it gets us the file's contents, and we're good to go.
不过,就我们的目的而言,这并不重要:我们只是要求 Git 进行检索commit:path
,它会为我们找到树和 blob ID。或者,我们要求 Git 进行检索:n:path
,它会在path
插槽的索引条目中找到 blob ID n
。然后它为我们获取文件的内容,我们就可以开始了。
This colon-and-number syntax works everywhere in Git, while the --ours
and --theirs
flags only work in git checkout
. The funny colon syntax is described in gitrevisions
.
这种冒号和数字语法在 Git 中的任何地方都适用,而--ours
和--theirs
标志仅适用于git checkout
. 有趣的冒号语法在 中描述gitrevisions
。
9The use-case for git checkout -- path
is this: suppose, whether or not you are merging, you made some changes to a file, tested, found those changes worked, then ran git add
on the file. Then you decided to make more changes, but have not run git add
again. You test the second set of changes and find they are wrong. If only you could get the work-tree version of the file set back to the version you git add
-ed just a moment ago.... Aha, you can: you git checkout -- path
and Git copies the index version, from slot zero, back to the work-tree.
9用例git checkout -- path
是这样的:假设,无论您是否正在合并,您对文件进行了一些更改、测试、发现这些更改有效,然后git add
在该文件上运行。然后您决定进行更多更改,但没有git add
再次运行。您测试第二组更改并发现它们是错误的。如果您能将文件的工作树版本设置回git add
刚才您设置的版本.... 啊哈,您可以:您git checkout -- path
和 Git 将索引版本从插槽零复制回工作-树。
Subtle behavior warning
微妙的行为警告
Note, though, that using --ours
or --theirs
has another slight subtle difference besides just the "extract from index and therefore don't resolve" behavior. Suppose that, in our conflicted merge, Git has detected that some file was renamed. That is, in the merge base, we had file doc.txt
, but now in HEAD
we have Documentation/doc.txt
. The path we need for git checkout --ours
is Documentation/doc.txt
. This is also the path in the HEAD
commit, so it's OK to git checkout HEAD -- Documentation/doc.txt
.
但是请注意,除了“从索引中提取并因此不解析”行为之外,使用--ours
或--theirs
还有另一个细微的差别。假设在我们的冲突合并中,Git 检测到某个文件被重命名为. 也就是说,在合并库中,我们有 file doc.txt
,但现在HEAD
我们有Documentation/doc.txt
. 我们需要的路径git checkout --ours
是Documentation/doc.txt
。这也是HEAD
提交中的路径,所以可以git checkout HEAD -- Documentation/doc.txt
.
But what if, in the commit we're merging, doc.txt
did notget renamed? In this case, we should10be able to git checkout --theirs -- Documentation/doc.txt
to get theirdoc.txt
from the index. But if we try to git checkout MERGE_HEAD -- Documentation/doc.txt
, Git won't be able to find the file: it's not in Documentation
, in the MERGE_HEAD
commit. We have to git checkout MERGE_HEAD -- doc.txt
to get their file ... and that would notresolve Documentation/doc.txt
. In fact, it would just create ./doc.txt
(if it was renamed there's almost certainly no ./doc.txt
, hence "create" is a better guess than "overwrite").
但是如果在我们合并的提交doc.txt
中没有被重命名呢?在这种情况下,我们应该10能够从索引中git checkout --theirs -- Documentation/doc.txt
获取它们doc.txt
。但是如果我们尝试git checkout MERGE_HEAD -- Documentation/doc.txt
,Git 将无法找到该文件:它不在Documentation
, 在MERGE_HEAD
提交中。我们必须git checkout MERGE_HEAD -- doc.txt
得到他们的文件......这不会解决Documentation/doc.txt
。事实上,它只会创建./doc.txt
(如果它被重命名,几乎可以肯定没有./doc.txt
,因此“创建”是比“覆盖”更好的猜测)。
Because merging uses HEAD
's names, it's generally safe enough to git checkout HEAD -- path
to extract-and-resolve in one step. And if you're working on resolving files and have been running git status
, you should know whether they have a renamed file, and therefore whether it's safe to git checkout MERGE_HEAD -- path
to extract-and-resolve in one step by discarding your own changes. But you should still be aware of this, and know what to do if there isa rename to be concerned with.
因为合并使用HEAD
的名称,所以git checkout HEAD -- path
一步提取和解析通常是足够安全的。如果您正在解析文件并且一直在运行git status
,您应该知道它们是否有重命名的文件,因此git checkout MERGE_HEAD -- path
通过丢弃您自己的更改来一步提取和解析是否安全。但你还是应该意识到这一点,并知道该怎么做,如果有是去关注的重命名。
10I say "should" here, not "can", because Git currently forgetsthe rename a little bit too soon. So if using --theirs
to get a file that you renamed in HEAD
, you have to use the old name here too, and then rename the file in the work-tree.
10我在这里说“应该”,而不是“可以”,因为 Git 目前忘记重命名有点太快了。因此,如果--theirs
用于获取您在 中HEAD
重命名的文件,则您也必须在此处使用旧名称,然后在工作树中重命名该文件。