Git 使用指南之分支管理

Git 的分支管理。使用过 SVN 等其它版本控制系统的童鞋可能会说:“SVN 也有分支管理啊”。但由于创建和切换分支慢的一批…那么,Git 分支管理功能有性能提升吗?”。

当然有!Git 的分支是与众不同的。无论创建、切换和删除分支,Git 都能在 1 秒钟 之内就能完成!无论你的版本库是 1 个文件还是 1 万个文件。这种跟版本库大小无关的特性,到底是怎么做到的??!。

版权说明: 本文思路以及内容主要来自廖雪峰老师的 Git 教程 (强烈推荐膜拜原文),并结合个人使用所作,只作为学习记录使用。如内容有侵权请联系删除,禁止转载!

更多 Git 相关内容,请关注博主 Git 博文系列:

之一 >>> Git 使用指南之初识

之二 >>> Git 使用指南之时光穿梭机

之三 >>> Git 使用指南之远程仓库

之四 >>> Git 使用指南之分支管理

之五 >>> Git 使用指南之 WorkFlow (工作流)

之六 >>> Git 使用指南之 Git 自定义

之七 >>> Git 使用指南之 HEAD 指针

之八 >>> Git 使用指南之 Git 中的黑魔法


Meet Branchs Management

从使用场景上解释,是这么个概念:

【场景模拟】 ↓↓↓↓

参与一个项目开发任务,需要为在线平台增加一个新功能,半个月过去了,开发任务按照预期完成了 50%,就在此时在线平台突然出现 BUG ……

如果立刻提交,由于代码还未完成,不完整的代码库会影响项目内其他人工作;如果等代码全部写完再一次提交,又存在丢失工作进度的巨大风险。

【解决思路】 >>>> 使用 Git 分支管理 可以帮助你完美解决上述问题~~~

借用一句话就是:分支就像是科幻电影里面的平行宇宙,当你正在电脑前努力学习 Git 的时候,另一个你正在另一个平行宇宙里努力学习 SVN。如果两个平行宇宙(两条平行的时间线)互不干扰,那对现在的你也没啥影响。并且如果在某个时间点,两个平行宇宙合并了,结果你既学会了 Git 又学会了 SVN!

有了分支管理,你可以创建一个属于你自己的分支,项目内其他人是无法看到的,还继续在原来的分支(master)上正常工作。而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,既安全,又不影响别人工作。


Branch 指针

深入一点理解分支的(本质)话,是这么个概念:

Git 中的分支,本质上仅仅是 指向提交对象(Commit objects)的可变指针,指向当前的最新提交(Commit,也称为:快照)。

在介绍版本回滚时,我们曾提到过:每次提交,Git 都把它们串成一条时间线,这条时间线就是一个分支,指针指向时间线上的最新时间点(commit)。

实际上,当我们起初使用 Git 的时候,就已经使用到分支了,因为 Git 的默认分支名字是 master,如果你有心的话,会发现执行 git init 后,命令行的输出头部已经默认在 master 分支了。 但是这个时候,还并未创建 master 分支,只有当有一个提交的时候,才会创建 master 分支。原因在于,分支的指针要指向提交的呀。


这里需要重新认识一下 HEAD 指针,你才能更深入的了解 Branch 的使用:

HEAD 指针

前面,我们介绍过 HEAD 指针是一个特别的指针,用于记录当前工作的位置(当前版本库所处的分支,以及当前分支的提交点)

严格来说,一般情况(默认)下 HEAD 指针不是直接指向 commit 的,而是指向 branch 的,branch 才是指向提交的!!!

事实上,HEAD 指针除了可以指向 branch 也可以移动 HEAD 指针指向快照(commit),但当 HEAD 指向 branch 时且执行提交操作(git add)后会和 branch 指针一起向后移动;当 HEAD 不指向 branch(指向分支上的某一个 commit)时且执行提交(git add)后会使得 HEAD 指针处于特殊的 detached([dɪˈtætʃt],游离)状态,关于 HEAD 指针在 detached 状态的说明可参加系列博文。

通俗来讲,分支(Branch)代表着不同的基于主时空(master branch)的平行时空,无干预时互不影响。HEAD 指针等同于处于不同平行宇宙时间线下的 你自己:你在哪儿(时间点),指针就在哪;你在哪个分支(时间线),HEAD 指针就指向哪个分支的指针。


Branch Working Principle Diagram

这一小节,我们通过图解的方式来简单分析一下: Git 分支管理的工作机制,来增强对 Git 分支的认识。

我们知道,一开始的时候,Git 默认创建的 master 分支是一条线,Git 用 master 指向最新的提交,再用 HEAD 指向 master,就能确定当前分支,以及当前分支的提交点。

master 分支上,每一次提交,master 分支都会向前移动一步,随着你不断提交,master 分支的线也就越来越长:

创建并切换分支时(以 develop 分支为例),Git 将新建一个指针 develop,并将 develop 指针指向 master 相同的提交,然后再将 HEAD 指向 develop。表示当前分支在 develop 上:

这也可以看出,为什么 Git 创建分支会如此之快,工作区的文件都没有任何变化,并且只是增加了一个 develop 指针以及改变了 HEAD 的指向,此后对工作区的修改和提交就是针对 develop 分支了。

比如新提交一次后,develop 指针往前移动一步,而 master 指针不变:

一段时间后,我们成功在 develop 分支上完成新功能的开发,此时我们想把新功能整合到在线平台,也就是想把 develop 合并到 master 上,Git 如何实现呢?最简单的方法,就是直接把 master 指向 develop 的当前提交,就完成了合并:

可以看出,Git 合并分支也很快!就改改指针,工作区内容也不变!合并完分支后,甚至可以删除 develop 分支。删除 develop 分支就是把 develop 指针给删掉,删掉后,我们就剩下了一条 master 分支:


通过上面的学习,相信你已经对 Git 的分支管理有了相对深入的认识,下面正式开始 Git 分支管理的学习

玩转 Branch 必备技能

有关分支的命令不多,无非是换着花样的增删改查,掌握好以下基本的命令,以后就可以在 Branch 的草原上策马奔腾潇潇洒洒啦~~~

重要的分支操作命令格式:CMD 13 –>>> git branch <options>

创建分支

创建分支的命令非常简单,命令格式如下:

1
$ git branch <branch name>

使用起来非常简单,似乎简单到你只需要想个分支的名字就好了。但是在创建分支的时候,要想下:是否要从当前分支的内容基础上去开辟一条新分支???


查看分支

如何查看本地仓库以及远程库中的分支情况呢?三个命令,让你想看什么分支就看什么分支:

1 –> 如何查看本地分支:

1
$ git branch

2 –> 如何查看远程分支:

1
$ git branch -r

3 –> 如何查看本地和远程的所有分支:

1
$ git branch -a

重命名分支

实际项目开发过程中,有时会涉及到 分支的重命名

【场景一】:重命名本地分支

当本地的开发分支还没有推送到远程分支的时候,会在本地进行分支的重命名。

  1. 在当前分支时:
1
$ git branch -m <new branch name>
  1. 不在当前分支时:
1
$ git branch -m <old branch name> <new branch name>

======================================================================

如果是已经推送到远端,应该怎么做呢?

【场景二】:重命名远程库分支

假设是在当前分支,并且远端分支与本地分支名是一致的。

  1. 重命名本地分支
1
$ git branch -m <new branch name>
  1. 删除远程分支
1
$ git push origin --delete <old branch name>
  1. 上传新命名的本地分支
1
$ git push origin <new branch name>
  1. 关联修改后的本地分支与远程分支
1
$ git branch --set-upstream-to origin/<new branch name>

检出/切换分支

检出分支的 “检出” 二字,算是个关于 Git 分支的专业术语了,可以理解为切换当前分支。

CMD 14 –>>> git checkout <-b> <branch name>

checkout 表征 “检出” 含义时,checkout 操作是移动 HEAD 指针,将 HEAD 指针指向要切换的分支的指针处。

使用场景有两个:

  1. 已经存在的分支,现在要切换过去。
1
$ git checkout <branch name>
  1. 创建一个新分支,且切换到新分支,这个一步到位的话需要 -b 参数

以当前分支为基础,创建一个新分支:

1
$ git checkout -b <branch name>

以指定的某一个提交,创建一个新分支:

1
$ git checkout -b <branch name> <Commit-ID(SHA1)>

CMD 15 –>>> git switch <-c> <branch name>

记忆力好的看官可能想到,前面撤销修改操作使用过 git checkout -- <file>,太迷惑了……事实上,Git提供了新的 git switch 命令来专门处理分支切换。

对应 checkout 的两种使用场景:

  1. 已经存在的分支,现在要切换过去。
1
$ git switch <branch name>
  1. 创建一个新分支,且切换到新分支,这个一步到位的话需要 -b 参数

以当前分支为基础,创建一个新分支:

1
$ git switch -c <branch name>

删除分支

分支生命周期完成后,我们就可以放心删除了:

当本地分支删除后,推动到远程仓库后,远程仓库并不能自动删除对应的远程分支。意味着,分支的完全删除是分两个部分的:

  1. 本地分支的删除
  2. 远程分支的删除。

1 –> 删除本地分支:

1
2
3
4
5
6
## 1. 一般删除:删除前会检查 merge 状态,只有满足删除条件才会执行删除操作
$ git branch -d <lacal branch name>

## 2. 强制删除:会直接执行删除操作
# 如果要丢弃一个没有被合并过的分支,可以通过-D 强行删除
$ git branch -D <local branch name>

参数 -D-d 要粗暴一点。当被删除分支有新内容没有被合并的时候,使用 -D,会直接删除;使用 -d,会提示该分支有新内容没有被合并,不执行删除。删除需谨慎,建议非特殊情况下,使用温柔的 -d 要好一点,以免小手一抖,/(ㄒoㄒ)/~~

2 –> 删除远程分支:

1
$ git push origin --delete <remote branch name>

以上,是分支的增删改查独立操作,但是 Git 创造这个分支,并不只是为了让它们自个儿和自个儿玩的,还需要它们之间的相互协作和配合。

就像日常项目开发过程中,分好开发任务,你和你的小伙伴新建了两个分支,你写你的 Butter,他写他的 Fly,到开发完成之后,肯定要合在一起,才能成就 Butterfly。合的这个动作,就涉及到了分支合并的概念。

Branch 合并大事记

合并指定分支到当前分支的命令格式为:CMD 16 –>>> git merge <branch name>

git merge 命令可以加 -m "message" 参数添加合并提交日志。

Git 中分支的合并是非常智能的,目前有两种模式。两种模式的选择,不需要我们参与,而是 Git 根据分支情况不同,自行判断选择最适合的 Merge 模式。

个人在使用 Git 的过程中,执行分支合并时:有时需要输入提交信息,有时不需要,起初作为小白的我懵的不知所以然,后来才知道,原来是合并模式的问题啊!!!

两种合并模式是:

  1. Fast-Forward(快进式)
  2. Recursive Strategy Merge(递归策略合并式,也称为三方合并式)

Fast-Forward

如图,有两个分支,master 分支和 feature 分支。当这两个分支处于上面的关系时,当进行合并操作时,就会出现 fast-forward

【说明】:由于当前 master 分支所指向的提交是 feature 分支的直接上游,所以 Git 只是简单的将指针向前移动。换句话说,当你试图合并两个分支时,如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候,只会简单的将指针向前推进(指针右移)—->这就叫做 “快进(fast-forward)”。

合并后的分支指针位置如下:


Recursive Strategy Merge

这个合并方式,是为补充 fast-forward 而出现的。

因为你知道,在项目开发过程中,很多人开发的情况下,出现 fast-forward 的情况并不是很多,很多是类似下面这种。提交历史是分叉的,无法满足执行 fast-forward 的条件:

【说明】:,master 分支所在提交并不是 feature 分支所在提交的直接上游(祖先),Git 不得不做一些额外的工作。 出现这种情况的时候,Git 会使用两个分支的末端所指的快照(C4 和 C5)以及这两个分支的工作祖先(C3),做一个简单的三方合并,生成一个新的提交(C6)。


Branch Demo

【2.玩转 Branch 必备技能】 && 【3.Branch 合并大事记】,说起来就是一堆理论,这一小节我们基于 GitTestProject 来实操一下:

1)从 master 分支末尾,创建并切换 featureA 分支,并创建一个提交:

1
2
3
4
$ git checkout -b featureA
$ touch ATest.txt
$ git add .
$ git commit -m "Add ATest File For featureA"

2)从 master 分支末尾,创建并切换 featureB 分支,并创建一个提交:

1
2
3
4
5
$ git checkout master
$ git checkout -b featureB
$ touch BTest.txt
$ git add .
$ git commit -m "Add BTest File For featureB"

3) 切换回 master 分支

1
$ git switch master

测试分支 featureA && featureB 完成后,就可以开始尝试分支的合并功能了:

4) Fast-Forward 合并

master 分支合并 featureA 时,是快进式合并:

1
2
3
4
5
6
$ git merge featureA
Updating 79c3a2c..fc2702b
Fast-forward
ATest.txt | 0
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 ATest.txt

图例如下:

5) Recursive Strategy Merge 合并

master 分支合并 featureA 后, 再合并 featureB 时,已经不满足快进式条件了,此时合并会触发一个三方合并,产生一个新的提交。

执行合并 featureB 命令,会跳到一个 VIM 页面,让我们编辑这个新提交的提交信息,你可以看到默认的提交信息是 “Merge branch ‘branch name’”。此时,按 i 进入编辑模式可编写提交信息, 编写好后,通过 + wq 保存并退出 VIM 页面即可完成合并。

1
2
3
4
5
$ git merge featureB
Merge made by the 'recursive' strategy.
BTest.txt | 0
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 BTest.txt

图例如下:


最后再提供一下用来查看分支合并图的命令:

CMD 16 –>>> git log –graph –pretty=oneline –abbrev-commit

1
2
3
4
5
6
7
8
9
10
11
12
13
 $ git log --graph --pretty=oneline --abbrev-commit
* 5f264b1 (HEAD -> master) Merge branch 'featureB'
|\
| * 4528426 (featureB) Add BTest File For featureB
* | fc2702b (featureA) Add ATest File For
|/
* 79c3a2c (origin/master) Add git_rm_test.txt
* fe3235b git tracks changes
* d6ddc31 Git local data management test
* 0f5a696 understand how stage works
* ebba382 Add test code
* da1fadc Add help info
* 65586b3 ADD Project Base

其中,--pretty=oneline 参数表示提交以单行显示,--abbrev-commit 参数表示减少头部数据(缩短 SHA1 数值)。


No Fast Forward

Fast Forward 意为 “快进模式”。主要使用在多分支合并的情况下。即:当前分支合并另一个分支的时候(如果合并的过程中没有 Conflict 冲突的时候,关于 Conflict 的说明见下一小节,这里不用深究),则会通过直接移动两个分支的指针,来达到合并的过程,这个过程就叫做 Fast Forward。

那么,何为 No Fast Forward???No Fast Forward 指的是,合并时禁用 fast forward 模式,采用 Recursive Strategy Merge 模式进行合并。

为什么要禁用 Fast Forward 模式,这就不得不提到 Fast Forward 的弊端了:

1 –> Fast Forward 弊端

在 Fast Forward 模式下,当我们 merge 合并后,将会删除无用的分支。即:删除分支后,会丢掉分支的所有信息。

什么是丢掉分支信息呢?看下面的例子 >>>

假设,新建一个名为 dev 的分支,在分支上进行了两次操作:1.Func-Add User;2.Func-Add Permission,然后切换至 master 主分支,使用 Fast Forward 模式进行合并分支,查看日志信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ git merge dev
Updating 5f264b1..8a39810
Fast-forward
dev_function.txt | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 dev_function.txt

$ git log --graph --pretty=oneline --abbrev-commit
* 8a39810 (HEAD -> master, dev) Func-Add Permission
* 3d6d64d Func-Add User
* ebba382 Add test code
* da1fadc Add help info
* 65586b3 ADD Project Base

可以看到,对于功能 Func-Add User && Func-Add Permission,无法判断是 master 分支还是 dev 分支提交的。

怎么办???


2 –> No Fast Forward(–no-ff)

如果要强制禁用 Fast Forward 模式,Git 就会在 merge 时生成一个新的 commit,这样,从分支历史上就可以看出分支信息。命令格式如下:

1
$ git merge --no-ff -m "message" <branch name>

对比一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ git switch master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 5 commits.
(use "git push" to publish your local commits)

$ git merge --no-ff -m "Merge with no-ff" dev
Merge made by the 'recursive' strategy.
dev_function.txt | 3 +++
1 file changed, 3 insertions(+)

$ git log --graph --pretty=oneline --abbrev-commit
* 28157d9 (HEAD -> master) Merge with no-ff
|\
| * 02636d0 (dev) Func-Add Permission(no-ff)
| * b324182 Func-Add User(no-ff)
|/
* ================= Shortcut Line ================
* 8a39810 Func-Add Permission
* 3d6d64d Func-Add User
......
* ebba382 Add test code
* da1fadc Add help info
* 65586b3 ADD Project Base

可以看到,使用 no-ff 合并的方式可以保留分支信息。

如果合并分支之后,将 dev 分支删除了,你就找不到 dev 分支了,但在分支示意图上仍然可以看到分支结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ git branch -d dev
Deleted branch dev (was 02636d0).

$ git log --graph --pretty=oneline --abbrev-commit
* 28157d9 (HEAD -> master) Merge with no-ff
|\
| * 02636d0 Func-Add Permission(no-ff)
| * b324182 Func-Add User(no-ff)
|/
* ================= Shortcut Line ================
* 8a39810 Func-Add Permission
* 3d6d64d Func-Add User
*
......
* ebba382 Add test code
* da1fadc Add help info
* 65586b3 ADD Project Base

|————————————————————

友情提示:

我们知道 Git 创建、合并和删除分支都非常快(操作指针)。所以,Git 鼓励用户使用分支完成某个任务,合并后再删掉分支,这和直接在 master 分支上工作效果是一样的,但过程更安全。

————————————————————|


和平解决 Branch 合并冲突

有人在的地方就有江湖,有分支在的地方,就有冲突~~~

很多时候,合并操作不会如此顺利。 如果你在两个不同的分支中,对同一个文件的同一个部分进行了不同的修改,Git 就没法干净的合并它们,于是就会发生冲突。

例如,分别在 master 和 featureA 下,在 ATest.txt 文件添加一行任意内容,然后两个分支合并,就会发生冲突。

1
2
3
4
$ git merge featureA
Auto-merging ATest.txt
CONFLICT (content): Merge conflict in ATest.txt
Automatic merge failed; fix conflicts and then commit the result.

这是,可以通过 git status,查看冲突的详细信息。冲突提示信息中,指明冲突文件为: ATest.txt。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(master *+|MERGING) GitTestProject $ git status
On branch master
Your branch is ahead of 'origin/master' by 9 commits.
(use "git push" to publish your local commits)

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 modified: ATest.txt

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

需要说明的是:如果遇到冲突的话,git 就无法自动合并了,接下来要靠我们自己手动解决冲突,方法是:

  1. 查看造成冲突的文件,修改冲突部分;
  2. 对修改后冲突文件,执行 git add 操作;
  3. 创建一个修改冲突的提交。

先了解一下发现冲突的解决思路,接下来,一步一步仔细看~

1 –> 查看造成冲突的文件,修改冲突部分

冲突文件 ATest.txt 内容如下(Git 虽然无法解决冲突, 但是使用简单的三个符号,标明了冲突的地方,以及冲突的两个分支在该地方发生冲突时的内容):

1
2
3
4
5
6
7
<<<<<<< HEAD
# This is a test about merge conflict.
=======
# This ia a test about merge conflict.
>>>>>>> featureA
~
~

符号说明如下:

符号 分隔符
======= 分隔符
<<<<<<< HEAD 至 ======= master 分支中该地方的内容
======= 至 >>>>>>> featureA featureA 分支中该地方为内容

接下来编辑 ATest.txt 文件,根据功能需要完成合并,确认之后,把 Git 冲突标识符号给删除掉即可。

2 & 3 –> 修改后冲突文件,add && commit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ git add ATest.txt

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

All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)

Changes to be committed:
modified: ATest.txt

(master +|MERGING) GitTestProject $ git commit -m "Dealing Merge Conflict"
[master fb50835] Dealing Merge Conflict

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

nothing to commit, working tree clean

关联本地分支&&远程分支

学习了分支的概念后,你应该意识到:多人协作项目中,为了保证本地分支和远程库中分支保持数据同步,团队中的每一个 Partner 使用 git pull 或者 git push 拉取(或推送)最新分支内容时,需要明确指定从本地的哪个分支 拉取/推送 到远程的哪个分支,这是重要的!!!

Git Pull && Git Push 参数详解

补充说明在远程仓库中提到的:CMD 10 –>>> git push && git pull

1 –> git push

1
2
3
4
5
6
7
8
9
10
1.将本地当前分支 推送到 与本地当前分支同名的远程分支上(使用前提:本地分支&&远程分支实现关联)
$ git push

2.将本地当前分支 推送到 与本地当前分支同名的远程分支上
$ git push origin <local current branch name>

3.将本地当前分支 推送到 远程指定分支上
$ git push origin <local current branch name>:<remote brance name>

推荐使用第二种!!!

2 –> git pull

1
2
3
4
5
6
7
8
9
10
1.将与本地当前分支同名的远程分支 拉取到 本地当前分支上(使用前提:本地分支&&远程分支实现关联)
$ git pull

2.将远程指定分支 拉取到 本地当前分支上
$ git pull origin <remote branch name>

3.将远程指定分支 拉取到 本地指定分支上
$ git pull origin <remote brance name>:<local current branch name>

推荐使用第二种!!!

注意:pull or push 自身的动作,决定了后面跟随最近的仓库是远程的还是本地的。pull From –> remote brance name;push To –> local branch name。


实际中,上面推荐使用的第二种(可以直接使用)和第一种语法都常使用,然而第一种的使用前提是:需要先实现本地分支&&远程分支关联

将本地仓库,以及远程仓库(“中央仓库”)关联起来,可以简化命令,但同样带来了混淆(两面性),你可以根据喜好选择性使用,适合自己的才是最好的。

那么如何将本地仓库,以及远程仓库(“中央仓库”)关联起来呢???

推荐一个用来查看分支关联情况的命令:

1
$ git branch -vv

关联方法

将本地分支同远程分支进行关联,可以分为以下三种场景(以 dev 分支为例):

场景一:本地库已创建分支 dev,而远程库没有

1
2
3
4
5
6
7
# 添加参数:-u
$ git push -u origin dev

或者

# 添加参数:--set-upstream
$ git push --set-upstream origin dev

关联思路:推送时实现关联,之后就可以直接使用 git push 进行分支分容的推送了。

场景二:远程库已创建分支 dev,而本地库没有

1
2
3
4
5
6
7
# 分为两步:

## Step1:将远程分支 pull 到本地
$ git pull origin dev

# Step2:创建本地分支并且进行关联
$ git checkout -b dev origin/dev

关联思路:先拉取远程分支到本地,然后本地创建切换分支且进行关联。之后就可以直接使用 git pull 进行远程分支内容拉取了。

场景三:本地库 && 远程库均已创建分支 dev

事实上,git branch 命令支持直接将 本地当前分支 直接与 远程分支 相关联:

1
2
3
4
5
6
7
# 添加参数:-u
$ git branch -u origin/dev

或者

# 添加参数:--set-upstream-to
$ git branch --set-upstream-to=origin/dev

当然,前提是本地和远程库中均有分支 dev。注意,这里只是以 dev 为例,并不是本地库和远程库必须同名!!!


撤销关联

下面的命令,可以 撤销 本地当前分支 与对应的远程分支 的关联关系

1
$ git branch --unset-upstream

Demo

这一小节我们来看分支关联的测试实例(以 GitTestProject 为例):

场景一实例:远程库已创建分支 dev,本地无分支

1)查看需要关联的远程分支名:

1
2
3
4
$ git branch -a
* master
remotes/origin/dev
remotes/origin/master

可以看到,远程也包含了 dev 分支:remotes/origin/dev,而本地是没有的。

2)根据关联方法中的介绍,这里有两种思路,来进行关联:

思路一:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 本地新建一个和远程分支同名(dev)的分支
$ git checkout -b dev
Switched to a new branch 'dev'

# 将本地当前分支关联远程分支
(dev) GitTestProject $ git branch --set-upstream-to=origin/dev
Branch 'dev' set up to track remote branch 'dev' from 'origin'.

$ git branch -vv
FeatureA c07648e Merge conflic test in FeatureA
* dev fb50835 [origin/dev] Dealing Merge Conflict
featureB 4528426 Add BTest File For featureB
master fb50835 [origin/master: ahead 11] Dealing Merge Conflict

思路二:

注意,测试前需要将上面的关联取消掉,然后删除分支,再开始执行下面的命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 将远程分支 pull 到本地
$ git pull origin dev
From github.com:TheNightIsYoung/GitTestProject
* branch dev -> FETCH_HEAD
Already up to date.

# 创建本地分支并且进行关联
(master) GitTestProject $ git checkout -b dev origin/dev
Switched to a new branch 'dev'
Branch 'dev' set up to track remote branch 'dev' from 'origin'.

(dev) GitTestProject $ git branch -vv
FeatureA c07648e Merge conflic test in FeatureA
* dev fb50835 [origin/dev] Dealing Merge Conflict
featureB 4528426 Add BTest File For featureB
master fb50835 [origin/master: ahead 11] Dealing Merge Conflict

3)提交测试是否成功关联

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ touch assi_demo.txt

GitTestProject $ git add assi_demo.txt
(dev +) GitTestProject $ git commit -m "Branch assi Test"
[dev 3cb60e7] Branch assi Test
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 assi_demo.txt

(dev) GitTestProject $ git pull
Already up to date.

(dev) GitTestProject $ git push
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Delta compression using up to 12 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 251 bytes | 125.00 KiB/s, done.
Total 2 (delta 1), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To github.com:TheNightIsYoung/GitTestProject.git
fb50835..3cb60e7 dev -> dev

可以发现,已经成功关联分支,并可以提交代码到远程分支。


场景二实例:本地库已创建分支 dev,远程无分支

为了满足场景二需要,我们需要先删除场景一中的关联关系,并且删除远程的 dev 分支:

1
2
3
4
5
6
7
8
9
$ git branch --unset-upstream
$ git push origin --delete dev
To github.com:TheNightIsYoung/GitTestProject.git
- [deleted] dev

$ git branch -a
* dev
master
remotes/origin/master

1)查看当前仓库分支情况:

1
2
3
4
$ git branch -a
* dev
master
remotes/origin/master

可以发现,远程库中只有 master 分支,而本地仓库中包含两个分支:dev && master。

2)根据关联方法中的介绍,这里有两种思路,来进行关联:

思路一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 将本地当前分支 推送到 远程库 
$ git push origin dev
Enumerating objects: 30, done.
Counting objects: 100% (30/30), done.
Delta compression using up to 12 threads
Compressing objects: 100% (26/26), done.
Writing objects: 100% (29/29), 2.70 KiB | 345.00 KiB/s, done.
Total 29 (delta 16), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (16/16), done.
remote:
remote: Create a pull request for 'dev' on GitHub by visiting:
remote: https://github.com/TheNightIsYoung/GitTestProject/pull/new/dev
remote:
To github.com:TheNightIsYoung/GitTestProject.git
* [new branch] dev -> dev

# 查看分支关联情况,发现 dev 分支未关联
(dev) GitTestProject $ git branch -vv
* dev 3cb60e7 Branch assi Test
master fb50835 [origin/master: ahead 11] Dealing Merge Conflict

# 将本地当前分支 与 远程分支进行 关联
(dev) GitTestProject $ git branch -u origin/dev
Branch 'dev' set up to track remote branch 'dev' from 'origin'.

# 重新查看分支关联情况,发现本地 dev 分支已和远程关联
(dev) GitTestProject $ git branch -vv
* dev 3cb60e7 [origin/dev] Branch assi Test
master fb50835 [origin/master: ahead 11] Dealing Merge Conflict

思路二:

注意,测试前需要将上面的关联取消掉,然后删除远程分支,再开始执行下面的命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 将本地当前分支 推送到 远程库,同时使用 -u 参数进行关联
$ git push -u origin dev
Enumerating objects: 30, done.
Counting objects: 100% (30/30), done.
Delta compression using up to 12 threads
Compressing objects: 100% (26/26), done.
Writing objects: 100% (29/29), 2.70 KiB | 212.00 KiB/s, done.
Total 29 (delta 16), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (16/16), done.
remote:
remote: Create a pull request for 'dev' on GitHub by visiting:
remote: https://github.com/TheNightIsYoung/GitTestProject/pull/new/dev
remote:
To github.com:TheNightIsYoung/GitTestProject.git
* [new branch] dev -> dev
Branch 'dev' set up to track remote branch 'dev' from 'origin'.

# 重新查看分支关联情况,发现本地 dev 分支已和远程关联
(dev) GitTestProject $ git branch -vv
* dev 3cb60e7 [origin/dev] Branch assi Test
master fb50835 [origin/master: ahead 11] Dealing Merge Conflict

如果你还不放心的话,还可以进行一次提交,然后使用 git push 进行推送,自己尝试一下吧~~~


Data Flow In Git

前面我们提到过 Git 的三大分区,以及各区之间的数据传递流程图,这里我们已经可以给出 Git 的整体构架图:

工作区(Working Directory),简言之就是你工作的区域。对于 Git 而言,就是的本地工作目录。工作区的内容会包含提交到暂存区和版本库(当前提交点)的内容,同时也包含自己的修改内容。

暂存区(Stage Area, 又称为索引区 Index),是 Git 中一个非常重要的概念。在工作目录下有一个 .git 的目录,里面有个 index 文件,存储着关于暂存区的内容。git add 命令将工作区内容添加到暂存区。

本地仓库(Rocal Repository),版本控制系统的仓库,存在于本地。当执行 git commit 命令后,会将暂存区内容提交到仓库之中。在工作区下面有 .git 的目录,这个目录下的内容不属于工作区,里面便是仓库的数据信息,暂存区相关内容也在其中。这里也可以使用 merge 或 rebase 将远程仓库副本合并到本地仓库。图中的只有 merge,注意这里也可以使用 rebase。

远程版本库(Remote Repository),与本地仓库概念基本一致,不同之处在于一个存在远程,可用于远程协作,一个却是存在于本地。通过 push/pull 可实现本地与远程的交互。

远程仓库副本,可以理解为存在于本地的远程仓库缓存。如需更新,可通过 git fetch/pull 命令获取远程仓库内容。使用 fech 获取时,并未合并到本地仓库,此时可使用 git merge 实现远程仓库副本与本地仓库的合并。git pull 根据配置的不同,可为 git fetch + git merge 或 git fetch + git rebase。

这里引用知乎上,博主波罗学的说法。git pull 和 git fetch 的区别前参加 –> 传送门


Get Branch From RemoteRepo

日常的项目开发过程中,更常见的是从既存的远程仓库中拉取代码到本地,然后进行开发任务。前面提到过,会使用 git clone <repo addr> 命令,可以将项目整个克隆到我们的本地仓库。

然而,本地 Clone 仓库默认只会 clone 下 master 分支(clone 到指定目录):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Clone 到指定目录语法:git clone <repo addr> <myDirName>
$ git clone git@github.com:TheNightIsYoung/GitTestProject.git MyLocalRepo
Cloning into 'MyLocalRepo'...
remote: Enumerating objects: 59, done.
remote: Counting objects: 100% (59/59), done.
remote: Compressing objects: 100% (25/25), done.
remote: Total 59 (delta 22), reused 58 (delta 21), pack-reused 0
Receiving objects: 100% (59/59), 5.00 KiB | 639.00 KiB/s, done.
Resolving deltas: 100% (22/22), done.

$ ls MyLocalRepo/
ATest.txt Client/ git_rm_test.txt Server/
BTest.txt dev_function.txt readme.txt
$ cd MyLocalRepo/

倘若远程仓库有多个分支,我们会发现,使用 git branch 查看本地分支时,只有一个 master 分支:

1
2
$ git branch
* master

此时,你可以使用 git branch -a 查看都有那些远程分支:

1
2
3
4
5
$ git branch -a
* master
remotes/origin/HEAD -> origin/master
remotes/origin/develop
remotes/origin/feature

但是,实际上大多数时候我们是需要在其他分支进行工作的,所以我们需要可以灵活的将远程的其它分支拉取下来。比如有时候你只想拉取远程仓库指定的某一个分支,有时候你想拉取远程的所有分支等等。

Pull a specified branch

如何 Git 命令拉取远程仓库中指定的某一个分支呢?这里提供三种方法供大家参考:

1 –> git clone -b 命令获取

命令格式:

1
$ git clone -b <remote branch name> <remote repo addr> 

git clone -b <分支名称> <仓库地址> 命令可以将指定的某一个远程分支拉取到我们本地,而且拉取的本地分支自动和远程同名分支建立关联(追踪)关系,并且会将新创建的 HEAD 指向刚拉取下来的分支。

1
2
3
4
5
6
7
8
$ git clone -b dev git@github.com:TheNightIsYoung/GitTestProject.git MyLocalRepo
Cloning into 'MyLocalRepo'...
remote: Enumerating objects: 59, done.
remote: Counting objects: 100% (59/59), done.
remote: Compressing objects: 100% (25/25), done.
remote: Total 59 (delta 22), reused 58 (delta 21), pack-reused 0
Receiving objects: 100% (59/59), 5.00 KiB | 1023.00 KiB/s, done.
Resolving deltas: 100% (22/22), done.

分支拉取结束以后,执行 git branch -a 命令,查看分支情况如下:

1
2
3
4
5
6
$ cd MyLocalRepo/
(dev) MyLocalRepo $ git branch -a
* dev
remotes/origin/HEAD -> origin/master
remotes/origin/dev
remotes/origin/master

当然,这里你也可以根据【关联本地分支&&远程分支】中介绍的方法,同时拉取其它分支进行工作(以 master 为例):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ git pull origin master
From github.com:TheNightIsYoung/GitTestProject
* branch master -> FETCH_HEAD
Already up to date.
Jie Guo (dev) MyLocalRepo $ git branch -a
* dev
remotes/origin/HEAD -> origin/master
remotes/origin/dev
remotes/origin/master

(dev) MyLocalRepo $ git checkout -b master origin/master
Switched to a new branch 'master'
Branch 'master' set up to track remote branch 'master' from 'origin'.
(master) MyLocalRepo $ git branch -a
dev
* master
remotes/origin/HEAD -> origin/master
remotes/origin/dev
remotes/origin/master

2 –> git fetch 命令获取

关于 git fetch 命令说明:

1
2
3
4
5
6
7
# 1.将某个远程主机的所有更新,全部取回本地副本中,但不会将任何新内容合并到我们最近的工作文件中。
$ git fetch <远程主机名>
# 或者简写为:
$ git fetch

# 2.如果只想取回特定分支的更新
$ git fetch <远程主机名> <分支名>

开始拉取指定分支 >>>>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 1.拉取整个远程代码库
$ git clone git@github.com:TheNightIsYoung/GitTestProject.git MyLocalRepo
Cloning into 'MyLocalRepo'...
remote: Enumerating objects: 59, done.
remote: Counting objects: 100% (59/59), done.
remote: Compressing objects: 100% (25/25), done.
remote: Total 59 (delta 22), reused 58 (delta 21), pack-reused 0
Receiving objects: 100% (59/59), 5.00 KiB | 1023.00 KiB/s, done.
Resolving deltas: 100% (22/22), done.

gitWorkS $ cd MyLocalRepo/

# 将远程仓库的所有分支拷贝到本地,建立远程库副本
(master) MyLocalRepo $ git fetch

# 切换到想要拉取的指定某一个分支的本地分支
(master) MyLocalRepo $ git checkout dev
Switched to a new branch 'dev'
Branch 'dev' set up to track remote branch 'dev' from 'origin'.

切换分支后,你就可以看到本地仓库的 dev 开发分支和远程仓库的 dev 开发分支同步了。

这里不好理解的话,你可以在 git fetch 之后,使用 git checkout -b dev origin/dev + git merge origin/dev 实现同样的效果。


3 –> git checkout 命令获取

这一方法你可以先自己思考下,提示需要借助 git pull 命令~~~

先不要看下面的答案!

先不要看下面的答案!!

先不要看下面的答案!!!

参考下面的详细步骤,是否和你思考的一样?:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ git clone git@github.com:TheNightIsYoung/GitTestProject.git MyLocalRepo
Cloning into 'MyLocalRepo'...
remote: Enumerating objects: 59, done.
remote: Counting objects: 100% (59/59), done.
remote: Compressing objects: 100% (25/25), done.
remote: Total 59 (delta 22), reused 58 (delta 21), pack-reused 0
Receiving objects: 100% (59/59), 5.00 KiB | 1023.00 KiB/s, done.
Resolving deltas: 100% (22/22), done.

gitWorkS $ cd MyLocalRepo/
(master) MyLocalRepo $ git branch -a
* master
remotes/origin/HEAD -> origin/master
remotes/origin/dev
remotes/origin/master

(master) MyLocalRepo $ git checkout -b dev origin/dev
Switched to a new branch 'dev'
Branch 'dev' set up to track remote branch 'dev' from 'origin'.

(dev) MyLocalRepo $ git pull origin dev
From github.com:TheNightIsYoung/GitTestProject
* branch dev -> FETCH_HEAD
Already up to date.

至此,你就可以灵活的实现 从远程仓库拉取指定某一分支,或者拉取指定的某些分支了~~~


Sync remote branch

那么,如何一次性同步远程仓库中的所有分支到本地仓库中呢?

1 –> 跟踪(关联)所有远程分支

1
2
3
4
5
6
7
8
9
10
 $ git branch -r | grep -v '\->' | while read remote; do git branch --track "${remote#origin/}" "$remote"; done
Branch 'dev' set up to track remote branch 'dev' from 'origin'.
fatal: A branch named 'master' already exists.

$ git branch -a
dev
* master
remotes/origin/HEAD -> origin/master
remotes/origin/dev
remotes/origin/master

2 –> 将本地所有分支与远程保持同步

1
2
$ git fetch --all
Fetching origin

3 –> 拉取所有分支代码

1
2
3
$ git pull --all
Fetching origin
Already up to date.

Author

Waldeinsamkeit

Posted on

2017-07-04

Updated on

2022-03-08

Licensed under

You need to set install_url to use ShareThis. Please set it in _config.yml.

Comments

You forgot to set the shortname for Disqus. Please set it in _config.yml.