恢复git合并冲突标志 [英] Restoring git merge conflict flags

查看:140
本文介绍了恢复git合并冲突标志的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图找出一种方法来与我的团队的其他成员分享合并冲突。我们有一些非常大的分支,合并它们会产生很多冲突。我尝试了几种不同的方法,并且我当前的尝试涉及将处于冲突状态的文件推送到远程回购(在文件中留下合并标记),然后通过文件运行令人讨厌的长别名来grep并重新创建手动合并文件(LOCAL,BASE,REMOTE)。



我最近找到了


git checkout --conflict = merge - (file)

命令对于本地分支很有效,但尽快当它被推送到远程时,这个命令不再用于恢复合并标志。



有没有办法强制git重新标记文件是否有冲突人们可以使用正常的合并工具来解决它们吗?

解决方案


有没有办法强制git重新标记文件为冲突,以便人们可以使用正常的合并工具来解决它们?

不,没有写你自己的代码无论如何。 (有人应该编写一些代码,也许应该是这样的 - 现在是时候为此提供一个工具了。虽然有一些很难的角落案例。)



这里的问题是,在Git中,冲突的合并是由存储在Git的索引中的状态表示的。

暂时退回,让我们定义索引以及当前提交 HEAD 工作树(或工作树,工作树和一些类似的变体):


  • 当前提交,也称为HEAD或 HEAD ,非常简单。 (我喜欢使用 HEAD ,像这样的计算机文本格式,特别指Git名称 HEAD 。也可以在Git 1.8.5或更高版本中使用 @ ,如果存在当前分支,则此特殊名称指当前分支,并且然后当前分支定位该分支的 tip commit ,即当前提交。或者,在分离的HEAD模式下,直接包含 HEAD 当前提交的哈希ID,无论哪种方式,都会命名为当前提交。)

  • 工作树 ,你在哪里工作。 Git的内部数据结构包含文件的提交和版本化副本,因此不适合其他任何应用程序,所以Git会将这些版本提取到普通文件中,然后您可以像往常一样阅读和操作它们。



    工作树还可以保存尚未提交但不想提交的文件。这些是文件。 (从技术上讲,未跟踪的文件是工作树中尚未包含在索引中的任何文件,但我们尚未定义索引。:-))

    Git倾向于抱怨没有跟踪的未跟踪文件;您可以通过在 .gitignore 文件中列出文件或路径名称模式来关闭这些投诉。请注意,将文件名添加到 .gitignore 不会使文件未被跟踪。如果文件被跟踪,它将保持跟踪。 .gitignore 条目主要是关闭投诉,并且当您说添加所有文件时,Git不会自动添加这些未跟踪文件。


  • 索引位于这两者之间。通常,当您第一次签出提交或分支时,索引内容与 HEAD 提交内容相匹配,Git也会将其提取到工作树中。然后,您可以随意修改所有您喜欢的工作树,但索引会继续匹配HEAD。您必须 git add 文件将它们从工作树复制回索引。



    因此, index基本上代表你将做的 next commit 。当你运行 git commit 时,Git将索引变成一个新的提交(它会自动成为新的HEAD提交)。只有你将复制回到索引中的东西才会被提交,这意味着你可以通过 git add 几个文件将更改分成几个提交一次。 (而且,您可以使用 git add -p 来仅添加文件的部分,而不是整个文件,以便索引版本本身)




如果您从不进行任何合并,或者从来没有任何合并冲突,我们可以停在这里,并完成索引。当然,你正在进行合并,并且它们正在发生冲突,所以我们需要仔细观察。



索引每个索引条目有四个阶段插槽 h3>

索引按工作树中的路径名记录文件。如果您修改了这些文件中的一部分,并且 git add 它们准备好进行下一次提交,则会更新这些文件的索引版本。但是如果您在冲突合并期间运行 git ls-files --stage ,则会出现一个秘密。没有其他时间这通常显示出来 - 只是在冲突的合并期间。秘密在于每个文件可以在索引中高达三次,在 stage slots 中被编号。插槽零是正常的日常插槽:

  $ git ls-files --stage 
[snip]
100644 d8d18736e74c7a5f61d794770a2dd94786501d12 0 Makefile
100644 046dcab7645305cbf4b94adef54a859234ac3caa 0自述文件
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 lib / __ init__.py

全零值的列表示每个文件都位于零槽中。



在冲突合并期间, README 或其他名称之一,在插槽1,2和3中最多可以有三个条目。在这种情况下,Git知道存在合并冲突。插槽1保存文件的合并基础版本。插槽2包含 HEAD - 我们的版本,插槽3包含 MERGE_HEAD - 他们的版本。 (根据定义,零槽位此时未被占用。)

这些槽中的几个槽可以是空的。 (我曾经说过,其中至多有一个可能是空的,但我错了:通过重命名/重命名或重命名/删除冲突,我们实际上可以看到多个空插槽。)空插槽指示 no 文件在合并的三个输入中的一个或多个输入中具有该名称。然而,任何存在较高编号的条目,表示存在冲突的合并。



正如您所见,你的工作来解决这些冲突。指定文件的工作树版本通常包含Git尝试解决此问题的最佳尝试,但由于Git无法解决冲突,因此工作树版本中存在冲突标记。解决冲突后,您应该像往常一样在路径上运行 git add (或者如果分辨率为 git rm 删除文件)。这将清除较高阶段的插槽,同时还将工作树文件复制到第零个插槽,除非该文件真的被删除。现在冲突得到解决。



如果您处于合并过程中,尚未提交结果,并且已编辑或甚至解析文件,但希望恢复如果你注意到,你可以使用:

pre $ g $ checkout -m - < path>

(或者与相同 - 冲突 )。您可以添加 =< style> ,它允许您指定冲突风格: merge diff3 (我更喜欢 diff3 ,其中包括文件合并版本的文本)。这将删除阶段0条目(如果创建了条目),并恢复较高阶段的冲突条目。这种特殊形式的 git checkout ,但要求原始未合并条目在索引中可用。



在任何情况下,只有解决了所有更高阶段的索引条目后,您才能 进行新的提交。也就是说,如果 git ls-files --stage 显示任何不是阶段零的条目,则可能不会进行新的提交。 p>


...只要它被推送到远程,这个[ git checkout -m ]命令不再用于恢复合并标志。


实际上,它早在那之前。一旦提交,恢复冲突的能力就会消失。这将永久清除所有较高阶段的索引条目。但是,您无法推送索引,也无法推送文件:只能推送提交。这意味着要推进部分合并(让其他人处理它),必须解决合并和提交。现在它已不再进行,不能再进行了。



所需要的是一个可以保存完整索引状态的工具,工作树文件,合并状态包括两个提交 HEAD MERGE_HEAD 这两个提交的ID,这表示合并基础的ID - 也许甚至是未被跟踪和/或被忽略的文件(la git stash )存储到非分支引用中存储的特殊提交或一组提交中。这个提交或这些提交可以从一个存储库传输到另一个存储库。相同工具的反向版本可以恢复合并状态,索引状态和工作树。所有构建这样的工具所需的组件都存在(因为 git ls-files --stage git update-index 两者都存在)。但编写这个工具对会很复杂,可能至少与 git stash 脚本一样困难。


I am trying to work out a method of sharing merge conflicts with other members of my team. We have some very large branches and merging them creates a lot of conflicts. I have tried several different methods, and my current attempt involves pushing the files in a conflicted state to the remote repo (leaving the merge markers in the files), and then running an annoyingly long alias to grep through the files and re-create the merge files manually (LOCAL, BASE, REMOTE).

I recently found the

git checkout --conflict=merge -- (file)

command, which works great on a local branch, but as soon as it gets pushed to a remote, this command no longer works to restore the merge flags.

Is there a way to force git to re-flag a file as conflicted so people can use the normal merge tools to resolve them?

解决方案

Is there a way to force git to re-flag a file as conflicted so people can use the normal merge tools to resolve them?

No, not without writing your own code anyway. (Someone should write some code, and maybe that should be be—it's time to have a tool for this. There are a bunch of corner cases that are hard, though.)

The problem here is that a conflicted merge, in Git, is represented by state stored in Git's index.

Stepping back for a moment, let's define the index, along with current commit or HEAD, and work-tree (or work tree, working tree, and a bunch of similar variants):

  • The current commit, known also as HEAD or HEAD, is pretty straightforward. (I like to use HEAD, in computer-text layout like this, to mean specifically the Git name HEAD. You can also use @ in Git version 1.8.5 or later. This special name refers to the current branch, if there is a current branch, and the current branch then locates the tip commit of that branch, which is the current commit. Or, in "detached HEAD" mode, HEAD directly contains the hash ID of the current commit. Either way, this names the current commit.)

  • The work tree is, quite simply, where you do your work. Git's internal data structures that hold commits and versioned copies of files are not suitable for anything else, so Git extracts the versions into ordinary files, which you can then read and manipulate as usual.

    The work tree can also hold files that you have not yet, and do not want to, commit. These are untracked files. (Technically, an untracked file is any file in the work-tree that is not already in the index, but we haven't defined the index yet. :-) )

    Git tends to complain about untracked files being untracked; you can shut off these complaints by listing the files, or their path name patterns, in .gitignore files. Note that adding a file name to .gitignore does not make the file untracked. If the file is tracked, it stays tracked. The .gitignore entry primarily just shuts off complaints, and also makes Git not automatically add these untracked files when you say "add all files".

  • The index sits halfway between these two. Normally, when you first check out a commit or branch, the index contents match the HEAD commit contents, which Git also extracts into the work tree. You can then modify the work-tree all you like, but the index continues to match HEAD. You must git add files to copy them from the work tree, back into the index.

    As such, the index essentially represents the next commit you will make. When you run git commit, Git turns the index into a new commit (which automatically becomes the new HEAD commit). Only things you copied back into the index get committed, which means you can split up changes into several commits by just git adding a few files at a time. (And, you can use git add -p to add just part of a file, rather than the whole file, so that the index version itself is partway between the HEAD commit version and the work-tree version.)

If you never do any merges, or never have any merge conflicts, we could stop here and be done with the index. Of course, you're doing merges, and they are hitting conflicts, so we need to look closer.

The index has four "stage slots" per index entry

The index records files by their path names in the work-tree. If you modify some of these files, and git add them to be ready for the next commit, this updates the index version of the files. But there is a secret that shows up if you run git ls-files --stage during a conflicted merge. There is no other time that this normally shows up—it's only during conflicted merges. The secret is that each file can be in the index up to three times, in stage slots that are numbered. Slot zero is the normal, everyday slot:

$ git ls-files --stage
[snip]
100644 d8d18736e74c7a5f61d794770a2dd94786501d12 0   Makefile
100644 046dcab7645305cbf4b94adef54a859234ac3caa 0   README
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0   lib/__init__.py

The column of all-zero values indicates that each of these files is in slot zero.

During a conflicted merge, though, README, or one of the other names, may have up to three entries in slots 1, 2, and 3. In this case, Git knows that there was a merge conflict. Slot 1 holds the merge base version of the file. Slot 2 holds the HEAD or --ours version, and slot 3 holds the MERGE_HEAD or --theirs version, of that same file. (Slot zero is, by definition, unoccupied at this time.)

Several of these slots can be empty. (I used to say that at most one of them could be empty, but I was wrong: with rename/rename or rename/delete conflicts, we can in fact see more than one empty slot.) The empty slot or slots indicate that no file had that name in one or more of the three inputs to the merge. The existence of any higher-numbered entry, though, indicates that there is a conflicted merge going on.

It is, as you have seen, your job to resolve these conflicts. The work tree version of the named file normally contains Git's best attempt to resolve the merge as of this point, but since Git was not able to resolve the conflict, the work tree version has conflict markers in it. After you resolve the conflict, you should run git add on the path as usual (or git rm if the resolution is to remove the file). This clears out the higher-stage slots, while also copying the work-tree file to slot zero unless the file is really removed. Now the conflict is resolved.

If you are in the middle of a merge, have not committed the result yet, and have edited or even resolved a file but wish to restore it to its original unmerged state, you can, as you noted, use:

git checkout -m -- <path>

(or the same with --conflict). You can add =<style>, which allows you to specify the conflict style: merge or diff3 (I prefer diff3, which includes text from the merge base version of the file). This removes the stage-zero entry, if you made one, and restores the higher-stage conflicting entries. This particular form of git checkout, though, requires that the original unmerged entries be available in the index.

In any case, you cannot make a new commit until all higher-stage index entries are resolved. That is, if git ls-files --stage shows any entries that are not stage zero, you may not make a new commit.

... as soon as it gets pushed to a remote, this [git checkout -m] command no longer works to restore the merge flags.

In fact, it's long before that. The ability to restore the conflict goes away once you commit. This permanently cleans out all the higher-stage index entries. But you cannot push an index, and you cannot push a file: you can only push commits. This means that to push a partial merge (to let someone else deal with it), you must resolve the merge and commit. Now it's no longer in progress and cannot be made to be in progress any more.

What is needed is a tool that can save the full index state, the work-tree files, the merge state including the IDs of the two commits HEAD and MERGE_HEAD—this implies the ID of the merge base—and perhaps even untracked and/or ignored files (a la git stash) into a special commit or set of of commits stored on a non-branch reference. This commit, or these commits, can then be transferred from one repository to another. A reverse version of the same tool can restore the merge state, index state, and work-tree. All the components necessary to build such a tool exist (because git ls-files --stage and git update-index both exsit). But writing this tool-pair would be complicated, probably at least as difficult as the git stash script.

这篇关于恢复git合并冲突标志的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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