主页
文章
分类
标签
关于
Git的分支管理
Git入门
发布于: 2025-3-11   更新于: 2025-3-11   收录于: Git
文章字数: 4668   阅读时间: 10 分钟   阅读量:

前言

Git的分支管理是Git最难的部分,且是其重中之重,关于 Git的分支管理,最好移步廖雪峰老师,我这里主要是做一个学习的记录,可能某些地方表述有缺失或者误解。

含义

我们每一次的提交都是一个版本,都对应一个版本号,而一个分支就是将这些版本串在一起的一条时间线,多个分支就相当于从某个版本结点开始出现了另一条时间线,在另一条时间线里可能会做着与原时间线不同的事,它也可能在未来的某个结点与原时间线合并。

从具体应用上来讲,我们默认有一个master分支,通常这个分支存放的是已经具备完整功能的主代码,当我们需要开发一个新功能时,我们会新建一个分支feature来进行开发,在功能开发完毕后合并到原分支当中。

创建和合并分支

在默认情况下,我们有一个master分支,master会指向自己的最新提交版本,而我们的HEAD则是指向master,来间接指向对应版本。 alt text 当我们创建一个新的分支dev,此时dev会指向与master指向相同的版本,当我们切换到dev时,则是把HEAD指向dev。 alt text 此时如果我们进行版本提交,就会在dev上进行版本的调整,而master是不会受到影响的。 alt text 当我们在dev上完成的工作,想把版本合并到master上,就是将master指向与dev指向相同的版本。 alt text 在合并结束后,我们还可以删除dev节点,此时master节点也不会受影响。 alt text

具体实现: 我们可以利用switch来进行分支的创建

1
2
git switch -c dev
# Switched to a new branch 'dev'

此时我们就创建了dev分支,并且显示我们已经切换到了该分支。

  • git switch命令加上-c参数表示创建并切换分支
  • 我们可以通过 git banch <branch> 来创建分支,但此时不会切换,还会在master节点上,我们需要 git switch <branch>来切换分支
  • switch命令是git新版本出现的,在旧版本下,我们可以通过git checkout <branch>来切换分支

创建完分支后,我们可以通过git branch来查看所有分支,*代表当前所处分支

1
2
3
git branch
# * dev
#   master

现在我们在新的分支上做一些修改,并进行提交:

1
2
git add test.txt
git commit -m "test: change branch dev"

完成之后,我们将分支切换回master,就会发现内容变回去了

1
git switch master

切回到master分支上,现在我们将dev上的内容合并到master上:

1
2
3
4
5
git merge dev
# Updating c5c4206..4e39e03
# Fast-forward
#  test.txt | 3 ++-
#  1 file changed, 2 insertions(+), 1 deletion(-)

我们再次查看内容,发现master的内容与dev一致了。同时,输出信息告诉我们这次合并是Fast-forward“快进模式”,也就是直接把master指向dev的当前提交。

我们查看一下log,可以看到里面也有了dev分支当中的提交记录:

1
2
3
4
5
6
git log
# commit 4e39e03e39fdf07f087f3d02a1442b0383f82720 (HEAD -> master)
# Author: MJYEE <mjyeecloud@outlook.com>
# Date:   Tue Mar 11 23:20:02 2025 +0800

#     test: change branch dev

合并完成后,我们就可以删除掉dev分支了:

1
git branch -d dev

«««< HEAD

冲突

前面创建和合并分支我们都是在最简单的情况下进行的,但是在多人合作的情况下有时就会发生修改冲突。

比如说我现在仓库里的test.txt,在master上它内容为:

1
2
this is the third commit
here is the new branch dev

然后我在dev分支下修改并提交为:

1
2
3
this is the third commit
here is the new branch dev
i have change it once again

此时如果合并,就是前面的快速合并,也就是最简单的情况,但如果我的master又更改且提交了:

1
2
this is the third commit
change master

alt text

此时如果我们用master去合并dev,就会发现它无法合并:

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

Git告诉我们test.txt中发生了合并冲突,让我们修复再提交结果,此时我们查看一下status,也发现它有无法合并的部分:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
git status
# #On branch master
# 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:   test.txt

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

我们现在直接打开test.txt:

1
2
3
4
5
6
7
this is the third commit
<<<<<<< HEAD
change master
=======
here is the new branch dev
i have change it once again
>>>>>>> dev

Git用<<<<<<<=======>>>>>>>标记出不同分支的内容,我们只需要将其中的内容修改为同一个就行:

1
2
this is the third commit
the same result

然后再添加和提交:

1
2
git add test.txt
git commit -m "test: fix the merge conflict"

这样就解决了冲突: alt text

同时我们在log下也可以发现master已经成功合并dev:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
git log
# commit 7ee940cd287608256ddc4418b0b59bf4477e27de (HEAD -> master)
# Merge: 47a6eb5 e75c1df
# Author: MJYEE <mjyeecloud@outlook.com>
# Date:   Wed Mar 12 15:03:44 2025 +0800

#     test: fix the merge conflict

# commit 47a6eb5fd7c475e7051dac9ca7f8cfc86882c392
# Author: MJYEE <mjyeecloud@outlook.com>
# Date:   Wed Mar 12 14:53:53 2025 +0800

#     test: change master

# commit e75c1df7450eff4eafa98f83ccc8f435d22373bc (dev)
# Author: MJYEE <mjyeecloud@outlook.com>
# Date:   Wed Mar 12 14:53:00 2025 +0800

#     test: change dev

或者用git log --graph命令可以看到分支合并图

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
git log --graph 
*   commit 7ee940cd287608256ddc4418b0b59bf4477e27de (HEAD -> master)
|\  Merge: 47a6eb5 e75c1df
| | Author: MJYEE <mjyeecloud@outlook.com>
| | Date:   Wed Mar 12 15:03:44 2025 +0800
| | 
| |     test: fix the merge conflict
| | 
| * commit e75c1df7450eff4eafa98f83ccc8f435d22373bc (dev)
| | Author: MJYEE <mjyeecloud@outlook.com>
| | Date:   Wed Mar 12 14:53:00 2025 +0800
| | 
| |     test: change dev
| | 
* | commit 47a6eb5fd7c475e7051dac9ca7f8cfc86882c392
|/  Author: MJYEE <mjyeecloud@outlook.com>
|   Date:   Wed Mar 12 14:53:53 2025 +0800
|   
|       test: change master
| 

所以当我们的合并遇到冲突时,我们需要先解决冲突,然后再提交,完成最终合并。

禁用Fast forward模式

在我们前面的情况下,我们都是Fast forward模式,在这个模式下,当我们删除合并后的分支后,我们就会丢失原本分支的信息,不知道这个合并的分支是什么,比如master和并dev后,在log中,我们能看到哪个版本是dev下提交的,但是如果我们在这个模式下删除后,这个消息就会消失。

如果我们要保留分支信息,需要禁用这个Fast forward模式,如果要禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息 我们先来创建一个dev分支,并修改提交test.txt中的内容:

1
2
3
4
5
git switch -c dev
# 修改test.txt的内容不写了
git add test.txt
git commit -m "test: create dev and change test.txt"
git branch master

现在我们用master合并dev,并禁用Fast forward模式:

1
2
3
4
git merge --no-ff -m "master merge dev with no-ff" dev
# Merge made by the 'recursive' strategy.
#  test.txt | 4 +---
#  1 file changed, 1 insertion(+), 3 deletions(-)

此时我们打开log:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
git log --graph 
# *   commit 16d440d1aadda7737ec3062b0f1e558ba9101c85 (HEAD -> master)
# |\  Merge: 7ee940c aae17ed
# | | Author: MJYEE <mjyeecloud@outlook.com>
# | | Date:   Wed Mar 12 16:00:18 2025 +0800
# | | 
# | |     master merge dev with no-ff
# | | 
# | * commit aae17ed2ba9b2eda69f5c8d250dc50020cf74dce (dev)
# |/  Author: MJYEE <mjyeecloud@outlook.com>
# |   Date:   Wed Mar 12 15:56:53 2025 +0800
# |   
# |       test: create dev and change test.txt

可以看到,不使用Fast forward模式,merge后就像这样,master直接接在了dev的后头,而不是指向和dev同一个版本上 alt text

此时我们再来删除dev,就可以发现里面还有dev分支的信息

分支策略

在我们正式开发时,我们会有几个原则来管理分支

  • master分支应该是稳定分支的,也就是仅用来发布新版本的,平时不能在上面干活
  • 干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本,当我们组内有多人时,每个人都会有自己的dev分支来独立开发,在完成之后进行合并
  • 2个人都修改了同一个文件的内容才会引发冲突,而不是因为合并了2个不同的分支会引发冲突。事实上,在共同开发过程中,由于大家进行的不是相同的开发任务,只要约定某些公用的文件不去提交本地的修改,大多数情况下合并都不会有冲突。

alt text

Bug 分支

在我们日常开发中bug是肯定会遇见的,而在git中,我们可以通过新建一个临时的bug分支来修复bug,修复完毕后,将分支合并回去,再删除bug分支。

现在举一个具体情况,我们正在开一个dev分支,突然收到一个任务,在master上有一个bug,我们需要新建一个issue-666的分支来修复它。但是我们的dev上还有没提交的内容,因为还没完成它,所以不想直接commit,这个时候我们可以先把工作现场隐藏起来,等完成任务后在恢复现场:

1
2
git stash
# Saved working directory and index state WIP on dev: b07e215 test: change dev

这个时候,我们发现我们修改的内容全部不见了,status里面也是干净的,而且我们现在可以正常切换分支。

现在我们就可以放心地修复bug了,首先我们要确定的就是从哪个分支上创建bug分支,因为是dev是从master上分支来的,同样dev也会有master上的bug,这个稍后再提,现在我们决定从master分支上新建bug分支:

1
2
git switch master
git switch -c issue-666

假定我们的bug出现在test.txt的文本内容上,修改完成之后,我们进行提交,并切回master分支,合并bug分支并删除:

1
2
3
4
5
git add test.txt
git commit -m "fix: fix a bug"
git switch master
git merge --no-ff -m "merged bug fix 666" issue-666
git branch -d issue-666

完成bug的修复后,我们现在该回到dev分支上干活了,可切换回去后发现,原先的内容还没恢复,我们可以先看看stash下的内容:

1
2
3
4
git switch dev
git status
git stash list
# stash@{0}: WIP on dev: b07e215 test: change dev

Git把stash内容存在某个地方了,需要恢复一下,有两个办法:

  • git stash apply stash@{0}恢复,但是恢复后,stash内容并不删除,需要用git stash drop stash@{0}来删除
  • git stash pop,恢复的同时把stash内容也删了
1
2
3
4
5
6
7
8
9
git stash pop
# On branch dev
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git restore <file>..." to discard changes in working directory)
#         modified:   test.txt

# no changes added to commit (use "git add" and/or "git commit -a")
# Dropped refs/stash@{0} (f60cb5a5c16ededefd272e160203ca0ee5f982b7)

现在再用git stash list就看不见内容了。

前面提到了dev分支是从master中分出来的,也存在master的bug,我们也需要修复dev上的bug,但不需要重复前面的操作,只需要将bug分支所做的提交复制到dev上即可,并不是把整个master分支merge过来。 对于这个问题,我们可以使用cherry-pick,把特定分支的一个提交复制到当前分支:

1
2
3
4
5
# 7dd948e是我这边对应的bug提交版本号,不是merge时的版本号
git cherry-pick ae73231
# [dev f6f97bb] fix: fix a bug
#  Date: Wed Mar 12 16:38:55 2025 +0800
#  1 file changed, 1 insertion(+), 1 deletion(-)

Git自动给dev分支做了一次提交,注意这次提交的commit是与原先的bug提交的版本号不同,因为它们只是做了相同的提交操作,但不是一个提交。

同样的如果说你的dev修改了原先的bug文件,这时候进行提交会出现合并冲突,你需要按照前面的操作先解决冲突再提交。

因为master和dev同有一个bug,既然可以在master分支上修复bug后,在dev分支上可以“重放”这个修复过程,那么直接在dev分支上修复bug,然后在master分支上“重放”也是可以的,不过仍然需要git stash命令保存现场,才能从dev分支切换到master分支

删除未合并分支

当我们在一个dev分支开发时,突然被告知这个功能不再需要了,让我们把这个分支删除,此时master尚未合并该分支,如果我们执意要删除当前分支会发生什么:

1
2
3
git branch -d dev
# error: The branch 'dev' is not fully merged.
# If you are sure you want to delete it, run 'git branch -D dev'.

Git就会告知我们该分支没有完成合并,如果我们强制删除需要用大写的D:

1
2
git branch -D dev
# Deleted branch dev (was f6f97bb).

小结

以上便是git中分支的常用操作,再来简单梳理一遍

  • git branch <branch>用于创建一个分支, git switch -c <branch> 用于创建并切换到该分支, git switch <branch> 用于切换到指定分支
  • git branch用于查询分支, git branch -d <branch>用于删除分支, git branch -D <branch>用于强制删除分支
  • git merge <branch>用于合并分支, git merge --no-ff -m "xxx" <branch> 用于在非Fast Forward模式下合并并记录合并信息
  • git stash用于隐藏工作现场去处理其他分支, git stash pop用于恢复上一个隐藏的现场并丢弃, git stash apply stash@{0} 用于恢复指定现场, git stash drop stash@{0}用于丢弃指定现场
  • 当发生合并冲突时, 修改冲突的地方并重新提交, 即可解决冲突
  • 但有bug需要修复时, 先隐藏当前分支现场, 然后去特定分支新建bug分支, 修复并合并bug分支, 在将bug分支的提交复制到其他工作分支git cherry-pick xxxx