最佳实践
最佳实践之:如何安全地避免冲突
大家在小组协同使用 Git 和 Gerrit 时,经常会遇到因为他人在自己之前提交了代码,
自己提交时因为代码冲突需要解决后才能提交的问题。这其实和 Gerrit 没有关系,单独用
Git 也会遇到同伴在自己之前提交的问题。
一个比较笨但比较好用 git 使用模式如下:
本地 git clone 一个 develop 分支,仅用作和远端 Git 库同步(不要在上面开发),
即仅在这个本地 develop 分支上执行 pull 操作,不要和本地其它分支做 merge 。
本地其它分支,用作开发、review、 入库,即作为开发分支,这些开发分支创建时基于本地
最新 develop。开发分支入库后,如果不需要继续开发就删除它, 再重建其它 feature 的
开发分支(仍基于最新的 develop)。
当大家发现通过 review 后,却不能提交代码时,一般是产生了冲突,因为自己的好伙伴
手好快先于自己提交了(肯定练过)。
这个时候 checkout 到 develop 上,pull 一下最新 develop 代码。 然后再切换到当前
开发的分支上, 执行 git rebase develop, 若有冲突解决冲突, 没有冲突,rebase 完后
(add, and rebase –continue), 再发 review(新的 patch set,用来解决冲突的), 一般
就可以过了。
最佳实践之:小量修改、时刻提交
基于 Gerrit 的 Review 工作流,提倡每个 Change 都是容易 Review 的。这意味着每个 Change
的大小要比较小,小到什么程度呢?大家可以参考 Web GUI 中的 Size 一栏的长度与颜色:
- 绿色:表示足够小,是可 Review 的,大家阅读起来很舒服
- 黄色:表示不够小,Review 起来可能有点累
- 红色:表示变更很大,基本上是不可 Review 的
我们尽量避免出现黄色与红色的,这就是小量修改的原则。
时刻提交说的是:一次 commit 并且 fetch 远程的变更并且合并到本地分支之后,马上进行一次
git push origin HEAD:refs/for/{BRANCH} 的操作。不要进行多次 commit 之后才 push。
时刻提交的好处是减少冲突、减少合并提交产生 Change-Id 无法生效的问题,并且出了问题容易解决。
最佳实践之:PUSH 简写,并指定 Reviewer
大家使用 git Push Review 的时候,可能很不习惯这一部分:
git push origin master:refs/for/master
这条命令的分支,称为魔术分支,并不是真实存在的,仅用于 Gerrit 做 Review。
如果大家的分支工作流程可以指定只用一个分支来 Push Review,就可以使用本条最佳实践:
cd /path/to/your/repository
vim .git/config
修改 git config 文件的远程仓库 origin 部分:
[remote "origin"]
url = ssh://{MY-USERNAME}@teamcode.fangdd.net:29418/{PROJECT-NAME}
fetch = +refs/heads/*:refs/remotes/origin/*
push = HEAD:refs/for/{BRANCH}
这样,你就可以简单地使用
git push origin
来 Push 你的代码给大家 Review 了。
如果你想让某些同伴来 Review 你的代码,你可以使用 r 参数来添加 Reviewer:
[remote "origin"]
url = ssh://{MY-USERNAME}@teamcode.fangdd.net:29418/{PROJECT-NAME}
fetch = +refs/heads/*:refs/remotes/origin/*
push = HEAD:refs/for/{BRANCH}%r={SOMEONE}@fangdd.com,r={SOMEONE}@fangdd.com
这样,当你 git push origin 后,Reviewer 就能收到邮件通知了。
最佳实践之:分支合并之 merge vs rebase
分支合并的最好的工具,应该是 merge,例如
# 将本地的 develop 分支合并到 release 分支
git checkout release
git merge develop
# 将远程 origin 的 develop 分支合并到本地的 develop 分支
git fetch origin develop
git merge origin/develop
或者
git fetch origin/develop
使用 merge 来合并分支,会无条件地保留所有的提交记录。
rebase 是 git 提供的用于将提交记录重演的工具,这种重演也叫变基,通俗地说,就是回到某个
历史节点上,让历史重新演进。rebase 就是一个月光宝盒。
由于 rebase 可以将一个分支上的历史在另一个分支上重演,因此常常备大家用作分支合并的另一
选择。rebase 会在重演的时候变更提交记录的 id(sha-1),本质上不是用来作分支合并的。
在 gerrit 提供的 git 服务中,rebase 可能在不当的重演过程中,产生两个问题:
- 一是可能是重演时产生新的提交或除 id 不同其它完全相同的重复提交,并丢失或不能生成 Change-Id
- 二是可能产生除 id 不同其它完全相同的重复提交,由于 Change-Id 相同而无法 push。
因此,在使用 gerrit 作为 review 工作流的实践中,请:
- 使用 merge
- 少用或不用 rebase
关于 rebase 的使用,可以
git help rebase
不了解 rebase 的原理而滥用,会造成不少难以理解的问题,但如果已经了解 rebase,活用 rebase
会带来很多的好处(所谓好处,就是你能感受到的便利)。
关于 merge 与 rebase 的讨论,可以参考 git book: Git-分支-变基
最佳实践之:SSH - 使用主机别名
这个实践,可以让你使用一个很短的名字,代替 {MY-USERNAME}@teamcode.fangdd.net:29418,
也可以使用指定的密钥来进行服务器认证。
vim ~/.ssh/config
加入以下内容
# 这一节配置的目的,是让 teamcode 代表 {MY-USERNAME}@teamcode.fangdd.net:29418
Host teamcode
Hostname teamcode.fangdd.net
User {MY-USERNAME}
Port 29418
# 如果你有多个密钥,下面一行可以指定私钥
#IdentityFile /path/to/your/id_rsa
保存退出
经过以上配置,对于下面的地址
ssh://{MY-USERNAME}@teamcode.fangdd.net:29418/fangdd/engineering/teamcode
等价于
ssh://teamcode/fangdd/engineering/teamcode
它的优势在于获得一个与 username, host, port 无关的地址,而且更短。这在需要统一的地址
引用的场景中,尤其重要。
常见问题之:合并提交造成 Change-Id 无法生成
当远程仓库的代码有更新时,如果你使用
git pull
或者
git fetch origin {BRANCH}
git merge origin/{BRANCH}
来合并远程的变更的时候,会产生 4 种结果:
- 远程无更新
- Fast Forward,无新的变更(no new changes)
- 合并提交,有新的变更,多数情况无法自动生成 Change-Id
- 合并冲突,手动解决冲突后再提交,能顺利生成 Change-Id
问题在于第 3 种结果,是大家最容易遇到并且很困惑的。你可以马上使用
git commit --amend
来尝试这次的合并提交生成 Change-Id。
对 git 熟悉的并且精通脚本的同学,可以在 prepare-commit-msg 上作文章,但
可能会产生副作用,不建议使用!
最好的解决方法还是遵循《最佳实践之:小量修改、多次提交》的流程。
常见问题之:合并分支后,push 时提示 no new changes
假如在 develop 分支开发,提测时,将 develop 合并到 release 分支,如果合并的结果是
Fast Forward,那么 git push origin HEAD:refs/for/release 就会提示 no new changes
错误,无法 push。
原因是在 Fast Forward 情况下,并没有新提交,没有新的变更,仅仅是将 release 的指针指向了
develop,而此时 develop 所指的提交已经是最新的了。
推荐大家维护一个叫 release-notes.md 的文件,从 develop 分支合并到 release 分支之后,
在 release 分支上修改 release-notes.md 文件,说明本次提交的主要内容。
这个文件的内容的组织,可以参考 git for windows 的 release。
由于修改了 release-notes.md,有了新的变更,就可以 git push origin HEAD:refs/for/release
了。由 release 分支向 master 分支合并,也是一样的做法。
常见问题之:missing Change-Id in commit message footer
产生这个问题的原因有多种,导致 commit-msg 钩子没有执行,下面是 3 个常见原因
- 克隆项目的时候,没有下载 commit-msg 钩子。
- 在分支合并的时候,产生了合并提交,git 无法执行 commit-msg 钩子
- 在 rebase 或 cherry-pick 的时候,无法执行 commit-msg 钩子
第 1 个原因,解决办法请参考工作流
第 2 个原因,参见《常见问题之:合并提交造成 Change-Id 无法生成》
第 3 个原因,参见《最佳实践之:分支合并之 merge vs rebase》
如果缺失 Change-Id 的提交是最新的一次提交,只需要执行
git commit --amend
保存退出即可。
假如最新 3 次提交记录如下
# 此历史列表由以下命令得出
# git log -3 --pretty=format:"%h %s"
6d3d39b 修复团购按钮错位 1px 的Bug
11f0806 Merge origin/develop into develop
c8a32de 修复用户无法注册的Bug
其中的 11f0806 缺失了 Change-Id,需要补上,有多少办法,不同的办法解决于你不同的意图。
办法一:reset。reset 最适用于修改第 2 条提交记录。
# 将 11f0806 以后的提交全部放弃,其后的变更从 git 索引区回到工作目录
git reset 11f0806
git commit --amend
# 将 reset 影响的文件重新加回到索引区
git add .
git commit --m "修复团购按钮错位 1px 的Bug"
办法二:rebase。rebase 比较通用,可以修改多次之前(历史比较远)的提交。
# 我们要修改 11f0806,所以历史回到 c8a32de
# 也可以用 git rebase 11f0806^
git rebase c8a32de
在弹出来的编辑器中,将 11f0806 前面的 pck 改为 edit
edit 11f0806 Merge origin/develop into develop
pick 6d3d39b 修复团购按钮错位 1px 的Bug
保存退出后,历史定格在 11f0806,等待你的篡改^_^
git commit --amend
改完后,让历史继续前进,直到现在为止
git rebase --continue