Featured image of post Git,你能帮帮我吗?其二

Git,你能帮帮我吗?其二

看看 Git 分支(branch)吧!

上一节已经介绍了平时会怎么用 Git 进行单分支仓库的管理,这一节就来讲讲 Git 要怎么进行多分支协作吧!

头图信息请参考上一节内容,谢谢~

分支,是那个分支吗?

我们上一节已经了解过 Git 在单分支下的日常工作流了。值得注意的是,我们说的是 “单分支”,那么自然,Git 是支持,同时鼓励使用多分支的。那么分支是什么呢?

也许有过 Galgame 经验,或者玩过有分支剧情游戏的你已经想到所谓的 “分支” 是什么东西了。没错,很像这么回事儿,不过功能更丰富一些,因为你不止是体验若干个分支的剧情,Git 甚至可以允许你在没有冲突的前提下合并两个分支!如果有一款游戏支持用 Git 来操控分支的话,也许就可以手动后宫了……

咳咳,不开玩笑了。我们来看看分支具体是什么样的。先来个分支图:

一个也许简单的 Git 分支示意图

gitGraph commit id: "initial commit" commit branch feature1 checkout feature1 commit id: "new feat1, first commit" commit checkout main merge feature1 id: "merge feature1" branch feature2 checkout feature2 commit id: "new feat2" checkout main commit merge feature2 id: "finish, merge feat2"
Git 分支示意图

(嘶,mermaid 竟然直接有 gitGraph 的功能,NB)

那么可以看到,我们这里有三条分支:一条 main, 一条 feature1 以及一条 feature2。有时我们开启了一个分支,有时我们又将两个分支进行了合并。上面的图是怎么生成的呢(双关意)?下面是用到的代码:

 1gitGraph
 2    commit id: "initial commit"
 3    commit
 4    branch feature1
 5    checkout feature1
 6    commit id: "new feat1, first commit"
 7    commit
 8    checkout main
 9    merge feature1 id: "merge feature1"
10    branch feature2
11    checkout feature2
12    commit id: "new feat2"
13    checkout main
14    commit
15    merge feature2 id: "finish, merge feat2"

Mermaid 的 gitGraph 很有趣的地方在于,上面的代码几乎就是为了实现这样的提交树/分支形状所需要的 Git 命令。我们可以不管 id 后面的部分,因为这些在实际 commit 的时候应该是用 -m 来指定的提交信息才对。

那么,这些命令都干嘛了?要怎么用命令来操控分支?

和分支相关的命令们

下面来讲讲上面出现的(和没出现的一些)命令吧~

  • git checkout

    说实在的,这个命令真的是个很大的坑。git checkout 从 Git 诞生之初就已经存在,它是集创建、管理、变更分支或提交等功能为一体的一个命令。造成这个情况的主要原因在于 git checkout 实际上不是在我们现有的对 Git 存储模型的理解上进行操作,而是在 Git 更贴近实现层面的操作,即移动 “指针”。

    然而,我们这里先不打算介绍这么深入/详细。我们还是从实用角度来聊聊这个命令。观察上面的 Mermaid 图,我们可以看到,好像 git checkout 的功能没有直接体现在图上。然而仔细观察的话可以猜到,git checkout 在这里的作用是更换分支。比如,git checkout main 就是告诉 Git “现在我要切换分支到 main 分支上”。这是 git checkout 的主要用途之一。另外我们还可以用 git checkout - 来像 cd - 一样切换到上一个分支。

    我们还可以对这个命令多讲一些。如果给它带上 -b 的参数则可以用来创建一个新分支。比如 git checkout -b new-branch 就可以创建一个新的名为 new-branch 的分支,同时你还会直接切换到该分支上。而如果你在后面带的参数是某个文件或者单纯的 .,则是要让 Git 该文件/所有文件里没有暂存的更改。

    上面说的都是比较老派的做法。相信你也一定从上面的 Mermaid 图中猜到了新式的创建新分支的方法,那就是:

  • git branch

    这个命令是用来管控和单个分支相关的操作的。我们简要介绍一下。

    如果后面不带任何的参数,则是会打印出可用分支。如果要创建一个新的分支,就可以用 git branch <another-branch>, 就是让 Git 尝试创建一个名为 <another-branch> 的分支。当这个分支已经存在的时候,Git 就会报错,告诉你已经有了叫这个名字的分支了。

    要注意的是,git branch <branch-name> 只会创建分支,并不会把当前分支更改到这个新分支上。要想在创建分支后切换分支,除了传统方式 git checkout 外,还可以使用更现代(?)的命令:git switch -c <branch-name>。我们后面会介绍到。

    除了创建分支以外,我们肯定还希望能实现查看/删除/重命名分支。我们干脆都列在下面吧。如果不想看可以跳过这一段。

    • 要查看分支,可以直接 git branch。如果要看所有的分支(包括远程的),可以使用 git branch -a 来查看。你还可以使用 -v 来输出上次提交的信息。
    • 要创建分支,就像上面说的,在后面补上你要的分支名称,即 git branch <branch-name>。如果这个分支已经存在则会报错,另外这个命令只会创建,并不会切换过去。
    • 如若要从某个提交上创建分支,还可以在 <branch-name> 后面添加上 <commit-hash>。至于 <commit-hash> 是什么,我们在后面关于 Git 的一些概念里进行介绍。
    • 想要删除分支,可以用 git branch -d <branch-name> 来删掉它。要是你要删除当前分支,请先切换到别的分支哦。
    • 要是打算重命名分支,可以考虑像操作文件一样 移动 它:git branch -m <branch-name> <new-name>。依旧,这个命令也只能更改别的分支。

    So, that’s it! Git 针对单分支的操作都可以用 branch 子命令来做到。那么,我们要怎么切换分支呢?除了 checkout 以外,“比较现代”(存疑)的方法是使用:

  • git switch

    这个命令是相对较新的用来切换分支的命令。可以通过 git switch <branch-name> 来简单地实现切换。有趣的是,我们还可以用 git switch -c <branch-name> 来创建新分支的同时切换过去。也就是说,git switch -c 命令和 git checkout -b 几乎是等价的。另外我们可以使用 git switch - 来直接跳回上一个分支。

    另外还可以考虑使用 git switch -m <branch-name> 来在切换分支的同时把当前分支合并到要切换的分支上。这一点还是相当不错的,因为我们经常会遇到这样的情形:在 dev 分支上完成某个特性之后,经过测试希望能合并到 main 分支上。如果没有这条命令的话,我们可能需要先 git checkout main 之后再 git merge dev,而有了这条命令我们就可以简单地 git switch -m main 了。

    总之,如果你需要切换分支,你就可以使用 switch 这个命令。语义很明确,不是吗?

  • git merge

    这个命令,如它的名字一样,是用来合并分支的,或者,不那么明显地,合并到当前分支。它的使用方式相对而言比较简单,就是单纯的 git merge <branch-name>

    这个命令的主要问题是,合并过程中会出现恶魔般的 冲突。解决冲突实在是一件令人头痛的事情(在我看来)。为了避免(逃避)合并冲突后的麻烦,你可以考虑 --abort 参数来告诉 Git 如果合并失败就什么都别动。然而,要是你真想合并,到底还是要解决冲突的。

    其实解决冲突就是一个 “选择应用谁的代码” 的过程。Git 会在发生冲突的地方用箭头标出来本分支和被合并分支的内容,你要做的就是把你不要的那个部分删掉然后保存。另外,合并会创建一个新的提交。如果你不喜欢默认提交信息,可以考虑使用 -e 参数来告诉 Git 你打算自己编辑合并产生的提交的提交信息。

    最后就是 Git 合并时有不同的策略。我们这里不多介绍,大部分情况可以使用 ff 模式,即 Fast Forward 模式。这个模式会让你的提交树看起来是一条直线,即如果历史提交相同的话就让两个分支有同样的提交了。

分支相关的基本命令我们就先介绍到这里吧。有了上面的介绍,相信你已经可以运用 Git 的分支功能了吧~

Git 的概念们

然而,止步于介绍使用 Git 的方式,总是觉得不够透彻。知其然还要知其所以然,我们既然是在介绍 Git,那就尝试把 Git 更深一些(其实也没那么深)的概念多介绍一些吧。

仓库 (Repository)

我们几乎所有的 Git 项目都是从建立或者克隆 Git 仓库开始的。仓库是一个比较大的概念,我们和 Git 相关的所有内容都是要从仓库出发的,所有的信息都会存储在仓库中。

那么 “所有信息” 都有什么呢?这个问题会比较深,我们从表观的理解来讲,首先肯定得有我们工作内容息息相关的内容,毕竟 Git 就是用来管理它们的。另外就是和 Git 相关的内容了,大部分都存储在 .git 文件夹中,还有一些零散的 .gitignore 文件。其中 .git 存储了这个仓库的所有和 Git 直接相关的内容,例如文件快照,提交记录,不同的分支记录等等,都会以特殊的结构记录下来。这也意味着,如果你删了 .git 文件夹,那么这个仓库就没了,Git 的记录就全都消失啦。删除之前要好好想清楚咯~

然后 .gitignore 也是能控制 Git 行为的文件。它能够让 Git 不记录某些文件。比如说你有一些测试文件,它们其实不应该被记录在仓库里,只希望在本地有一份方便测试而已,那么就可以把他们的名字或者所在文件夹写进 .gitignore 里。

总之,Git 仓库就是这么个总的玩意儿了。有时我们会简称仓库英文为 repo,我还挺喜欢这个名字。

工作目录 (Working directory / Working tree)

这实际上就是我们正在编辑的项目目录。比如说我们从网上克隆了一个仓库之后,我们会进入这个仓库的目录里。这个仓库的根目录就是所谓的工作目录了。至于为什么叫 “工作树”,我个人看法是因为 Git 分支的存在让整个仓库像树一样伸展开,或者是说目录下的文件层级结构像树一样吧。不过怎么想都觉得有点怪,毕竟如果是说仓库分支的话,我们应该是在树叶上而不是在树上吧……

然而,不深究的话,我们干活儿的地方就是工作目录。就是这样。

暂存区 (Staging Area)

其实我们应该已经介绍过暂存区了。就像它的名字一样,暂存区是用来暂时存下 “觉得改的差不多了” 的内容的地方。我们用 git add 命令来把修改好的内容放在暂存区内等待提交。如果感觉暂存区的内容有不妥的地方,我们可以随时打回来重新修改。我们也可以把一些内容从暂存区撤下来。总之,暂存区给了我们再次考虑的机会。而假如我们认为 “暂存区的东西我很满意,可以提交了”,我们就可以用 git commit 来提交 暂存区 的内容到分支上(或者仓库,取决于你怎么看这个行为)。

总之,暂存区就是一个介于 “保存文件” 和 “保存整个工作目录状态” 之间的一个地方。这也决定了 Git 的工作流是 修改文件 -> 保存文件 -> 交给暂存区 -> 提交至分支/仓库

分支 (Branch)

相信你已经对分支有所了解了。我们在创建仓库的同时,会创建一个主分支,曾经主分支名称为 master,后来因为一些政治原因,现在更多叫 main 了。除了主分支外,我们还可以有很多别的分支。这些分支允许我们在仓库里存储不同的信息,不同分支间不会产生干扰,而在我们希望的时候我们又可以对分支们做出诸如合并、删除等的改动。

分支就像平行世界一样,我们可以让两个分支拥有同样的过去,在某个地方发生变化,最后独立演化下去。而分支胜过平行世界的地方在于,我们可以在没有直接分歧的情况下把两个分支合并在一起,而不会出现 “我才是蜘蛛侠” 的问题。

分支可以说是 Git 的灵魂和精髓了。推荐多运用分支进行项目管理,相当好用。遇事不决开个分支先测试一下,这不失为一个好办法。

提交 (Commit)

我们有了一个分支之后我们就需要向这个分支不断做出提交了。每一次的提交都会让这个分支的记录变多一些,分支实际上也是记录的每一次的提交。大白话讲,提交就是存档,只不过这些存档要依附在某个世界线(分支)上而已。

提交可以说是组成分支的部分。当我们查看分支具体有什么的时候,映入我们眼帘的就是每一次的提交记录。所谓的合并分支,也不过是比较两个分支之间的提交情况,如果没有冲突的提交就可以顺利合并了。

要注意的是,在 Git 里我们不提交文件本身,我们提交的是文件的变更。也正是由于 变更 这一关键特征,让 Git 可以高效地进行版本控制,不过坏处也有,那就是面对二进制文件就显得有点笨笨的了:二进制文件可以认为是一变全变的,不像文本那样可以有明显的局部改动。这也说明我们应该尽量让 Git 记录纯文本的文件而非二进制文件。

另外,需要再提醒的是,提交只会提交暂存区内的内容。如果有改动发生但没有放在暂存区里的话,提交是不会搭理这些改动的。这一点还请注意。

远程(Remote)

虽然我们还没有介绍太多和远程仓库/托管平台的内容,但远程仓库确实是在 Git 设计之初就已经有了的关键概念了。

我们介绍过,Git 一开始的设计目的是所谓 分布式 版本管理系统。这个 分布式 就在于每个人都可以拥有一份源代码,然后大家可以互相传递自己的修改,也可以自由选择是否进行合并别人的修改。这样去中心化的特点是相当超前的设计。而为了实现这样的设想,我们必须让 Git 拥有连接到别人仓库的能力。远程也正是这么个东西。

Git 可以把网络上的仓库作为自己的远程库来使用。我们通常不直接和远程库中的文件交互,而是把提交作为基础单元和远程库进行交互。当我们有了新的提交或者新的分支时,我们就可以把本地的这些改动 推送 (push) 到远程仓库;当远程仓库有了新的变动时,我们可以把新的变动 拉取 (pull) 到本地来。我们会在下一节对 Git 的远程功能进行更详细的介绍。

总之,Git 的远程仓库让一份代码可以被保存在多个位置,并且让我们和这些位置的仓库进行交互,这样就能让我们和别人进行协作了。然而,由于现实协作的众多需求,最终 Git 还是发展出了很多代码托管平台,来方便大家存储 Git 的远程库,并让大家在远程库上进行协作,避免直接塞给别人电脑上。

后记

我必须立刻承认我这篇文章离不开 tldr,准确来说是 tealdeer 的帮助。很难想象没有 tldr 我要怎么介绍可用命令。唉,我还是对 Git 不够熟悉。如果里面有任何的错漏,又或是对这个系列有什么建议,请直接告诉我,谢谢,我会及时修改的(球球了,告诉我哪里写的不好吧,呜呜呜)。

另外我还想推荐一个很不错的网站,Learn Git Branching,一个让你在实际操作中练习 Git 分支管理的网页,从进行提交,创建分支,合并分支,到变基 (Rebase),远程库协作等复杂操作,全都有涉猎。我花了一下午通关,收获很大,因此墙裂建议。

下一节就是我们的最后一节内容,我打算聊聊 Git 的远程协作功能,以及协作时的注意事项等等。另外,由于深感 Git 命令之繁杂,我有计划做一个小工具来通过问答的方式给出合适的 Git 命令。我暂时将这个工具命名为 Giao,希望不会难产吧,哈哈。有兴趣的话也可以关注我/给我提建议,谢谢啦。

Licensed under CC BY-NC-SA 4.0
最后更新于 Aug 16, 2025 18:36 UTC