我们已学习了Git的基本知识,本文学习协作。我们将了解协作的工作流程,了解拉取、推送等知识,并将学习如何将工作成果推送到Github,了解如何对开源项目作贡献。
工作流程
我们介绍过,有两种版本控制系统:集中式和分布式。Git属于分布式,彼此可以直接同步工作成果。在一个团队里,往往有多人一起协作,如果每个人彼此经常进行同步,就太复杂且容易出错。我们可以使用一种集中式的工作流程。
每个人都有本地的存储库,同时也有一个中心存储库。但这与集中式版本控制系统相比有什么不同呢?
它没有单点故障!集中式版本控制系统如果中心节点故障,所有人都将无法工作,而Git的集中式工作流程每个人都有存储库,可以各自离线进行工作,在必要的时候也才与中心存储库进行同步。
有些开发团队创建私有服务器存储中心存储库,而有的团队则使用Github或gitlab等公开服务作为中心存储库,只要设置只允许团队内部人员访问即可。
我们来看一下。

JOHN可以将工作成果推送(PUSH)到中心存储库,而AMY可以从中心存储库拉取(PULL),如果碰到冲突,AMY进行冲突处理。AMY也可以将她的工作成果推送到中心存储库,JOHN可以进行拉取。这种方式一般用于团队的闭源开发模式。
还有一种开源开发的(集成管理)模式。它有维护者(Maintainer)和贡献者(Contributor)共同组成。

官方存储库是公开可读的,但只允许维护者写入。贡献者将官方存储库复制过来进行开发,当完成开发后,通知维护者。维护者获取开发者开发的版本并进行检查,如果没有问题,就可以将贡献者的成果推送到官方存储库。
创建 Github 仓库
我们选择Github,登录并创建Mars存储库,可以添加描述,设置私有private或公用public。这里我们选择public。可以选择生成Readme文件,.gitignore文件以及某个许可协议。

创建之后,该存储库的URL为 https://github.com/用户名/Mars,我们能看到目前只有一个分支,一个默认由Github生成的提交,以及README.md文件。
添加协作者
github的存储库即便设为public,其他人也是不能向其提交推送的。需要添加到团队成员里,才有推送权限。

被添加的成员将会收到电子邮件的邀请通知,一旦接受邀请,他将被允许向该存储库进行推送。
克隆存储库
任务团队成员都可以克隆存储库,复制存储库的URL链接,然后到本机执行克隆命令。
git clone https://github.com/kelemi001/Mars.git
存储库名称可以一样,也可以改名,比如MarsProject。
git clone https://github.com/kelemi001/Mars.git MarsProject
键入命令查看历史快照。
git log –oneline –all –graph
我们能看到有个主分支,以及两个红色的分支,origin/main和origin/HEAD,origin表示远程分支。
我们不能切换到远程分支比如 git switch origin/main 将会提示出错。
使用git branch查看,将只会看到一个本地分支。
需要熟悉的一个命令是:git remote。
git remote
git remote -v

获取(Fetching)
本地的存储库与远程存储库是不连接的,远程添加了快照提交,本地是不知道的。我们得使用 git fetch 命令下载新提交并进行合并。
origin/main是一个远程跟踪分支,它告诉我们远程的main分支在哪里。目前本地有两个分支,git可不管该分支是远程的还是本地的。
如果远程分支没有分叉,可以使用快速合并;相反如果有分叉,我们必须像之前的章节介绍过的那样进行解决。
实际操作:
登录GitHub,编辑README.md,并进行提交。

查看本地历史提交,没有刚才提交的快照的。通过执行
git fetch
这个命令省略了origin main,默认从origin main获取。
再检查历史记录,发现远程的提交已同步过来了。
git log –oneline –all — graph
查看本地与远端跟踪的分支的差异,发现是不一致的。
git branch -vv
我们用之前讲过的命令进行合并
git merge origin/main
然后查看发现本地和远程已一致了。
git log –oneline –all –graph
git branch -vv
拉取(pulling)
绝大多数情况下,我们先从远程获取更新,然后再进行合并。有一个命令可以将这两个操作合二为一,就是拉取pull
pull= fetch+merge
假设,远程中心存储库有了更新,本地存储也有了更新,这时进行拉取操作的话,我们就面临选择。
一种是三方合并。如图,上一次同步在A,远程更新了在C,而本地更新了在B通,过三方合并,创建新提交M。显然,这种合并有点破坏简洁,变得非线性了。

另一种是变基。首先获取远程的C并将基准点指向C,然后加上本地的修改B。这种方式比较简洁保持线性。

哪一种更好?这个争论已有多年了,具体就要看个人喜好及应用场景了。
实际操作下。
登录GitHub,编辑README.md,并进行提交。
再回到本地,创建一个新文件file1.txt: echo hello>file1.txt。提交。
查询历史提交记录,git log –oneline –all –graph
此时本地存储库并不知道远端的更新。
我们进行拉取:
git pull
未加rebase参数,表示进行三方合并。拉取之后,可以用git log 查看确认创建了一个新提交进行了三方合并。
如果加rebase参数,
git pull –rebase
则是一个变基合并。

推送(pushing)
目前本地比远端新,我们想把新提交推送到远端中央存储库。比如本地端有A、B、C三个提交,而远端只有A、B二个提交,执行推送时,远端存储库同步C并将分支指向到C,然后将本地的origin/mail也指向C。
命令是
git push origin main
由于本地和远端的分支都是main可以省略,默认的远端存储库的名字是origin,也是可以省略的。
git push
当推送时,远端已有新的提交,就会被拒绝。比如上次同步是A、B,现在本地增加了C有A、B、C三个提交,远端更新了D是A、B、D,则会被拒绝。有一个命令可以强制同步:
git push -f
但除非很确定,否则不要执行该命令,它将丢掉远端的更新,强制与本地一致。

一般的做法是我们先获取远端的提交。

然后进行合并。

再推送到远端。

存储凭证
推送时,会弹出要求输入用户凭证的界面,有点繁琐。我们可以将凭证存储减少这种干扰。
git config –global credential.helper cache
这种方法将会凭证存储到缓存,15分钟内不会要求输入凭证。
我们也可以将任证永久存储,不同操作系统有所不同。
MAC系统,使用钥匙串:
git credential-osxkeychain
git config –global credential.helper osxkeychain
Windows系统,需要安装Credential Manager for Windows工具。
具体可以访问以下网址获取。
github.com/Microsoft/Git-Credential-Manager-for-Windows
共享标签
默认情况下,Git不会将标签tags传到远端存储库,如果有此需求,就要明确指出。
我们实际操作下。
创建标签v1.0:
git tag v1.0
然后明确推送它:
git push origin v1.0
查看远端存储库,可以看到该标签。生成标签后,它允许用户下载打包的源码。


如果要删除远端的tag,可以显式指明。
git push origin –delete v1.0
本地仍然存在这个tag的,如果也想删除的话,可以执行:
git tag -d v1.0
发布
Github 中与标签密切相关的功能之一是发布管理。我们可以创建一个发布版本,将我们的软件与源代码二进制文件和发布说明一起打包。
登录访问Github的Mars存储库,创建一个发布,命名为v1.0。该操作将会自动创建一个v1.0的标签。我们提供发布的标题,以及发布的说明等信息。我们也可以添加二进制包,比如编译的版本等。如果该发布不是一个稳定版,可以选中“Set as a Pre-release”。
如果发现有错误,也可以很方便地删除发布。

另外要知道,发布不是Git的功能,它只是Github的功能。
共享分支
到目前为止,我们只有main,现在我们来看一个分支。
分支类似于标签(tags),默认情况下是私有。如果要与团队成员在分支上进行协作,推送时需要明确指定,就像之前的tags一样。
实践下。
先创建分支feature/change-password
git switch -C feature/change-password
不带参数的推送。
git push
我们得到一个错误,大致的意思是当前分支没有上游对应的分支。
查看分支链接。
git branch -vv
我们看到,feature/change-password 没有链接到远端的分支。
如果我们想查看远端都有哪些分支,可以用这个。
git branch -r
我们将本地的feature/change-password分支明确地推送到远端
git push -u origin feature/change-password
-u 相当于 –Upstream,这个命令执行成功之后,我们的分支也被推送到远端了。
我们可以登录Github验证下。也可以通过以下命令查看。
git branch -vv
git branch -r

现在远端也已跟踪了feature/change-password,我们可以像在main分支一样操作feature/change-password了,我们可以在该分支做些新的提交并同步推送到远端存储库。
如果想删除远端分支,可以用以下命令。
git push -d origin feature/change-password
-d 参数表示删除。检查远端分支跟踪情况:
git branch -r
git branch -vv
我们看到提示远端分支已丢失,但本地分支是存在的,如果想删除,则使用之前章节介绍过的方法删除。
git branch
git switch main
git branch -d feature/change-password

协作过程
我们模拟一个协作实例。假设Amy和我协作开发一个新功能。
首先创建一个分支,这次我们在Github上创建,分支名称为feature/change-password。
回到本地控制台,检查
git branch
自然是没有这个新分支的,获取下
git fetch
但当我们再用git branch检查发现,仍然只有main,用git branch -r 检查是能看到远端的feature/change-password分支的。
所以,当我们用运行 git fetch时,git 只是获取了远端的跟踪。我们必须手动创建本地私有分支并与远端进行关联。
git switch -C feature/change-password origin/feature/change-password

现在我们到Amy的客户端:
首先是复制Git存储库。
git clone https://github.com/kelemi001/Mars.git
检查分支,只有主分支main。
git branch
同样我们需要手动创建分支feature/change-password,并与远端的分支关联。
git switch -C feature/change-password origin/feature/change-password

然后,Amy创建新提交,并推送到远端。
echo password > file1.txt
git commit -am “Update file1”
git push
可以到Github上进行验证新提交。
回到自己控制台,进行拉取,并将feature/change-password分支的提交合并到main
git pull
git switch main
git merge feature/change-password
检查,本地main与本地及远端feature/change-password分支指向同一点了。
git log –oneline –all –graph
然后,我们再将主分支推送到GitHub。检查本地远端的main和feature/change-password都指向同一点了。
git push
git log –oneline –all –graph

当分支功能开发完成后,我们需要将分支关闭,避免污染主分支的线性简洁。
先将远端的跟踪分支删除。
git push -d origin feature/change-password
再将本地的分支删除。
git branch -d feature/change-password
检查确认远端的分支已删除了。
git branch -r

那Amy客户端如何操作?
首先获取
git pull
检查分支仍然是存在的
git branch
删除本地分支。
git branch -d feature/change-password
但用 git branch -r 检查发现,远端跟踪分支仍然存在。
可以使用下面命令清除。
git remote prune origin

以上就是两个或多个人协作完成分支功能的方式。
拉取请求
我们希望其他团队成员对我们的代码提供反馈,这时我们会使用拉取请求。拉取请求的作用是,在合并并分支到主分支之前,团队可以先进行讨论。
假设 Amy 要实现一个新功能,创建新分支并将该分支推送到Github。
git switch -C feature/login
echo hello > file3.txt
git add .
git commit -m “Write hello to file3”
git push -u origin feature/login
现在到Github:
找到 Pull Request 标签,并创建新Pull Request。
选择分支,这里是main和feature/login,并写明title和description。以及选择参与讨论者。

参与讨论者会收到email,登录GitHub时会收有拉取请求。
添加评论意见,并提交为注释comment。

回到Amy客户端,再添加一个新提交:
echo Hello > file3.txt
git commit -am “Capitalize Hello.”
git push
Amy做了提新交,需要我们再次发表意见。可以选择“Viewed”,并评论类似”Everything looks perfect. Let’s go ahead with merging!”
并选择 “Approve”。

现在我们能够进行合并了,谁来合并?
有两种不同的观点:评论者或发起请求者。
但各有各的道理,具体要看项目的实际情况。
合并可以在Github上创建一个合并,然后点击删除分支。


再到Amy的控制台,拉取刚才在远端的操作,因为已经不再需要feature/login分支了,清除远端的分支跟踪,并删除本地的该分支。
git switch main
git pull
git remote prune origin
检查确认已清除
git branch -r
然后删除本地分支
git branch -d feature/login
解决冲突
在关闭拉取请求并进行合并时,可能会出现冲突,我们看实例。
在Amy的本地创建分支feature/logout,修改file1.txt文件提交一个快照,并将分支推送到Github。
git switch -C feature/logout
echo hello > file1.txt
git commit -am “Write hello to file1”
git push -u origin feature/logout
登录Github能看到这个feature/logout分支,我们人为给它创造一些冲突。回到本地控制台,切换到main分支,修改同一个文件file1.txt文件并推送到Github。
git switch main
echo world > file1.txt
git commit -am “Write world to file1”
git push

现在,假设Amy已完成分支feature/logout的工作,想创建一个拉取请求pull request。Github会提示不能自动进行合并,但仍然可以创建拉取请求,我们设置该拉取请求标题为Feature: Logout,系统提示有冲突必须要解决。
处理冲突一般有两种选择:命令行 或者 使用Github界面。命令行的方式前面章节有讲过,通过拉取最新的代码,然后解决冲突,并新提交一个快照并推送到Github。这里我们就用Github界面来处理。
通过编辑冲突文件file1.txt,完成后点“Mark as resolved”,再进行合并就可以了。

Github Issues
Github上进行问题跟踪,跟拉取请求一样重要,可以跟踪Bugs、新功能等问题。我们实际来操作下。
在Githubr存储库Mars上创建一个Issue,设置标题、描述,也可以附件文件。并赋给某个人参与讨论,可与标签(Label,下节讨论)关联。也可以与拉取请求关联,当拉取请求关闭时,该问题也自动关闭。还可以与里程碑(MileStone)等进行关联。


标签(Label)
在每个Github存储库中,默认已存在一些标签,我们也可以自定义标签。可以设置标签的标题、描述以及颜色等。并可以将问题(Issue)赋于某个标签。

里程碑(Milestones)
Github的里程碑用于跟踪多个问题(Issues),我们可以将一系列Issues加到某个里程碑,然后观察问题处理的进度。
如图,我们创建里程碑,名称为2.0.0,截止日期为2025/11/10,并将问题赋给该里程碑。
然后我们可以查看该里程碑,到问题全部处置完成后,里程碑进度将为100%。

为开源项目做贡献
直接看实例,比如我们想对存储库 chenyongping001/game-hub 做贡献,显然我们不能将修改提交上去的,因为该存储库不属于我们。
我们可以在Github上进行 fork ,将该存储库复制过来。

克隆到本地工作目录进行操作。操作完成推送到GitHub。
git clone https://github.com/kelemi001/game-hub.git
cd game-hub
git switch -C bugfix
echo hello > README.md
git commit -am “Update README”
git push -u origin bugfix
然后再到Github上,启动拉取请求。其中基准分支可以选择我们要贡献的Fork的存储库。

开源存储库的维护者将收到电子邮件,登录后可以维护,如果没有问题,可以进行合并。

保持 Fork 仓库更新
有个问题:从何保持Fork仓库更新呢?Fork仓库如图所示。

我们可以给本地库添加与原始存储库的引用,然后从原始存储库拉取,上传则向Fork存储库。这样就能保持Fork为最新。

实际操作下。
我们知道, git remote 命令用于查看远程存储库。加 -v 参数可以查看更详细的信息。
git remote -v
我们我看有两个通道:fetch 用于获取新对象,push 用于推送对象。这两个通道都映射到URL。
我们添加原始存储库。
git remote add upstream https://github.com/chenyongping001/game-hub.git
其中upstream 是命名,可以顺便取。
再用 git remote -v 查看,能看到新的远端存储库。
git remote -v
我们可以重命名该远程存储库,比如改成 base。
git remote rename upstream base
我们也可以用下面命令删除。这里我们暂不删除。
git remote rm base

然后我们修改原始存储库,比如修改README.md,并进行提交。
再回到Fork库。执行命令。
git fetch base
如果不指明 base,默认是获取 origin的。
查询下当前的的提交并进行合并。
git log –oneline –all –graph
git merge base/main
现在本地存储库已与原base存储库一致了。我们需要将更改推送到Fork存储库。
git push
在Github检查Fork存储库。发现已做了更新。
将主分支合并到当前的分支总是一个不错的实践,可以减少未来可能的冲突。
git switch bugfix
git merge main
小结
本文介绍协作的工作流程,了解拉取、推送等知识,并介绍如何将工作成果推送到Github,了解如何对开源项目作贡献。
