分布式版本控制系统 Git | 七

分布式版本控制系统 Git | 七


Git 分支


分支简介

提及 Git 分支的内容,我们要先理解 Git 保存的并不是文件的变化或者差异,而是一系列的不同时刻的文件快照。

Git 在进行提交操作的时候,会保存一个提交对象(commit object)。这个提交对象包含了一个指向暂存内容快照的指针,还包括提交者的姓名,邮箱,提交的信息以及指向它的父对象的指针。当然,首次提交产生的提交对象没有父对象。其他普通提交操作产生的对象都有一个父对象,当多个分支合并产生的提交对象则有多个父对象。

分支创建与合并

分布式版本控制系统 Git | 三

上面这篇文章中,我们提到每次提交,Git 会将它们串成一条时间线,这时间线就是一个分支。未新建分支之前,这个就是主分支,即是 master 分支。

Git 如何创建新分支?使用 git branch <branch_name>,这里它会创建一个可以移动的新指针。比如创建一个 dev 分支:

$ git branch dev

这时,便会在提交对象上创建一个指针,如下图:

图 1

现在创建了一个分支,但是 Git 如何分辨两个分支,如何知道目前在哪个分支?这里要提及一个特殊指针 HEAD

HEAD 严格来说不是指向提交,默认情况下,而是指向 master,而 master 或者新创建的 dev 才是指向提交,HEAD 指向的就是当前分支。刚才 git branch dev 仅仅是创建了一个新分支,并不会自动切换到新分支上去。

图 2

HEAD 指向的是当前所在的分支,可以使用命令 git log 查看各个分支当前所指的对象。配合参数 --decorate

$ git log --oneline --decorate
861b17e (HEAD -> master, dev) Initial commit of project

此时,masterdev 分支均指向校验和为 861b17e 开头的提交对象。

切换分支

切换已经存在的分支,可以使用 git checkout <branch> 或者 git switch <branch>,这里使用的是 git switch 更容易理解:

$ git switch dev
Switched to branch 'dev'

这样 HEAD 就指向了 dev 分支。

图 3

上面是将创建跟切换操作分开。Git 还提供一个命令能够创建的同时切换到分支:

$ git switch -c iss007

或者

$ git checkout -b iss007

使用带 -c 参数的 git switch 命令,或者使用带 -b 参数的 checkout 命令。

其实上面的命令可以分解为:

$ git branch iss007
$ git switch iss007

$ git branch iss007
$ git checkout iss007

假设现在在 iss007 分支上工作,并做了提交。在这个过程中,iss007 分支会不断向前推进。

$ vim ISSUE
$ git commit -a -m "Fix issue 007 [issue 007]"

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NYvfSXtJ-1581255095931)(https://i.loli.net/2020/02/09/5OYUiPpcM4jEbXf.png)]

假设,这个时候有另外一个紧急的问题需要解决。使用 Git 这个时候不需要将这个问题跟 issue 007 混合在一起。也不用去还原关于 iss007 的修改,再添加关于这个紧急问题的修改,只需要需要回到 master 分支,创建新分支解决紧急问题。

这里需要注意,切换到 master 分支之前,需要留意工作目录和暂存区有没有未提交的文件,它会检测出分支冲突阻止切换分支(待会介绍这种情况)。这里假设工作目录是干净的:

$ git switch master
Switched to branch 'master'

$ git switch -c hotfix
Switched to a new branch 'hotfix'
$ vi ISSUE
$ git commit -a -m "fixed the hot issue"

图 5

现在紧急问题已经修复,这个时候就可以合并回 master 分支部署到线上,如下:

$ git switch master
$ git merge hotfix
Updating 861b17e..48a6cfa
Fast-forward
 ISSUE | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 ISSUE

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hpR80hXQ-1581255095932)(https://i.loli.net/2020/02/09/YusZcQIDdCVJ8wl.png)]

注意 Fast-forward,这个表示的是“快速模式”,也就是直接将 master 指向 hotfix 的当前提交,合并速度快。

注意:,并不是所有的合并都能 Fast-forward

同时,我们可以删除 hotfix 分支,因为已经完成任务了:

$ git branch -d hotfix
Deleted branch hotfix (was 48a6cfa).

冲突解决

现在紧急的问题已经修复部署上线,可以回到 iss007 分支继续修改文件。假设 iss007 分支也经过修改解决了问题。

图 7

这个时候也考虑合并会 master 部署到线上:

$ git switch master
$ git merge iss007
CONFLICT (add/add): Merge conflict in ISSUE
Auto-merging ISSUE
Automatic merge failed; fix conflicts and then commit the result.

这个时候会出现合并冲突,因为 iss007 分支与 hotfix 分支修改的是同一个文件。Git 不会自动创建新的合并提交。需要手动解决冲突之后再提交。

使用 git status 查看处于未合并状态的文件:

$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

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

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

现在查看冲突的文件:

<<<<<<< HEAD
Fix hot issue.
=======
Fix issue 007.
Fix issue continue and done.
>>>>>>> iss007

Git 用 <<<<<<<=======>>>>>>> 标记出不同分支的内容,修改内容如下:

Fix hot issue.
Fix issue 007.
Fix issue continue and done.

将标记的部分删除,根据需求解决冲突,然后用 git add 将其标记为冲突已解决。

$ git add ISSUE

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

Changes to be committed:
        modified:   ISSUE

这个时候就可以提交部署了:

git commit -a -m "Merge branch 'iss007'"
[master 7059dd0] Merge branch 'iss007'

分支管理策略

git branch 除了创建和删除分支。不加参数时,命令能够得到所有分支的列表:

$ git branch
  dev
  iss007
* master

* 号表示当前的分支(即使 HEAD 指针指向的分支)。

加上 -v 参数也可以查看每个分支最后的提交信息:

$ git branch -v
  dev    861b17e Initial commit of project
  iss007 5267039 Fix issue 007 again
* master 7059dd0 Merge branch 'iss007'

git branch 命令还有两个选项 --merged--no-merged。这两个选项分别过滤合并或尚未合并到当前的分支。

例如:查看已经合并到当前的分支:

$ git branch --merged
  iss007
* master

因为已经合并过 ·iss007`,所以这个时候也可以删除该分支,这里并不会失去任何东西:

$ git branch -d iss007
Deleted branch iss007 (was 5267039).

查看所有包含未合并工作的分支,可以使用 git branch --no-merged:

$ git branch --no-merged
  dev

这里显示了 dev 分支,它包含了还未合并的工作,现在尝试使用删除命令会运行失败:

$ git branch -d dev
error: The branch 'dev' is not fully merged.
If you are sure you want to delete it, run 'git branch -D dev'.

如果真的想要删除这些未合并的工作,可以根据下面的提示,使用 -D 选项强制删除。

抓取分支

多人协作的情况下,大家都会往 masterdev 分支推送各自的修改。

但是当协作者修改的是同个文件,且先一部提交到远程仓库时,这个时候,当你修改完问题提交的时候,就会提示冲突。

解决办法:用 git pull 抓取文件,在本地合并解决冲突再推送即可。

这里参考上面冲突解决的步骤就可以。

变基(Rebase)

每次合并再 push 后,分支会变得混乱。

Git 有一种成为 rebase 的操作,能够让 Git 的提交历史变成直线。

这里我用之前学习的例子来加以说明。

和远程分支同步后,对文件做了两次提交,用 git log 查看:

* 24be579 (HEAD -> master) add author
* 94448fe add comment
* 6a7291a store Git Learn
* e36f4e9 (origin/master) add content about co-operative and update catalog
* 6cd4087 add knowledge about feature branch

注意到 Git 用 (Head -> master)(origin/master) 标识当前分支的 HEAD 和远程 origin 的位置分别是 24be579 add authore36f4e9 add content..,本地比远程分支快 3 个提交

现在尝试推送本地分支:

$ git push origin master
To github.com:username/git_learn.git
 ! [rejected]        master -> master (fetch first)
error: failed to push some refs to '[email protected]:username/git_learn.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

失败了,说明有人先往远程库推送了分支。先 pull 一下

$ git pull
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 3 (delta 1), reused 3 (delta 1), pack-reused 0
Unpacking objects: 100% (3/3), done.
From github.com:damengsanqianqiu/git_learn
   e36f4e9..538bd7c  master     -> origin/master
CONFLICT (add/add): Merge conflict in hello.py
Auto-merging hello.py
Automatic merge failed; fix conflicts and then commit the result.

这里自动合并失败,手动解决冲突(详情略)。

解决冲突,再提交。再用 git status 查看状态

$ git status
On branch master
Your branch is ahead of 'origin/master' by 4 commits.
  (use "git push" to publish your local commits)

加上刚才合并的提交,现在我们分支比远程分支超前 4 个提交。

git log 看看

$ git log  --graph --pretty=oneline --abbrev-commit
*   2027993 (HEAD -> master) fix conflict of hello.py
|\  
| * 538bd7c (origin/master) set exit = 1
* | 24be579 add author
* | 94448fe add comment
* | 6a7291a store Git Learn
|/  
* e36f4e9 add content about co-operative and update catalog

现在分支比较乱,这个时候,rebase 就派上用场了,用 git rebase 试试:

$ git rebase
First, rewinding head to replay your work on top of it...
Applying: add comment
Using index info to reconstruct a base tree...
M hello.py
Falling back to patching base and 3-way merge...
Auto-merging hello.py
Applying: add author
Using index info to reconstruct a base tree...
M hello.py
Falling back to patching base and 3-way merge...
Auto-merging hello.py

再用 git log 看看,

$ git log --graph --pretty=oneline --abbrev-commit
* 2027993 (HEAD -> master) fix conflict of hello.py
* 24be579 add author
* 94448fe add comment
* 6a7291a store Git Learn
* 538bd7c (origin/master) set exit = 1

原来分叉的提交现在变成一条直线。我们注意到 Git 把我们本地的提交“挪动”了位置,放到了 538bd7c (origin/master) set exit = 1之后,这样整个提交历史就成了一条直线。rebase 操作前后,最终的提交内容是一直的,但是,本地的 commit 修改内容已经变化了,他们的修改不再基于 e36f4e9 add content...,而是基于 538bd7c (origin/master) set exit = 1,但最后的提交 2027993 内容是一致的。

这就是 rebase 操作的特点:把分叉的提交历史“整理”成一条直线,看上去更直观。缺点就是本地的分叉提交已经被修改过了。

关于变基与合并,这里会有不同的观点,有的觉得提交历史就是记录发生过什么,使用变基会将痕迹抹除,另外的观点会觉得提交历史就是项目过程发生的事情,没人在意第一版的手稿,大部分手册也是多次修订后才能方便使用。

这里并没有必要追究一个完整简单的答案。需要根据实际情况去判断作出选择。

不过,总体的原则是,尚未推送或分享的本地修改,可以根据情况执行变基清理历史,已推送到别处的提交,不要执行变基操作。


以上就是本篇的主要内容。


目前,关于 Git 的介绍就已经算是更新完成。但是,这些内容还远远没有将 Git 讲透彻。但是这些内容能够让你简单了解 Git,也希望这些内容能够让你上手学习,慢慢了解。

这里若是希望更深入了解 Git 的使用可以查看官方文档:

https://git-scm.com/book/en/v2

发布了82 篇原创文章 · 获赞 43 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_45642918/article/details/104241133