问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

Git分支详解:从基础操作到高级应用

创作时间:
作者:
@小白创作中心

Git分支详解:从基础操作到高级应用

引用
CSDN
1.
https://blog.csdn.net/cold___play/article/details/136780650

Git分支是Git版本控制系统中的一个重要概念,它允许开发者在不同的分支上进行独立开发,从而实现代码的并行开发和管理。本文将详细介绍Git分支的基本操作、原理、合并、冲突解决等多个方面,帮助读者全面理解Git分支的使用方法。

Git分支基础操作

新增分支

在Git中使用分支很简单,只要使用git branch命令即可:

git branch cat

切换分支

要切换分支,就是git checkout

git checkout tiger

删除分支

如果要删除的分支还没有被完全合并,Git会有贴心小提示:

git branch -d tiger

因为tiger的内容还没有被合并,所以使用-d参数无法将其删除。这时只需改用-D参数即可将其强制删除:

git branch -D tiger

分支原理

可以把分支想象成一张贴纸,贴在某一个Commit上面:

当做了一次新的Commit之后,这个新的Commit会指向它的前一个Commit:

而接下来“当前的分支”,也就是HEAD所指的这个分支,会贴到刚刚做的那个Commit上,同时HEAD也会跟着前进:

切换分支时的逻辑

Git在切换分支时主要做了以下两件事:

  1. 更新暂存区和工作目录
  2. 变更HEAD的位置

合并分支

合并示例

在前面的例子中,从master分支开了一个cat分支,随后修改了index.html和新增了cat3.html文件,我们进行两次Commit,现在大概是这个样子:

任务执行得差不多了,就要准备合并回来了。如果想要用master分支来合并cat分支,就要先切换回master分支:

git checkout master

接下来,使用git merge命令合并分支:

git merge cat

查看一下文件列表:

因为master现在已经合并了cat分支,所以在cat分支新增的cat3.html文件在master分支也出现了。

A合并B与B合并A区别

在此假设从master分支创建了cat和dog两个分支,并且当前正在cat分支:

cat分支与dog分支都是来自master分支,所以不管master是要合并cat分支还是dog分支,Git都会直接使用快转模式(Fast Forward)进行合并,也就是master直接“收割”cat或dog的成果。

但如果是cat与dog这两个分支要互相合并就不一样了,虽然它们有同样的来源,但要合并就不会这么顺利了。在这种情况下,Git会生成一个额外的Commit来处理这件事。一般的Commit只会指向某一个Commit,但这个Commit会指向两个Commit,明确地标记是来自哪两个分支。

来看看会怎么演变。假设想用cat分支来合并dog分支,可用如下命令:

git merge dog

执行这个命令时会弹出一个Vim编辑器窗口,为了进行这次合并,Git做出了这个额外的Commit对象,这个Commit会分别指向cat分支和dog分支,HEAD随着cat分支往前,而dog分支停留在原地:

如果改由dog分支来合并cat分支,则使用如下命令:

git merge cat

程序上与刚才几乎是一样的。这时的状态如图:

其实就结果来看,不管是谁合并谁,这两个分支的文件最后都得到了。

合并过的分支要保留吗

结论:都可以,看自己的需要。

基本上,分支就是一个只有40字节的文件而已。这个分支,也就是这40个字节,会标记出它当前指向哪一个Commit。不管是上节提到的快转模式(Fast Forward)还是非快转模式,只要该分支被合并过,就代表“这些内容本来只有你有,现在我也有了”。

既然合并后,原本没有的内容都有了,而分支本身又像一张贴纸一样“地位低微”,那么它也就没有利用价值了。所以,合并过的分支想删就删吧。删除分支就只是把一张贴纸撕下来而已,原来被这张贴纸贴着的东西并不会因此而不见。

当然,没有被合并过的分支就是另一回事了。回到原来的问题——合并过的分支要留着吗?都可以,如果想要删掉,或者已经和这个分支建立“感情”了,想留着做纪念也可以。

使用Rebase合并

使用Rebase合并分支

准备环境:

git branch dog
git checkout dog
echo "dog1" > dog1.txt
echo "dog2" > dog2.txt
git add .
git commit dog1.txt -m "dog1"
git commit dog2.txt -m "dog2"
git branch cat
git checkout cat
echo "cat1" > cat1.txt
echo "cat2" > cat2.txt
git add .
git commit cat1.txt -m "cat1"
git commit cat2.txt -m "cat2"

状态如图:

在Git中还有一个命令叫作git rebase,也可以用来做与git merge命令类似的事情。从字面上来看,rebase是“re”加上“base”,其中文含义大致是重新定义分支的参考基准。

所谓“base”,就是指“这个分支是从哪里生成的”。以上面这个例子来说,cat与dog两个分支的base都是master。接着使用git rebase命令来“组合”cat和dog这两个分支:

git rebase dog

用通俗的话来说,上述命令的含义大致就是“我(即cat分支)现在要重新定义我的参考基准,并且将使用dog分支作为我新的参考基准”。

Rebase合并方式和一般的合并方式的一个很明显的区别,就是使用Rebase方式合并分支,Git不会做出一个专门用来合并的Commit。

Rebase原理

Rebase的过程大概是这样的:

  1. 先将c68537这个Commit接到053fb2这个Commit上。因为c68537的上一层Commit原本是e12d8e,现在要接到053fb2上,所以需要重新计算这个Commit的SHA-1值,重新做出一个新的Commit对象35bc96。
  2. 再拿b174a5这个Commit接到刚刚做出来的Commit对象35bc96上。同理,因为b174a5这个Commit要接到新的Commit上,所以它会重新计算SHA-1值,得到一个新的Commit对象28a76d。
  3. 最后,原本的cat是指向b174a5这个Commit的,现在转而指向最后做出来的那个新的Commit对象28a76d。
  4. HEAD还是继续指向cat分支。

原来Commit的去向

原本的那两个Commit(灰色),即c68537和b174a5的去向是哪里?

它们还是在Git的空间中占有一席之地,只是因为已经没有分支指着它们了,所以如果没有特别去记这两个Commit的SHA-1值,它们就会慢慢被边缘化。

但它们并没有马上被删除,只是默默地待在那里,直到有一天被Git的资源回收车拉走。

谁Rebase谁有区别吗

仅从最后的文件来说并没有什么区别,但就历史记录来说则有区别,谁Rebase谁,会造成历史记录的先后顺序不同。

取消Rebase

如果是一般的合并,只要git reset HEAD^ --hard一行命令,删除这个合并的Commit后,这些分支就会退回合并前的状态。但是,从上面的结果可知,Rebase并没有做出那个合并专用的Commit,而是整个都串在一起了,与一般的Commit差不多。所以这时如果执行git reset “HEAD^ --hard命令,只会删除最后一个Commit,但并不会回到Rebase前的状态。

要想取消Rebase,可使用以下三种方式:

  1. 使用Reflog
  2. 使用ORIG_HEAD

在Git中有另一个特别的记录点——ORIG_HEAD。ORIG_HEAD会记录“危险操作”之前HEAD的位置。例如,分支合并或Reset之类的都算是所谓的“危险操作”。通过这个记录点来取消这次Rebase相对来说更简单:

git rebase dog
git reset ORIG_HEAD --hard

实际也是回到a9aa5bf位置的Commit。

合并冲突

发生冲突

Git可以检查出简单的冲突,所以并不是改到同一个文件就一定会发生冲突,但改到同一行就会出现冲突。假设在cat分支改动了index.html文件的内容:

git add .
git commit index.html -m "cat branch update"

然后在dog分支也改动了index.html文件的内容:

git add .
git commit index.html -m "dog branch update"

这时进行合并,不管是一般的合并还是使用Rebase进行合并,都会出现冲突。下面先使用一般的合并:

git merge dog

这时Git发现那个index.html文件出现问题了。我们先看一下当前的状态:

因为index.html文件的两边都被改动了,所以Git把它标记成both modified状态。

解决冲突

查看index.html文件的内容:

Git把有冲突的段落标记出来了,上面是HEAD,也就是当前所在的cat分支,中间是分隔线,下面是dog分支的内容。

也就是说,<<<<<<< HEAD=========之间的内容是当前cat分支的修改,===========>>>>>>>>>> dog之间的内容是dog分支的修改。

这个问题看来是沟通不良造成的。要解决问题,就要把两边的人请过来讨论一下,看看到底该用谁的code。最后决定还是采纳cat分支的内容,顺便把那些标记修掉。最后内容如下:

改完后,切记把这个文件加回暂存区:

git add index.html

然后就可以Commit并完成这一回合了:

git commit -m "conflict fixed"

处理非文本文件的冲突

因为上面的index.html文件是文本文件,所以Git可以标记出发生冲突的点在哪里,用肉眼就能看得出来大概该怎样解决,但如果是图像文件之类的二进制文件该怎么办?例如,在cat分支和dog分支同时加了一张叫作cute_animal.jpg(可爱的动物)的图片,则合并时出现冲突的信息为:

$ git merge dog
warning: Cannot merge binary files: cute_animal.jpg (HEAD vs. dog)
Auto-merging cute_animal.jpg
CONFLICT (add/add): Merge conflict in cute_animal.jpg
Automatic merge failed; fix conflicts and then commit the result.

这时要把两边的人请过来,讨论到底谁才是最可爱的动物。最后决定猫才是最可爱的动物,所以决定用cat分支的文件:

git checkout --ours cute_animal.jpg

如果要用dog分支,则使用--theirs参数:

git checkout --theirs cute_animl.jpg

决定之后,像前面一样将内容加到暂存区,准备Commit,然后结束这一回合。

从过去某个Commit创建分支

历史记录如下:

要从add sql.的Commit(62fb48e)中再做出新的分支,首先要回到那个Commit的状态,这时使用的是git checkout命令:

git checkout 62fb48e

从这段信息中可以看出,当前正处于detached HEAD状态。可以从现在这个Commit开出新的分支。使用Checkout命令的-b参数直接创建分支并自动切换过去:

git checkout -b xxx

如果不想两步操作,也可以一行直接搞定:

git branch xxx 62fb48e

意思就是“请帮我在62fb48e这个Commit上开一个xxx分支”。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号