Loading

02、分支(branch)

 

1、分支是什么

  为了理解 Git 分支的实现方式,我们需要回顾一下 Git 是如何储存数据的。或许你还记得上篇博文中的内容,Git 保存的不是文件差异或者变化量,而是文件快照。
  在 Git 中提交时,会保存一个提交(commit)对象,该对象包含一个指向暂存内容快照的指针,包含本次提交的作者等相关附属信息,包含零个或多个指向该提交对象的父对象指针:首次提交是没有直接祖先的,普通提交有一个祖先,由两个或多个分支合并产生的提交则有多个祖先。
  为直观起见,我们假设在工作目录中有三个文件,准备将它们暂存后提交。暂存操作会对每一个文件计算校验和(即前文提到的 SHA-1 哈希字串),然后把当前版本的文件快照保存到 Git 仓库中(Git 使用 blob 类型的对象存储这些快照),并将校验和加入暂存区域:
 
 
 
 
1
$ git add .
2
$ git commit -m 'initial commit of my project'
 
 
  当使用 git commit 新建一个提交对象前,Git 会先计算每一个子目录(本例中就是项目根目录)的校验和,然后在 Git 仓库中将这些目录保存为树(tree)对象。之后 Git 创建的提交对象,除了包含相关提交信息以外,还包含着指向这个树对象(项目根目录)的指针,如此它就可以在将来需要的时候,重现此次快照的内容了。
  现在,Git 仓库中有五个对象:三个表示文件快照内容的 blob 对象;一个记录着目录树内容及其中各个文件对应 blob 对象索引的 tree 对象;以及一个包含指向 tree 对象(根目录)的索引和其他提交信息元数据的 commit 对象。概念上来说,仓库中的各个对象保存的数据和相互关系看起来如图所示:
  单个提交对象在仓库中的数据结构,作些修改后再次提交,那么这次的提交对象会包含一个指向上次提交对象的指针(译注:即下图中的 parent 对象)。两次提交后,仓库历史会变成下图的样子:
  现在来谈分支。Git 中的分支,其实本质上仅仅是个指向 commit 对象的可变指针。Git 会使用 master 作为分支的默认名字。在若干次提交后,你其实已经有了一个指向最后一次提交对象的 master 分支,它在每次提交的时候都会自动向前移动。
  分支其实就是从某个提交对象往回看的历史
  那么,Git 又是如何创建一个新的分支的呢?答案很简单,创建一个新的分支指针。比如新建一个 testing 分支,可以使用 git branch 命令:
 
 
 
 
1
$ git branch testing
 
 
  这会在当前 commit 对象上新建一个分支指针
 
  那么,Git 是如何知道你当前在哪个分支上工作的呢?其实答案也很简单,它保存着一个名为 HEAD 的特别指针。请注意它和你熟知的许多其他版本控制系统(比如 Subversion 或 CVS)里的 HEAD 概念大不相同。在 Git 中,它是一个指向你正在工作中的本地分支的指针(译注:将 HEAD 想象为当前分支的别名。)。运行 git branch 命令,仅仅是建立了一个新的分支,但不会自动切换到这个分支中去,所以在这个例子中,我们依然还在 master 分支里工作。
  要切换到其他分支,可以执行 git checkout 命令。我们现在转换到新建的 testing 分支:
 
 
 
 
1
$ git checkout testing
 
 
  这样 HEAD 就指向了 testing 分支。
  这样的实现方式会给我们带来什么好处呢?好吧,现在不妨再提交一次:
  每次提交后 HEAD 随着分支一起向前移动。非常有趣,现在 testing 分支向前移动了一格,而 master 分支仍然指向原先 git checkout 时所在的 commit 对象。现在我们回到 master 分支看看:
 
 
 
 
1
$ git checkout master
 
 
  HEAD 在一次 checkout 之后移动到了另一个分支
  这条命令做了两件事。它把 HEAD 指针移回到 master 分支,并把工作目录中的文件换成了 master 分支所指向的快照内容。也就是说,现在开始所做的改动,将始于本项目中一个较老的版本。它的主要作用是将 testing 分支里作出的修改暂时取消,这样你就可以向另一个方向进行开发。现在我们在 master 分支上在提交一次,现在的情况就变成了下图。
  现在我们的项目提交历史产生了分叉,因为刚才我们创建了一个分支,转换到其中进行了一些工作,然后又回到原来的主分支进行了另外一些工作。这些改变分别孤立在不同的分支里:我们可以在不同分支里反复切换,并在时机成熟时把它们合并到一起。而所有这些工作,仅仅需要 branch 和 checkout 这两条命令就可以完成。
  由于 Git 中的分支实际上仅是一个包含所指对象校验和(40 个字符长度 SHA-1 字串)的文件,所以创建和销毁一个分支就变得非常廉价。说白了,新建一个分支就是向一个文件写入 41 个字节(外加一个换行符)那么简单,当然也就很快了。
  这和大多数版本控制系统形成了鲜明对比,它们管理分支大多采取备份所有项目文件到特定目录的方式,所以根据项目文件数量和大小不同,可能花费的时间也会有相当大的差别,快则几秒,慢则数分钟。而 Git 的实现与项目复杂度无关,它永远可以在几毫秒的时间内完成分支的创建和切换。同时,因为每次提交时都记录了祖先信息(译注:即 parent 对象),将来要合并分支时,寻找恰当的合并基础(译注:即共同祖先)的工作其实已经自然而然地摆在那里了,所以实现起来非常容易。Git 鼓励开发者频繁使用分支,正是因为有着这些特性作保障。
 

2、分支的操作

  先初始化一个 git 仓库,并且在仓库中添加一个文件。
 
 
 
 
1
[denggh@18:04:59 Desktop]$ mkdir tmp && cd tmp
2

3
[denggh@18:05:08 tmp]$ git init
4
Initialized empty Git repository in C:/Users/denggh/Desktop/tmp/.git/
5

6
[denggh@18:05:11 tmp] (master)$ echo 'hi git' > hi.txt
7

8
[denggh@18:05:44 tmp] (master)$ ll
9
total 1
10
-rw-r--r-- 1 denggh 197121 7  5  4 18:05 hi.txt
11

12
[denggh@18:05:47 tmp] (master)$ git add .
13
warning: LF will be replaced by CRLF in hi.txt.
14
The file will have its original line endings in your working directory
15

16
[denggh@18:05:50 tmp] (master)$ git commit -m '第一次提交文件到git仓库'
17
[master (root-commit) def6362] 第一次提交文件到git仓库
18
 1 file changed, 1 insertion(+)
19
 create mode 100644 hi.txt
20

 
 

2.1、分支查看:git branch

 
 
 
 
1
[denggh@18:11:21 tmp] (master)$ git branch
2
* master
3

4
[denggh@18:11:26 tmp] (master)$ git branch -v
5
* master def6362 第一次提交文件到git仓库
6

7
// git branch 是分支操作命令,第一个是查看分支,第二个是查询分支的commit id 及最后一次提交信息
 
 

2.2、分支创建:git branch <分支名称>

 
 
 
 
1
[denggh@18:11:40 tmp] (master)$ git branch dev
2

3
[denggh@18:15:28 tmp] (master)$ git branch -v
4
  dev    def6362 第一次提交文件到git仓库
5
* master def6362 第一次提交文件到git仓库
 
 
  执行创建分支的命令之后,我们发现 refs/heads 目录下多了一个 dev文件,我们这个时候查看 dev 与 master 文件内容。
 
 
 
 
1
[denggh@18:18:07 tmp] (master)$ cat .git/refs/heads/master
2
def6362f790dccbf5d04b68034ef8f7c12d9bf3e
3

4
[denggh@18:19:00 tmp] (master)$ cat .git/refs/heads/dev
5
def6362f790dccbf5d04b68034ef8f7c12d9bf3e
 
 
  所以从中我们可以理解,创建分支只是创建了一个指针,想必这个时候读者对上节的内容应该更能理解了:

2.3、分支切换:git checkout <分支名称>

 
 
 
 
1
[denggh@18:25:05 tmp] (master)$ git checkout dev
2
Switched to branch 'dev'
 
 
  分支切换之后,我们发现 HEAD 文件的内容发生了变化,比对前后的内容改变的内容如下:
 
 
 
 
1
// 切换前 HEAD 内容
2
ref: refs/heads/master
3

4
// 切换后 HEAD 内容
5
ref: refs/heads/dev
 
 
  我们发现 HEAD 里面存的是一个文件的位置,他指向的是 refs/heads 目录下的某个文件,而 refs/heads 目录下文件的内容又是一个指针,所以这个时候我们更能清楚的理解上节的内容了。
2.4、删除分支:git branch -d <分支名称>
 
 
 
 
1
[denggh@18:25:11 tmp] (dev)$ git branch -d dev
2
error: Cannot delete branch 'dev' checked out at 'C:/Users/denggh/Desktop/tmp'
3
// 这里报错了,所以删除分支时不能在被删除的那个分支上
4

5
[denggh@18:33:27 tmp] (dev)$ git checkout master
6
Switched to branch 'master'
7

8
[denggh@18:33:44 tmp] (master)$ git branch -d dev
9
Deleted branch dev (was def6362).
10

11
[denggh@18:33:59 tmp] (master)$ git branch
12
* master
13

14

15
// 有时候删除分支时会报错,因为被删除的分支上有文件变化,且没有合并,如果删除分支就会丢失修改内容
16
// 如果确认修改内容不要了,使用 -D 就可以了。
17
[denggh@18:36:52 tmp] (master)$ git branch -d dev
18
error: The branch 'dev' is not fully merged.
19
If you are sure you want to delete it, run 'git branch -D dev'.
20

21
[denggh@18:36:59 tmp] (master)$ git branch -D dev
22
Deleted branch dev (was 165f0e6).
23

24
[denggh@18:37:07 tmp] (master)$ git branch
25
* master
26

 
 

2.5、分支重命名:git branch -m <分支名称> <新名称>

 
 
 
 
1
[denggh@18:40:26 tmp] (master)$ git branch de
2

3
[denggh@18:40:29 tmp] (master)$ git branch
4
  de
5
* master
6

7
[denggh@18:40:32 tmp] (master)$ git branch -m de dev
8

9
[denggh@18:40:38 tmp] (master)$ git branch
10
  dev
11
* master
 
 
2.6、分支合并:git merge -m '<message>' <分支名称>
 
 
 
 
1
[denggh@18:43:37 tmp] (master)$ git branch
2
  dev
3
* master
4

5
[denggh@18:43:41 tmp] (master)$ git merge -m '合并 dev 分支到 master 上' dev
6
Updating def6362..4ab5312
7
Fast-forward (no commit created; -m option ignored)
8
 hi.txt | 2 +-
9
 1 file changed, 1 insertion(+), 1 deletion(-)
 
 
  如上,现在有两个分支 master 和 dev。现在要把 dev 分支上内容合并到 master 上。则切换到 master 上执行名称:
 
 
 
 
1
$ git merge -m '合并 dev 分支到 master 上' dev
 
 

2.7、分支合并冲突解决

 
 
 
 
1
[denggh@18:48:18 tmp] (master)$ git merge -m '合并 dev 分支到 master 上' dev
2
Auto-merging hi.txt
3
CONFLICT (content): Merge conflict in hi.txt
4
Automatic merge failed; fix conflicts and then commit the result.
5

6
// 上面的意思是把 dev 分支合并到 master 上时,在自动合并 hi.txt 的时候发生了冲突。需要我们手动解决冲突然后在进行提交。
 
 
  当两个分支都进行了不同的开发时,尤其是修改了同一个文件之后。在进行合并的时候,就会比较容易导致冲突。现在我们看一看冲突文件 hi.txt 的内容
 
 
 
 
1
<<<<<<< HEAD
2
hi master!
3
=======
4
hi dev!
5
>>>>>>> dev
 
 
  冲突地方以 ======= 当做分隔符号
  <<<<<<< HEAD 和 =======  之间的内容也就是 1 和 3 行之间的内容第 2 行,是 HEAD 所指的分支上的内容,也就是 master 分支内容
  >>>>>>> dev 和 =======  之间的内容也就是 3 和 5 行之间的内容第 4 行,是 dev 所指的分支上的内容,也就是 dev 分支内容
   根据实际需求,手动修改文件
 
 
 
 
1
hi dev!
 
 
  文件修改之后,我们在命令行中查看一下状态
 
 
 
 
1
[denggh@18:55:22 tmp] (master|MERGING)$ git status
2
On branch master
3
You have unmerged paths.
4
  (fix conflicts and run "git commit")
5
  (use "git merge --abort" to abort the merge)
6

7
Unmerged paths:
8
  (use "git add <file>..." to mark resolution)
9
        both modified:   hi.txt
10

11
no changes added to commit (use "git add" and/or "git commit -a")
 
 
  我们执行 git status 发现提示和之前都不一样了。这里提示的意思是:在 master 分支上你有存在未合并的文件,没有合并的文件的是 hi.txt 。如果你解决了冲突,就使用 git add <file> 命令来标记冲突已经解决了。
 
 
 
 
1
[denggh@18:55:49 tmp] (master|MERGING)$ git add hi.txt
2

3
[denggh@18:57:24 tmp] (master|MERGING)$ git commit -m '合并dev分支上内容'
4
[master 4ff90a1] 合并dev分支上内容
5

6
[denggh@18:57:30 tmp] (master)$
 
 
  像上面那样,使用 git add 命令标记冲突解决,然后 git commit 提交即可。
posted @ 2020-05-04 19:18  源来是这样  阅读(930)  评论(0)    收藏  举报