彷徨えるフジワラ

年がら年中さまよってます

コミットログ入力画面カスタマイズ機能実現の顛末

いつもであれば、年末の振り返りで他の修正と一緒に、「利用者から要望のあった○○○○に対処するパッチを投げて、取り込んでもらいました」で終わらせるところなのですが、下準備のために相当量のパッチを投げる必要があったので、備忘録代わりに公開しておきます。

(2014-09-17 追記) 総計 75 個の修正をもって、この機能に関する対応は一段落しました。

事の始まり

2012年からの個人的なバックログとして、「コミットログ入力画面のカスタマイズ機能」がありました。

列挙系情報 (例: ファイル一覧等) に対するテンプレート機能が強化されている現状なら、初期表示内容をテンプレート機能で生成する実装に置き換えてやれば、比較的簡単に実現できるだろう、というのが、当初の目論見でした。
他の修正作業が煮詰まったので空き時間ができた際に、関連ソースコードを調べてみたところ……何やら嫌な臭いの立ち込める実装が……

見つけてしまった問題を放置できない性分なので、ここは腹を括って、ザクザク修正パッチを投げることにしました。

カスタマイズ機能追加までの道のり

まず最初に、".hg/last-message.txt" へのコミットメッセージ保存に関して、「保存に失敗する」、「想定外のエディタ起動がある」あるいは「保存していない」等の問題への対処として、以下の修正が取り込まれました。 (x8)

  • f042d4b263f4: localrepo: save manually edited commit message as soon as possible
  • 95aab23a806b: rebase: use "commitforceeditor" instead of "ui.edit()" for "--collapse"
  • bcfc4f625e57: tag: save manually edited commit message into ".hg/last-message.txt"
  • 57d0c8c3b947: qnew: save manually edited commit message into ".hg/last-message.txt"
  • 1e686e55780c: qfold: save manually edited commit message into ".hg/last-message.txt"
  • 5d22cadd1938: histedit: save manually edited commit message into ".hg/last-message.txt" ("fold" コマンド向け)
  • 434619dae569: amend: save commit message into ".hg/last-message.txt"
  • a0f437e2f5a9: histedit: save manually edited commit message into ".hg/last-message.txt" ("message" コマンド向け)

次に、上記の暫定的な対処を含め、本来使うべき「エディタ起動〜 ".hg/last-message" へのコミットメッセージ保存」のフレームワークを使っていなかったものへの対処として、以下の修正が取り込まれました。(x9)

  • 213fd1a99cd9: histedit: use "editor" argument of "commit()" instead of explicit "ui.edit()"
  • b9a16ed5acec: qnew: use "editor" argument of "commit()" instead of explicit "ui.edit()"
  • 51069bf6366b: qrefresh: relocate message/patch-header handling to delay message determination
  • 49148d7868df: qrefresh: use "editor" argument of "commit()" instead of explicit "ui.edit()"
  • 0054a77f49df: localrepo: add "editor" argument to "tag()"
  • 25d6fdc0294a: context: move editor invocation from "makememctx()" to "memctx.__init__()"
  • 19d98da5c018: histedit: pass "editor" argument to "memctx.__init__()" for "collapse" command
  • 1a833fcf5a14: amend: use "editor" argument for "memctx.__init__" to save commit message
  • 244b177a152e: cmdutil: omit redundant "savecommitmessage()" in "tryimportone()"

更に、エディタ呼び出し関数の引き当てを集約するための対処として、以下の修正が取り込まれました。(x19)

  • dcf20f244c2a: cmdutil: introduce "getcommiteditor()" to simplify code paths to choose editor
  • 288a793c3167: fetch: use "getcommiteditor()" instead of explicit editor choice
  • 47bfe5d433ac: histedit: use the editor gotten by "getcommiteditor()" for "message"
  • 6a48713cb72e: histedit: use "getcommiteditor()" instead of explicit editor choice for "fold"
  • 37a3ac247c0d: histedit: use "getcommiteditor()" instead of explicit editor choice for "--continue"
  • 5b70ece79ea7: rebase: use "getcommiteditor()" instead of explicit editor choice
  • afff78be4361: transplant: use "getcommiteditor()" instead of explicit editor choice
  • 6f6ccb0bb6af: backout: avoid redundant message examination
  • edc55317de90: backout: use "getcommiteditor()" instead of explicit editor choice
  • 37a302f0e297: commit: use "getcommiteditor()" instead of explicit editor choice
  • 0b5d6c062774: amend: use "getcommiteditor()" instead of explicit editor choice
  • 3e717c9376fc: graft: use "getcommiteditor()" instead of explicit editor choice
  • 308aaeb956e2: import: use "getcommiteditor()" instead of explicit editor choice
  • d4b8fc753455: tag: use the editor gotten by "getcommiteditor()" instead of "ui.edit()"
  • 272785489ed3: cmdutil: enhance "getcommiteditor()" for specific usages in MQ
  • d009f6555b81: mq: fold the code path to invoke editor into specific logic (qnew)
  • 4941caa9f0f8: mq: use the editor gotten by "getcommiteditor()" instead of "ui.edit()" (qnew)
  • edc6ced48d2d: mq: fold the code paths to invoke editor into specific logic (qrefresh/qfold)
  • 7d408720453d: mq: use the editor gotten by "getcommiteditor()" instead of "ui.edit()" (qrefresh/qfold)

SCMBootCamp in Nagoya #2 の当日朝の時点で、ここまでのパッチが取り込まれていたので、急遽以下のスライドをでっち上げて発表させてもらいました(笑)

以上の修正が取り込まれることで、本筋の修正を提案するための下準備がやっと完了です。ゼェゼェ……

コミットログ編集時に表示される「更新サブリポジトリ一覧」や「コミット時にアクティブなブックマーク」に相当する情報に関しては、該当するテンプレートキーワードが存在していなかったので、これらの追加を含めた以下の修正を取り込んでもらいました。(x4)

  • e353fac7db26: cmdutil: separate building commit text from 'commitforceeditor'
  • 2b41ee1b5ea1: templatekw: add 'currentbookmark' keyword to show current bookmark easily
  • 764adc332f6e: templatekw: add 'subrepos' keyword to show updated subrepositories
  • 5375ba75df40: cmdutil: make commit message shown in text editor customizable by template

以上の修正により、当初の目的である「テンプレート機能によるコミットログ入力画面のカスタマイズ機能」が実現できたことになります。ばんざーい!!

利便性向上のための追加変更

さて、これまでの修正により、「コミットログ入力/記録時の初期表示内容の変更機能」という当初の目的は達成できたものの、これだけでは少々不便ですので、個人的な趣味で(笑)色々追加実装を提案することにしました。

まずは、「どういった用途でのエディタ起動か?」を判定可能にする情報を導入します。(x13)

  • 6ce282ed801e: cmdutil: introduce 'editform' to distinguish the purpose of commit text editing
  • 20fd00ee432e: fetch: pass 'editform' argument to 'cmdutil.getcommiteditor'
  • 135176a198d0: gpg: pass 'editform' argument to 'cmdutil.getcommiteditor'
  • a44b7b6f3cd7: histedit: pass 'editform' argument to 'cmdutil.getcommiteditor'
  • cbbd957358ff: mq: pass 'editform' argument to 'cmdutil.getcommiteditor'
  • 3646716b11a7: rebase: pass 'editform' argument to 'cmdutil.getcommiteditor'
  • dabf8fb8a91e: shelve: pass 'editform' argument to 'cmdutil.getcommiteditor'
  • 7e71a65bf94f: transplant: pass 'editform' argument to 'cmdutil.getcommiteditor'
  • a5bb0c4001ae: backout: pass 'editform' argument to 'cmdutil.getcommiteditor'
  • b02ab548ab5c: graft: pass 'editform' argument to 'cmdutil.getcommiteditor'
  • 0bbe8ef901d1: tag: pass 'editform' argument to 'cmdutil.getcommiteditor'
  • 41e969cb9468: commit: pass 'editform' argument to 'cmdutil.getcommiteditor'
  • 97acb4504704: import: pass 'editform' argument to 'cmdutil.getcommiteditor'

この情報により、getcommiteditor をフックするようなケースで、呼び出し元に応じて選択的な処理ができるようになりますし、以下の様な、「コミット種別に応じて、テンプレートの内容/利用の有無を切り替える」ことが可能になります。(x1)

  • 9d92b9d1e282: cmdutil: look commit template definition up by specified 'editform'

但し、「コミット種別毎にテンプレートを切り替える」だけだと、種別毎のテンプレートを from scratch で定義する必要があるので、"[committemplate]" セクション自体を style 定義の mapfile 的に使えるようにしてみました。(x1)

  • de5cee8ba088: cmdutil: use '[committemplate]' section like as map file for style definition

また、変更提案の過程で Matt から要望があり、エディタ起動の際にコミット種別情報を HGEDITFORM 環境変数に設定する修正 (x1) と:

通常コミットとマージコミットを明確に区別できるようにする修正を行いました。(x5)

  • 75618a223e18: commit: change "editform" to distinguish merge commits from others
  • f5ff18f65b73: commit: change "editform" to distinguish merge commits from other (--amend)
  • f3200bf460a8: import: change "editform" to distinguish merge commits from others
  • d0d3e5c6eb3c: rebase: change "editform" to distinguish merge commits from others
  • de783f2403c4: transplant: change "editform" to distinguish merge commits from others

個人的には、通常コミットとマージコミットの区別は、テンプレートの条件判定機能を使って:

{ifeq(p2rev, "-1", 通常コミット時表示[, マージコミット時])}

上記の様な切り替えで十分だろうと考えていたのですが、外部エディタ側での挙動切り替え等も考えると、確かに識別情報のレベルで明確に区別しておく必要がありますね。

それから、機能拡張として、もう一点。

開発者 ML において、「コミットログ入力時に差分も表示して欲しい」⇒「大量の変更が表示される可能性を考えると現実的ではない」というやりとりが定期的に見受けられたので、テンプレート機能に対して「差分表示」のための関数 diff() を追加しました。

コミットログにもありますが、以下のようなテンプレート記述により、行頭に "HG: " が付加された差分を表示できるようになりました。

{splitlines(diff()) % 'HG: {line}\n'}

なお、コマンドラインでの -I/-X 指定は、diff() には効果を持ちません。これらのオプションはあくまで「コマンドの処理対象ファイル/リビジョンの限定」が目的であるためです。

diff() の表対象ファイルを限定したい場合は、diff() の引数としてパターンを指定してください。最大2つのパターン指定が可能です。

diff(includepattern [, excludepattern])

「複数の -I オプション」相当のパターンを指定したい、といったケースに関しては、「正規表現の "|" による結合」での単一パターン化を想定しています。パターン指定のデフォルト種別は glob なので、複数パターンを結合する場合は、パターン冒頭に "re:" を明示してください。


注意: ここから >>>>

テンプレート関数 diff()Mercurial 3.2 から利用できますが、対応修正が取り込まれた Mercurial 3.3 (2015-02-01 リリース予定) よりも前の版では、committemplate で使用した際に、以下のような障害が発生しますのでご注意ください。

  • 対象ファイルや -I/-X オプションの指定を伴うコミットの際に、対象外のファイルがコミットされてしまう
  • --amend 付きコミットの際に、例外で処理が中断される

なお、Mercurial 3.3 よりも前の版では、対象ファイルや -I/-X オプションの指定を伴うコミットでの committemplate 使用の際に、{files} 系の情報が正しくない、という問題も発生しますので、こちらも併せてご注意ください。

注意: ここまで <<<<

作業過程での関連修正

純粋に「作業過程で見つけたバグの修正・機能改善」をあげるのであれば、他にも幾つかあるのですが、とりあえずここでは「コミットログ編集機能」周りに限定したものをあげておきます。

● "hg commit" でのエディタ起動周りの修正

オプション指定時の挙動の是正や、現時点では意味を持たないオプションを扱うコードの除去を行いました (x2)

  • a1a1bd09e4f4: amend: invoke editor forcibly when "--edit" option is specified
  • e6e34c17b1cc: commit: abolish useless "--force-editor" internal option for "hg commit"
● リビジョン生成系コマンドの --edit オプション対応

コミットメッセージ保存周りを修正した段階では、一部のリビジョン生成系コマンドが --edit オプションに対応していませんでした。

「コミットログ入力/記録時の初期表示内容の変更機能」の視点だけで見るならば、エディタが起動されない分には、放置しても構わないと言えますが、以下の様な点で問題があります。

  • コマンド体系全体で見た場合、相似性/整合性が欠けることになる
  • getcommiteditor 呼び出しが無いと、コミットログ内容の確定に関する処理を、網羅的に集約できない

そこで、全てのリビジョン生成系コマンドを --edit オプションに対応させるための対処として、以下の修正も取り込んでもいらいました。(x5)

  • 0986af9e7006: gpg: accept '--edit' like other commands creating new changeset
  • 51035af2c0bf: backout: accept '--edit' like other commands creating new changeset
  • 3eb43b706174: shelve: add option combination tests for refactoring in succeeding patch
  • aad28ff87788: shelve: refactor option combination check to easily add new ones
  • 37a5decc6924: shelve: accept '--edit' like other commands creating new changeset

「コマンド体系全体で見た場合、相似性/整合性が欠ける」点に関しては、MQ コマンドにおける「--edit--message/--logfile の組み合わせの禁止」も、今となっては不要に思われる制限(他のコマンドでは利用可能な組み合わせ)ですので、以下の修正も取り込んでもらいました。(x2)

  • 7a51bced398b: qrefresh: allow to specify '--message/'--logfile' and '--edit' at the same time
  • 635a8201e356: qfold: allow to specify '--message/'--logfile' and '--edit' at the same time

また、オプション追加等の過程で、既存のオプションに使われているヘルプドキュメントの不統一が目に付いたので、これに関しても修正を取り込んでもらいました。(x2)

● "hg import --exact" でのエディタ起動

hg import でのパッチ取り込みにおいて、--exact による「厳密適用」指定と、--edit による「コミットログの編集」は、相容れない組み合わせですので、以下のような修正を取り込んでもらいました(x2)

  • e116abad3afa: import: disallow meaningless combination of "--exact" and "--edit"
  • ffaaa80fa724: import: avoid editor invocation when importing with "--exact" for exact-ness