转载:Gerrit Missing Change-Id

转载:Gerrit “Missing Change-Id”

出处:gerrit “missing Change-Id”

场景

你用 git push 向 gerrit 提交了待审核代码,一切都很顺利,你脑袋里冒出了”代码头上加了’佛祖保佑’果然有效”的想法.
此时 git 打印出如下提示,你的内心OS同步打印 “心情 -5” :

1
2
3
4
5
6
7
8
9
10
11
12
remote: Resolving deltas: 100% (14/14)
remote: Processing changes: refs: 1,done
remote: ERROR: missing Change-Idincommit message footer
remote:
remote: Hint: To automatically insert Change-Id,installthe hook:
remote: gitdir=$(git rev-parse --git-dir);scp-p -P 29418 liux@gerrit.xxxxx.com:hooks/commit-msg${gitdir}/hooks/
remote: And then amend the commit:
remote: git commit --amend
remote:
To ssh://liux@121.xx.xx.xx:29418/kaiba_admin_yunying.git
! [remote rejected] HEAD -> refs/for/master(missing Change-Idincommit message footer)
error: failed to push some refs to'ssh://liux@121.xx.xx.xx:29418/sample_project.git'

套路

大前提: commit-msg 文件必须已经在该项目中存在.
使用ls命令检查该文件是否存在:

1
2
$ cd project_dir
$ ls.git/hooks/commit-msg

如果该文件不存在,则按照 git push 时产生的提示信息,获取该文件:
1
$ gitdir=$(git rev-parse --git-dir);scp-p -P 29418 liux@gerrit.xxxxx.com:hooks/commit-msg${gitdir}/hooks/

上面的命令可以直接从 git push 产生的错误信息中复制出来.
如果要手敲该命令,别忘了把用户名换成自己的.

方法一: 使用 amend 选项生成 Change-Id:

如果缺失 Change-Id 的是最后一个 (head) commit, 使用以下命令即可解决问题:

1
$ git commit --amend

该命令会打开默认的 commit message 编辑器,一般是 vi.
这时什么都不用修改,直接保存退出即可 (:wq).
再次查看 git log,就会发现缺失的 Change-Id 已经被补上了. 再次git push 即可.

方法二: 如果缺失 Change-Id 的不是最后一个 commit, 可用 reset 方法:

比如,如果缺失 Change-Id 的 commit 是 git log 中的第二个 commit, 则可以用 git reset 命令将本地分支回退到该 commit.
(但其实用 git reset 找回 Change-Id 是普通青年才干的事情,文艺青年有更优雅的办法.见方法三)
首先执行 git log, 找出缺失了 Change-Id 的 commit,并复制其 commit-id:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ git log
commit 8e1cad33bcd98e175cba710b1eacfd631a5dda41
Author: liux <liux@xxxx.cn>
Date: Mon Dec 19 17:43:00 2016 +0800

testcommit"with amended commit message"

Change-Id: I9d2af0cc31423cf808cd235de0ad02abf451937d

commit 1a9096a34322885ac101175ddcac7dab4c52665d
Author: liux <liux@xxxx.cn>
Date: Mon Dec 19 15:23:36 2016 +0800

testcommit-msg hook

......

发现是第二个 commit 缺失 Change-Id. 将代码 reset 到这个 commit, 并执行 amend:
1
2
$ git reset 1a9096a34322885ac101175ddcac7dab4c52665d
$ git commit --amend

: 上面的 git reset 用法不会毁灭你写的代码,放心执行即可.
这时 git log 可以发现该 commit 已经补全了 change-Id.
下一步是把 git reset 撤消掉的代码重新 commit, 然后 push 即可:
1
2
3
$ git add ......
$ git commit -m "你的提交日志"
$ git push review HEAD:refs/for/master

方法三: 使用交互式 rebase 找回任意提交位置的 Change-Id:

前面方法二中给出的例子是第二个提交缺失 Change-Id,这时用 git reset 还可以解决问题.
但如果你在一个方案上已经工作了一个月,生成了100个本地 commit,提交时才发现 git log 中第99个 commit 缺失 Change-Id. 如果这时还用 git reset 来找回 Change-Id ……
不要香菇,不要蓝瘦.文艺青年表示有办法优雅的解决问题: 交互式 rebase.

第一步,找到缺失 Change-Id 的那个 commit:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ git log
commit 8aaaa749db4a5b105aa746659c5cd266ac82fffe
Author: liux <liux@xxxx.cn>
Date: Mon Dec 19 17:43:24 2016 +0800

I am commit message 3

Change-Id: Ic89d5ce6ce4de70d1dcb315ce543c86a2b3ac003

commit 8e1cad33bcd98e175cba710b1eacfd631a5dda41
Author: liux <liux@xxxx.cn>
Date: Mon Dec 19 17:43:00 2016 +0800

I am commit message 2

Change-Id: I9d2af0cc31423cf808cd235de0ad02abf451937d

commit 1a9096a34322885ac101175ddcac7dab4c52665d
Author: liux <liux@xxxx.cn>
Date: Mon Dec 19 15:23:36 2016 +0800

I am commit message 1

commit d714bcde0c14ba4622d28952c4b2a80882b19927
Author: shangsb <shangsb@czfw.cn>
Date: Wed Dec 14 09:20:52 2016 +0800

这是一个提交

Change-Id: I629b2bedff95491875f63634ad3da199612735b6
......

发现是 “I am commit message 1” 这个提交没有 Change-Id.

第二步,编辑交互式 rebase 的命令文件:

执行git rebase -i, 参数为 该提交的上一个提交的 commit-id (本例中为 “表单” 那个提交):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ git rebase -i d714bcde0c14ba4622d28952c4b2a80882b19927
这个命令会打开默认的编辑器,一般为 vi. 内容如下:
pick 1a9096a I am commit message 1
pick 8e1cad3 I am commit message 2
pick 8aaaa74 I am commit message 3
# Rebase d714bcd..8aaaa74 onto d714bcd
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

可以将这个文件理解为 git rebase 的内嵌脚本.其命令写法已经在下面的注释里给出了.
这里不赘述,仅给出最终要将该文件编辑成什么样子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
reword 1a9096a I am commit message 1
pick 8e1cad3 I am commit message 2
pick 8aaaa74 I am commit message 3
# Rebase d714bcd..8aaaa74 onto d714bcd
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

即: 将缺失了 Change-Id 的 commit 前面的pick 改为 reword即可 (简写 r也可以). 保存退出 (:wq)
注1: 上述文件中 commit 的顺序是和 git log 显示的顺序相反的: git log 为最新的在最前; 上述文件为 最新的在最后.
注2: 如果进入该模式后,却不确定该怎么改,这时不要担心,直接退出编辑则什么都不会发生 (:q!)
注3: 如果没有搞清楚原理,就要注意,除了按需把 pick 改为 reword外,不要做其他改动.尤其注意不要删除任何行 (被删除的那行对应的提交将丢失).
注4: 你应该已经发现,有多个 commit 缺失 Change-Id 的情况也可以用该方法一次性处理.

第三步,逐个编辑 commit-msg:

上一步打开的文件保存退出后,git会逐个打开被你标注了 reword 的提交日志页面.
不需要修改任何东西,逐个保存退出即可 (一路 :wq).

第四步,再次提交:

用 git log 查看提交日志,会发现缺失的 Change-Id 都生成了. 愉快的提交代码吧!

1
$ git push review HEAD:refs/for/master

心法:

gerrit 的 Change-Id 机制:

首先要明确, Change-Id 是 gerrit (代码审核平台)的概念, 与 git (版本管理) 是没有关系的.
简单来说, Change-Id 是 gerrit 用以追踪具体提交的机制. 这里不贴网上已有的解释,举两个栗子大家体会下:

  1. 你已经用 git push 将代码提交 gerrit 审核了,这时你发现代码中有疏漏,修改了一下,执行 git commit —amend, 再次推送还可以成功. 这就是因为 gerrit 检查到两次 push 的 commit 有同一个 change-id, 就认为是同一个提交,因此可以 amend.
  2. git push 将代码提交到 gerrit 审核,到 gerrit 网站一看,大红字标着 Can Not Merge 字样. 我想常用 gerrit 的同学肯定都遇到过这问题. 之前我的做法是, git reset 后,更新代码,再重新提交. 现在的做法是,不用 git reset 了,直接 git commit —amend, 删掉 commit log 中的 change-id 那行,然后wq保存退出.这时 gerrit 的那个钩子脚本会再生成一个不同的 change-id ,这时再更新代码,重新提交即可成功. 这里只简要介绍该方法,具体步骤将在 代码冲突 场景中详解.
    Change-Id 的生成机制请继续向下看.
    git 的 hook 机制:
    钩子(hooks)是一些在$GIT-DIR/hooks目录的脚本, 在被特定的事件(certain points)触发后被调用。当git init命令被调用后, 一些非常有用的示例钩子脚本被拷到新仓库的hooks目录中; 但是在默认情况下它们是不生效的。 把这些钩子文件的”.sample”文件名后缀去掉就可以使它们生效。
    hook机制可以理解为回调.各个钩子其实就是一段 bash 脚本,各钩子脚本的名字都是固定的.可以查看git项目根目录下的 .git/hooks 这个文件夹,看看都有哪些可用的钩子.
    1
    2
    3
    4
    $ cdproject_dir
    $ ls .git/hooks/
    applypatch-msg.sample commit-msg.sample pre-applypatch.sample prepare-commit-msg.sample pre-rebase.sample
    commit-msg post-update.sample pre-commit.sample pre-push.sample update.sample
    如果有自己感兴趣的 git 事件要处理,修改相应的钩子脚本罗辑即可.然后把 .sample 后缀去掉,钩子就生效了.
    在 gerrit 的 Change-Id 生成机制中,其实 gerrit 就是利用了 commit-msg 的钩子,在我们提交代码后,按一定规则去修改了我们的提交日志,在其末尾添加了这么一行:
    1
    Change-Id: .......
    这个钩子脚本是什么时候被加入我们的项目中的呢? 其实就是你在 git push 出错时 gerrit 网站给你的提示中的那句命令:
    1
    $ gitdir=$(git rev-parse --git-dir);scp -p -P 29418 liux@gerrit.kaiba315.com:hooks/commit-msg${gitdir}/hooks/
    执行该命令即可得到生成 Change-Id 的钩子脚本. 这条命令做了以下事情:
    1
    2
    3
    4
    5
    6
    // git rev-parse --git-dir 这条命令将找到该项目的 git 目录,并将其赋值给 gitdir 这个变量.
    // 一般就是项目根目录下的 .git/ 这个目录.
    $ gitdir=$(git rev-parse --git-dir)

    // 执行 scp 命令,从 gerrit 代码服务器将钩子脚本文件 commit-msg 下载到项目的钩子目录下 (一般是 .git/hooks/)
    $ scp -p -P 29418 liux@gerrit.kaiba315.com:hooks/commit-msg ${gitdir}/hooks/
    查看该脚本,会发现它是用 awk 命令处理了 .git/COMMIT_EDITMSG 这个文件.
    所以如果想手动生成 Change-Id ,只要执行下面命令,就可以生成一个可用的 Change-Id:
    1
    2
    3
    4
    5
    6
    7
    $ cdproject_dir
    $ echo "some commit" > /tmp/test_generate_change_id
    $ .git/hooks/commit-msg/tmp/test_generate_change_id
    $ cat /tmp/test_generate_change_id
    some commit

    Change-Id: Ic89d5ce6ce4de70d1dcb315ce543c86a2b3ac003
    利用 git commit —amend 重新生成 Change-Id 的原理:
    git commit —amend , 看名字就知道,是对某个 commit 做出修改的.这种修改既可以包含文件修改,也可以仅包含提交日志修改.
    我们用 —amend 对 commit 做出修改后, commit-msg 的钩子会被重新触发, Change-Id 就会被生成出来.
    用交互式 git rebase 来生成 Change-Id 也是同一个道理.
    另:
    通过总结历次缺失 Change-Id 的例子,发现基本我们自己通过 git commit 生成的提交都会很顺利的生成 Change-Id.
    通过 git merge, git revert 等命令由 git 自己生成的 commit 则有较高概率会缺失 Change-Id.
    嗯,我们发现了一个伟大的定律! 然并卵… 并不知道怎么解决这个问题.
    因此提倡尽量用 git rebase 代替 git merge 来更新代码.
    事实上, git rebase 更新代码 相较 git merge 更新代码,有诸多优势,只是略复杂些.强烈建议用 git rebase 方式更新代码.