文章目录
- 前言
- 一、对分支的理解
- 二、分支的创建
- 三、分支的切换
-
- [3.1 切换到 dev 分支](#3.1 切换到 dev 分支)
- [3.2 在 dev 分支上进行文件的修改和提交](#3.2 在 dev 分支上进行文件的修改和提交)
- [3.2 来回切换 master 和 dev 分支,查看修改的内容](#3.2 来回切换 master 和 dev 分支,查看修改的内容)
- 四、分支的合并
- 五、分支的删除
- 六、冲突的合并
-
- [6.1 模拟制造冲突](#6.1 模拟制造冲突)
- [6.2 解决冲突](#6.2 解决冲突)
- 七、分支管理策略
-
- [7.1 分支合并模式](#7.1 分支合并模式)
-
- [7.1.1 Fast-forward 模式](#7.1.1 Fast-forward 模式)
- [7.1.2 No-fast-forward 模式](#7.1.2 No-fast-forward 模式)
- [7.2 分支策略:如何高效管理项目的分支](#7.2 分支策略:如何高效管理项目的分支)
-
- [7.2.1 为什么需要分支策略?](#7.2.1 为什么需要分支策略?)
- [7.2.2 分支策略示例](#7.2.2 分支策略示例)
- [八、在开发 dev 分支时处理 master 分支 Bug](#八、在开发 dev 分支时处理 master 分支 Bug)
-
- [8.1 处理开发中的 dev 分支](#8.1 处理开发中的 dev 分支)
- [8.2 新建分支以处理 master 分支 上的 Bug](#8.2 新建分支以处理 master 分支 上的 Bug)
- [8.3 继续开发 dev 分支](#8.3 继续开发 dev 分支)
- [8.4 解决 dev 分支与 master 分支上的冲突](#8.4 解决 dev 分支与 master 分支上的冲突)
- 九、删除未合并的分支:解决中途放弃的功能分支
前言
版本控制系统,如 Git 在软件开发和协作中扮演着关键的角色。它们帮助团队协同工作,跟踪代码的历史变化,解决冲突,以及确保项目的稳定性。而分支管理是版本控制中的一个核心概念,它使开发者能够在不同的方向上并行工作,而不会干扰彼此的进度。本文将深入探讨Git分支管理的方方面面。
一、对分支的理解
分支在Git中扮演着重要的角色,它们就像科幻电影中的平行宇宙,让你能够在不干扰主要项目的情况下开展工作。
想象一下,当你正在电脑前专注学习C++编程时,另一个你在另一个平行宇宙里可能正在努力学习JAVA编程。这两个平行宇宙互不干扰,就像两个独立的世界。然而,在某个时间点,这两个平行宇宙可能会合并,结果是你既掌握了C++,又掌握了JAVA!
让我们通过下面的图示来更好地理解这个概念:
在版本控制中,我们已经知道,每次提交都会形成一个快照,Git 会将这些提交串成一条时间线,这条时间线就可以看作是一个分支。目前为止,只有一个主要的时间线,在 Git 中,这个分支通常被称为主分支,即master
分支。
主分支(master
分支)的时间线:
每次提交,master
分支都会向前移动一步,这意味着随着不断的提交,master
分支的时间线也会变得越来越长。同时,HEAD
指针始终指向当前所在的分支,通常是 master
分支。
分支的概念让我们能够在不破坏主要项目的情况下进行实验、开发新功能或修复错误。接下来,我们将深入了解如何创建、切换、合并和删除分支,以及如何处理合并中的冲突,以及其他与分支相关的重要概念。
二、分支的创建
首先,我们可以使用命令 git branch
来查看当前 Git 中存在哪些分支:
输出显示只有一个分支,即master
分支,而且前面有一个星号(*
),表示当前所在的分支是master
分支。
要创建一个分支,使用的命令依然是 git branch
,只是需要在后面加上新建分支的名称,例如创建一个 dev
分支:
现在已成功创建一个名为dev
的新分支。运行git branch
命令来列出分支时,可以看到两个分支:dev
和master
,而星号(*)表示当前所在的分支是master
分支。
同时,可以查看 .git/refs/heads/
目录中的内容,也可以看到 dev
和 master
这两个分支:
如果再继续查看这两个分支的内容:
可以发现这两个分支指向的 commit id
是相同的,其原因在于, dev
是基于当前 master
分支创建的,因此它们指向内容一样。
例如下图所示:
三、分支的切换
3.1 切换到 dev 分支
此时如果想要切换到刚刚创建的dev
分支,又该如何操作呢?这里使用到的命令是 git checkout [分支名称]
。注意,这里的 git checkout
命令不需要带上 --
,带上这个选项的意思是撤销工作区中的修改。
例如,切换到 dev
分支:
当切换成功后,再次使用 git branch
命令查看当前 Git 中的分支,可以看到星号(*
)就指向了 dev
分支了。
并且,我们可以查看 HEAD
指针的内容:
发现 HEAD
指向的也是 dev
分支,则说明分支已经切换成功了。
HEAD
指向图示:
3.2 在 dev 分支上进行文件的修改和提交
此时,可以尝试修改 ReadMe
文件,新增一行内容:
在修改之后,进行 add
和 commit
操作:
3.2 来回切换 master 和 dev 分支,查看修改的内容
现在,dev
分支的工作完成了,此时再次切换回 master
分支:
然后再查看 ReadMe
文件的内容:
竟然发现刚才修改的内容并不存在,现在赶紧切换会 dev
分支看看:
在 dev
分支上内容还在。为什么会出现这个现象呢?让我们再来看看 dev
分支和 master
分支的指向,发现两者指向的提交已经不一样了:
看到这里就能明白了,因为我们是在 dev
分支上进行修改并提交的,而 master
分支此刻的提交点并没有发生变化,所有两个分支查看到的 ReadMe
文件的内容会有所不同。
此时分支的状态如图如下所示:
当切换到 master
分支时,HEAD
就指向了 master
,当然看不到dev
分支的修改和提交了。
四、分支的合并
为了在 master
主分支上能看到新的提交,就需要将 dev
分支合并到 master
分支,而合并分支需要使用到的命令为:git merge [分支名]
。
例如,想要 master
主分支合并 dev
分支:
- 首先切换到
master
分支
- 合并
dev
分支
可以通过输出的信息看到,合并了 dev
分支之后,master
分支上的 ReadMe
的内容也更新了。
此时,Git 中分支的状态图如下:
查看dev
和 master
文件内容:
可以发现,这个合并分支的过程是直接把 master
指向 dev
的当前提交,只是移动指针,所以速度非常快。这个模式称为 Fast-forward
,即 "快进模式",从上文合并分支时的输出信息就可以看到。当然,分支合并也不是每次都是快进模式,还涉及到其他模式。
五、分支的删除
合并完成后,dev
分支对于我们来说就没用了,此时 dev
分支就可以删除掉,删除分支使用的命令是 git branch -d [分支名]
。注意如果当前正处于某分支下,就不能删除当前分支。
例如,切换至 dev
分支下,再尝试删除:
切换回 master
分支,删除 dev
分支:
此时,Git 中分支的状态图如下:
因为创建、合并和删除分支的速度非常快,所以 Git 鼓励我们使用分支完成某个任务,合并后再删掉分支,这和直接在master
分支上的工作效果是一样的,但过程更加的安全。因为一旦新创建的分支出现了问题,则可以直接删除掉,而不会影响 master
分支。
六、冲突的合并
6.1 模拟制造冲突
可是,在实际分支合并的时候,并不是想合并就能合并成功的,有时候可能会遇到代码冲突的问题。为了演示这问题,创建一个新的分支dev1
,并切换至这个分支。
我们可以使用命令 git checkout -b dev1
一步完成创建并切换的动作,示例如下:
在 dev1
分支下修改 ReadMe
文件,更改文件内容如下,并进行一次提交,例如:
然后再切换至 master
分支修改 ReadMe
文件,更改文件内容如下,并进行一次提交,
现在, master
分支和 dev1
分支各自都分别有新的提交,Git 的分支状态变成了这样:
6.2 解决冲突
如果此时尝试进行合并:
会发现出现了冲突,此时查看 ReadMe
文件:
其中,<<<<<<< HEAD
和 =======
之间的内容表示当前分支的修改内容,而另一个则是 dev
分支合并过来的内容。只有冲突的代码才会放到 <<<<<<< HEAD
和 >>>>>>> dev1
区间里面。此时,这个冲突 Git 不能帮我们解决,只有我们进行手动选择保留哪一个。
此时,手动删除 master
修改的内容,而保留 dev1
分支上修改的内容:
使用 git status
查看状态:
当修复完冲突之后,还需要再次执行add
和 commit
操作。
到这里冲突就解决完成,此时的状态变成了:
⽤带参数的 git log
命令也可以看到分支的合并情况:
git log
命令参数说明:
--graph
:以图形方式显示提交历史,显示分支和合并的关系。--pretty=oneline
:以一行的格式输出每个提交,其中包括提交哈希值和提交消息。--abbrev-commit
:使用较短的提交哈希值。
通过这些参数,可以得到一个以图形方式展示的提交历史,每个提交都以一行的格式显示,并且使用了较短的提交哈希值。提交历史中的每个提交都包括了提交哈希值和提交消息。
在上面输出中,可以看到提交历史中的不同分支(master
和dev1
)以及它们之间的分支合并关系。提交消息也提供了一些关于每个提交的描述信息,以便了解每个提交的内容和目的。
七、分支管理策略
7.1 分支合并模式
在Git中,分支合并可以采用不同的模式,这些模式影响了合并后的提交历史和分支之间的关系。以下是两种常见的分支合并模式:
7.1.1 Fast-forward 模式
Fast-forward(快进)模式是一种分支合并模式,它通常用于合并一个分支到另一个分支,其中目标分支没有新的提交。这种情况下,Git会直接将目标分支指向要合并的分支的最新提交,而不会创建新的合并提交。这会导致分支历史线性,并且目标分支会包含来自要合并的分支的所有提交。
Fast-forward 模式的特点包括:
- 分支历史线性,没有合并提交。
- 目标分支指向要合并分支的最新提交。
这种模式通常在合并较小的、独立的修复或特性分支时使用,以保持分支历史的整洁。
例如下面的例子,演示了 Fast-forward 模式:
上面演示了 Fast-forward 模式的分支合并。下面对其简单说明:
-
首先创建了一个新分支
dev2
,并在该分支上修改了ReadMe
文件,进行了一次提交。这导致dev2
分支前进,它现在指向了这个新提交。 -
然后,切换回
master
分支,并试图将dev2
分支合并到master
分支。由于在合并时,master
分支没有新的提交,Git 可以直接将master
分支移动到与dev2
相同的提交,这就是 Fast-forward 模式。 -
合并完成后,
master
分支指向了与dev2
分支相同的提交,这是一个快速合并,没有创建新的合并提交。可以看到合并信息中显示了 "Fast-forward"。 -
最后,运行了
git log
命令,以图形方式查看提交历史。在输出中,可以看到master
和dev2
分支现在都指向相同的提交9bbf560
,这是由于Fast-forward 合并。
Fast-forward 模式适用于在合并分支时,目标分支没有新的提交,因此可以直接移动目标分支指针,以保持历史线性。这样的合并通常用于合并短期、独立的修复或特性分支。
另外,在这种 Fast forward 模式下,删除分支后,查看分支历史时,会丢掉分支信息,看不出来最新提交到底是 merge
产生的的还是正常提交的。
7.1.2 No-fast-forward 模式
No-fast-forward(非快进)模式是一种分支合并模式,它用于合并分支时创建新的合并提交,即使目标分支没有新的提交。这种模式下,会保留要合并的分支的提交历史,而不是将其线性添加到目标分支。
No-fast-forward 模式的特点包括:
- 创建新的合并提交,以保留分支历史。
- 分支历史分叉,目标分支包含合并提交。
这种模式通常在合并较大的、长期存在的分支(如特性分支或开发分支)时使用,以保留分支的完整历史记录,并清楚地表示哪些提交来自于哪个分支。
要控制分支合并模式,可以使用git merge
命令的--no-ff
选项,来强制创建一个合并提交,即使是 Fast-forward 情况。例如:
bash
git merge --no-ff -m "提交信息" feature-branch
这将会在合并时创建一个新的合并提交,无论是否存在 Fast-forward 的条件。
例如下面的例子,演示了 No-fast-forward 模式:
在上面示例中,演示了No-fast-forward模式的分支合并。面对其简单说明:
-
首先切换到
dev2
分支,并在该分支上再次修改了ReadMe
文件,然后进行了一次提交。这导致dev2
分支前进,它现在指向了这个新提交。 -
接下来,切换回
master
分支,并使用了--no-ff
选项进行合并,还提供了合并提交消息,以表示这是一个使用非 Fast-forward 模式合并的操作。 -
合并完成后,Git 创建了一个新的合并提交(合并提交消息中显示 "Merge made by the 'ort' strategy."),这个提交包含了
dev2
分支和master
分支的历史。在这个合并提交中,会显示两个分支的修改。 -
最后,运行了
git log
命令,以图形方式查看提交历史。在输出中,可以看到master
分支现在包含了一个合并提交8b20b97
,这个提交将dev2
分支的更改合并到了master
中。
No-fast-forward 模式适用于合并分支时要保留分支历史的情况,即使目标分支没有新的提交。这种模式可以帮助我们清楚地了解分支之间的关系,特别是在合并较大的、长期存在的分支时,以保留完整的历史记录。
7.2 分支策略:如何高效管理项目的分支
在软件开发中,Git 分支管理是一个至关重要的方面。它不仅影响到团队的协作,还能够确保项目的稳定性和版本控制。下面,将介绍一种常见的分支策略,以及如何使用它来高效地管理我们的项目。
7.2.1 为什么需要分支策略?
在大多数软件开发项目中,有多个开发者同时工作,每个开发者都会添加新功能、修复错误或进行其他更改。为了协调这些工作,需要一种良好的分支管理策略,以便保持项目的稳定性、可维护性和可追溯性。分支策略有助于解决以下问题:
-
保持主分支稳定 :主分支(通常是
master
分支)应该保持稳定,只用于发布新版本。这意味着不应该在主分支上进行日常开发,以免引入错误。 -
在开发分支上进行工作 :开发者应该在专门的开发分支(通常是
dev
分支)上进行工作。这些开发分支是不稳定的,可以用于添加新功能、修复错误等。 -
及时合并到主分支:当一个新版本准备好发布时,应将开发分支合并到主分支,并在主分支上发布版本。这确保了每个版本都是稳定的。
7.2.2 分支策略示例
让我们看一个常见的分支策略示例,其中包括主分支、开发分支和个人分支:
主分支(master
):
- 主分支是最稳定的分支,仅用于发布新版本。在日常开发中不要直接在主分支上工作。
- 当新版本准备好时,从开发分支合并到主分支,并发布版本。
开发分支(dev
):
- 开发者在开发分支上进行日常工作。这些分支可以包含多个功能、修复等。
- 当一个功能或修复完成时,将其合并到开发分支。
- 定期将开发分支合并到主分支,以准备发布新版本。
个人分支(Feature Branches):
- 每个开发者可以创建个人分支,用于独立开发特定功能或修复。
- 开发者可以在个人分支上进行实验和测试。
- 当功能或修复完成时,将个人分支合并到开发分支。
分支策略示意图:
下面的图示演示了分支策略的工作流程:
总而言之,使用适当的分支策略可以显著提高团队的协作效率,确保项目的稳定性,并简化版本控制。当多个开发者同时工作时,分支策略能够帮助团队更好地管理代码库,从而更容易地跟踪每个功能和修复的状态。
八、在开发 dev 分支时处理 master 分支 Bug
8.1 处理开发中的 dev 分支
假如我们现在正在 dev2
分支上进行开发,开发到一半的时候,突然发现 master
分支上面有存在 Bug,需要解决。在 Git 中,每个 Bug 都可以通过一个新的临时分支来修复,修复后,合并这个临时分支,然后将临时分支删除。
可现在 dev2
的代码在工作区中开发了一半,还无法提交,怎么办?例如:
此时,开发 dev2
分支还未完成,就通知了在 master
分支上存在一个 Bug 需要立即解决,但是没有提交就直接切换至 master
分支的话,就会提示 ReadMe
文件已经发生了修改。
那么如何解决这个问题呢?幸运的是,Git 提供了应该 git stash
命令,可以将当前的工作区信息进行储藏,被储藏的内容可以在将来某个时
间恢复出来。
例如,再次切换会 dev2
分支,储存工作区内容:
然后查看 .git
目录结构,可以发现多了一个 stash
文件:
使用 git status
命令查看状态,发现工作区是干净的了。
8.2 新建分支以处理 master 分支 上的 Bug
储藏 dev2
的工作区之后,由于我们要基于 master
分支来修复 Bug,所以需要切回 master
分支,再新建临时分支来修复 Bug,示例如下:
切换 master
分支,并创建并切换到 fix_bug
临时分支:
此时 ReadMe
文件内容如下:
假设需要在 aaabbb
后面加上 ccc
才算是修复了 Bug。
在 fix_bug
分支上修复 Bug,经过 add
和 commit
操作之后,切换会 master
分支进行合并。
至此,master
分支上的 Bug 便成功修复了,现在就可以继续开发 dev2
分支了。
8.3 继续开发 dev 分支
要继续开发 dev2
分支,首先需要恢复 dev2
工作区中储藏的内容。首先可以使用命令 git stash list
来查看储藏了哪些分支的工作区内容,然后使用 git stash pop
命令来恢复工作区内容,例如:
当恢复工作区内容之后,就可以继续在dev2
上进行开发了。开发完成之后,执行 add
和 commit
操作存放至暂存区:
8.4 解决 dev 分支与 master 分支上的冲突
但我们注意到了,修复了 master
分支上的 Bug,并完成了 dev2
上的开发,但是由于 dev2
分支是基于为修改 Bug 的 master
分支创建的,上面的 Bug 并没有修复。此时的状态图如下:
因为 master
分支目前是最新的提交,是要领先于新建 dev2
时基于的 master
分支的提交,所以我们在 dev2
中看不见修复 Bug 的相关代码。
我们的最终目的是要让 master
合并 dev2
分支,那么正常情况下我们切回 master
分支直接合并即可,但这样其实是有一定风险的。是因为在合并分支时可能会有冲突,而代码冲突需要我们手动解决(在 master
上解决)。并且无法保证对于冲突问题可以正确地一次性解决掉。
在实际的项目中,代码冲突不只一两行那么简单,有可能几十上百行,甚至更多,解决的过程中难免手误出错,导致错误的代码被合并到 master
上。此时的状态为:
那么如何解决这个问题呢?一个好的建议就是:最好在自己的 dev
分支上先合并 master
分支,最后再让 master
去合并 dev
,这样做的目的是在有冲突的情况下可以在本地分支解决并测试,而不影响 master
分支。
此时的状态为:
dev
分支先合并 master
分支:
master
分支再合并 dev
分支:
下面是处理 dev2
分支和 master
分支冲突的演示:
首先,dev2
分支去合并 master
分支,然后手动处理冲突:
当冲突处理完后进行 add
和 commit
操作,然后再切换至 master
分支,去合并 dev2
分支:
最后,可以通过 git log
命令查看整个分支的处理流程:
九、删除未合并的分支:解决中途放弃的功能分支
在软件开发中,添加新功能是家常便饭。然而,有时候你可能在开发某个新功能时,由于各种原因,不得不放弃它。你可能不希望将未完成的、实验性质的代码混合到主分支中,因此最好的做法是创建一个新的功能分支,在上面进行开发,完成后再将其合并,最终删除该功能分支。
但是,如果你在某个功能分支上开发了一半,然后突然被告知需要停止开发该功能,那么该功能分支就没有继续存在的必要了。这时,传统的git branch -d
命令不能用来删除该分支,因为它尚未完全合并到其他分支。让我们看一下如何处理这种情况。
假设我们创建了一个名为dev
的功能分支,但在开发过程中被中止。下面是删除未合并的功能分支的示例:
首先,我们创建并切换到dev
分支:
bash
[root@VM-16-9-ubuntu gitcode]# git checkout -b dev
Switched to a new branch 'dev'
然后,我们在dev
分支上进行一些修改和提交:
bash
[root@VM-16-9-ubuntu gitcode]# vim ReadMe
# 进行一些修改
[root@VM-16-9-ubuntu gitcode]# git add .
[root@VM-16-9-ubuntu gitcode]# git commit -m "modify ReadMe"
[dev b67e97b] modify ReadMe
1 file changed, 2 insertions(+)
接下来,切换回master
分支:
bash
[root@VM-16-9-ubuntu gitcode]# git checkout master
Switched to branch 'master'
此时,如果我们尝试使用传统的git branch -d
命令删除dev
分支,会收到一个错误消息,因为该分支尚未完全合并:
bash
[root@VM-16-9-ubuntu gitcode]# 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
选项,如下所示:
bash
[root@VM-16-9-ubuntu gitcode]# git branch -D dev
Deleted branch dev (was b67e97b).
通过-D
选项,我们成功删除了未合并的dev
分支。
因此,在软件开发中,管理分支是一项关键任务。当需要删除一个未合并的功能分支时,可以使用git branch -D
命令,但要小心,确保不再需要这个分支。删除未合并的分支可以保持代码库的整洁,但请谨慎操作,以免误删分支。