git merge losts commits

下面的脚本重现了一个git分支合并的误操作,导致分支合并后,虽然在提交历史上可以看到每个commit,但是feature分支上提交的修改丢失了。

[ -a git-merge-head.git ] && rm -rf git-merge-head.git[ -a git-merge-head-logs ] && rm -rf git-merge-head-logsgit --bare init git-merge-head.gitgit clone git-merge-head.git git-merge-head-logscd git-merge-head-logs/echo "1" > v01-master.txtgit add -A && git commit -m "v01-master"git checkout -b featureecho "2" >> v01-master.txtgit add -A && git commit -m "v02-feature"git checkout masterecho "3" > v03-master.txtgit add -A && git commit -m "v03-master"#1git merge feature -m ""#2git reset .#3git checkout .git commit -m "commit version lost modification of v02-feature"echo "4" > v04-master.txtgit add -A && git commit -m "v04-master"echo "5" > v05-master.txtgit add -A && git commit -m "v05-master"cat v01-master.txt

问题说明

可以看到上述命令最后cat v01-master.txt命令,其输出的文件内容如下,可以看到feature分支中的修改丢失掉了。

1

使用git log -- v01-master.txt命令查看文件提交历史,只有master分支的提交历史,feature分支的提交丢失了。

db8ba5a v01-master

错误分析

第一个出错的操作#1,在合并分支时,因为没有提交版本的说明,导致合并没完成,没有产生合并操作的commit,但这个时间合并的内容已经被加入了gitindex暂存区,如果此时手工commit这2个分支的合并操作,完全是没问题的。此时的git status输出如下:

On branch master

All conflicts fixed but you are still merging.

(use "git commit" to conclude merge)

Changes to be committed:

modified:   v01-master.txt

接下来第二个出错的操作#2git reset .操作默认是mixed模式,会回滚index暂存区和本地仓库到指定的版本,所有的修改内容仍然是存在于当前git的工作空间的,分支中的内容可以git add之后再git commit,仍然不会丢失,此时的git status输出如下:

On branch master

All conflicts fixed but you are still merging.

(use "git commit" to conclude merge)

Changes not staged for commit:

(use "git add ..." to update what will be committed)

(use "git checkout -- ..." to discard changes in working directory)

modified:   v01-master.txt

no changes added to commit (use "git add" and/or "git commit -a")

最后第三步操作#3是版本修改丢失的关键原因,它通过git checkout .操作从index暂存区恢复代码到工作空间,将本地工作空间中的修改直接覆盖掉了,导致了此次合并中feature分支的修改丢失了。

其实上面#2#3这2步操作接近于做了一次git reset --hard操作,只是因为之前的git merge操作没有完成,所以在.git目录里仍然存在MERGE_HEADMERGE_MODEMERGE_MSG文件,可以继续提交此次合并操作,但实际上所有的代码已经被还原了。

错误修复

执行以下操作后,修复之前的错误操作。

#4git format-patch HEAD...HEAD~2#5git reset --hard HEAD~3#6git merge feature -m "FIX: merge branch feature into master"#7git am -3 000*-master.patchrm -f 000*-master.patchcat v01-master.txtecho git log -- v01-master.txtecho git log --graph --decorate --all

首先由#4这个操作生成分支合并后的所有提交补丁文件。

其次使用#5操作回滚git版本到错误的合并操作的位置,这里测试脚本里使用HEAD来定位版本,实际中可以使用git提交时的hash版本号。

然后使用#6操作指令正确合并分支,然后将#4操作生成的补丁逐一应用,如果有冲突,则解决冲突并提交,然后应用下一个补丁,测试脚本里的2个补丁应用时,没有冲突,所以直接应用成功。

References

  1. git user manual
  2. git manual page
  3. git format-patch and git diff