合并冲突时如何使用git stash

34

我们做了一些不好的事情。

在合并冲突期间,我们运行了git stash save,现在我们无法恢复我们的工作。

我们尝试过的方法:

git pull -Xours origin master
git stash apply --index

而且:

 git pull origin master
 git stash save --keep-index "merge conflicts"
 git stash apply stash@{1}

请帮忙!


你实际上做了什么重要的事情吗?(你实际上需要恢复存储的更改吗?)你能否只是重置尝试合并的内容,然后重新开始? - Cascabel
1
是和否,分别表示需要进行超过一天的合并冲突解决。 - bukzor
4
如果你需要超过一天的时间来解决合并冲突,那么可能是时候重新考虑分支处理和工作分配(或合并频率)的策略了。这样漫长的合并冲突解决过程会成为难以发现的 bug 的源头,因为在一个提交中进行了大量更改。 - Grizzly
你遇到了什么错误?为什么不使用简单的“git stash apply”呢? - inger
2
@inger:由于stash save清除索引而stash apply不修改它,因此您最终会得到常规的未暂存更改,而不是合并。我想在合并过程中应用这些更改,但不知道如何做。 - bukzor
你尝试过使用界面查看你的仓库吗?GitX能显示有时在CLI上很难找到的暂存区。这可能会有所帮助。 - willoller
6个回答

34
问题似乎在于git stash没有保存您尝试合并的分支的引用。在合并期间,此引用存储在一个名为MERGE_HEAD的引用中。
要解决此问题并返回到先前的状态,您需要找到您尝试合并的修订版(假设它是d7a9884a380f81b2fbf002442ee9c9eaf34ff68d),并在应用stash后将MERGE_HEAD设置为它。
然后,您可以应用stash(使用--index重新暂存之前暂存的所有内容),并设置您的MERGE_HEAD
git stash apply --index
git update-ref MERGE_HEAD d7a9884a380f81b2fbf002442ee9c9eaf34ff68d

虽然我认为上述内容是正确的,但我并不完全有信心仅通过设置MERGE_HEAD和取消存储就足以恢复合并状态。如果可能的话,我会避免在冲突期间使用git stash。 - user1338062
我遇到了同样的问题,尝试了你的解决方案。它抱怨说 .git/MERGE_MSG 不存在,我创建了一个带有自定义合并消息的文件,然后提交了。我担心我会被标记为合并中所有更改的作者,但似乎一切都按照应该的方式进行了。其他作者得到了适当的认可。谢谢 :) - Eldamir
我在我的pre-commit钩子中使用了stash和pop,导致删除.git/MERGE_HEAD文件时出现文件未找到错误,当尝试提交合并时。第二次尝试提交时会成功,但实际上并没有“合并”。 - jigglypuff

2

今天我也做了同样的事情,并采用了不同的方法(经过试错)来回到之前存储状态,以便我可以继续解决冲突并完成合并。

首先,在目标分支中取消存储部分合并后,我捕获了剩余冲突文件的列表(文本文件或编辑器选项卡)。这只是取消存储后未暂存文件的列表,因为已解决冲突的文件在存储之前已被暂存。

$ git status
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   myproject/src/main/java/com/acme/package3/Class3.java
#   modified:   myproject/src/main/java/com/acme/package3/Class4.java
#

接下来,我创建了一个补丁并将分支重置回合并前的状态:

$ git diff HEAD > ~/merge-with-resolved-conflicts.patch
$ git reset --hard HEAD

然后我创建了一个临时分支(派生自合并目标分支),并应用了补丁:

$ git checkout -b my-temp-branch
$ git apply ~/merge-with-resolved-conflicts.patch
$ git commit -a -m "Merge with resolved conflicts"

现在,我的临时分支(my-temp-branch)的HEAD包含了所有已经合并的内容,包括冲突已解决的文件和仍然存在冲突的文件。

然后我切换回原始分支,再次进行合并,并查看git状态。

$ git checkout my-branch
$ git merge other-branch
$ git status

状态显示有冲突的所有文件完整列表:
# Unmerged paths:
#   (use "git add <file>..." to mark resolution)
#
#   both modified:      myproject/src/main/java/com/acme/package1/Class1.java
#   both modified:      myproject/src/main/java/com/acme/package2/Class2.java
#   both modified:      myproject/src/main/java/com/acme/package3/Class3.java
#   both modified:      myproject/src/main/java/com/acme/package3/Class4.java
#

现在我需要比较这两个文件列表。第二个列表中的任何文件,但不在第一个列表中,已经被解决了(在这个示例中,Class1.java和Class2.java)。因此,对于每个这样的文件,我从临时分支中获取了已解决冲突的版本(类似于cherry-pick,但是针对单个文件而不是整个提交):

$ git checkout my-temp-branch myproject/src/main/java/com/acme/package1/Class1.java
$ git checkout my-temp-branch myproject/src/main/java/com/acme/package2/Class2.java

完成这一步骤后,我回到了存储之前的状态,因此可以继续解决剩余的冲突并提交合并。

1

你已经解决了所有的冲突,然后你执行了git stash。这将重置你的工作树到合并前的状态,并将所有的解决方案放入一个stash中。

所以,你需要做的就是重新执行合并,然后将stash中的所有内容(-- .)检出到工作树中。注意,在你的仓库根目录下运行此命令。

git merge conflict-branch
cd "$(git rev-parse --show-toplevel)"
git checkout stash@{0} -- .
git commit

顺便提一下,当进行合并操作时,你可以使用git worktree add代替git stash


1

当你处于冲突状态(索引和工作目录),你将无法执行 git stash - 它会报错说存在未合并的条目。

确保你确实已经进行了存储。查看 git statusgit stash show 的输出。


在使用git stash之前,我已经解决了冲突并继续使用了git add。在我使用git add后,我进行了进一步的编辑,然后执行了git stash save msg。所以这是成功的。 - steven_moy
1
@bukzor,实际上我刚在Windows 7上用msysgit 1.8.3测试了一下。如果文件保持冲突状态(即没有尝试解决合并冲突),那么git stash save确实会中止,而不会将有冲突的文件存储。因此,这是正确的,不是错误的 - user456814

0

我解决(在合并冲突期间 git stash pop)的方法是:

  • 创建并检出一个新的(本地)分支 mytemporarybranch

    git branch mytemporarybranch && git checkout mytemporarybranch

  • 提交到这个 mytemporarybranch

    git commit -m "我的混乱合并和压缩"

  • 检出 myoriginalbranch

    git checkout myoriginalbranch

  • 正确合并(这次不要 squash pop/apply!)

  • squash 合并 mytemporarybranchmyoriginal 分支

    git merge --squash mytemporarybranch


0
根据你最后的评论:你可以使用
git stash megre --no-commit <branch>

将索引置于“合并”状态,而无需提交更改
然后使用您想要的修改它:
如果您已经在贮藏中完成了合并:
git reset #to remove the "conflicts" flags
git checkout <initial commit> -- ./ #to revert everything to the previous working state,
git stash apply   #apply your changes

一旦所有东西都处于期望的状态,git commit


关于bukzor的评论: git checkout <tree-ish>git checkout <tree-ish> -- <files>之间实际上有很大的区别。
参考文献中了解到git checkout
  • git checkout <branch> :此形式通过更新索引、工作树和HEAD来切换分支,以反映指定的分支或提交。

  • git checkout [-p|--patch] <tree-ish> -- <pathspec> :当给出<paths>或--patch时,git checkout不会切换分支。它会从索引文件或命名的<tree-ish>(通常是提交)中更新工作树中的命名路径。

git checkout <initial commit>确实会丢弃合并信息。

git checkout <initial commit> -- ./(注意额外的-- ./),另一方面,将保留合并信息,并将每个已跟踪文件恢复到其在<initial commit>中的状态。


git checkout 会取消合并状态,对吗?这样我最终将得到一个正常的提交,其中包含了 master 中的所有更改。 - bukzor
git stash apply 命令还清除了合并状态,按照 Evan 的回答中建议的方式明确设置 MERGE_HEAD 引用即可解决我的问题。 - Robert Byrne

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接