[gerrit場景教程] gerrit "missing Change-Id"
本系列是為公司寫的 git & gerrit 場景使用手冊的一篇.
套路部分為解決問題的步驟,心法部分為所用到的知識點的梳理.
場景:
你用 git push 向 gerrit 提交了待稽核程式碼,一切都很順利,你腦袋裡冒出了"程式碼頭上加了'佛祖保佑'果然有效"的想法.
此時 git 打印出如下提示,你的內心OS同步列印 "心情 -5" :
remote: Resolving deltas: 100% (14 /14 )
remote: Processing changes: refs: 1, done
remote: ERROR: missing Change-Id in commit message footer remote:
remote: Hint: To automatically insert Change-Id, install the hook:
remote: gitdir=$(git rev-parse --git- dir ); scp -p -P 29418
[email protected]: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-Id in commit
message footer)
error: failed to push some refs to 'ssh://[email protected]:29418/sample_project.git'
|
套路:
大前提: commit-msg 檔案必須已經在該專案中存在.
使用ls命令檢查該檔案是否存在:
$ cd project_dir
$ ls
.git /hooks/commit-msg
|
如果該檔案不存在,則按照 git push 時產生的提示資訊,獲取該檔案:
$ gitdir=$(git rev-parse --git- dir ); scp -p -P 29418 [email protected]:hooks /commit-msg ${gitdir} /hooks/
|
上面的命令可以直接從 git push 產生的錯誤資訊中複製出來.
如果要手敲該命令,別忘了把使用者名稱換成自己的.
方法一: 使用 amend 選項生成 Change-Id:
如果缺失 Change-Id 的是最後一個 (head) commit, 使用以下命令即可解決問題:
$ 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:
$ git log
commit 8e1cad33bcd98e175cba710b1eacfd631a5dda41
Author: liux <[email protected]>
Date: Mon Dec 19 17:43:00 2016 +0800
test commit "with amended commit message"
Change-Id: I9d2af0cc31423cf808cd235de0ad02abf451937d
commit 1a9096a34322885ac101175ddcac7dab4c52665d
Author: liux <[email protected]>
Date: Mon Dec 19 15:23:36 2016 +0800
test commit-msg hook
......
|
發現是第二個 commit 缺失 Change-Id. 將程式碼 reset 到這個 commit, 並執行 amend:
$ git reset 1a9096a34322885ac101175ddcac7dab4c52665d
$ git commit --amend
|
注: 上面的 git reset 用法不會毀滅你寫的程式碼,放心執行即可.
這時 git log 可以發現該 commit 已經補全了 change-Id.
下一步是把 git reset 撤消掉的程式碼重新 commit, 然後 push 即可:
$ 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:
$ git log
commit 8aaaa749db4a5b105aa746659c5cd266ac82fffe
Author: liux <[email protected]>
Date: Mon Dec 19 17:43:24 2016 +0800
I am commit message 3
Change-Id: Ic89d5ce6ce4de70d1dcb315ce543c86a2b3ac003
commit 8e1cad33bcd98e175cba710b1eacfd631a5dda41
Author: liux <[email protected]>
Date: Mon Dec 19 17:43:00 2016 +0800
I am commit message 2
Change-Id: I9d2af0cc31423cf808cd235de0ad02abf451937d
commit 1a9096a34322885ac101175ddcac7dab4c52665d
Author: liux <[email protected]>
Date: Mon Dec 19 15:23:36 2016 +0800
I am commit message 1
commit d714bcde0c14ba4622d28952c4b2a80882b19927
Author: shangsb <[email protected]>
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 (本例中為 "表單" 那個提交):
$ 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 的內嵌指令碼.其命令寫法已經在下面的註釋裡給出了.
這裡不贅述,僅給出最終要將該檔案編輯成什麼樣子:
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" 即可. 儲存退出 (: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 都生成了. 愉快的提交程式碼吧!
$ 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 這個資料夾,看看都有哪些可用的鉤子.
$ cd
project_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 的鉤子,在我們提交程式碼後,按一定規則去修改了我們的提交日誌,在其末尾添加了這麼一行:
Change-Id: .......
這個鉤子指令碼是什麼時候被加入我們的專案中的呢? 其實就是你在 git push 出錯時 gerrit 網站給你的提示中的那句命令:
$ gitdir=$(git rev-parse --git- dir );
scp -p -P 29418 [email protected]:hooks /commit-msg
${gitdir} /hooks/
|
執行該命令即可得到生成 Change-Id 的鉤子指令碼. 這條命令做了以下事情:
//
git rev-parse --git- dir
這條命令將找到該專案的 git 目錄,並將其賦值給 gitdir 這個變數.
//
一般就是專案根目錄下的 .git/ 這個目錄.
$ gitdir=$(git rev-parse --git- dir )
//
執行 scp 命令,從 gerrit 程式碼伺服器將鉤子指令碼檔案 commit-msg 下載到專案的鉤子目錄下 (一般是 .git /hooks/ )
$ scp -p -P 29418 [email protected]:hooks /commit-msg ${gitdir} /hooks/
|
檢視該指令碼,會發現它是用 awk 命令處理了 .git/COMMIT_EDITMSG 這個檔案.
所以如果想手動生成 Change-Id ,只要執行下面命令,就可以生成一個可用的 Change-Id:
$ cd
project_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 方式更新程式碼.
git rebase -i:
// TODO: 待續...