Git 学习笔记02丨Git 分支
几乎所有的版本控制系统都以某种形式支持分支。 使用分支意味着你可以把你的工作从开发主线上分离开来,以免影响开发主线。而 Git 的分支模型被称为它的“必杀技特性”,使得Git 从众多版本控制系统中脱颖而出。
Git 的分支模型的学习,我是通过Learn Git Branching学习的,感谢他们的动画以及练习,本篇笔记正是总结自这个网站的。
Git 本地分支命令
1. 基础篇
-
Git Commit
Git 仓库中的提交记录可看作是项目的快照,但非常轻量(将当前版本与仓库上一版本进行对比,将所有差异打包一起作为一个提交记录),保存了提交的历史记录。
$ git commit
-
Git Branch
创建一个名称为
newImage
的分支:$ git branch newImage
早建分支,多用分支——即使创建再多的分支也不会造成储存或内存上的开销,并且按逻辑分解工作到不同的分支要比维护那些特别臃肿的分支简单多了。
切换到
newImage
的分支:$ git checkout newImage # 创建新分支的同时切换到该新创建的分支: $ git checkout -b newImage
-
合并分支#1——Git Merge
在 Git 中合并两个分支时会产生一个特殊的提交记录,有两个父节点。(也就说,合并会将这两个父节点本身及它们所有的祖先都包含进来)。
假定此时所在分支为
master
,现要将bugFix
合并到master
:$ git merge bugFix
注意到上图的
master
包含了对代码库的所有修改。 -
合并分支#2——Git Rebase
Rebase 实际上就是取出一系列的提交记录,“复制”它们,然后再在另外一个地方逐个放下去,从而创造个更加线性的提交历史。
假定此时所在分支为
bugFix
,现要将该分支里的工作直接移到master
分支上:$ git rebase master # 由下图得到一个更线性的提交序列 $ git rebase bugFix # master分支的引用向前移动一下(图中未显示)
2. 高级篇
-
分离 HEAD,在提交树上移动
HEAD
,是一个对当前检出记录的符号引用——即指向你正在其基础上进行工作的提交记录。它总是指向分支名,且是当前分支上最近一次提交记录。若要查看
HEAD
的指向:$ cat .git/HEAD # 若HEAD指向的是一个引用,则可用下面指令查看: $ git symbolic-ref HEAD
分离
HEAD
即是让其指向某个具体的提交记录,而非分支名:# 如下图,本来HEAD->master,而master->C1 $ git checkout C1 # 现在,HEAD->C1
注意图中的
C1
,在实际程序中是一长串哈希值的简化,你不得不用git log
去 查看提交记录的哈希值。在 Git 操作中只需要提供唯一标识提交记录的前几个字符即可。⭐ 强制修改分支位置:
$ git branch -f master HEAD # 将master分支,强制地指向HEAD指向的提交
-
相对引用
相对引用提供一个简洁的 引用 提交记录的方式,下面是两个有关相对引用的操作符:
-
使用
^
向上移动 \(1\) 个提交记录,如master^
,相当于“master
的父节点”;master^^
,相当于“master^^
是master
的第二个父节点”。使用举例:
$ git checkout C3 $ git checkout HEAD^ $ git checkout HEAD^ $ git checkout HEAD^
另外,使用
^
再跟上一个数字,并不是指定向上返回几代,而是指定合并提交记录的某个父提交(某个分支若有多个父提交),如下举例:
$ git checkout master^ # 默认指向(指向C1)
$ git checkout master^2 # 指向另一个父亲C2
-
使用
~<num>
向上移动多个提交记录,其数字代表移动后退几步$ git checkout HEAD~4
-
-
撤销变更
有两种方法来撤销变更:
-
git reset
(用于本地分支):将分支记录回退几个提交记录来实现撤销改动。(也就是向上移动分支,原来指向的提交记录就跟从来没有提交过一样。),如下举例:$ git reset HEAD~1 # Git将master分支移到上一个提交记录C1,本地代码库根本不知道有过C2提交(处于未加入暂存区状态)
-
git revert
(用于远程分支):撤销更改并分享给别人,如下举例:$ git revert HEAD
-
3. 移动提交记录
-
整理提交记录——Git Cherry-pick
将指定的提交号复制到当前所在的位置(
HEAD
)下面:$ git cherry-pick <提交号> ...
该方法需要你指定所需要的提交记录(并且知道这些提交记录的哈希值),如下举例:
$ git cherry-pick C2 C4 # Git将C2、C4抓过来放到当前分支下
-
交互式 rebase
倘若不清楚所需的提交记录的哈希值(从一系列的提交记录中找到想要的记录),可以用下面这种方法。
交互式 rebase 指的是使用带参数
--interactive
的 rebase 命令, 简写为-i
。增加了这个选项,Git 会打开一个 UI 界面(一般是文本编辑器,下图是用对话框来模拟)并列出将要被复制到目录分支的备选提交记录(还会显示每个提交记录的哈希值和提交说明)。
当 rebase UI界面打开时, 你能做3件事:
- 调整提交记录的顺序(通过鼠标拖放来完成)
- 删除你不想要的提交(通过切换
pick
的状态来完成) - 合并提交。 遗憾的是由于某种逻辑的原因,我们的课程不支持此功能,因此我不会详细介绍这个操作。简而言之,它允许你把多个提交记录合并成一个。
4. 杂项
-
提交的技巧#1——使用 rebase
你之前在
newImage
分支上进行了一次提交,然后又基于它创建了caption
分支,然后又提交了一次。但此时你想对的某个以前的提交记录进行一些小小的调整。比如设计师想修改一下newImage
中图片的分辨率,尽管那个提交记录并不是最新的了。可以用以下的方法:
- 先用
git rebase -i
将提交重新排序,然后把我们想要修改的提交记录挪到最前 - 然后用
commit --amend
来进行一些小修改 - 接着再用
git rebase -i
来将他们调回原来的顺序 - 最后我们把
master
移到修改的最前端,就搞定了。
- 先用
-
提交的技巧#2——使用 cherry-pick
与1同样的问题,但是可以更加快捷:
- 先用
git cherry-pick
将要修改的提交记录揪出来 - 然后用
commit --amend
来进行一些小修改 - 再用
git cherry-pick
把原记录所在的下面记录都揪到新修改的记录后面。
- 先用
-
Git Tag
由于分支很容易被人为移动,当有新的提交时,它也会跟着移动。大部分分支只是临时的且还一直在变。如果有软件发布新的大版本,或者是修正一些重要的 Bug 或是增加某些新特性,比分支更好的且能够永远指向这些提交的方法,就是Git的
tag
了。tag
并不会随着新的提交而移动,也不能检出到某个标签上面进行修改提交,仿佛提交树上的一个“锚点”,标识了某个特定位置。如下,命名个标签v1
,明确让其指向提交记录C1
。$ git tag v1 C1
-
Git Describe
Git 专门设计了一个命令用来描述离你最近的标签——
git describe
,它能够帮你在提交历史中移动了多次以后找到方向。其语法为:$ git describe <ref> # <ref> 是任何能被 Git 识别成提交记录的引用,若没有指定,Git会以你目前检出的位置(HEAD) 输出结果为: <tag>_<numCommits>_g<hash> <tag>表示离<ref>最近的标签,<numCommits>表示这个<ref>与<tag>相差多少个提交记录,<hash>表示的是你所给定的<ref>所表示的提交记录哈希值的前几位。
Git 远程仓库
1. Git Clone
Git Clone
命令的作用,是在本地创建一个远程仓库的拷贝(比如从 github.com
)。
$ git clone [url]
#下载一个项目和它的整个代码历史
2. 远程分支
克隆后,会发现本地仓库多了一个 origin/master
分支,该类型的分支即是远程分支,它反映了远程仓库在你最后一次与它通信时的状态(能够理解本地工作与公共工作的差别),此外,你还不能直接在这些分支上进行操作(需要完成自己的工作后,用远程分享自己的工作成功)。
远程跟踪——当你克隆时, Git 会为远程仓库中的每个分支在本地仓库中创建一个远程分支(比如
origin/master
)。然后再创建一个跟踪远程仓库中活动分支的本地分支,默认情况下这个本地分支会被命名为master
。因此,在克隆时,会出现下面输出:local branch "master" set to track remote branch "o/master"
当你对它进行checkout
时,会自动与你的HEAD
分离,如下:
$ git checkout origin/master
$ git commit
大部分人会将主要的远程仓库命名为
origin
,当使用git clone
某个仓库时,Git会帮你将远程仓库的名称设置为origin
了。
3. 从远程仓库获取数据——Git Fetch
git fetch
命令(使用 http://
或 git://
协议,与远程仓库通信)能够完成仅有的但很重要的两步:
- 从远程仓库下载所有的仓库中缺失的提交记录
- 更新远程分支指针(如
origin/master
)
注意,该命令不会改变你本地仓库的状态(即,不会更新master
分支),也不会修改你磁盘上的文件。也就说,该命令执行后,所需的所有数据都单纯地下载了,但并没有修改你本地的文件。
$ git fetch
$ git fecth origin <>
如下举例,我们只下载远程仓库的foo
分支中的最新提交记录,并更新了origin/foo
:
git fetch origin foo
4. ⭐先抓取更新再合并到本地——Git Pull
当远程分支中有新的提交时,我们如何将这些变化更新到我们的工作当中?肯定是先抓取更新再合并到本地分支,而Git Pull
正是合并了这两个操作:git fetch
并git merge origin/master
$ git pull
$ git commit
# 注意,C4即是提交的新记录
$ git pull
$ git pull origin master:foo
# 相当于
# git fetch origin master:foo 本地先创建一个foo分支,再从远程master分支下载提交记录,在合并到foo
# git merge foo 再merge到我们当前检出的分支bar
【附】:合并特性分支:
前面的git pull
就是fetch
和merge
的简写,而git pull --rebase
就是fetch
和 rebase
的简写,它应用在哪些场景?
假设你周一克隆了一个仓库,然后开始研发某个新功能。到周五时,你新功能开发测试完毕,可以发布了。但是你的同事这周写了一堆代码,还改了许多你的功能中使用的 API,这些变动会导致你新开发的功能变得不可用。但是他们已经将那些提交推送到远程仓库了,因此你的工作就变成了基于项目旧版的代码,与远程仓库最新的代码不匹配了。
如下图,我们本地提交新记录C3
,然后执行git pull --rebase
,首先更新了本地仓库的远程分支,然后合并了新变更到我们的本地分支(包含了远程仓库的变更C2
),然后执行git push
再将合并的结果C3'
推送到远程仓库中。
5. ⭐ 发布你的成果——Git Push
git push
负责将你的变更上传到指定的远程仓库,并在远程仓库上合并你的新提交记录。一旦 git push
完成, 你的朋友们就可以从这个远程仓库下载你分享的成果了。
$ git push
我们还可以为push
指定参数,语法为:
$ git push <remote> <place>
我们通过place
参数来告诉 Git 提交记录来自于 master
, 要推送到远程仓库origin
中的 master
,将远程仓库中没有的提交记录都添加上去。它实际就是要同步的两个仓库的位置,此时,你不需要检出place
(比如你现在检出C3
),就可以直接使用该含参命令了。甚至,我们还可以如下,指定独立的来源与独立的目的地。
$ git push origin <source>:<destination>
# 如果目的分支不存在,则会帮你在远程仓库中根据该名称进行创建
举例如下:
$ git push origin foo^:master
6. 本地任意分支跟踪远程分支
设置远程追踪分支的方法是使用git branch -u
命令,比如,我希望用foo
分支去跟踪远程分支,则需要:
$ git branch -u origin/master foo
# foo 就会跟踪 origin/master了(若不使用该命令,默认情况下是master跟踪origin/master)
# 接下来commit与push都是基于foo分支了
$ git commit
$ git push