【Git】轻松学会 Git(二):掌握 Git 的分支管理


前言

版本控制系统,如 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命令来列出分支时,可以看到两个分支:devmaster,而星号(*)表示当前所在的分支是master分支。

同时,可以查看 .git/refs/heads/ 目录中的内容,也可以看到 devmaster 这两个分支:

如果再继续查看这两个分支的内容:

可以发现这两个分支指向的 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 文件,新增一行内容:

在修改之后,进行 addcommit 操作:

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 分支:

  1. 首先切换到 master 分支
  2. 合并 dev 分支


可以通过输出的信息看到,合并了 dev 分支之后,master 分支上的 ReadMe 的内容也更新了。

此时,Git 中分支的状态图如下:


查看devmaster 文件内容:
=

可以发现,这个合并分支的过程是直接把 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 查看状态:

当修复完冲突之后,还需要再次执行addcommit 操作。

到这里冲突就解决完成,此时的状态变成了:

⽤带参数的 git log 命令也可以看到分支的合并情况:

git log 命令参数说明:

  1. --graph:以图形方式显示提交历史,显示分支和合并的关系。
  2. --pretty=oneline:以一行的格式输出每个提交,其中包括提交哈希值和提交消息。
  3. --abbrev-commit:使用较短的提交哈希值。

通过这些参数,可以得到一个以图形方式展示的提交历史,每个提交都以一行的格式显示,并且使用了较短的提交哈希值。提交历史中的每个提交都包括了提交哈希值和提交消息。

在上面输出中,可以看到提交历史中的不同分支(masterdev1)以及它们之间的分支合并关系。提交消息也提供了一些关于每个提交的描述信息,以便了解每个提交的内容和目的。

七、分支管理策略

7.1 分支合并模式

在Git中,分支合并可以采用不同的模式,这些模式影响了合并后的提交历史和分支之间的关系。以下是两种常见的分支合并模式:

7.1.1 Fast-forward 模式

Fast-forward(快进)模式是一种分支合并模式,它通常用于合并一个分支到另一个分支,其中目标分支没有新的提交。这种情况下,Git会直接将目标分支指向要合并的分支的最新提交,而不会创建新的合并提交。这会导致分支历史线性,并且目标分支会包含来自要合并的分支的所有提交。

Fast-forward 模式的特点包括:

  • 分支历史线性,没有合并提交。
  • 目标分支指向要合并分支的最新提交。

这种模式通常在合并较小的、独立的修复或特性分支时使用,以保持分支历史的整洁。

例如下面的例子,演示了 Fast-forward 模式:

上面演示了 Fast-forward 模式的分支合并。下面对其简单说明:

  1. 首先创建了一个新分支dev2,并在该分支上修改了ReadMe文件,进行了一次提交。这导致dev2分支前进,它现在指向了这个新提交。

  2. 然后,切换回master分支,并试图将dev2分支合并到master分支。由于在合并时,master分支没有新的提交,Git 可以直接将master分支移动到与 dev2 相同的提交,这就是 Fast-forward 模式。

  3. 合并完成后,master 分支指向了与 dev2 分支相同的提交,这是一个快速合并,没有创建新的合并提交。可以看到合并信息中显示了 “Fast-forward”。

  4. 最后,运行了git log命令,以图形方式查看提交历史。在输出中,可以看到masterdev2分支现在都指向相同的提交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 情况。例如:

git merge --no-ff -m "提交信息" feature-branch

这将会在合并时创建一个新的合并提交,无论是否存在 Fast-forward 的条件。

例如下面的例子,演示了 No-fast-forward 模式:

在上面示例中,演示了No-fast-forward模式的分支合并。面对其简单说明:

  1. 首先切换到dev2分支,并在该分支上再次修改了ReadMe文件,然后进行了一次提交。这导致dev2分支前进,它现在指向了这个新提交。

  2. 接下来,切换回master分支,并使用了--no-ff选项进行合并,还提供了合并提交消息,以表示这是一个使用非 Fast-forward 模式合并的操作。

  3. 合并完成后,Git 创建了一个新的合并提交(合并提交消息中显示 “Merge made by the ‘ort’ strategy.”),这个提交包含了 dev2 分支和master 分支的历史。在这个合并提交中,会显示两个分支的修改。

  4. 最后,运行了git log命令,以图形方式查看提交历史。在输出中,可以看到master分支现在包含了一个合并提交8b20b97,这个提交将dev2分支的更改合并到了master中。

No-fast-forward 模式适用于合并分支时要保留分支历史的情况,即使目标分支没有新的提交。这种模式可以帮助我们清楚地了解分支之间的关系,特别是在合并较大的、长期存在的分支时,以保留完整的历史记录。

7.2 分支策略:如何高效管理项目的分支

在软件开发中,Git 分支管理是一个至关重要的方面。它不仅影响到团队的协作,还能够确保项目的稳定性和版本控制。下面,将介绍一种常见的分支策略,以及如何使用它来高效地管理我们的项目。

7.2.1 为什么需要分支策略?

在大多数软件开发项目中,有多个开发者同时工作,每个开发者都会添加新功能、修复错误或进行其他更改。为了协调这些工作,需要一种良好的分支管理策略,以便保持项目的稳定性、可维护性和可追溯性。分支策略有助于解决以下问题:

  1. 保持主分支稳定:主分支(通常是master分支)应该保持稳定,只用于发布新版本。这意味着不应该在主分支上进行日常开发,以免引入错误。

  2. 在开发分支上进行工作:开发者应该在专门的开发分支(通常是dev分支)上进行工作。这些开发分支是不稳定的,可以用于添加新功能、修复错误等。

  3. 及时合并到主分支:当一个新版本准备好发布时,应将开发分支合并到主分支,并在主分支上发布版本。这确保了每个版本都是稳定的。

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,经过 addcommit 操作之后,切换会 master 分支进行合并。

至此,master 分支上的 Bug 便成功修复了,现在就可以继续开发 dev2 分支了。

8.3 继续开发 dev 分支

要继续开发 dev2 分支,首先需要恢复 dev2 工作区中储藏的内容。首先可以使用命令 git stash list 来查看储藏了哪些分支的工作区内容,然后使用 git stash pop 命令来恢复工作区内容,例如:

当恢复工作区内容之后,就可以继续在dev2上进行开发了。开发完成之后,执行 addcommit 操作存放至暂存区:

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 分支,然后手动处理冲突:

当冲突处理完后进行 addcommit 操作,然后再切换至 master 分支,去合并 dev2 分支:

最后,可以通过 git log 命令查看整个分支的处理流程:

九、删除未合并的分支:解决中途放弃的功能分支

在软件开发中,添加新功能是家常便饭。然而,有时候你可能在开发某个新功能时,由于各种原因,不得不放弃它。你可能不希望将未完成的、实验性质的代码混合到主分支中,因此最好的做法是创建一个新的功能分支,在上面进行开发,完成后再将其合并,最终删除该功能分支。

但是,如果你在某个功能分支上开发了一半,然后突然被告知需要停止开发该功能,那么该功能分支就没有继续存在的必要了。这时,传统的git branch -d命令不能用来删除该分支,因为它尚未完全合并到其他分支。让我们看一下如何处理这种情况。

假设我们创建了一个名为dev的功能分支,但在开发过程中被中止。下面是删除未合并的功能分支的示例:

首先,我们创建并切换到dev分支:

[root@VM-16-9-ubuntu gitcode]# git checkout -b dev
Switched to a new branch 'dev'

然后,我们在dev分支上进行一些修改和提交:

[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分支:

[root@VM-16-9-ubuntu gitcode]# git checkout master 
Switched to branch 'master'

此时,如果我们尝试使用传统的git branch -d命令删除dev分支,会收到一个错误消息,因为该分支尚未完全合并:

[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选项,如下所示:

[root@VM-16-9-ubuntu gitcode]# git branch -D dev 
Deleted branch dev (was b67e97b).

通过-D选项,我们成功删除了未合并的dev分支。

因此,在软件开发中,管理分支是一项关键任务。当需要删除一个未合并的功能分支时,可以使用git branch -D命令,但要小心,确保不再需要这个分支。删除未合并的分支可以保持代码库的整洁,但请谨慎操作,以免误删分支。

猜你喜欢

转载自blog.csdn.net/qq_61635026/article/details/133247991