Git分支冲突合并

Git分支冲突原因及解决方法,顺便看看"快进(fast-forward)"和三方合并的概念。

新建分支

假如git仓库中已经有了一些提交。

现在,系统中有一个问题需解决,将此问题命名为#53。

远程仓库上为#53创建一个新分支iss53,本地使用带有-b参数的git checkout命令检出分支。

GIT新建分支

$ git checkout -b iss53
Switched to a new branch "iss53"

或者

$ git branch iss53
$ git checkout iss53

git新建分支

提交分支

现在,在iss53分支上修复#53问题并执行提交。

在处理#53问题过程中,iss53分支不断向前推进。

也就是说,HEAD指针目前指向iss53分支。

$ vim index.html
$ git commit -a -m 'added a new footer [issue 53]'

git新建分支

创建hotfix分支

假如,现在有一个紧急bug需修复,要切换分支。

切换分支前,发现工作目录和暂存区有未被提交的文件。

它可能与即将检出hotfix分支产生冲突,导致分支切换被阻止。

最佳做法是在切换分支前,保证当前分支处于干净状态,可通过提交做到这一点。

也可使用git stash命令对修改执行暂存。

具体参见Stashing and Cleaning

现在,假设已将修改全部提交,可以切换分支。

为说明问题先切回master分支。

$ git checkout master
Switched to branch 'master'

现在工作目录和iss53分支像最初创建时一样。

注:当切换分支时,git会重置工作目录,看起来像回到分支最后一次提交。git会自动添加、删除、修改文件,以确保此时工作目录回到分支最后一次提交时的状态。

接下来,在本地检出hotfix分支,在hotfix分支上修复bug。

$ git checkout -b hotfix
Switched to a new branch 'hotfix'

$ vim index.html

$ git commit -a -m 'fixed the broken email address'
[hotfix 1fb7853] fixed the broken email address
 1 file changed, 2 insertions(+)

新建git分支

基于master分支新开出的hotfix分支 ,在修复bug后将代码合并回master分支。

使用git merge执行分支合并:

$ git checkout master

$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
 index.html | 2 ++
 1 file changed, 2 insertions(+)

git fast-forward

合并分支时出现"快进(fast-forward)"这个词。

说明当前master分支所指向的提交,是hotfix提交的直接上游,因此,git只是简单的将指针向前移动。

换句话说,在合并两个分支时,若顺着一个分支走下去能到达另一个分支,那么git在做两者合并时,只会简单的将指针向前推进(指针右移)。

这种情况下的合并操作,没有分歧要解决——这就是“快进(fast-forward)”。

现在,最新修改已在master分支所指向的快照中。

git创建分支

Git所存储的并非一系列更改集(changeset),而是一系列快照。

执行一次commit会存储一个commit对象,它包含一个指针,指向提交内容的快照。

Git中master分支和其他分支一样,master分支在git中频繁出现,是因为运行git init命令时会自动创建master分支。

合并分支

紧急bug的修改发布后(hotfix),准备回到被打断的工作中(iss53)。

可先删除hotfix分支,已不再需要它。

git branch命令加上 -d参数可删除分支:

$ git branch -d hotfix
Deleted branch hotfix (3a0874c).

切换到iss53分支:

$ git checkout iss53
Switched to branch "iss53"

$ vim index.html

$ git commit -a -m 'finished the new footer [issue 53]'
[iss53 ad82d7a] finished the new footer [issue 53]
1 file changed, 1 insertion(+)

切换git分支

hotfix分支上所做的修改并未包含在iss53分支中,若要拉取hotfix所做的修改,使用git merge master命令,将master分支合并到iss53分支,或者等到iss53分支完成后,将其合并回master分支。

假如已经修复了#53问题,并打算将iss53分支合并回master分支。

所做工作与之前合并hotfix分支差不多,切换到想合并的分支上,执行git merge命令。

$ git checkout master
Switched to branch 'master'

$ git merge iss53
Merge made by the 'recursive' strategy.
index.html |    1 +
1 file changed, 1 insertion(+)

三方合并

这次与之前合并hotfix分支有所不同,因为,这个开发历史是从一个更早的位置分叉出来(diverged)。

现在,master分支所在的提交,并非iss53分支所在提交的直接上游(祖先),git不得不做一些额外工作。

出现这种情况,git会使用两个分支末端所指向的快照(C4 和 C5),及这两个分支的工作上游/祖先(C2),做一个简单的三方合并。git分支合并

与之前将分支指针向前推进(fast-forward)有所不同,此次git将三方合并结果生成一个新的快照,且自动创建一个新的提交指向它。

这被看作一次合并提交,它的特别之处在于它不止一个父提交。

git三方快照

需指出的是git会自行决定选哪一个提交,作为最优共同上游/祖先,作为合并的基础。

这和CVS、Subversion (1.5 版本之前)不同,这些古老的版本管理系统,要用户自行选择最佳合并(基础)版本。

Git在合并操作上,比其他版本管理系统要简单很多。

既然修改已经合并进来,已不再需要iss53分支,可删除此分支。

$ git branch -d iss53

合并冲突

有时合并操作不会如此顺利,若在两个不同分支中,对同一文件同一部分作了不同修改,git就无法对它们执行合并。

如果#53问题和hotfix涉及到同一文件的同一处,合并时就会出现合并冲突:

$ git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.

这种情况git会合并,但不会自动执行提交,git会停下来,等待管理员解决合并产生的冲突。

合并冲突出现后使用git status命令查看,找出因合并冲突而处于unmerged状态的文件:

$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")

Unmerged paths:
  (use "git add <file>..." to mark resolution)

    both modified:      index.html

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

因合并冲突待解决的文件,会以未合并状态标识出来。

Git在文件冲突处加入冲突标记,打开冲突文件,手动解决冲突。

冲突文件包含一些特殊区间片段,看起来像下面这样:

<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
 please contact us at support@github.com
</div>
>>>>>>> iss53:index.html

HEAD所指向版本(master分支所在位置),在这个片段的上半部分(======= 上半部分),iss53分支所指示的版本在=======下半部分。

解决冲突时需选择由=======分割部分中的一个,或自行合并这些内容。如,将内容换成下面这段:

<div id="footer">
please contact us at email.support@github.com
</div>

上述冲突解决方案仅保留其中一个分支的修改,且 <<<<<<<、======= 、>>>>>>> 这些行被完全删除。

在解决冲突后,使用git add命令将冲突标记为已解决。

若需图形化工具解决冲突,可运行git mergetool,该命令启动一个合适的可视化合并工具,一步一步引导解决冲突:

$ git mergetool

This message is displayed because 'merge.tool' is not configured.
See 'git mergetool --tool-help' or 'git help config' for more details.
'git mergetool' will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc3 codecompare vimdiff emerge
Merging:
index.html

Normal merge conflict for 'index.html':
  {local}: modified file
  {remote}: modified file
Hit return to start merge resolution tool (opendiff):

若不想使用默认工具,在“one of the following tools”这句后跟着目前支持的合并工具。

选一款顺手的输入名字即可打开。

等退出合并工具后,git会询问刚才的合并是否成功。

如果回答[是],git会将文件标记为冲突已解决。

运行git status确认合并冲突都已解决。

$ git status
On branch master
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:

    modified:   index.html

若对结果感到满意且确定冲突都已解决并添加,使用git commit执行提交。

默认提交信息看起来类似如下所示:

Merge branch 'iss53'

Conflicts:
    index.html
#
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
#    .git/MERGE_HEAD
# and try again.

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# All conflicts fixed but you are still merging.
#
# Changes to be committed:
#    modified:   index.html

若觉以上描述信息不够充分,可加入更多说明。