git分支管理
分支简介
Git 保存的不是文件的变化或者差异,而是一系列不同时刻的文件快照。在进行提交操作时,Git 会保存一个提交对象(commit object)。该提交对象包含如下的内容:
-
一个指向暂存内容快照的指针
-
作者的姓名和邮箱
-
提交时输入的信息以及指向它的父对象的指针
首次提交产生的提交对象没有父对象,普通提交操作产生的提交对象有一个父对象,而由多个分支合并产生的提交对象有多个父对象。
当使用 git commit
进行提交操作时,Git 会先计算每一个子目录的校验和(SHA-1 哈希算法),然后在 Git 仓库中将这些校验和保存为树对象。 随后,Git 便会创建一个提交对象,它除了包含上面提到的那些信息外,还包含指向这个树对象(项目根目录)的指针。如此一来,Git 就可以在需要的时候重现此次保存的快照。
举例:
假设现在有一个工作目录,里面包含了三个将要被暂存和提交的文件。 暂存操作会为每一个文件计算校验和(SHA-1 哈希算法),然后会把当前版本的文件快照保存到 Git 仓库中(Git 使用 blob 对象来保存它们),最终将校验和加入到暂存区域等待提交:
1 | $ git add README test.rb LICENSE |
现在,Git 仓库中有五个对象:
-
三个 blob 对象(保存着文件快照),每个文件对应一个blob对象
-
一个树对象(记录着目录结构和 blob 对象索引)
-
一个提交对象(包含着指向前述树对象的指针和所有提交信息)
做些修改后再次提交,那么这次产生的提交对象会包含一个指向上次提交对象(父对象)的指针。
注意:首次提交的产生的commit object中的
parent
为空
Git 的分支,其实本质上仅仅是指向提交对象(commit object)的可变指针。 Git 的默认分支名字是 master
。 在多次提交操作之后,你其实已经有一个指向最后那个提交对象的 master
分支。 它会在每次的提交操作中自动向前移动。
Git 的 “master” 分支并不是一个特殊分支。 它就跟其它分支完全没有区别。 之所以几乎每一个仓库都有 master 分支,是因为
git init
命令默认创建它,并且大多数人都懒得去改动它。
下图展示了分支及提交历史的关系:
分支创建
Git 是怎么创建新分支的呢? 很简单,它只是为你创建了一个可以移动的新的指针。 比如,创建一个 testing 分支, 你需要使用 git branch
命令:
1 | $ git branch testing |
这会在当前所在的提交对象上创建一个指针。
那么,Git 又是怎么知道当前在哪一个分支上呢?
也很简单,它有一个名为 HEAD
的特殊指针。 请注意它和许多其它版本控制系统(如 Subversion 或 CVS)里的 HEAD
概念完全不同。 在 Git 中,它是一个指针,指向当前所在的本地分支
(译注:将
HEAD
想象为当前分支的别名)。
在本例中,你仍然在 master
分支上。 因为 git branch
命令仅仅创建一个新分支,并不会自动切换到新分支中去。
你可以简单地使用 git log
命令查看各个分支当前所指的对象。 提供这一功能的参数是 --decorate
。
1 | $ git log --oneline --decorate |
正如你所见,当前 master
和 testing
分支均指向校验和以 f30ab
开头的提交对象。
1 | f30ab (HEAD -> master, testing) add feature |
注意:新版本的git中不用添加
--decorate
也可以显示HEAD
指针
分支切换
要切换到一个已存在的分支,你需要使用 git checkout
命令。 我们现在切换到新创建的 testing
分支去:
1 | $ git checkout testing |
这样 HEAD
就指向 testing
分支了。
此时通过git log
命令,可以发现HEAD
指针已经指向testing
分支
1 | $ git log --oneline |
那么,这样的实现方式会给我们带来什么好处呢? 现在不妨再提交一次:
1 | # 当前已经处于testing分支 |
此时通过git log
命令,发现testing
分支向前移动了,但是 master
分支却没有:
1 | $ git log --oneline |
现在我们切换回master
分支看看:
1 | $ git checkout master |
可见master
分支上的log中不不能看到testing
分支的相关提交,此时各个提交的关系图如下:
git checkout master
命令做了两件事情:
-
一是使
HEAD
指回master
分支 -
二是将工作目录恢复成
master
分支所指向的快照内容
也就是说,你现在做修改的话,项目将始于一个较旧的版本。 本质上来讲,这就是忽略 testing
分支所做的修改,以便于向另一个方向进行开发。
注意:
分支切换会改变你工作目录中的文件
在切换分支时,一定要注意你工作目录里的文件会被改变。 如果是切换到一个较旧的分支,你的工作目录会恢复到该分支最后一次提交时的样子。 如果 Git 不能干净利落地完成这个任务,它将禁止切换分支。
此时,我们不妨再稍微做些修改并提交:
1 | $ vim test.rb |
现在,这个项目的提交历史已经产生了分叉(参见 项目分叉历史)。 因为刚才你创建了一个新分支,并切换过去进行了一些工作,随后又切换回 master
分支进行了另外一些工作。 上述两次改动针对的是不同分支:你可以在不同分支间不断地来回切换和工作,并在时机成熟时将它们合并起来。 而所有这些工作,你需要的命令只有 branch
、checkout
和 commit
。
你可以简单地使用 git log
命令查看分叉历史。 运行 git log --oneline --decorate --graph --all
,它会输出你的提交历史、各个分支的指向以及项目的分支分叉情况:
1 | $ git log --oneline --decorate --graph --all |
此时的HEAD
指针指向master
分支。
由于 Git 的分支实质上仅是包含所指对象校验和(长度为 40 的 SHA-1 值字符串)的文件,所以它的创建和销毁都异常高效。 创建一个新分支就相当于往一个文件中写入 41 个字节(40 个字符和 1 个换行符),如此的简单能不快吗?
分支的新建与合并
场景描述
让我们来看一个简单的分支新建与分支合并的例子,实际工作中你可能会用到类似的工作流。 你将经历如下步骤:
-
开发某个网站。
-
为实现某个新的需求,创建一个分支。
-
在这个分支上开展工作。
正在此时,你突然接到一个电话说有个很严重的问题需要紧急修补。 你将按照如下方式来处理:
-
切换到你的线上分支(production branch)。
-
为这个紧急任务新建一个分支,并在其中修复它。
-
在测试通过之后,切换回线上分支,然后合并这个修补分支,最后将改动推送到线上分支。
-
切换回你最初工作的分支上,继续工作。
新建分支开发正常需求
首先,我们假设你正在你的项目上工作,并且已经有一些提交。
比如:
1 | $ git log --oneline |
现在,你已经决定要解决你的公司使用的问题追踪系统中的 #53 问题。 想要新建一个分支并同时切换到那个分支上,你可以运行一个带有 -b
参数的 git checkout
命令:
1 | $ git checkout -b 'iss53' |
这条命令相当于如下2条命令的缩写:
1 | $ git branch iss53 # 创建分支 |
此时 git log --oneline
的输出如下:
1 | $ git log --oneline |
你继续在 #53
问题上工作,并且做了一些提交。 在此过程中,iss53
分支在不断的向前推进,因为你已经检出到该分支(也就是说,你的 HEAD
指针指向了 iss53
分支)
1 | $ git commit -am "实现功能YY [issue 53]" |
此时git log
输出如下:
1 | $ git log --oneline |
创建hotfix分支处理紧急问题
现在你接到那个电话,有个紧急问题等待你来解决。 有了 Git 的帮助,你不必把这个紧急问题和 iss53
的修改混在一起,你也不需要花大力气来还原关于 #53
问题的修改,然后再添加关于这个紧急问题的修改,最后将这个修改提交到线上分支。 你所要做的仅仅是切换回 master
分支。
但是,在你这么做之前,要留意你的工作目录和暂存区里那些还没有被提交的修改,它可能会和你即将检出的分支产生冲突从而阻止 Git 切换到该分支。 最好的方法是,在你切换分支之前,保持好一个干净的状态。 有一些方法可以绕过这个问题(即,保存进度(stashing) 和 修补提交(commit amending)),我们会在 储藏与清理 中看到关于这两个命令的介绍。 现在,我们假设你已经把你的修改全部提交了,这时你可以切换回 master
分支了:
1 | $ git checkout master |
此时,你的工作目录和你在开始 #53
问题之前一模一样,现在你可以专心修复紧急问题了。
请牢记:当你切换分支的时候,Git 会重置你的工作目录,使其看起来像回到了你在那个分支上最后一次提交的样子。 Git 会自动添加、删除、修改文件以确保此时你的工作目录和这个分支最后一次提交时的样子一模一样。
接下来,你要修复这个紧急问题。 让我们建立一个针对该紧急问题的分支(hotfix
branch),在该分支上工作直到问题解决:
1 | $ vim A.java # 修复问题 |
此时查看git log
日志如下:
1 | $ git log --oneline --graph --all |
将hotfix分支合并回master分支
你可以运行你的测试,确保你的修改是正确的,然后将hotfix
分支上的改动合并回你的 master
分支来部署到线上。 你可以使用 git merge
命令来达到上述目的:
1 | $ git checkout master # 切换回master分支 |
在合并的时候,你应该注意到了"快进(fast-forward)"这个词。 由于当前 master
分支所指向的提交是你当前提交(有关 hotfix
的提交)的直接上游,所以 Git 只是简单的将指针向前移动。
换句话说,当你试图合并两个分支时,如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候,只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做 “快进(fast-forward)”。
fast-forward模式会直接移动指针,不会创建新的提交记录。
此时git log
的输出如下:
1 | $ git log --oneline --all --graph |
现在,最新的修改已经在 master
分支所指向的提交快照中,你可以着手发布该修复了。
删除hotfix分支,恢复原先正常工作
关于这个紧急问题的解决方案发布之后,你准备回到被打断之前时的工作中。 然而,你应该先删除 hotfix
分支,因为你已经不再需要它了 —— master
分支已经指向了同一个位置。 你可以使用带 -d
选项的 git branch
命令来删除分支:
1 | MINGW64 /d/coding/git-playground (master) |
现在你可以切换回你正在工作的分支继续你的工作,也就是针对 #53
问题的那个分支(iss53
分支):
1 | $ vim B.java |
此时的git log
输出如下:
1 | $ git log --oneline --all --graph |
你在 hotfix
分支上所做的工作并没有包含到 iss53
分支中。
-
如果你需要拉取
hotfix
所做的修改,你可以使用git merge master
命令将master
分支合并入iss53
分支 -
或者你也可以等到
iss53
分支完成其使命,再将其合并回master
分支
分支合并
假设你已经修正了 #53
问题,并且打算将你的工作合并入 master
分支。 为此,你需要合并 iss53
分支到 master
分支,这和之前你合并 hotfix
分支所做的工作差不多。 你只需要检出到你想合并入的分支,然后运行 git merge
命令:
1 | $ git checkout master |
此时会弹窗提示你输出一个提交信息:
输入完成后:
1 | $ git merge iss53 |
此时再次查看git log
1 | $ git log --oneline --all --graph |
这和你之前合并 hotfix
分支的时候看起来有一点不一样。 在这种情况下,你的开发历史从一个更早的地方开始分叉开来(diverged)。 因为,master
分支所在提交并不是 iss53
分支所在提交的直接祖先,Git 不得不做一些额外的工作。 出现这种情况的时候,Git 会使用两个分支的末端所指的快照(C4
和 C5
)以及这两个分支的工作祖先(C2
),做一个简单的三方合并。
合并前的分支图如下:
和之前将分支指针向前推进所不同的是,Git 将此次三方合并的结果做了一个新的快照并且自动创建一个新的提交指向它。 这个被称作一次合并提交,它的特别之处在于他有不止一个父提交。
合并后的分支图如下:
注意:
-
看每条线上的
*
即可
和之前将分支指针向前推进所不同的是,Git 将此次三方合并的结果做了一个新的快照(即:C6
)并且自动创建一个新的提交指向它。 这个被称作一次合并提交,它的特别之处在于他有不止一个父提交(C6
有C5
、C4
两个父提交)。
要指出的是,Git 会自行决定选取哪一个提交作为最优的共同祖先,并以此作为合并的基础;这和更加古老的 CVS 系统或者 Subversion (1.5 版本之前)不同,在这些古老的版本管理系统中,用户需要自己选择最佳的合并基础。 Git 的这个优势使其在合并操作上比其他系统要简单很多。
既然你的修改已经合并进来了,你已经不再需要 iss53
分支了。 现在你可以在任务追踪系统中关闭此项任务,并删除这个分支。
1 | $ git branch -d iss53 |
遇到冲突时的分支合并
有时候合并操作不会如此顺利。 如果你在两个不同的分支中,对同一个文件的同一个部分进行了不同的修改,Git 就没法干净的合并它们。 如果你对 hotfix
问题的修改和有关 master
的修改都涉及到同一个文件的同一处,在合并它们的时候就会产生合并冲突,比如我们的master
分支如下:
1 | /d/coding/git-playground (master) |
此时我们创建了一个hotfix
分支,并在该分支上对A.java
的第一行做了修改:
1 | $ cat A.java |
期间,master
分支针对A.java
的第一行做了也做了修改:
1 | MINGW64 /d/coding/git-playground (master) |
此时的git log
如下:
1 | $ git log --oneline --all --graph |
此时我们尝试将hotfix
分支的内容合并入master
分支:
1 | MINGW64 /d/coding/git-playground (master) |
此时 Git 做了合并,但是提示在A.java
中存在冲突,因此自动合并失败Automatic merge failed;
,此时我们需要根据提示:fix conflicts and then commit the result.
先解决A.java
中存在的冲突,你可以在合并冲突后的任意时刻使用 git status
命令来查看那些因包含合并冲突而处于未合并(unmerged)状态的文件:
1 | MINGW64 /d/coding/git-playground (master|MERGING) |
任何因包含合并冲突而有待解决的文件,都会以未合并状态标识出来:
1 | MINGW64 /d/coding/git-playground (master|MERGING) # 注意此处会提示merging |
此时的git log
信息如下:
1 | MINGW64 /d/coding/git-playground (master|MERGING) |
Git 会在有冲突的文件中加入标准的冲突解决标记,这样你可以打开这些包含冲突的文件然后手动解决冲突。 出现冲突的文件会包含一些特殊区段,比如此处的A.java
,看起来像下面这个样子:
1 | MINGW64 /d/coding/git-playground (master|MERGING) |
这表示 HEAD
所指示的版本(此处HEAD -> master
,也就是你的 master
分支所在的位置,因为你在运行 merge
命令的时候已经检出到了这个分支)在这个区段的上半部分(=======
的上半部分),而 hotfix
分支所指示的版本在 =======
的下半部分。 为了解决冲突,你必须选择使用由 =======
分割的两部分中的一个,或者你也可以自行合并这些内容。 例如,你可以通过把这段内容换成下面的样子来解决冲突:
1 | MINGW64 /d/coding/git-playground (master|MERGING) |
解决冲突时需删除
=======
和<<<<<<< HEAD
与>>>>>>> hotfix
等内容。
上述的冲突解决后, <<<<<<<
, =======
, 和 >>>>>>>
这些行被完全删除了。 在你解决了所有文件里的冲突之后,对每个文件使用 git add
命令来将其标记为冲突已解决。 一旦暂存这些原本有冲突的文件,Git 就会将它们标记为冲突已解决。
1 | MINGW64 /d/coding/git-playground (master|MERGING) |
如果你对结果感到满意,并且确定之前有冲突的的文件都已经暂存了,这时你可以输入 git commit
来完成合并提交。 默认情况下提交信息看起来像下面这个样子:
1 | MINGW64 /d/coding/git-playground (master|MERGING) # MERGING还在 |
此时可以通过git log
查看仓库的状态:
1 | $ git log --oneline --all --graph |
此时如果通过idea
中自带的git
可视化工具查看,展示如下:
可见:
分支管理
查看有哪些分支
现在已经创建、合并、删除了一些分支,让我们看看一些常用的分支管理工具。
git branch
命令不只是可以创建与删除分支。 如果不加任何参数运行它,会得到当前所有分支(即包含所有合并或者未合并到当前分支的分支)的一个列表:
1 | $ git branch |
注意 master
分支前的 *
字符:它代表现在检出的那一个分支(也就是说,当前 HEAD
指针所指向的分支):
1 | MINGW64 /d/coding/git-playground (master) |
这意味着如果在这时候提交,master
分支将会随着新的工作向前移动。 如果需要查看每一个分支的最后一次提交,可以运行 git branch -v
命令:
1 | $ git branch -v |
本例中hotfix已经merged到master上
查看哪些分支已经合并或者尚未合并到本分支
为了更好的演示本例,我们需要额外创建一个分支testing
,并在该分支上做一些提交:
1 | MINGW64 /d/coding/git-playground (master) |
此时我们切换回master
分支:
1 | $ git checkout master |
--merged
与 --no-merged
这两个有用的选项可以过滤这个列表中已经合并或尚未合并到当前分支的分支。
-
如果要查看哪些分支已经合并到当前分支,可以运行
git branch --merged
:
1 | MINGW64 /d/coding/git-playground (master) |
此处的当前分支为master
因为之前已经合并了 hotfix
分支,所以现在看到它在列表中。 在这个列表中分支名字前没有 *
号的分支通常可以使用 git branch -d
删除掉;你已经将它们的工作整合到了另一个分支,所以并不会失去任何东西。
1 | MINGW64 /d/coding/git-playground (master) |
-
如果要查看哪些分支尚未合并到当前分支,可以运行
git branch --no-merged
:
1 | MINGW64 /d/coding/git-playground (master) |
此处的当前分支为master
因为它包含了还未合并的工作,尝试使用 git branch -d
命令删除它时会失败:
1 | $ git branch -d testing |
如果真的想要删除分支并丢掉那些工作,如同帮助信息里所指出的,可以使用 -D
选项强制删除它。
远程分支
基本使用
远程引用是对远程仓库的引用(指针),包括分支、标签等等。
你可以通过 git ls-remote (remote)
来显式地获得远程引用的完整列表:
1 | MINGW64 /d/coding/git-playground (master) |
或者通过 git remote show (remote)
获得远程分支的更多信息:
1 | $ git remote show origin |
然而,一个更常见的做法是利用远程跟踪分支。
远程跟踪分支是远程分支状态的引用。 它们是你不能移动的本地引用,当你做任何网络通信操作时,它们会自动移动。 远程跟踪分支像是你上次连接到远程仓库时,那些分支所处状态的书签。
远程分支以 (remote)/(branch)
形式命名。 例如,如果你想要看你最后一次与远程仓库 origin
通信时 master
分支的状态,你可以查看 origin/master
分支。 你与同事合作解决一个问题并且他们推送了一个 iss53
分支,你可能有自己的本地 iss53
分支;但是在服务器上的分支会指向 origin/iss53
的提交。
让我们来看一个例子。 假设你的网络里有一个在 git.ourcompany.com
的 Git 服务器。 如果你从这里克隆,Git 的 clone
命令会为你自动将其命名为 origin
,拉取它的所有数据,创建一个指向它的 master
分支的指针,并且在本地将其命名为 origin/master
。 Git 也会给你一个与 origin 的 master
分支在指向同一个地方的本地 master
分支,这样你就有工作的基础。
“origin” 并无特殊含义
远程仓库名字 “origin” 与分支名字 “master” 一样,在 Git 中并没有任何特别的含义一样。 同时 “master” 是当你运行
git init
时默认的起始分支名字,原因仅仅是它的广泛使用,“origin” 是当你运行git clone
时默认的远程仓库名字。 如果你运行git clone -o booyah
,那么你默认的远程分支名字将会是booyah/master
。
如果你在本地的 master
分支做了一些工作,然而在同一时间,其他人推送提交到 git.ourcompany.com
并更新了它的 master
分支,那么你的提交历史将向不同的方向前进。
也许,只要你不与 origin
服务器连接,你的 origin/master
指针就不会移动。
演示说明:
本地git clone
之后:
1 | MINGW64 /d/tmp |
在本地分支做了一些改动:
1 | MINGW64 /d/tmp/git-playground (master) |
期间其他人往orgin/master
上提交了一些内容:
本例中git clone
时的版本为ed9cf4a3
,此时通过git status
命令:
1 | MINGW64 /d/tmp/git-playground (master) |
可以发现git提示Your branch is ahead of 'origin/master' by 2 commits.
,此时的本地master分支相比与刚刚clone时,往后推进了2个版本,而origin/master
引用仍然指向版本ed9cf4a3
。
如果需要同步你的工作,运行 git fetch origin
命令。 这个命令查找 “origin” 是哪一个服务器(在本例中,它是 git.ourcompany.com
),从中抓取本地没有的数据,并且更新本地数据库,移动 origin/master
指针指向新的、更新后的位置。
比如
1 | MINGW64 /d/tmp/git-playground (master) |
此时再次运行git log
:
1 | MINGW64 /d/tmp/git-playground (master) |
可视化的结果如下
此时:
关联多个远程仓库
为了演示有多个远程仓库与远程分支的情况,我们假定你有另一个内部 Git 服务器,仅用于你的 sprint 小组的开发工作。 这个服务器位于 git.team1.ourcompany.com
。 你可以运行 git remote add
命令添加一个新的远程仓库引用到当前的项目,这个命令我们会在 Git 基础 中详细说明。 将这个远程仓库命名为 teamone
,将其作为整个 URL 的缩写。
1 | $ git remote add git.team1.ourcompany.com |
现在,可以运行 git fetch teamone
来抓取远程仓库 teamone
有而本地没有的数据。 因为那台服务器上现有的数据是 origin
服务器上的一个子集,所以 Git 并不会抓取数据而是会设置远程跟踪分支 teamone/master
指向 teamone
的 master
分支。
推送
当你想要公开分享一个分支时,需要将其推送到有写入权限的远程仓库上。 本地的分支并不会自动与远程仓库同步 - 你必须显式地推送想要分享的分支。 这样,你就可以把不愿意分享的内容放到私人分支上,而将需要和别人协作的内容推送到公开分支。
如果希望和别人一起在名为 serverfix
的分支上工作,你可以像推送第一个分支那样推送它。 运行 git push (remote) (branch)
:
1 | $ git push origin serverfix |
这里有些工作被简化了。 Git 自动将 serverfix
分支名字展开为 refs/heads/serverfix:refs/heads/serverfix
,那意味着,“推送本地的 serverfix 分支来更新远程仓库上的 serverfix
分支。
我们将会详细学习 Git 内部原理 的 refs/heads/
部分,但是现在可以先把它放在儿。 你也可以运行 git push origin serverfix:serverfix
,它会做同样的事 - 相当于它说,“推送本地的 serverfix 分支,将其作为远程仓库的 serverfix 分支”
可以通过这种格式来推送本地分支到一个命名不相同的远程分支。 如果并不想让远程仓库上的分支叫做 serverfix
,可以运行 git push origin serverfix:awesomebranch
来将本地的 serverfix
分支推送到远程仓库上的 awesomebranch
分支。
下一次其他协作者从服务器上抓取数据时,他们会在本地生成一个远程分支 origin/serverfix
,指向服务器的 serverfix
分支的引用:
1 | $ git fetch origin |
要特别注意的一点是当抓取到新的远程跟踪分支时,本地不会自动生成一份可编辑的副本(拷贝)。 换一句话说,这种情况下,不会有一个新的 serverfix
分支 - 只有一个不可以修改的 origin/serverfix
指针。
可以运行 git merge origin/serverfix
将这些工作合并到当前所在的分支。 如果想要在自己的 serverfix
分支上工作,可以将其建立在远程跟踪分支之上:
1 | $ git checkout -b serverfix origin/serverfix |
这会给你一个用于工作的本地分支,并且起点位于 origin/serverfix
。
跟踪分支(上游分支upstream)
创建跟踪分支
从一个远程跟踪分支检出一个本地分支会自动创建一个叫做 “跟踪分支”(有时候也叫做 “上游分支”)。 跟踪分支是与远程分支有直接关系的本地分支。 如果在一个跟踪分支上输入 git pull
,Git 能自动地识别去哪个服务器上抓取、合并到哪个分支。
当克隆一个仓库时,它通常会自动地创建一个跟踪 origin/master
的 master
分支。 然而,如果你愿意的话可以设置其他的跟踪分支 - 其他远程仓库上的跟踪分支,或者不跟踪 master
分支。 最简单的就是之前看到的例子,运行 git checkout -b [branch] [remotename]/[branch]
。 这是一个十分常用的操作所以 Git 提供了 --track
快捷方式:
1 | MINGW64 /d/tmp/git-playground (master) |
可以看到branch 'serverfix' set up to track 'origin/serverfix'.
如果想要将本地分支与远程分支设置为不同名字,你可以轻松地增加一个不同名字的本地分支的上一个命令:
1 | MINGW64 /d/tmp/git-playground (serverfix) |
现在,本地分支 sf
会自动从 origin/serverfix
拉取。
设置已有的本地分支跟踪一个刚刚拉取下来的远程分支,或者想要修改正在跟踪的上游分支,你可以在任意时间使用 -u
或 --set-upstream-to
选项运行 git branch
来显式地设置。
git branch
-u
–set-upstream-to=
Set up
's tracking information so is considered 's upstream branch. If no is specified, then it defaults to the current branch.
1 | $ git branch -u origin/serverfix |
查看跟踪分支
如果想要查看设置的所有跟踪分支,可以使用 git branch
的 -vv
选项。 这会将所有的本地分支列出来并且包含更多的信息,如每一个分支正在跟踪哪个远程分支与本地分支是否是领先、落后或是都有。
-v
-vv
–verbose
When in list mode, show sha1 and commit subject line for each head, along with relationship to upstream branch (if any).
If given twice(即:
-vv
), print the path of the linked worktree (if any) and the name of the upstream branch, as well (see alsogit remote show <remote>
). Note that the current worktree’s HEAD will not have its path printed (it will always be your current directory).
建议直接使用-vv
,可以看到最详细的信息,比如:
1 | $ git branch -vv |
这里可以看到 iss53
分支正在跟踪 origin/iss53
并且 “ahead” 是 2,意味着本地有两个提交还没有推送到服务器上。 也能看到 master
分支正在跟踪 origin/master
分支并且是最新的。 接下来可以看到 serverfix
分支正在跟踪 teamone
服务器上的 server-fix-good
分支并且领先 2 落后 1,意味着服务器上有一次提交还没有合并入同时本地有三次提交还没有推送。 最后看到 testing
分支并没有跟踪任何远程分支。
需要重点注意的一点是这些数字的值来自于你从每个服务器上最后一次抓取(fetch)的数据。 这个命令并没有连接服务器,它只会告诉你关于本地缓存的服务器数据。 如果想要统计最新的领先与落后数字,需要在运行此命令前抓取所有的远程仓库。 可以像这样做
1 | $ git fetch --all # Fetch all remotes. |
拉取
当 git fetch
命令从服务器上抓取本地没有的数据时,它并不会修改工作目录中的内容。 它只会获取数据然后让你自己合并。
然而,有一个命令叫作 git pull
在大多数情况下它的含义是一个 git fetch
紧接着一个 git merge
命令。 如果有一个像之前章节中演示的设置好的跟踪分支,不管它是显式地设置还是通过 clone
或 checkout
命令为你创建的,git pull
都会查找当前分支所跟踪的服务器与分支,从服务器上抓取数据然后尝试合并入那个远程分支。
由于
git pull
的魔法经常令人困惑所以通常单独显式地使用fetch
与merge
命令会更好一些。
删除远程分支
设你已经通过远程分支做完所有的工作了 - 也就是说你和你的协作者已经完成了一个特性并且将其合并到了远程仓库的 master
分支(或任何其他稳定代码分支)。 可以运行带有 --delete
选项的 git push
命令来删除一个远程分支。 如果想要从服务器上删除 serverfix
分支,运行下面的命令:
1 | $ git push origin --delete serverfix |
基本上这个命令做的只是从服务器上移除这个指针。 Git 服务器通常会保留数据一段时间直到垃圾回收运行,所以如果不小心删除掉了,通常是很容易恢复的。