分支是什么?我们可以简单地把每个分支理解为一个平行宇宙,每个平行宇宙之间的变化不会影响到其他的宇宙,只有我们强行合并的时候,平行宇宙之间的改动才会互相影响。

那为什么我们需要分支呢?在开发软件的时候,我们经常需要同时开发不同的版本,比如版本1是稳定版本,而版本2需要开发新的功能A,版本3需要开发新的功能B,如果同时在一个分支中开发三个版本,那会乱套的。所以我们需要创建两个单独的分支给版本2和版本3进行开发,当各个版本的功能完善后,我们就可以将新的改变合并到稳定版本中,这样我们的开发流程即分工明确,也不会出错。

branch
并行开发,互不影响
git merge
合并新的功能

创建分支

在我们初始化一个版本库的时候,Git会默认创建一个master分支,提交也都默认提交到master中。我们有个HEAD指针,这个指针指明我们当前在哪个分支的commit中工作,如果我们切换到别的分支,我们的HEAD也会转移指向。

项目分叉历史。

如果要创建分支,我们直接使用git branch <branch_name>即可,我们先创建一个分支dev,然后查看所有分支:

$ git branch dev 
$ git branch
  dev
* master

如果我们想要将 HEAD 切换到 dev 分支,我们可以使用 checkout:

$ git checkout dev 
Switched to branch 'dev'
$ git branch
* dev
  master

使用 git checkout -b <branch_name>,能直接创建并切换到新的分支:

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

dev 分支中的文件和 master 的文件是一样的,当 HEAD 指向 dev 的时候,我们对文件夹中的修改是不会影响到 master 分支的。我们把 file1.txt 的内容改为 more content in dev branch,然后提交:

$ git commit -am "Modified file1 in dev branch" # -am参数: 添加所有改变,然后提交

合并分支

如果要把 dev 分支中的更新推送到 master 中,我们需要切换到 master,然后再将 dev 的更新合并过来:

$ git checkout master
Switched to branch 'master'

$ git merge dev
Updating 5c10f81..323b3eb
Fast-forward
 file1.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

$ git log --oneline --graph
* 323b3eb (HEAD -> master, dev) Modified file1 in dev branch
* 5c10f81 Added more content in file1
* 0aef827 Modified file2 and file3
...

默认的 merge 是使用 Fast forward 方式,并不会保存分支图案,如果想要 log 保存分支的信息,我们在merge的时候需要添加 -no-ff 参数更新comment:

$ git reset --hard HEAD^ 
$ git merge --no-ff -m "Add merge info" dev
Merge made by the 'recursive' strategy.
 file1.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --oneline --graph
*   16572d5 (HEAD -> master) Add merge info
|\  
| * 323b3eb (dev) Modified file1 in dev branch 
|/  
* 5c10f81 Added more content in file1
...

分支冲突

在多人同时开发不同分支的时候,分支冲突是很常见的现象。比如程序员A在master中对file1进行修改并提交,而程序员B在dev分支中对file1也进行了修改并提交,那么在我们把dev合并到master的时候,我们就会发现冲突。

如果程序员A在file1中添加新的内容 new content in master,然后程序员B在file2中添加新的内容new content in dev,那么当我们查看各个分支log的时候,则会有以下的信息

# master 的 log
f844b75 (HEAD -> master) new content in master
dda9338 Add merge info
323b3eb Modified file1 in dev branch
...
# dev 的 log
aa1a832 (HEAD -> dev) new content in dev
323b3eb Modified file1 in dev branch
5c10f81 Added more content in file1
...

然后我们尝试把dev合并到master,就会输出冲突的信息:

$ git branch 
  dev
* master
$ git merge dev
Auto-merging file1.txt
CONFLICT (content): Merge conflict in file1.txt
Automatic merge failed; fix conflicts and then commit the result.

当git发现file1在master和dev上的版本不同时,就会提示冲突,我们只要打开file1,就会看到git给文件的标记:

# file1.txt
more content in dev branch
<<<<<<< HEAD
new content in master
=======
new content in dev
>>>>>>> dev        

我们手动合并两者的不同,然后提交就可以了。

# file1.txt
more content in dev branch
merged content from master and dev
$ git commit -am "solve conflict""
[master f0687a0] solve confict

这时候我们再看看master的log:

$ git log --oneline --graph
*   f0687a0 (HEAD -> master) solve confict
|\  
| * aa1a832 (dev) new content in dev
* | f844b75 new content in master
* |   dda9338 Add merge info
|\ \  
| |/  
| * 323b3eb Modified file1 in dev branch
|/  
* 5c10f81 Added more content in file1
...

标签管理

标签是我们对commit设置的别名,可以帮助我们对commit进行更好的管理,如果你的老板叫你发布一个版本,然后告诉你commit id是f844b3…,这样的交流是容易出错的,因为id号又长又臭,很难管理。如果我们使用标签,比如每个版本都有专属的版本号,比如v2.0之类的,这样我们发布和管理commit就更方便了。

如果想要创建标签,只要切换到想要打标签的分支上,使用git tag <name>就好了:

$ git branch
  dev
* master
$ git tag v1.0 # 新的标签
$ git log --oneline
f0687a0 (HEAD -> master, tag: v1.0) solve confict
aa1a832 (dev) new content in dev
f844b75 new content in master
...

tag默认情况下是给当前分支的最新commit打标签,如果想要给旧的commit打上标签,使用git tag <tag_name> <commit_id>就好了:

$ git tag v0.9 f844b75
$ git log --oneline
f0687a0 (HEAD -> master, tag: v1.0) solve confict
aa1a832 (dev) new content in dev
f844b75 (tag: v0.9) new content in master
...

如果这个时候我们想要回到过去的版本,只需要使用标签名就可以了:

$ git checkout v0.9
$ git log --oneline
f844b75 (HEAD, tag: v0.9) new content in master
dda9338 Add merge info

注意:标签是和commit挂钩的,如果这个commit同时出现在多个分支上,那么这些分支都能查看此标签。

总结

$ git branch # 查看当前版本库所有分支
$ git branch <name> # 创建新的分支
$ git checkout <name> or git switch <name> # 切换分支
$ git checkout -b <name> or git switch -c <name> # 创建并切换至新的分支
$ git merge <name> # 将name分支合并至当前分支
$ git log --oneline --graph # 查看具体log信息
$ git branch -d <name> # 删除分支
$ git tag <tag_name> # 为当前的commit创建新的标签
$ git tag -d <tag_name> # 删除标签

实践练习

在new_dir版本库中,创建一个新的分支叫dev,然后切换至dev分支,对file.txt进行修改,并commit。再切换回master,对file.txt也进行修改,并commit,最后再把dev分支合并到master,将file.txt中的冲突解决清楚后,创建新的commit,完成合并。