1. 前言
本文利用 GitHub 平台进行一个多人项目开发流程的演练,以加深课上所学内容。
参考孟老师的文章:五⼤场景玩转 Git,只要这一篇就够了!
2. 初始化项目
2.1 新建远程项目
一个多人项目通常是从远程创建新仓库开始,这里我们在 GitHub 新建一个仓库 git101 :
新建仓库时我添加了 .gitignore 文件来进行一个初始化提交,不然默认一个空项目后面不好画图演示。.gitignore 文件顾名思义,在里面指定的文件或文件夹都不会被 Git 跟踪,通常用于指定一些只有本地会用到的文件或者保密性高的私人文件。
值得一提的是,由于某个国家某些自身原因,10月1日开始,GitHub 新仓库的默认分支名都由原来的 master 变成了 main ,需要改回来的话要手动设置,这里我懒得设置了,main 就 main 吧。
2.2 clone 项目到本地
通常一个多人项目需要在项目中把所有的成员包含进来,给他们写权限,这样才能推送自己的代码到远程,这里简单起见,就以网页端直接修改文件模拟另一位小伙伴的工作。项目路径有 http 和 ssh 两种,它们的区别是: http 不需要额外配置,但推送代码的时候需要验证身份;ssh 使用秘钥通信,推送时不需要验证,但需要额外配置,因为我配置过 ssh ,就直接用 ssh 路径了:
现在可以开始工作了,首先将项目克隆到本地然后查看一下仓库分支和提交状态:
# 克隆项目
git clone 项目地址
# 查看所有分支
git branch -a
# 查看提交记录
git log --oneline
可以看到添加 .gitignore 时生成的提交记录以及分支状态,注意,有一条 remotes/origin/main 分支,这条分支只用于同步远程仓库相应分支的状态,不可直接在这些分支上进行操作,后面同步远程代码时会展示它的用法,所以目前仓库的分支状态如图所示,一个节点代表一个提交记录,分支和HEAD指针通过虚线指向提交记录:
3. 在本地创建分支完成工作
通常团队中的每个人负责一个模块的开发,不能直接在主分支上做开发,要保持它的整洁,这里我们新建一个 dev 分支,在上面做三次提交:
# 新建并切换到 dev 分支
# 相当于两条命令:git branch dev && git checkout dev
git checkout -b dev
# 将所做修改存到暂存区
git add .
# 提交到本地仓库
git commit -m "提交说明,没有空格可不用双引号"
此时分支状态如图:
4. 整理提交记录
工作完成后,就需要将所做修改合并到主分支了,但在合并之前,我们往往需要对开发分支的提交记录进行一些整理以保持提交记录整洁,比如上面的所做的三次提交,或许我们发现 d2 和 d3 所做工作其实干了同一件事,或者 d2 只是随手提交了一个没什么意义的 log,总之由于种种原因,我不想让 d2 出现在最终提交记录里面了,这时候就可以用 rebase 整理一下提交记录:
# 整理 HEAD 向前三个提交
git rebase -i HEAD~3
之后会用 vim 打开一个修改提交记录的文件,里面有删除合并等各种修改说明,这里我们想删除 d2 ,所以将前面的 pick 改成 drop:
保存退出后不出意外是有冲突的,因为 d3 是基于 d2 修改的,现在 d2 没了,就需要解决 d3 产生的冲突,我们可以随时用 git status
查看工作区状态:
这里提示 dev.txt 文件存在冲突,那我们就打开 dev.txt ,修改冲突到 d3 的状态后加入暂存区然后继续剩下的 rebase 工作:
# 继续未完成的 rebase 工作
# 如果想撤销 rebase ,使用 --abort 参数
git rabase --continue
rebase 完成之后还会打开一个 d3 的提交记录文件,因为修改过这条分支上的提交记录,理应在最新的提交记录上做一下说明,这里我就不做修改了:
整理提交记录后就变成了这个样子:
5. 合并工作到主分支
合并到主分支非常容易,只需要切换到主分支然后 merge 即可,但在此之前,我们还有些事要办。
5.1 模拟远程提交
团队项目不只有一个人,在我们完成工作时,有可能其他人已经率先提交了工作,这里在网页端修改 .gitignore 文件来模拟远程提交:
这里我清空了 .gitignore 文件的内容并添加了一行 *.log
,这表示忽略所有以 .log 结尾的文件,最后将这次提交命名为 r1 ,这时候远程仓库的分支状态发生了改变:
5.2 拉取远程分支的更新
当远程存在更新的提交记录时我们一定不能做合并工作,要始终保持主分支和远程仓库一致。
一般在我们对和远程仓库有关联的本地分支做任何修改时都应该先拉取分支状态,这通常只需要 git pull
即可搞定,它相当于 git fetch && git merge origin/main
两条命令,在这里我想演示一下远程分支 origin/master 的作用,所以将两条命令分开执行:
# fetch 的作用:
# 1. 从远程仓库下载本地缺失的提交记录
# 2. 更新远程分支(origin/master)到最新状态(注意,本地分支没动)
git fetch
# 合并 origin/master 到 main 分支
git merge origin/master
fetch 后的状态:
merge 后:
5.3 合并开发分支
至此,本地主分支终于和远程同步了,可以放心的合并开发分支了:
# 合并 dev 到 main
git merge dev
可以看到合并时默认会多出来一个合并节点,暂且叫它 m1 吧,合并后的状态:
查看分支状态也可以使用 gitk
命令,只是不怎么好看:
这里还有个问题,合并分支时,如果在分叉后主分支没有更新的提交记录,merge 默认行为是快进式合并,就是直接把 dev 分支上面的提交记录拿过来而不是新建合并节点,这样做无法保留分支开发的历史,通常我们需要加上 --no-ff 参数来取消默认行为,它们的区别见下图:
6. 推送到远程仓库
这下本地功能开发完成,也进行了合并,可以放心的推送到远程仓库啦:
git push
完成后远程仓库分支也进行了更新:
登录 GitHub 查看,dev.txt 也已经出现:
7. 补充
7.1 关于master和main
嘴上说着 main 就 main,画图时还是一不小心写了 master 上去,这该死的习惯呐。。
话说这种事情能影响到代码命名也是离谱,哪个程序员看到 master 没事会联想到奴隶主??
几年后,红黑树,黑盒测试可能都得换个名字? ̄▽ ̄
7.2 恢复文件新命令
如果你的 Git 版本 >= 2.23,在使用 git status
时可能会注意到一个新命令:
研究了下,原来是之前的 checkout 命令承载了太多的功能,又是分支切换又是文件恢复,有点复杂导致难以学习,社区引入了两个更易于理解的命令 switch 和 restore 来拆分 checkout,它们的用法也更加直观。
首先是 switch,没啥说的,就像它的名字一样,切换分支:
# -c,或者 --create,新建并切换
git switch dev
然后是 restore,它可以恢复工作区,暂存区,以及任意提交点的文件(但不会回退分支,这仍然需要reset),常用参数说明如下:
# 不加参数时默认是 -W(--worktree),表示恢复工作区(从暂存区取出文件覆盖工作区)
# -S(--staged),表示恢复暂存区(从最新提交点取出文件覆盖暂存区)
# -s(--source) commitID或HEAD相对引用,从任意提交点取出文件覆盖工作区
git restore 文件名或匹配规则
这样,当我们想撤销工作区的修改(未add ):
git restore .
当我们想撤销已经 add 的内容(未commit):
# 仅仅 -S 或者 --staged 只会恢复暂存区而不会恢复到工作区
git restore -SW .
撤销 commit 需要 reset,而如果我们只需要某个提交点的文件而无需回退:
# 注意这并不会覆盖暂存区,如果想覆盖,手动 add 即可
# 想取消本次restore,用默认restore从暂存区取出文件覆盖工作区即可
git restore -s commitID或者HEAD引用 .
switch 和 restore 还有许多参数,可用 --help 查看,覆盖了 checkout 几乎所有功能,这样一来,checkout 是要逐渐退出舞台了?