作者:jicanmeng
时间:2014年08月30日
下图是从<<Pro Git>>中截取的图,进行了一些修改。有两个分支:master和bugFix。分别使用git merge
和git rebase
命令后,看看是什么样子。
git merge
表示合并,格式如下:
$ git merge [选项……] <commit>
合并操作的大多数情况,只需要提供一个<commit>(提交ID或对应的引用:分支、里程碑等)作为参数。合并操作将<commit>对应的目录树和当前工作分支(HEAD指向的分支)的目录树的内容进行合并。合并后的操作以当前分支的提交作为第一个父提交,以<commit>作为第二个父提交。
执行git merge
命令:
$ git checkout master
Already on 'master'
$ git merge bugFix
$
<<Pro Git>>上是这么描述git merge
的过程的:
Git 会用两个分支的末端(C4 和 C6)以及它们的共同祖先(C2)进行一次简单的三方合并计算,生成一个新的提交。
现在执行git log master
命令,
$ git log master --graph --oneline
* C7 Merge branch 'bugFix'
|\
| * C6
| * C5
* | C4
* | C3
|/
* C2
* C1
* C0
$
可以看到,现在C5和C6已经成为master分支的一部分。而且,如前面所说,C4是C7的第一个父提交。
<<Pro Git>>上面提到一种特殊情况:
如果顺着一个分支走下去可以到达另一个分支的话,那么 Git 在合并两者时,只会简单地把指针右移,因为这种单线的历史分支不存在任何需要解决的分歧,所以这种合并过程可以称为快进(Fast forward)。
还是以图6-1为例,我们执行命令验证一下上面的说法:
$ git checkout -b hotfix C1
Switched to a new branch 'hotfix'
$ git merge master
$
执行第一条命令后,创建了hotfix分支,如下图:
执行了第二条命令后,git就会执行所谓的“fast-forwarding”,如下图:
我们考虑另外一种情况:当HEAD直接指向一个commit对象时,执行git merge
命令又会怎么样?还是以图6-1为例,我们来验证一下:
$ git checkout C1
Note: checking out '2ce2bd3'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
git checkout -b new_branch_name
HEAD is now at 2ce2bd3... after initial
$ git merge master
$
执行第一条命令后,HEAD直接指向了C1这个commit对象,如下图:
执行第二条命令后,根据“fast-forward”原则,HEAD直接指向了C4这个commit对象,如下图:
根据前面的操作,我们能够得出,git merge
其实合并的是commit对象,即使命令后的参数是一个分支名称,那么合并的也是这个分支指向的commit对象。
合并操作并非总会成功,因为合并的不同提交可能同时修改了同一个文件相同区域的内容,导致冲突。冲突会造成合并操作的中断,冲突的文件被标识,用户可以对标识为冲突的文件进行冲突解决操作,然后更新暂存区,再提交,最终完成合并操作。
下面看一个例子:
$ git merge bugFix
Auto-merging a.c
CONFLICT (content): Merge conflict in a.c
Auto-merging temp/b.c
CONFLICT (content): Merge conflict in temp/b.c
Automatic merge failed; fix conflicts and then commit the result.
$ cat .git/MERGE_MSG
Merge branch 'bugFix'
Conflicts:
a.c
temp/b.c
$ cat .git/MERGE_HEAD
7070f506fe45df4e0ebef333a1b20b5f9ec05d23
$
当合并失败后,我们可以直接从失败的信息后看到是哪个文件冲突导致了失败,或者从.git/MERGE_MSG文件中查看详细的失败信息。从上面的命令输出可以看到,是a.c文件和temp/b.c文件发生了冲突。
工作区的版本会同时包含成功的合并和冲突的合并,其中冲突的合并会用特殊的标记(<<<<<<<=======>>>>>>>)进行标识。查看当前工作区中冲突的文件:
$ cat a.c
#include
int main()
{
<<<<<<< HEAD
printf("hello,master\n");
=======
printf("hello,bugFix\n");
>>>>>>> bugFix
}
$
特殊标识<<<<<<<(七个小于号)和=======(七个等于号)之间的内容是当前分支所更改的内容。特殊标识=======(七个等于号)和>>>>>>>(七个大于号)之间的内容是所合并的版本更改的内容。
解决合并冲突的方法:
1. 编辑工作区的冲突文件,确定修改成为什么样子。
2. 提交到暂存区。
3. 提交到本地版本库。
放弃合并操作非常简单,只需要执行git reset
命令将暂存区和工作区重置即可。
git rebase
表示变基操作,命令行格式如下:
$ git rebase [--onto <newbase>] <since> [<till>]
$ git rebase --continue
$ git rebase --abort
第一条命令是最重要的,也最复杂。[]中的内容表示可以省略。有两条原则:
--onto <newbase>
时,默认<newbase>为<since>这个commit对象。<till>
这个commit对象时,默认<till>这个commit对象为HEAD指向的分支所指向的commit对象.
对图6-1执行git rebase
命令:
$ git branch
bugFix
* master
$ git checkout master
Already on 'master'
$ git rebase master bugFix
$
上面的命令中,git reabse master bugFix
就等价于git checkout bugFix; git rebase master
两条命令。因为执行了git checkout bugFix
,HEAD就指向了bugFix分支。
下面说一说变基操作的过程:
git checkout
切换到<till>.
git rebase --continue
继续变基操作。或者执行git rebase --skip
跳过此提交。或者执行git rebase --abort
就此终止变基操作,切换回变基前的分支上.
和前面的合并操作一样,变基操作也会产生冲突。假设在打第一个patch时产生了冲突,产生冲突,变基操作会停止。此时我们可以使用git status
命令查看那些文件产生了冲突,手工编辑它们,修改为我们想要的最终结果,提交到暂存区。然后执行git rebase --continue
继续打第二个patch。当所有的patch都打完之后,变基操作就成功了。
可以通过一个例子看一下:
[jicanmeng@andy git-rebase3]$ ls
a.c
[jicanmeng@andy git-rebase3]$ git branch
bugFix
* master
[jicanmeng@andy git-rebase3]$ git log master --oneline
d543173 line-7-master2
4aee472 line-6-master
8ee450b add line number
97795cc initial commit
[jicanmeng@andy git-rebase3]$ git log bugFix --oneline
e4330fc line-7-bugFix2
3e323f9 line-6-bugFix
8ee450b add line number
97795cc initial commit
[jicanmeng@andy git-rebase3]$ git rebase master bugFix
First, rewinding head to replay your work on top of it...
Applying: line-6-bugFix
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
Auto-merging a.c
CONFLICT (content): Merge conflict in a.c
Failed to merge in the changes.
Patch failed at 0001 line-6-bugFix
When you have resolved this problem run "git rebase --continue".
If you would prefer to skip this patch, instead run "git rebase --skip".
To restore the original branch and stop rebasing run "git rebase --abort".
[jicanmeng@andy git-rebase3]$ git status
# Not currently on any branch.
# Unmerged paths:
# (use "git reset HEAD ..." to unstage)
# (use "git add/rm ..." as appropriate to mark resolution)
#
# both modified: a.c
#
no changes added to commit (use "git add" and/or "git commit -a")
[jicanmeng@andy git-rebase3]$ cat .git/HEAD
d5431739857c2d23d392ccbd22ea0f80a8b5bb5a
[jicanmeng@andy git-rebase3]$ cat .git/refs/heads/master
d5431739857c2d23d392ccbd22ea0f80a8b5bb5a
[jicanmeng@andy git-rebase3]$ cat .git/refs/heads/bugFix
e4330fcc7f68343b54081ec9572a48a0b5c1c867
[jicanmeng@andy git-rebase3]$ vim a.c
[jicanmeng@andy git-rebase3]$ git rebase --continue
You must edit all merge conflicts and then
mark them as resolved using git add
[jicanmeng@andy git-rebase3]$ git add a.c
[jicanmeng@andy git-rebase3]$ git rebase --continue
Applying: line-6-bugFix
Applying: line-7-bugFix2
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
Auto-merging a.c
CONFLICT (content): Merge conflict in a.c
Failed to merge in the changes.
Patch failed at 0002 line-7-bugFix2
When you have resolved this problem run "git rebase --continue".
If you would prefer to skip this patch, instead run "git rebase --skip".
To restore the original branch and stop rebasing run "git rebase --abort".
[jicanmeng@andy git-rebase3]$ git status
# Not currently on any branch.
# Unmerged paths:
# (use "git reset HEAD ..." to unstage)
# (use "git add/rm ..." as appropriate to mark resolution)
#
# both modified: a.c
#
no changes added to commit (use "git add" and/or "git commit -a")
[jicanmeng@andy git-rebase3]$ cat .git/HEAD
1b2d21ef016afb32fdbd949e53bec3927c5bd157
[jicanmeng@andy git-rebase3]$ cat .git/refs/heads/master
d5431739857c2d23d392ccbd22ea0f80a8b5bb5a
[jicanmeng@andy git-rebase3]$ cat .git/refs/heads/bugFix
e4330fcc7f68343b54081ec9572a48a0b5c1c867
[jicanmeng@andy git-rebase3]$ git log HEAD --oneline
1b2d21e line-6-bugFix
d543173 line-7-master2
4aee472 line-6-master
8ee450b add line number
97795cc initial commit
[jicanmeng@andy git-rebase3]$ git log master --oneline
d543173 line-7-master2
4aee472 line-6-master
8ee450b add line number
97795cc initial commit
[jicanmeng@andy git-rebase3]$ git log bugFix --oneline
e4330fc line-7-bugFix2
3e323f9 line-6-bugFix
8ee450b add line number
97795cc initial commit
[jicanmeng@andy git-rebase3]$ vim a.c
[jicanmeng@andy git-rebase3]$ git add a.c
[jicanmeng@andy git-rebase3]$ git rebase --continue
Applying: line-7-bugFix2
[jicanmeng@andy git-rebase3]$ cat .git/HEAD
ref: refs/heads/bugFix
[jicanmeng@andy git-rebase3]$ cat .git/refs/heads/master
d5431739857c2d23d392ccbd22ea0f80a8b5bb5a
[jicanmeng@andy git-rebase3]$ cat .git/refs/heads/bugFix
5f96023e8ddacea3d41e49da8710a16190904bcf
[jicanmeng@andy git-rebase3]$ git branch
* bugFix
master
[jicanmeng@andy git-rebase3]$ git log master --oneline
d543173 line-7-master2
4aee472 line-6-master
8ee450b add line number
97795cc initial commit
[jicanmeng@andy git-rebase3]$ git log bugFix --oneline
5f96023 line-7-bugFix2
1b2d21e line-6-bugFix
d543173 line-7-master2
4aee472 line-6-master
8ee450b add line number
97795cc initial commit
[jicanmeng@andy git-rebase3]$
需要注意的地方有: