如何从错误的原点分支修复损坏的git合并?

时间:2014-09-16 14:17:00

标签: git merge

我们使用的中央git存储库有两个分支,很久以前就分道扬..有人意外地将旧分支合并到新分支中,导致一些冲突。

现在我有一个合并提交位于较新分支的顶部,如下所示:

commit xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Merge: xxxxxxxx xxxxxxxxx
Author: xxxxxxxxxxxxxxxxxxxxx
Date:   Fri Sep 12 16:04:23 2014 -0400

    Merge branch 'master' of xxxxxx:xxxxxxx/xxxxxxx into working-branch

    Conflicts:
        Gemfile.lock
        config/xxxx.rb
        config/xxxx.yml
        public/.xxx/xxxxxxx.xml

这里到底发生了什么?这是合并失败吗?如果是这样,为什么它出现在遥控器上?如何判断是否有其他文件被更改?我是否需要尝试还原此更改或只是回滚这四个文件?

我被建议在合并之前对提交进行硬重置,然后强制将其推回到存储库。如果我这样做,我应该注意哪些奇怪的效果?

4 个答案:

答案 0 :(得分:2)

使用git reset撤消合并,然后再次重做合并

步骤1)撤消合并:

git reset --hard xxxxxxx~ # xxxxxxx is the name of working branch

步骤2)再次进行合并

git merge xxxxxxxx/xxxxxxxx # e.g.: origin/master

从这一点开始, NOT 建议做强制推送,你能做的最好的事情就是解决冲突,然后提交。如果您再次发现任何问题,请返回步骤1

步骤2b)(替代步骤2)执行rebase

git rebase xxxxxxxx/xxxxxxxx  e.g.: origin/master

如果你做了rebase,你将能够通过提交合并提交或你的工作xxxxxx:xxxxxxxx / xxxxxxxx(rebase与在远程分支顶部挑选所有本地非推送提交相同),它听起来一开始很乏味,但它可以让你更好地控制如何合并每个原子变化

步骤3)进行新的更改

即使您需要放弃远程分支的更改(个人而言,我会在执行此操作之前与我的同事协调),避免使用强制推送,而是可以使用checkout with path将旧内容带到当前版本并再次提交在分支的尖端(这也将允许您和您的队友通过差异清楚地看到您在做什么)。实例

git checkout xxxxxx~ . # e.g. xxxxxx could be master
git checkout xxxxxx@{2} .
git checkout XXXX4f42134 .
etc...

注意:使用 git log git reflog 来查找包含您当地历史记录所需内容的提交

步骤4)推送您的更改(无需强制推送)

git push

如果您的推送再次被拒绝(由于存储库上的新更改),请执行 git fetch a返回步骤2

答案 1 :(得分:2)

这不是合并失败,成功合并......或者至少是"成功"从git的角度来看。有人让我叫他萌,因为合并的人做了一个" git merge"停止了四个文件的冲突。然后Moe告诉git他,Moe已经解决了git发现的冲突,创建了成功的合并提交。 (也许Moe 正确地解决了它们,或者可能没有:git不知道,我们也不知道。)

创建合并提交后,Moe继续为您提供(现在它已经发布了#34;假设您使用带有中央服务器的推送模型,这意味着Moe推动了合并)。这就是你如何得到它以及为什么它出现在遥控器上的答案?"。 (所有这些都假设您从服务器获得了合并;如果您自己完成了合并,那么您可以使用Moe"它可以使用,因为这首先是您的存储库。 )

让我们一次完成剩下的一部分......

"这里到底发生了什么?"

几乎可以肯定的是,Moe在分支git pull remote-name master上做了working-branch。实际上,pull命令只是git fetch后跟git merge:从命名的远程获取,然后从当前分支(working-branch)合并到在获取时在遥控器上看到的命名分支。 1 然后他修复了冲突,或者至少告诉git他做了,并且已经犯了。

"如何判断是否有其他文件被更改?"

合并提交只是一个包含两个或更多父提交的提交。 "第一" parent是进行合并的分支(上面是working-branch),剩下的父代是合并的分支(上面的master of xxx...)。

与任何提交一样,要查看"之前的某些提交" - 通常"直接父级" - 以及提交之间的更改,您使用git diff并告诉它将先前的提交与该提交进行比较。例如,对于普通(单父,非合并)提交1234567,您可以执行以下操作:

git diff 1234567^ 1234567

(添加--name-only--name-status--stat或您想要的任何git diff选项,以选择差异的显示方式)。如果普通提交是分支b的提示,则可以使用git diff b^ b来拼写,例如:该点是命名父(b^1234567^,或者它的实际原始commit-ID)然后命名提交本身。

(旁注:此帽子^后缀是" parent"的特殊语法,它是一般gitrevisions语法的一部分。普通帽意味着^1,第一个父级; ^2表示第二个父级; ^3表示第三个父级,如果有第三个父级;依此类推。我们将在下面使用此内容。)

使用一个命令git show显示这个确切的差异(以及日志消息)的简便方法。为git show提供普通提交的名称或ID,它将显示日志消息和针对该提交的第一个也是唯一的父级的差异。但是,如果您要求git show merge 提交执行此操作,则会执行一些不同的操作:它会向您显示"组合差异"。

请记住,合并提交是一个提交,其中至少有两个父。这意味着至少有两个差异生成:first-parent vs the merge,second-parent vs merge。 (事实上​​,每个父母都有一个,但是超过两个父母的合并是相对罕见的,所以我们可以只关注"两个"案例。)

如果您想查看哪些文件已更改,没有任何状态或实际差异,则需要git diff --name-only。选择一个或另一个父项,为其命名,并命名合并。我们假设合并提交可以命名为HEAD。然后:

git diff --name-only HEAD^ HEAD

会将HEAD的第一位父母与HEAD进行比较,并根据该比较显示哪些文件发生了变化。

要查看从第二个父级更改的文件,请为第二个父级命名:

git diff --name-only HEAD^2 HEAD

等等。

使用git diff,使用git show也可以通过简短的方式来完成这一系列的-m:添加-m标记,然后进行比较与每个父母。 (请注意,如果您遗漏 git show标记,foo.txt将显示"合并差异",仅显示 - 引用手册 - & #34;从所有父母修改的文件。"这不是你想要的:如果文件-m在一个-m差异中被修改而在另一个git show差异中没有被修改,它不会被所有父母修改,并且git show --name-only -m HEAD 不会显示它。)所以:

git reset --hard

将完成这一操作(并包含每个差异的日志消息和父级,因此您可以判断哪一个来自哪个父级)。

"我是否需要尝试恢复此更改或只是回滚这四个文件?"

安德鲁C has already noted恢复合并会让你有一个不同的问题需要稍后解决。但是,它确实避免了执行历史记录重写所带来的问题"。对于那些具有"预重写"的人,会出现这些问题(并且可以发生) 历史;你在评论中说:

"我根本不想合并 - 这完全是一个错误。"

这表明,在上面,我在谈论合并的Moe,我实际上在谈论。你没有从别人那里得到这个合并,你自己做了。

如果是这样的话,那实际上是的事情,因为可能只有才有这个历史记录,这个合并你会喜欢删除。这意味着没有其他人可以看到您从历史记录中删除合并所导致的问题。

执行...- o - o - * - M <-- HEAD=working-branch / ...- o - o - o <-- (other ID, probably stored in FETCH_HEAD) 将改变存储在本地存储库中的分支名称中的分支提示。实际上,这可以删除合并&#34; ...在您的本地存储库中(仅限)。也就是说,如果历史目前看起来像这样:

git reset --hard HEAD^

和你working-branch,这告诉git将M从提交*(合并)重新指向标记为git reset的提交。这是因为HEAD调整了任何分支HEAD^名称,使其指向作为参数给出的提交。由于*名称提交HEADworking-branch名称resetworking-branch命令使*指向提交--hard。< / p>

--hard只需将工作目录与索引/登台区一起使用。重置索引是默认值,M添加work-dir。)< / p>

提交git fetch仍然存在于您的存储库中,但没有任何指向它。 2 因此似乎从历史记录中删除,最终,它真的将是。

&#34;在合并之前对提交进行硬重置,然后强制推送它......&#34;

如果您已经推动了合并,那么您只需要强制推送步骤(或因为其他人推动它而得到它:您确实从#34; Moe&#34;中获得了它)。但是,如果 被推,那么其他人可能会拥有它(就像Moe本人一样)。如果你强行推动,那些人必须考虑你的变化。

让我们说,为了描述起见,Larry和Curly也有Moe的合并:他们有自己的存储库副本,他们有origin - 来自的合并远程。此外,我们还说他们的遥控器版本名为working-branch

如果Larry有一个跟踪origin/working-branch的本地分支origin/working-branch,那么:

  • 他&#34;背后&#34; git fetch因此尚未在其当地分支机构进行提交:他所要做的就是origin/working-branch。这将更新他的origin/working-branch,并且他将不会落后&#34;现在,因为origin/working-branch实际上倒了一个。

  • 他&#34;即使是&#34; git fetch:他仍然必须working-branch,但一旦完成,他还必须重新指出自己的git checkout working-branch,以丢弃合并提交。最简单的方法是git reset --hard origin/working-branch后跟origin/working-branch

  • 他&#34;提前&#34; git fetch:现在他必须做最多的工作。他仍然必须origin/working-branch一如既往,但之后,他必须将他的新的合并后提交重新定义到现在合并前的git fetch并删除合并提交。当他的working-branch完成时,他将比以前更多头部,原因与第一个案例变得更少&#34;少于&#34;但是第一个案例那些&#34;未来&#34;提交是不需要的合并,他必须从他的 git fetch中丢弃它。

    有几种方法可以做到这一点; git 2.0让它更容易;有关详细信息,请参阅其他stackoverflow问题和答案。

Curly必须做同样的事情Larry必须做的事情,但如果Larry在&#34;背后&#34;所以拉里很容易,Curly可能仍然会#34;领先&#34;并且必须相对努力工作。


1 更具体地说,git fetch的工作方式,拟人化, 3 是:

  1. 调出遥控器。问他:&#34;嘿,whaddaya得到了吗?您拥有哪些分支和标签,以及它们映射到哪些提交ID?&#34;
  2. 获取名称列表(分支和标签,或更一般地说,&#34;引用&#34;)并找出要带来的内容和忽略的内容。
  3. 再说一遍,找出需要哪些额外的对象(如果有的话)来获取所有必要的提交,树,blob等等。把所有这些都带过来。
  4. 最后,以某种方式存储生成的提交ID和引用名称。
  5. 第4步有一些特殊的魔力:当git merge按照FETCH_HEAD的方式运行时,ID会存储在fetch =中。当以其他方式运行时,或者使用足够新版本的git时,它们将被存储为&#34;远程跟踪分支&#34; (通过与遥控器关联的FETCH_HEAD行)。 (即使使用pull,新版本的git也会更新远程跟踪分支,因此对于这种特殊情况,您可以同时执行这两种操作。)

    系统最容易考虑更新的远程跟踪分支:你的git调用他们的git,找出他们拥有的东西,并更新你的远程跟踪分支。当你git push&#34;时,你的git也会更新这些内容,这是你的git在互联网电话上调用git的另一次。因此,您的远程跟踪分支机构只是&#34;他们拥有的,上次git检查&#34;。

    FETCH_HEAD脚本早于其中某些内容,因此它会以稍微复杂的方式使用git fetch,但它最终会像使用非常现代的git和单独的FETCH_HEAD步骤一样工作:你的get获取他们的git的分支或分支,将它们隐藏在FETCH_HEAD中,然后使用git reflog内容进行合并。

    2 实际上,有一个指向它的reflog条目。您可以使用name@{history-specifier}或使用{{1}}语法找到这些内容。然而,reflogs最终会过期:它们意味着修复各种各样的&#34; oops&#34;输入错误,而不是永久存储。

    3 &#34;不要将计算机拟人化,他们讨厌这种情况。&#34; -source unknown

答案 2 :(得分:0)

假设糟糕的合并仍然在分支机构锁定你的回购,所以它不接受任何改变。

您有两种选择。 1)摧毁这种历史。 2)恢复合并。

第一个导致更清晰的历史记录,但可能会给其他下游开发人员带来问题(他们需要&#34;从上游rebase恢复&#34;如git rebase帮助页面中所述)。此外,第一个技巧仅在合并提交是分支提示时才有效。

第二种通常更安全,但如果您需要实际重新合并分支,则会导致问题。

选项1

git checkout working-branch
git reset --hard HEAD^1
git push -f 

现在帮助所有同事从上游的rebase中恢复

选项2

git checkout working-branch
git revert -m 1
git push

就你的问题而言

如果不查看更改,我们无法判断合并是否错误。有人进行了合并并推动了它,这就是我们所知道的。您可以使用git diff BAD_MERGE_SHA^1..BAD_MERGE_SHA

查看更改了哪些文件

答案 3 :(得分:0)

如果您尚未推送合并,则可以删除您的仓库并重新克隆它。服务器上不会更改任何内容。如果您已经推送了合并,那么您可以撤消更改并推送更改以撤消合并。