moztw.org

有 Mercurial Queue 的感覺真好!從 nothing 到 checkin-needed

Mercurial(hg) 是一款 mozilla 內部使用的版本控制系統,而 Mercurial Queue(MQ) 則是一個使用 Mercurial 時常用的套件,讓 up-stream 和 down-stream 的管理相處的和樂融融。雖然已經有非常多文件可供參閱[1][2][3][4],但是一個真實案例範例勝過萬卷規格。我們來利用朱尼爾小明的一天來看看 Mercurial Queue 是怎麼輔助開發的(以下故事純屬虛構,僅為示範 Mercurial Queue 而編造,如有雷同,純屬巧合)[5]:

使用 MQ 的 第一個 patch

又到了令人振奮的清晨,今天找到了一個有趣的 bug3939889[6],這個 bug 是要把網頁裡的文字翻譯成唐詩,對於有文學素養的朱尼爾小明是再有吸引力不過了,沒有第二句話就先去 bugzilla 認養下來,然後在 local repository 弄一個新的 queue[7] :

$ hg qq(ueue) -c bug3939889-tang-poem-translator

1

$ hg qq(ueue) -c bug3939889-tang-poem-translator

可以用這個指令來看一下剛剛的指令是否生效、以及現在手邊的 queue 有哪些:

$ hg qq
bug3939889-tang-poem-translator (active)
patches

123

$ hg qqbug3939889-tang-poem-translator (active)patches

很好,可以開始寫 patch 了!要怎麼把網頁的內容翻譯成唐詩呢?首先要把需要翻譯的文字找出來,於是我們新增一個patch:

$ hg qnew context-parser -m "bug3939889 - Part1: context parser"

1

$ hg qnew context-parser -m "bug3939889 - Part1: context parser"

來看一下目前 patch queue 裡面的狀況:

$ hg qser(ies) -v -s
0 A context-parser: bug3939889 - Part1: context parser

12

$ hg qser(ies) -v -s0 A context-parser: bug3939889 - Part1: context parser

非常好,新增成功了[8],開頭的 0 代表第 0 個 patch,A 代表這個 patch 是 applied 的狀態,在 patch 後面的說明是在 <code>qnew</code> 時所設定的[9]。目前這個 context-parser 還是一個空的patch,存放在 .hg 資料夾下:

$ cat .hg/patches-bug3939889-tang-poem-translator/context-parser

HG changeset patch

Parent 0000000000000000000000000000000000000000

User 朱尼爾 <junior@example.com>

bug3939889 - Part1: context parser

12345

$ cat .hg/patches-bug3939889-tang-poem-translator/context-parser # HG changeset patch# Parent 0000000000000000000000000000000000000000# User 朱尼爾 <junior@example.com>bug3939889 - Part1: context parser

經過了一早的努力,總算把這部分完成了。目前的改動都還只是在 working directory 下, patch 還是空的,可以這樣來檢視這個事實[10]:

$ hg diff
diff --git a/ContextParser.h b/ContextParser.h
new file mode 100644
--- /dev/null
b/ContextParser.h
@@ -0,0 1,999 @@
#ifndef mozilla_ContextParser_h
#define mozilla_ContextParser_h

/**

  • ContextParser is used to parse context in order to facilitate translation.
  • ...

$ hg qshow

HG changeset patch

Parent 0000000000000000000000000000000000000000

User 朱尼爾 <junior@example.com>

bug3939889 - Part1: context parser

123456789101112131415161718

$ hg diffdiff --git a/ContextParser.h b/ContextParser.hnew file mode 100644--- /dev/null b/ContextParser.h@@ -0,0 1,999 @@ #ifndef mozilla_ContextParser_h #define mozilla_ContextParser_h /* ContextParser is used to parse context in order to facilitate translation. * ... $ hg qshow# HG changeset patch# Parent 0000000000000000000000000000000000000000# User 朱尼爾 <junior@example.com>bug3939889 - Part1: context parser

hg qshow 可以用來看最新 patch 的內容,現在還是空的。我們只要利用 hg qref(resh) 這個指令,就可以把 working directory 下的修改儲存到 patch 中。

$ hg qref
$ hg diff
[ nothing ]
$ hg qshow

HG changeset patch

Parent 0000000000000000000000000000000000000000

User 朱尼爾 <junior@example.com>

bug3939889 - Part1: context parser

diff --git a/ContextParser.h b/ContextParser.h
new file mode 100644
--- /dev/null
b/ContextParser.h
@@ -0,0 1,999 @@
#ifndef mozilla_ContextParser_h
#define mozilla_ContextParser_h

/**

  • ContextParser is used to parse context in order to facilitate translation.
  • ...

1234567891011121314151617181920

$ hg qref$ hg diff[ nothing ]$ hg qshow# HG changeset patch# Parent 0000000000000000000000000000000000000000# User 朱尼爾 <junior@example.com>bug3939889 - Part1: context parser diff --git a/ContextParser.h b/ContextParser.hnew file mode 100644--- /dev/null b/ContextParser.h@@ -0,0 1,999 @@ #ifndef mozilla_ContextParser_h #define mozilla_ContextParser_h /* ContextParser is used to parse context in order to facilitate translation. * ...

在這裡我們可以觀察一下 hg qref 的效果:我們發現 hg diff 是空的,根據 hg qshow 的結果我們知道修改已經透過 hg qref 指令移至 patch 裡了。

一般我們會一邊寫,寫到一個段落確定沒問題就用 hg qref 把修改存到 patch 裡。萬一修改的結果並不是想要的,就可以用 hg revert FILE_PATH 將某個檔案未儲存在 patch 裡的修改還原,或是使用hg revert --all 把未儲存在 patch 裡的所有修改全部還原。如果想要再看看沒有這個 patch 跑起來的狀況,也可以隨時用 hg qpop 把 patch 拉掉,測完以後再 hg qpush 回來,是不是個老嫗能解又方便來著?

利用 queue 的操作,輕鬆玩轉 patch

qpush 和 qpop 是 MQ 中單一個 queue 內的操作,他們的作用就跟名字一樣: qpush 是 apply 下一個 unapplied 的 patch ,而 qpop 是 pop 當前的 patch ,使其變成 unapplied。利用這兩個操作,我們可以很容易的切換到不同的階段來觀察。例如:

$ hg qser // 目前 queue 中有三個 patch,都已經是 applied 的狀態
0 A part1:
1 A part2:
2 A part3:
$ hg qpop // 把當前的 patch(part3) 造成的更動先暫時拿掉
popping part3
now at: part2
$ hg qser
0 A part1:
1 A part2:
2 U part3:
$ hg qpop
popping part2
now at: part1
$ hg qser
0 A part1:
1 U part2:
2 U part3:
$ hg qpush // apply 下一個 patch
applying part2
now at: part2
$ hg qser
0 A part1:
1 A part2:
2 U part3:

12345678910111213141516171819202122232425

$ hg qser // 目前 queue 中有三個 patch,都已經是 applied 的狀態0 A part1: 1 A part2: 2 A part3: $ hg qpop // 把當前的 patch(part3) 造成的更動先暫時拿掉popping part3now at: part2$ hg qser0 A part1: 1 A part2: 2 U part3: $ hg qpop popping part2now at: part1$ hg qser0 A part1: 1 U part2: 2 U part3: $ hg qpush // apply 下一個 patchapplying part2now at: part2$ hg qser0 A part1: 1 A part2: 2 U part3:

讓我們回到小明的一天,當小明寫完 Part1 的 patch 之後,通常會做以下幾件事:

  1. 將 patch 丟到 try server 跑自動測試

一般我會為了 try 再新增一個空的 patch,再 push 到 try server :

$ hg qnew try -m TRY_SYNTAX
$ hg push -f -rtip TRY_SERVER_ADDRESS

12

$ hg qnew try -m TRY_SYNTAX$ hg push -f -rtip TRY_SERVER_ADDRESS

  1. 將 patch 上傳至 bugzilla 找 reviewer 審核

也就是把 .hg/patches-bug3939889-tang-poem-translator/context-parser 上傳至 bugzilla 並設定適合的 flag ,詳情請參閱[11]。

  1. 繼續寫下一個 patch

畢竟 reviewer 幾乎都很忙,有時候一等就是一個月,所以趕快前進吧!

現在我們把需要翻譯的文字找出來了,接下來要把文字翻譯成唐詩貼回去,所以我們再新增一個 patch :

$ hg qnew plaintext-to-poem -m "Part2: Plain test to poem translation"

1

$ hg qnew plaintext-to-poem -m "Part2: Plain test to poem translation"

再確認一下現在 queue 裡 patch 的狀況:

$ hg qser
0 A context-parser: bug3939889 - Part1: context parser
1 A try: TRY_SYNTAX
2 A plaintext-to-poem - Part2: Plain test to poem translation

1234

$ hg qser0 A context-parser: bug3939889 - Part1: context parser1 A try: TRY_SYNTAX2 A plaintext-to-poem - Part2: Plain test to poem translation

糟了,有一個 try patch 夾在中間,理想上這個 patch 應該疊在 Part2 patch 上的。有很多種方法可以做到這件事,這裡選用一個簡單的方法:

$ hg qpop context-parser
popping plaintext-to-poem
popping try
now <a href="http://www.theinnocents.org/">オンライン カジノ</a> at: context-parser
$ hg qser
0 A context-parser: bug3939889 - Part1: context parser
1 U try: TRY_SYNTAX
2 U plaintext-to-poem: Part2: Plain test to poem translation
$ hg qpush --move plaintext-to-poem
applying plaintext-to-poem
now at: plaintext-to-poem
$ hg qser
0 A context-parser: bug3939889 - Part1: context parser
1 A plaintext-to-poem: Part2: Plain test to poem translation
2 U try: TRY_SYNTAX

123456789101112131415

$ hg qpop context-parser popping plaintext-to-poempopping trynow <a href="http://www.theinnocents.org/">オンライン カジノ</a> at: context-parser$ hg qser0 A context-parser: bug3939889 - Part1: context parser1 U try: TRY_SYNTAX2 U plaintext-to-poem: Part2: Plain test to poem translation$ hg qpush --move plaintext-to-poem applying plaintext-to-poemnow at: plaintext-to-poem$ hg qser0 A context-parser: bug3939889 - Part1: context parser1 A plaintext-to-poem: Part2: Plain test to poem translation2 U try: TRY_SYNTAX

hg qpush --move 可以將一個還沒 apply 過的 patch 移到 qtip 的下一個並 apply,示意圖如下:

hg qpush - -move 示意圖
hg qpush –move 示意圖

到了中午,這個 patch 只完成了一半。吃飯的時候,突然發現 Part1 被 review- 了,怎麼會這樣呢?原來是 Part1 中測試的 code quality 沒有達到 mozillian 的水準,希望能夠再改進一點。好在有 mq ,這時我只要 hg qpop ,就可以回到之前的狀況修改 Part1 的 patch。改完以後重新送 review ,然後再 hg qpush 就可以繼續 Part2 的工作了。不過如果這時候的 hg qpush 出現 conflict ,就必須要手動 merge 。

到了下午茶的時光,總算初步把這個 patch 完成了,用$ hg diff -r qparent:qtip 檢視一下整個 bug 的更動,就可以上 bugzilla 請求 review 了。

在 queue 與 queue 之間穿梭,管理 bug 好簡單

在等待的同時,又看到一個有趣的 bug 28825252,有了 mq 想要多 maintain 一個 bug 並不困難,只要再增加一個 queue 就可以了:

$ hg qpop -a // 建立新的 queue 必須先讓當前的 queue 變成 empty,所以先 pop all
popping try
popping plaintext-to-poem
popping context-parser
patch queue now empty
$ hg qq -c bug28825252
$ hg qq // 列出所有的 queue
bug28825252 (active)
bug3939889-tang-poem-translator
patches

12345678910

$ hg qpop -a // 建立新的 queue 必須先讓當前的 queue 變成 empty,所以先 pop allpopping trypopping plaintext-to-poempopping context-parserpatch queue now empty$ hg qq -c bug28825252$ hg qq // 列出所有的 queuebug28825252 (active)bug3939889-tang-poem-translatorpatches

一個 bug 由一個 queue 來 maintain ,是不是很簡單明暸又清楚呢?

到了傍晚,reviewer 說只要再 rebase 到最新的 code 就可以 review 了,有了 mq 這點也很容易。只要切回本來的 queue,利用 rebase 套件就可以輕鬆達成了:

$ hg qq bug3939889-tang-poem-translator // 切換到本來的 queue
$ hg qpush -a
$ hg pull --rebase

123

$ hg qq bug3939889-tang-poem-translator // 切換到本來的 queue$ hg qpush -a$ hg pull --rebase

最後只要利用 hg qref -e 把 reviewer 的名字掛上後上傳,就可以到 bugzilla 標上 checkin-needed 了,是不是很容易呢?

相信我們看小明的一天以後,一定對 Mercurial Queue 有一定的了解了。關於 Mercurial Queue ,還有很多實用的技巧,我們下集待續。

[1] Mercurial | MDN, https://developer.mozilla.org/en-US/docs/Mercurial

[2] Using Mercurial, https://developer.mozilla.org/en/docs/Mercurial_FAQ

[3] Mercurial Queues, https://developer.mozilla.org/en-US/docs/Mercurial_Queues

[4] Mercurial: The Definitive Guide, http://hgbook.red-bean.com/

[5] 預設 Mecurial 及 Mecurial Queue 的環境已經設定好了,設定的部分請參照[3]。另外「一天」僅供參考,一個 bug 可能很快也可能很慢,端看 reviewer bug的難度及個人的熟練度。

[6] 這個 bug number 是虛構的,截至2014年12月為止 mozilla bugzilla 內 bug 的數量約為111萬個。

[7] 其實我一直不太明白,學理上應該是一個一個的 stack ,但是卻叫 Mercurial Queue 。

[8] 如果不想每次都打參數 -v -s,可以修改 ~/.hgrc:

[default]
qseries = -v -s

12

[default]qseries = -v -s

[9] 如果想修正也可以使用 hg qref -m “new message"

[10] 如果 qshow 沒有作用的時候,請參照 https://bitbucket.org/sfink/mqext

[11] 我也想成為 mozillian!教你如何貢獻到 mozilla code base