本节将引导读者学会使用 Git 进行团队协作,包括分支管理、远程仓库、冲突合并,以及 Pull Request 的使用。
12⅝.1 分支编织者 #
“就像命运编织者……”她吐出一个词,“一个很科幻的概念。”
“卟卟!答错啦!不过这也很接近真相了。”他摇了摇手指,“因为线上数据库着了大火,我决定转行做运维,我的‘命运线’就这样改变了;为了和你继续搭伙写代码,我把简历投到了和你一样的公司,我的‘命运线’也因此改变了。从来没有什么架构师在云端绘制着我们的未来,我们的命运因commit而分叉,因merge而扭结,纠纠缠缠,编织成高耦合般的死结……我们,才是命运的织工啊!”
——《百分百可塑性未来》
12⅝.2 红与蓝的线缠绕交错:分支管理 #
在 Git 中,分支(Branch)是一个非常重要的概念。它允许我们在同一个代码库中同时进行多个开发线(比如dev,v2, v4,…)的工作,而不会互相干扰。
默认情况下,Git 会创建一个名为 master(或 main,取决于你的配置)的分支作为主线。我们可以从这个主线分出新的分支来进行开发。
分支管理的主要子命令是 git branch,常用的命令包括:
创建分支:
git branch <branch-name>
# 当然,你愿意的话:
git checkout -b <branch-name>
两条命令的差别是,checkout -b 会在创建分支(如果不存在)的同时切换到那个分支上,而 branch 只是创建分支,不会切换。
切换到分支:
git checkout <branch-name>
在新分支上,我们可以进行独立的开发,提交修改,而不会影响到主线或其他分支上的代码。基本的操作参见 上一节。这里不再赘述。
列出所有分支:
git branch
# 如果想进行简单匹配:
git branch --list <pattern>
删除分支:
git branch -d <branch-name>
合并分支:
# 将 <branch-name> 分支的修改合并到当前分支
git merge <branch-name>
- 关于解决合并冲突,在稍后的 冲突合并 部分会有详细介绍。
上面所有 -x 的短选项都可以替换成 --xxx 的长选项,基本就是命令的全称,比如 --create、--delete、--branch 等等。如果记不住短选项的写法或者搞不清大小写,在查看帮助之前可以试试长选项。
通过分支,不同的人可以在不同的开发线上进行独立的工作,直到完成各自的工作,将它们合并回主线。这种方式极大地提高了团队协作的效率和代码管理的灵活性。
下一节我们将详细介绍远程仓库的使用,以及 Pull Request 的流程,这些都是团队协作中不可或缺的工具和流程。
12⅝.4 君ノ声ヲ届ケロ:远程仓库与 Pull Request #
在团队协作中,我们通常会使用远程仓库(Remote Repository)来共享代码。远程仓库是托管在服务器上的 Git 仓库,团队成员可以通过网络访问和操作它。
常见的远程仓库服务有 GitHub、GitLab、Bitbucket,国内有码云(Gitee)等。我们可以将本地仓库与远程仓库关联起来,这样就可以将本地的修改推送到远程仓库,或者从远程仓库拉取最新的修改。此外,当你要将一个项目下载到本地进行修改时,也需要先将远程仓库克隆到本地。
总之,远程仓库是团队协作的核心工具之一,它使得分布在不同地点的开发者能够高效地共享和管理代码。
12⅝.4.1 请把我的 Code 带回你的家:克隆远程仓库 #
如果你想要参与一个已经存在的项目,或者单纯是想要在本地进行修改、编译、测试,那么第一步就是将远程仓库克隆到本地。克隆操作会创建一个完整的本地仓库副本,包括所有的历史记录和分支。
git clone <remote-repository-url>
# 例如:
git clone https://github.com/username/repository.git
# 或者ssh格式
git clone user@host:path/to/repo.git
你可能需要输入远程仓库的访问凭证(如用户名和密码,或者 SSH 密钥)来完成克隆操作。具体方法请查看 这一部分。
克隆完毕后,当前目录下会出现 repository 目录,切换到这个目录,检查好分支 和 历史,你就可以开始进行修改、提交、推送等操作了。
是不会配置 SSH Pubkey 的雑魚喵 #
出于安全原因或者是防止滥用,很多远程仓库服务都要求用户使用 SSH 密钥进行身份验证。配置 SSH 公钥(SSH Pubkey)是一个相对简单的过程,以下是在 GitHub 上配置 SSH 公钥的步骤,其它平台的步骤类似:
-
生成 SSH 密钥对:
- 如果你已经有一个 SSH 密钥对,可以跳过这一步。
ssh-keygen -t rsa -b 4096 -C "你的邮箱@example.com"你会得到这样的交互:
Generating public/private rsa key pair. Enter file in which to save the key (C:\Users\CommandPrompt/.ssh/id_rsa): test_rsa Enter passphrase for "test_rsa" (empty for no passphrase): Enter same passphrase again: Your identification has been saved in test_rsa Your public key has been saved in test_rsa.pub The key fingerprint is: SHA256:6W+azL4jl-------------yzQmvVjj+CE 你的邮箱@example.com The key's randomart image is: +---[RSA 4096]----+ | | | | | . | |. . .=.. | | o .+S* | |o . . .%.=+ | |.o + *EoB= | |. + +oB=o++ | | ..o.. +O=. | +----[SHA256]-----+这会在你的用户目录下生成一个新的 SSH 密钥对,默认情况下会保存在
~/.ssh/id_rsa(私钥)和~/.ssh/id_rsa.pub(公钥)中。这里我们指定了一个自定义的文件名test_rsa,所以私钥会保存在./test_rsa,公钥会保存在./test_rsa.pub。(是的,如果自定义了名称,就会保存在当前目录下了。除非你指定了绝对路径)-
Windows 下,
~目录通常是C:\Users\你的用户名。在 cmd 中,你可以使用%USERPROFILE%\.ssh\来访问 SSH 目录;对于 PowerShell,则可以使用$env:USERPROFILE\.ssh\。而在 Git Bash、Cygwin 乃至 Linux 中,~仍然指向用户目录,可以直接使用。 -
注意 Windows 和 Linux 下的路径分隔符不同,Windows 使用反斜杠
\,而 Linux 使用正斜杠/。在 Git Bash 或 Cygwin 中,你可以使用 Unix 风格的路径(例如~/.ssh/id_rsa),而在 cmd 或 PowerShell 中则需要使用 Windows 风格的路径(例如%USERPROFILE%\.ssh\id_rsa)。不过较新的 Windows 版本已经支持在 cmd 和 PowerShell 中使用 Unix 风格的路径了。所以你可以偷懒直接使用 Unix 风格的路径。
-
将公钥添加到远程仓库服务:
- 以 GitHub 为例,登录到你的 GitHub 账户,进入 Settings -> SSH and GPG keys -> New SSH key。
- 在 Title 中输入一个描述性的名称(例如 “My Laptop SSH Key”),在 Key 中粘贴你生成的公钥内容(可以使用
cat ~/.ssh/test_rsa.pub来查看公钥内容,或者用记事本之类的编辑器打开)。 - 点击 Add SSH key 按钮完成添加。
在 GitHub 上添加 SSH Key配置完成后,你就可以使用 SSH 协议来访问远程仓库了。可以这样测试(Github):
ssh -T github.com如果配置成功,你会看到类似下面的消息:
Hi username! You've successfully authenticated, but GitHub does not provide shell access.
如果进行分支开发,或者是参与了一个开源项目,那么在你进行修改的过程中,主线(或者说上游)的代码可能会发生变化。为了保持你的分支与主线同步,你需要定期从远程仓库拉取最新的修改。
这时候就需要 ♪ Rise~ up into the clouds~ ♪ 了:
# 提前配置好orgin远程仓库,或者直接使用默认的origin
git pull origin <branch-name>
这条命令会从远程仓库 origin 的 <branch-name> 分支拉取最新的修改,并尝试将它们合并到当前分支上。如果有冲突,Git 会提示你解决冲突后再提交。解决冲突的方法会在后面的 冲突合并 部分进行详细介绍。
12⅝.4.2 请把你的更改留下:上传更改和 Pull Request #
完成了本地的修改和提交后,你需要将这些修改推送到远程仓库,以便其他团队成员可以看到你的更改,并且可以进行代码评审和合并。
git push origin <branch-name>
容易注意到克隆、拉取、推送三个操作分别对应了远程仓库的三个基本操作:下载、同步、上传。它们是团队协作中最常用的命令之一。其子命令的语法也相对符合直觉。
不过你往往会收到这样的消息:
ERROR: Permission to ------.git denied to --------.
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
排除 SSH 公钥配置 的问题,这通常是因为你没有权限访问该远程仓库——毕竟你 clone 的仓库是别人开的嘛。
那么,如何获得权限呢?当然是联系项目的维护者了!在 Github 这样的开源平台上,这样的申请流程被称为 Pull Request(PR)。Pull?不是 Push 吗?为什么叫 Pull Request 呢?やれやれ,很多时候换一个视角问题就清晰了。从维护者的角度来看,他们是从你的分支上拉取(Pull)你的修改的,所以叫 Pull Request。PR 是一种非常重要的协作流程,它允许开发者提交他们的修改,并且让项目维护者进行代码评审和合并。
下面演示一下在 GitHub 上创建 Pull Request 的流程:
-
创建你的 Fork: 在 Github 网页端的项目页面上,点击右上角的 Fork 按钮,将项目 Fork 到你的账户下。这样你就拥有了一个独立的仓库,可以在其中进行修改。
在原仓库 Fork 后,你将拥有一个独立的仓库。 -
克隆你的 Fork: 使用
git clone命令将你的 Fork 克隆到本地进行修改。git clone https://github.com/你的用户名/项目名.git # 或者 SSH git clone git@github.com:你的用户名/项目名.git- 如果项目引用了其它项目作为子模块(Submodule),你可能还需要使用
--recursive选项来一并克隆子模块。
- 如果项目引用了其它项目作为子模块(Submodule),你可能还需要使用
-
进行修改(可以创建分支): 按照之前的方法,参考项目本身的要求,对代码进行修改,并且提交到你的 Fork 的分支上。方法同前面介绍的分支管理和提交操作。
-
创建 Pull Request: 在 GitHub 网页端,进入你 Fork 的项目页面,切换到你修改的分支,点击 Compare & pull request 按钮。填写 Pull Request 的标题和描述,说明你的修改内容和目的,然后提交 Pull Request。
创建一个 Pull Request(这里我已经提交了一个测试用的 Pull Request,所以你看到的界面可能和你实际操作时略有不同)
界面上的 Pull Request 和 Draft Pull Request 的区别在于,Draft Pull Request 是一种草稿状态的 Pull Request,表示这个 Pull Request 还在开发中,还没有准备好进行评审和合并。你可以在 Draft Pull Request 中继续进行修改和提交,直到你觉得它已经准备好了,你就可以将它转换为正式的 Pull Request,这样项目维护者就可以开始评审了。
-
等待评审和合并: 项目的维护者会收到你的 Pull Request,他们会进行代码评审,提出修改建议或者直接合并你的修改。你可以在 Pull Request 的页面上查看评审的评论,并且根据反馈进行修改。
Pull Request 的讨论和历史
12⅝.4.3 结んで 开いて:代码评审与合并 #
不揃いだね!
如果你是项目的维护者,或者是一个 Pull Request 的评审者,那么你就会时常收到来自其他开发者的 Pull Request。你需要对这些 Pull Request 进行评审,检查代码的质量、功能的正确性,以及是否符合项目的规范。代码的评审我们不多赘述,简单来说,如果你发现了问题或者有改进的建议,你可以在 Pull Request 的页面上直接留言,提出你的意见和建议。开发者会根据你的反馈进行修改,并且更新他们的 Pull Request。或者你也可以自己直接修改他们的分支。直到你觉得这个 Pull Request 已经准备好被合并了,你就可以点击合并按钮,将它合并到主线分支上。
那么,前面留下的问题来了:如果在合并的过程中发生了冲突怎么办?当两个分支上修改了同一行代码,或者是修改了同一文件的不同部分,Git 就无法自动合并它们,这时候就会发生冲突。Git 会标记出冲突的部分,你需要手动编辑这些文件,解决冲突后再提交。
接下来将分别从 CLI 和 GUI 两个角度介绍一下如何解决合并冲突,其中 GUI 以 VS Code 为例,各平台的操作大同小异。
我们创建一个基本文件 foo.txt 位于 main 分支,内容是:
222222222222222
1111111111111111
33333333333333
4444444444444
555555555555
7777777777
66666666666
888888888
99999999
0000000
aaaaaa
bbbbb
cccc
ddd
ee
f
然后我们在 b2 分支上修改了 foo.txt 的内容,变成了:
#################
1111111111111111
222222222222222
33333333333333
4444444444444
555555555555
66666666666
7777777777
888888888
99999999
0000000
aaaaaa
bbbbb
cccc
ddd
ee
f
并切换回 main 分支上修改了 foo.txt 的内容,变成了:
******************
222222222222222
1111111111111111
33333333333333
4444444444444
555555555555
7777777777
66666666666
888888888
99999999
0000000
aaaaaa
bbbbb
cccc
ddd
ee
f
提交历史简图如下:
现在,我们回到 main 分支上,尝试将 b2 分支合并到 main 上:
# git checkout main
git merge b2
这时候就会发生冲突,Git 会提示你哪些文件发生了冲突,并且在这些文件中标记出冲突的部分。你需要打开这些文件,手动编辑它们,解决冲突后再提交。
commandprompt@Firefly-IV:test$ git merge b2
自动合并 foo.txt
冲突(内容):合并冲突于 foo.txt
自动合并失败,修正冲突然后提交修正的结果。
打开 foo.txt,你会看到下面的内容:
<<<<<<< HEAD
******************
222222222222222
=======
#################
>>>>>>> b2
1111111111111111
222222222222222
33333333333333
4444444444444
555555555555
66666666666
7777777777
888888888
99999999
0000000
aaaaaa
bbbbb
cccc
ddd
ee
f
这里,<<<<<<< HEAD 和 ======= 之间的内容是当前分支(main)上的修改,而 ======= 和 >>>>>>> b2 之间的内容是要合并的分支(b2)上的修改。你需要根据实际情况,选择保留哪部分内容,或者是进行修改来融合两者的修改。解决完冲突后,你就可以像提交普通的修改一样,提交你的更改了。
git add foo.txt
git commit -m "合适的提交信息,说明你是如何解决冲突的"
Visual Studio Code 提供的图形界面思想也大同小异,打开发生冲突的文件后,你会看到类似下面的界面:
根据你的需要,在左、右两侧选择接受哪一部分的修改,或者是接受两者的修改(如果它们不冲突的话)即可。在底部可以看到合并的结果。完成一个文件后,点击右下角的“完成合并”按钮,继续处理下一个文件。直到所有冲突都解决了,你就可以提交你的更改了。
在文本框中输入合适的提交信息,说明你是如何解决冲突的,然后点击“继续”按钮即可提交。
完成后的提交历史简图如下:
12⅝.4.4 あの日の約束も:约定与规范 #
进行团队协作时,除了技术上的操作之外,还有一些约定和规范需要遵守。这些约定和规范可以帮助团队成员更好地协作,提高代码的质量和可维护性。
比如下面是 ArceOS 的组件开发及管理规范:
除了开发上的规范外,程序的编译、分发和派生等流程也可能有一些约定和规范,比如经典的开源许可证(MIT、GPL、Apache 等),以及一些特定的构建工具和流程(如 CI/CD)。遵守这些约定和规范不只是遵守条文,更是对团队成员和用户的尊重,也是对项目质量的保证。