Git 学习之分支篇——重头戏

分支——你可以把你的工作从开发主线上分离开来,以免影响开发主线。但在很多版本控制系统中,常常需要完全创建一个源代码目录的副本,这样就显得略微低效,尤其对于大的项目,会耗费很多时间。
分支——Git 的‘必杀型特技’,正是这一特性使 Git 从众多版本控制系统中脱颖而出。Git 处理分支的方式难以置信的轻量,创建新分支几乎能在瞬间完成,且在不同分支间的切换也很便捷。

3.1Git 分支简介

Git起步所说,Git 保存的不是文件的变化或者差异,而是一系列不同时刻的文件快照。

假设现在我的工作目录里有三个将要被暂存的文件: README test.rb LICENSE。暂存操作会为每一个文件计算校验和,然后把当前版本的文件快照保存到 Git 仓库中(Git 使用 blob 对象来保存它们),最终将校验和加入到暂存区等待提交:

$ git add README test.rb LICENSE
$ git commit -m 'The initial commit of my project'

如图,进行git commit提交操作后,Git 仓库中有五个对象:

三个 blob 对象——保存文件快照;
一个树对象——记录着目录结构和 blob 对象索引;
一个提交对象——包含着指向前述树对象的指针和所有提交信息;

做些修改该后再提交,这次产生的提交对象会包含一个指向2上次提交对象(父对象)的指针,首次提交无父对象。


提交对象及其父对象

分支及其提交历史 C为最后一次提交

Git 分支:其实本质上仅仅是指向提交对象的可变指针。Git 的默认分支名字是 ‘master’。每次提交,分支会自动向前移动,指向最后那个提交对象。
注:Git 的 ‘master’ 分支并不是一个特殊分支,与其它分支没有区别,只是 git init默认创建它。

3.1.1 分支创建——`git branch [name]

执行 git branch testing,会在当前所在的提交对象上创建一个指针:

创建新分支 testing

特殊指针:HEAD
Git 有一个名为 HEAD 的特殊指针,它指向当前所在的本地分支(可将 HEAD 想象为当前分支的别名)。
HEAD 指向当前坐在分支 master

命令行查看各个分支所指对象,参数为--decorate

$ git log --oneline --decorate
f30ab (HEAD, master, testing) add feature #32 - ability to add new
34ac2 fixed bug #1328 - stack overflow under certain conditions
98ca9 initial commit of my project

如图,‘master’ 和 ‘testing’ 分支均指向校验和以 f30ab 开头的提交对象。

3.1.2 分支切换——git checkout

执行 git checkout testing

HEAD 指向当前所在的分支 testing

此时再次修改文件并提交:

$ vim test.rb
$ git commit -a -m 'made a change'
HEAD 分支随着提交操作自动向前移动

如图:HEAD 分支指向的当前 ‘testing’ 分支随着提交操作自动向前移动,但 ‘master’ 分支没有,它仍然指向运行 git checkout 时所指的对象。现在切回 ‘master’ 分支查看 git checkout master


这条命令做了两件事:

  • 使 HEAD 指回 ‘master’ 分支;
  • 将工作目录恢复成 ‘master’ 分支所指向的快照内容。
    即忽略了在 ‘testing’ 分支所做的修改,以便于向另一个方向进行开发。若 Git 不能干净利落地完成这个任务,它将禁止切换分支。
    若此时再做修改并提交:
$ vim test.rb
$ git commit -a -m 'made other changes'

现在这个项目的提交历史就产生了分叉。


项目产生分叉

命令行查看:

$ git log --oneline --decorate --graph --all
* c2b9e (HEAD, master) made other changes
| * 87ab2 (testing) made a change
|/
* f30ab add feature #32 - ability to add new formats to the
* 34ac2 fixed bug #1328 - stack overflow under certain conditions
* 98ca9 initial commit of my project

由于 Git 的分支实质上仅是包含所指对象校验和的文件,所以它的创建和销毁都异常搞笑。这也是 Git 与其它版本控制系统最鲜明的对比,同时,由于每次提交都会记录父对象,所以寻找恰当的合并基础也是同样的简单和高效。

3.1.3 分支合并——git merge

若我在上述切换到 ‘testing’ 分支时,修改了 README 内容为: testing,切换到回 ‘master’ 分之后,修改了 README 内容为:master,此时分支为 ‘master’,若要合并两个分支,执行:git merge testing


如上,由于两个文件内内容不同,执行命令后,会提示解决 README 文件内的冲突,然后再提交。
冲突文件样式

3.1.4 分支删除——`git branch -d [name]

如上,合并了 ‘testing’ 和 ‘master’ 分支内容后,若你不再需要 ‘testing’ 分支,你可在任务追踪系统中关闭此项任务,并删除这个分支。
git branch -d testing

3.3 分支管理

  • git branch: 不加任何参数运行时,会得到当前分支的一个列表
$ git branch
  iss53
* master
  testing

*字符后面的分支即为当前所在分支。

  • git branch -v:查看每一个分支的最后一次提交
$ git branch -v
  iss53 93b412c fix javascript issue
* master 7a98805 Merge branch 'iss53'
  testing 782fd34 add scott to the author list in the readmes
  • git branch --no-merged:查看还未合并的分支

  • git branch -d [name]:删除分支,当删除的分支还未合并时,会删除失败,如果确定要丢失,可使用 -D 选项强制删除。

3.4 远程分支

远程引用是对远程仓库的引用,包括分支、标签等。
git ls-remote——显式获得远程引用的完整列表。
git remote show——获得远程分支信息。

更常用的做法:远程跟踪分支
远程跟踪分支是远程分支状态的引用,像是你上次连接到远程仓库时,那些分支所处状态的书签。
它们以(remote)/(branch)形式命名。例如,当你从远程服务器克隆一个仓库时,Git 会自动将其命名为 ‘origin’,拉取它的所有数据,创建一个指向它的 ‘master’ 分支的指针,并且在本地将其命名为 ‘origin/master’,Git 也会给你一个与 ‘origin’ 的 ‘master’ 分支指向同一个地方的本地 ‘master’ 分支,这样你就有工作的基础。

git fetch origin查找 ‘orign’ 是哪一个服务器,从中抓取本地没有的数据,并且更新本地数据库,移动 ‘origin/master’ 指针指向新的、更新后的位置。

推送——git push (remote) (branch)
当你想要公开分享一个分支时,需要将其推送到有写入权限的远程仓库上。
例如,要将内容推送到 ‘master’ 分支上,执行 git push origin master

如何避免每次输入密码:
如果你正在使用 HTTPS URL 来推送,Git 服务器会询问用户名与密码。 默认情况下它会在终端中提示服务器是否允许你进行推送。Git 拥有一个凭证系统来处理这个事情:执行git config --global credential.helper store,这种模式会将凭证用明文的形式存放在磁盘中,并且永不过期。这意味着除非你修改了你在 Git 服务器上的密码,否则你永远不需要再次输入你的凭证信息。这种方式的缺点是你的密码是用明文的方式存放在你的 home 目录下。

跟踪分支

    1. 从远程拉取本地分支:
      从一个远程跟踪分支检出一个本地分支会自动创建一个叫做 “跟踪分支”(有时候也叫做 “上游分支”)。跟踪分支是与远程分支有直接关系的本地分支。如果在一个跟踪分支上输入 git pull,Git 能自动地识别去哪个服务器上抓取、合并到哪个分支。
      当克隆一个仓库时,它通常会自动地创建一个跟踪 origin/master 的 master 分支。 然而,如果你愿意的话可以设置其他的跟踪分支 - 其他远程仓库上的跟踪分支,或者不跟踪 master 分支。 运行git checkout -b [branch] [remotename]/[branch]。 这是一个十分常用的操作所以 Git 提供了 --track 快捷方式:git checkout [branch] [remotename]/[branch],你也可以将本地分支命名为其它名字。
    1. 本地已有分支跟踪:
      git branch -u [remotename]/[branch]git branch --set-upstream-to [remotename]/[branch]
      查看设置的所有跟踪分支:执行git branch -vv
      这回将所有的本地分支列出来并且包含更多的信息,如每一个分支正在跟踪哪个分支与本地分支是否是领先、落后或是都有。
      如果想要统计最新的领先与落后数字,需要在运行此命令前抓取所有的远程仓库。 可以像这样做:
git fetch --all
git branch -vv

拉取
git fetch: 令从服务器上抓取本地没有的数据,它并不会修改工作目录中的内容。 只会获取数据然后让你自己合并。
git pull:大多数情况下,它是一个git fetch紧接着一个git mergegit pull会查找当前分支所跟踪的服务器与分支,从服务器上抓取数据然后尝试合并入哪个远程分支。

删除远程分支
例如想删除 ‘testing’ 分支:
git push origin --delete testing
这个命令做的只是从服务器上移除这个指针,Git 服务器通常会保留数据一段时间直到垃圾回收运行,所以如果不小心删除掉了,通常是很容易恢复的。

3.5 变基

Git 中整合来自不同分支的修改主要有两种方法:merge以及rebase

3.5.1 变基的基本操作
  • 1.通过合并操作整合分叉的历史:



整合分支最容易的方法是 merge 命令。它会把两个分支的最新快照(C3 和 C4)以及二者最近的共同祖先(C2)进行三方合并,合并的结果是生成一个新的快照(并提交)。

    1. 通过变基操作来整合分叉的历史:
      过程:提取在 C4 中引入的补丁和修改,然后在 C3 的基础上应用一次。
      即使用 rebase 命令将提交到某一分支上的所有修改都移至另一分支上,就好像“重新播放”一样。
$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command

它的原理是首先找到这两个分支(即当前分支 experiment、变基操作的目标基底分支 master)的最近共同祖先 C2,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件,然后将当前分支指向目标基底 C3, 最后以此将之前另存为临时文件的修改依序应用。


将 C4 中的修改变基到 C3 上

现在回到 master 分支,进行一次快进合并。

$ git checkout master
$ git merge experiment

此时,C4' 指向的快照就和上面使用 merge 命令的例子中 C5 指向的快照一模一样了。 这两种整合方法的最终结果没有任何区别,但是变基使得提交历史更加整洁。 你在查看一个经过变基的分支的历史记录时会发现,尽管实际的开发工作是并行的,但它们看上去就像是串行的一样,提交历史是一条直线没有分叉。
一般我们这样做的目的是为了确保在向远程分支推送时能保持提交历史的整洁——例如向某个其他人维护的项目贡献代码时。 在这种情况下,你首先在自己的分支里进行开发,当开发完成时你需要先将你的代码变基到origin/master 上,然后再向主项目提交修改。 这样的话,该项目的维护者就不再需要进行整合工作,只需要快进合并便可。
请注意,无论是通过变基,还是通过三方合并,整合的最终结果所指向的快照始终是一样的,只不过提交历史不同罢了。 变基是将一系列提交按照原有次序依次应用到另一分支上,而合并是把最终结果合在一起。

变基的风险:不要对在你的仓库外有副本的分支执行变基
变基操作的实质是丢弃一些现有的提交,然后相应地新建一些内容一样但实际上不同的提交。
如果你已经将提交推送至某个仓库,而其他人也已经从该仓库拉取提交并进行了后续工作,此时,如果你用 git rebase 命令重新整理了提交并再次推送,你的同伴因此将不得不再次将他们手头的工作与你的提交进行整合,如果接下来你还要拉取并整合他们修改过的提交,事情就会变得一团糟。

变基的原则:只对尚未推送或分享给别人的本地修改执行变基操作清理历史,从不对已推送至别处的提交执行变基操作,这样,你才能享受到两种方式带来的便利。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • Git 基础 基本原理 客户端并不是只提取最新版本的文件快照,而是把代码仓库完整的镜像下来。这样一来,任何一处协同...
    __silhouette阅读 15,855评论 5 147
  • 一、电脑本地初始化一个仓库 1. git init: 初始化一个电脑上本地仓库 终端进入项目目录,输入: 该命令将...
    dragon_li阅读 2,876评论 1 4
  • 四、 分支开发工作流 现在你已经学会新建和合并分支,那么你可以或者应该用它来做些什么呢? 在本节,我们会介绍一些常...
    常大鹏阅读 2,082评论 3 24
  • 毛姆在《面纱》里说: 我从来都无法得知,人们究竟为什么会爱上另一个人,我猜也许我们的心上都有一个缺口,它是个空洞,...
    糖馒头阅读 108评论 0 0
  • 同样是植物我们把太多的赞美给予了花,忘却了那一颗颗挺拔魁梧的树。小树时没有花的娇嫩与可人,干直的一颗径直生长,青壮...
    井上燕二阅读 158评论 0 1