git学习(3)- Git分支的操作管理与工作流程

文章首发于:My Blog 欢迎大佬们前来逛逛

1. Git分支的概念

在Git提交的时候,会保存一个指向文件快照的指针,包含本次提交的作者等相关信息。

同时也包含多个指向文件内容快照的父指针。

我们首先创建三个文件,然后执行如下操作:

git add .
git commit -m 'my first project'

暂存操作会对每一个文件计算校验和(即第一章中提到的 SHA-1 哈希字串),然后把当前版本的文件快照保存到 Git 仓库中(Git 使用 blob 类型的对象存储这些快照)

此时Git仓库中的五个对象:

  1. 三个 blob 类型的对象
  2. 一个记录着目录树内容及其中各个文件对应 blob 对象索引的 tree 对象;
  3. 以及一个包含指向 tree 对象(根目录)的索引和其他提交信息元数据的 commit 对象

Git 的分支 本质上就是指向一个commit对象的可变指针

Git使用master作为分支的默认名字,并且master始终指向你最后一次提交的对象,他会自己更新


使用Git创建一个新的分支:叫做testing

git branch testing

则testing 会和 master一样,一起指向 最后一次提交的commit对象,testing 不会覆盖 master

Git如何知道你在哪个分支上工作的呢?

  • Git有一个HEAD指针:指向你正在工作中的本地分支的指针

运行git branch 之后我们仅仅是创建了一个分支,但是实际工作分支仍然是默认的master,即我们的HEAD指针指向master我们可以使用下面的命令切换分支

git checkout testing

这样我们的HEAD指针就指向了 testing分支


此时我们再次提交

git commit -m 'second pro'

此时我们的工作分支testing 和HEAD指针会指向我们最新的这次提交,而master分支指向的是之前的提交。

即我们的每次提交只会改变当前工作分支与 HEAD指针,其他分支保存的仍是之前的历史版本

如果此时我们输入命令:

git checkout master

则我们现在的工作目录是master分支,HEAD指针也指向master。

这就是实现了在历史版本中切换的目的!也就是说,现在开始所做的改动,将始于本项目中一个较老的版本。它的主要作用是将 testing 分支里作出的修改暂时取消,这样你就可以向另一个方向进行开发。


此时我们在master分支中进行提交:

git commit -m 'three pro'

就会产生如下效果:

我们的这次提交 与 上一次的 second pro 处于同一层中,即在树结构中他们的父节点都是之前master和HEAD所在的位置,即之前的节点拥有了两个父节点

由于 Git 中的分支实际上仅是一个包含所指对象校验和(40 个字符长度 SHA-1 字串)的文件,所以创建和销毁一个分支就变得非常廉价。

Git 在每次提交时都记录了祖先信息

2. 分支的新建与合并

2.1 新建与切换分支

首先我们已经有了几次的提交信息

现在我们接到了一个需求:我们需要实现一个新的功能,则我们需要创建一个新的分支

新建一个分支并且切换到该分支:

git checkout -b iss53

相当于执行下面的命令:

git branch iss53
git checkout iss53

相当于 master 和 iss53 两个指针同时指向了最新的一次提交,并且当前HEAD指针指向 iss53 分支

然后我们开始实现这个功能:

vim xxxx
git commit -m 'new question'

此时我们正在完成 iss53 分支的功能扩展,则此时分支如下所示:


此时我们突然接到了一个紧急电话(注意:iss53 功能并没有完成,但是这个严重错误的优先级非常高)!!! 我们的原来的某个分支版本上出现了一个严重错误,需要紧急修改

假设其在master分支上(当前仍在iss53分支)则我们需要切换到 master 分支上。

git checkout master

然后我们需要创建一个分支来解决这个严重错误。我们新建并切换

git checkout -b hotfix

然后我们解决这个严重错误:

vim xxx
git add xxx
git commit -m 'solve error'

我们便解决了这个问题,并且提交了,此时的分支如下所示:

注意:此时我们HEAD工作目录指向的是 hotfix(即解决了这个重大错误的分支)

我们需要合并到原始的master分支中:

要合并我们需要首先回到master中

git checkout master

2.2 合并分支

关于合并我们使用git merge命令

git merge hotfix

注意:此时我们执行的是 Fast—forword合并,即如果分支的直接父节点是原来的需要合并到的版本,则会直接把master移动到hotfix分支上

如下图所示:

2.3 删除分支

既然我们已经解决了 这个严重错误,并且我们也完成合并了,那么这个 hotfix显然没用了,直接删除这个分支即可:

git branch -d hotfix

则我们继续回到 iss53 来完成这一功能(之前说了,iss53并没有完成,只是 hotfix的优先级高

则切换分支:

git checkout iss53

经过一顿操作后,我们终于完成 iss53 的功能扩展,并且提交

vim ...
git add .
git commit -m '终于完成!'

则此时分支图如下所示:

则最终我们再把这个 扩展 功能与我们的master进行合并:

首先切换到master

git checkout master
git merge iss53

合并后,分支图如下所示:

注意我们的此次合并不同于刚才的严重错误 的合并,刚才master是 解决严重父亲的直接父节点,因此合并就是父节点移动到孩子节点。

但是此次合并的是同一祖先节点的不同兄弟节点,因此我们 采用两个分支与它们的最近公共祖先 三者一起进行合并,最终合并为 C6,并且master指向它。然后删除 iss53 分支即可

git branch -d iss53

3. 管理分支

  1. 列出当前所有分支:
git branch 

其中带星号的表示的是当前工作分支

  1. 查看各个分支的最后一个提交对象的信息
git branch -v

显示如下:

  iss53   93b412c fix javascript issue
* master  7a98805 Merge branch 'iss53'
  testing 782fd34 add scott to the author list in the readmes
  1. 显示已经与当前分支合并的分支:也就是说哪些分支是当前分支的直接上游。
git branch --merged

​ 未合并的分支:

git branch --no-merged
  • 如果我们把已经与当前分支合并的分支,如 iss53(功能如上节所示),则 在--merge 时会显示,则我们可以轻松的删除它: git branch -d

  • 如果我们的 --no-merged 时有任何分支,如testing,则我们无法删除它

    • 因为 Git 判断 iss53已经与master合并了,则master 已经包含了iss53的所有功能,则删除也无妨;testing尚未与 master合并,如果贸然删除可能会丢失数据,因此 Git 在删除 testing 此时会报错。
    • 强制删除: -D

4. 分支开发的工作流程

4.1 长期分支

即把master分支 放在一个比较稳定的即将发布的版本中,然后如果添加什么功能,则直接创建新的分支,这些新的分支是master的平行分支,即是master分支的平行的孩子们。

如果要合并分支,则可以随时 把这些功能分支合并到 master 分支中,即master指向的对象直接指向当前 这个功能的分支上,类似于上面的 解决重大错误的 分支的合并的合并策略。

如下图所示:

你可以用这招维护不同层次的稳定性

某些大项目还会有个 proposed(建议)或 pu(proposed updates,建议更新)分支,它包含着那些可能还没有成熟到进入 nextmaster 的内容。这么做的目的是拥有不同层次的稳定性:当这些分支进入到更稳定的水平时,再把它们合并到更高层分支中去。再次说明下,使用多个长期分支的做法并非必需,不过一般来说,对于特大型项目或特复杂的项目,这么做确实更容易管理。

4.2 特性分支

一个特性分支是指一个短期的,用来实现单一特性或与其相关工作的分支

在Git 中创建分支是非常轻松的,因此特性分支使用非常普遍

特性分支的流程:

  1. 工作目录在master中,干到C1的时候,突然出现了一个问题,则转到iss91解决这个问题
  2. iss91 中 干到C6的时候,发现 貌似有更好的办法 iss91v2 可以解决这个问题,则在合适的位置 C4进行另一个方法分支的解决
  3. 做到 C8的时候,突然没思路了,则先写会的,即先把master中其他的完成,转移到master完成到 C10之后, iss91v2 突然有思路了,则继续回到完成C11,之后提交 iss91v2
  4. 但是此时又冒出个奇妙的想法,因此 dumbidea在master中测试一下,做个实验。

最后我们便得到了合并后的分支:

  1. dumabidea 它出色的完成了功能,可以作为master的下一个版本
  2. 使用iss91v2然后抛弃了iss91版本
  3. 最后两个分支合。

5. 远程分支

如果你有一个本地分支 需要和别人分享, git push (远程仓库名) (分支名) 来推送到远程仓库

git push origin master

Git 自动把 master 分支名扩展为 refs/heads/master:refs/heads/master

即取出我在本地的master分支,推送到远程仓库的master分支上去。

运行下面实现相同的功能:

git push origin master:master

你可以把本地分支推送到某个命名不同的远程分支

若想把远程分支叫作 ylh,可以用 git push origin master:ylh 来推送数据。

其他人想要再次从服务器上获取数据时,他们将得到一个新的远程分支 origin/master,并指向服务器上 master 所指向的版本:

git fetch master

如果要把该远程分支的内容合并到当前分支,可以运行 git merge origin/master。如果想要一份自己的 master 来开发,可以在远程分支的基础上分化出一个新的分支来:

git checkout -b master2 origin/master

这会切换到新建的 master2 本地分支,其内容同远程分支 origin/master 一致,这样你就可以在里面继续开发了。

删除远程分支

git push origin :master

记住我们不久前见过的 git push [远程名] [本地分支]:[远程分支] 语法,如果省略 [本地分支],那就等于是在说“在这里提取空白然后把它变成[远程分支]”。

6. 分支衍和

一个分支中的修改整合到另一个分支中有两种方法:

  1. merge:合并
  2. rebase:衍和

最容易的整合方式: merge命令。


什么是衍和

merge 的时候,我们会把两个对象快照和他们的最近公共最先 三者进行一次合并

而现在我们可以把其中一个对象快照里的变化在另一个对象中重新再打一遍。在 Git 里,这种操作叫做衍合(rebase)。有了 rebase 命令,就可以把在一个分支里提交的改变移到另一个分支里重放一遍。

  1. 首先在C3的exp中执行下面的命令:
git rebase master

它的原理是回到两个分支最近的共同祖先,根据当前分支(也就是要进行衍合的分支 experiment)后续的历次提交对象(这里只有一个 C3),生成一系列文件补丁,然后以基底分支(也就是主干分支 master)最后一个提交对象(C4)为新的出发点,逐个应用之前准备好的补丁文件,最后会生成一个新的合并提交对象(C3'),从而改写 experiment 的提交历史,使它成为 master 分支的直接下游,如图 3-29 所示:

总结:C3的exp 现在 衍和到了 C3' 的位置

  1. 然后再执行一次 平行合并
git checkout master
git merge exp

就形成了如下的分支图:

虽然最后与 merge整合得到的结果没有任何区别,但衍合能产生一个更为整洁的提交历史。


衍和的复杂版本:

server的分支包含进来,在master上进行一次衍和操作:git rebase [主分支] [特性分支]

git rebase master server

使用 --onto:取出client分支,找出client与server的最近公共祖先,然后此LCA在 master上做一次衍和操作

git rebase --onto master server client

一旦分支中的提交对象发布到公共仓库,就千万不要对该分支进行衍合操作。

posted @ 2023-03-12 23:08  hugeYlh  阅读(73)  评论(0编辑  收藏  举报