[インデックス 13737] ファイルの概要
このコミットは、Mercurial のコードレビュー拡張機能 (codereview.py
) におけるエラーハンドリングの仕組みを修正するものです。特に、Mercurial 2.1 で導入されたコマンドのエラー処理に関する変更に対応し、デコレータの使用方法を見直すことで、Mercurial がコマンド引数の誤りについて適切なエラーメッセージを出力できるように改善しています。
コミット
commit 9b8c94a46f3ad978ed3e0fa9037bf18dec6c30b0
Author: Uriel Mangado <uriel@berlinblue.org>
Date: Sat Sep 1 19:55:29 2012 -0400
codereview.py: correct error handling without decorator
The decorator hides the number of function arguments from Mercurial,
so Mercurial cannot give proper error messages about commands
invoked with the wrong number of arguments.
Left a 'dummy' hgcommand decorator in place as a way to document
what functions are hg commands, and just in case we need some other
kind of hack in the future.
R=adg, rsc
CC=golang-dev
https://golang.org/cl/6488059
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9b8c94a46f3ad978ed3e0fa9037bf18dec6c30b0
元コミット内容
codereview.py: correct error handling without decorator
このコミットは、codereview.py
におけるエラーハンドリングをデコレータなしで正しく処理するように修正します。
デコレータがMercurialから関数の引数の数を隠してしまうため、Mercurialが誤った引数で呼び出されたコマンドに対して適切なエラーメッセージを提供できませんでした。
将来的に他の種類のハックが必要になる場合に備え、またどの関数がhgコマンドであるかを文書化する方法として、'dummy' の hgcommand
デコレータはそのまま残されています。
変更の背景
この変更の背景には、Mercurial 2.1 で導入されたコマンドのエラーハンドリングに関する重要な変更があります。Mercurial 2.1 以降、カスタムコマンドとして登録された関数(@hgcommand
デコレータで装飾された関数)は、エラーを示すために任意の文字列を返すことが許されなくなり、整数型の終了コードを返すことが必須となりました。以前のバージョンでは文字列を返すことが可能でしたが、Mercurial 2.1 では TypeError
トレースバックが発生するようになりました。
このコミット以前の codereview.py
では、hgcommand
デコレータがコマンド関数のラッパーとして機能し、関数が返したエラーメッセージ(文字列)を捕捉し、それをMercurialが期待する整数型の終了コードに変換していました。しかし、このラッパーデコレータの存在が、Mercurialがコマンド関数のシグネチャ(引数の数など)を正しく認識するのを妨げていました。その結果、ユーザーがコマンドを誤った引数で実行した場合に、Mercurialが提供すべき具体的なエラーメッセージではなく、一般的なエラーしか表示されないという問題が発生していました。
このコミットは、この問題を解決するために、デコレータによるエラーコード変換のロジックを削除し、代わりに各コマンド関数内で直接 hg_util.Abort
例外を発生させるように変更することで、Mercurialが引数の検証を適切に行えるようにしています。
前提知識の解説
- Mercurial (Hg): 分散型バージョン管理システム(DVCS)の一つ。Gitと同様に、コードの変更履歴を管理するために使用されます。
- Mercurial Extensions: Mercurialは拡張機能を通じてその機能をカスタマイズ・拡張できます。
codereview.py
は、Mercurialのコードレビュープロセスを支援するためのカスタムコマンドを提供する拡張機能の一部です。 - Python Decorators: Pythonのデコレータは、関数やメソッドの定義を変更せずに、その振る舞いを変更するための構文です。
@decorator_name
の形式で関数定義の直前に記述されます。このコミットでは、@hgcommand
デコレータがその対象です。 - エラーハンドリングと終了コード: プログラムが正常に終了したか、またはどのような種類のエラーが発生したかを示すために、プログラムは終了コード(または終了ステータス)を返します。慣例として、0は成功を、非ゼロはエラーを示します。Mercurial 2.1 では、コマンドの終了コードがより厳密に整数型である必要がありました。
hg_util.Abort
: Mercurialのユーティリティライブラリhg_util
に含まれる例外クラス。この例外を発生させることで、Mercurialのコマンド実行を中断し、指定されたエラーメッセージを表示させることができます。これは、Mercurialのコマンドラインインターフェースにおいて、ユーザーにエラーを通知する標準的な方法です。
技術的詳細
このコミットの主要な技術的変更点は以下の通りです。
-
hgcommand
デコレータの簡素化:- 変更前:
hgcommand
デコレータは、ラップされた関数が返すエラー(文字列または整数)を捕捉し、Mercurialが期待する整数型の終了コードに変換するロジックを含んでいました。特に、文字列エラーメッセージをhg_util.Abort
例外に変換していました。 - 変更後:
hgcommand
デコレータは、単に引数として渡された関数をそのまま返すだけの「ダミー」デコレータになりました (return f
)。これにより、デコレータがMercurialからコマンド関数のシグネチャを隠すことがなくなりました。コミットメッセージにあるように、これは「どの関数がhgコマンドであるかを文書化する方法」として、また将来的な拡張の可能性のために残されています。
- 変更前:
-
エラーハンドリングの変更:
- 変更前: 多くのコマンド関数内で、エラーが発生した場合にエラーメッセージ文字列を
return
していました(例:return "cannot specify CL name and file patterns"
)。これらの文字列はhgcommand
デコレータによって捕捉され、hg_util.Abort
例外に変換されていました。 - 変更後: 各コマンド関数内でエラーが発生した場合、直接
raise hg_util.Abort("エラーメッセージ")
を呼び出すように変更されました。これにより、エラー処理の責任がデコレータから個々のコマンド関数に移り、Mercurialの内部エラー処理メカニズムとより直接的に連携できるようになりました。
- 変更前: 多くのコマンド関数内で、エラーが発生した場合にエラーメッセージ文字列を
この変更により、Mercurialはコマンドが呼び出された際に、その引数の数を直接検証できるようになり、引数の不一致があった場合に、より具体的で役立つエラーメッセージをユーザーに提供できるようになります。例えば、必要な引数が不足している場合や、予期しない引数が渡された場合に、Mercurial自体がその問題を検出し、適切なエラーを報告できるようになります。
コアとなるコードの変更箇所
変更は lib/codereview/codereview.py
ファイルに集中しています。
-
hgcommand
デコレータの定義変更:--- a/lib/codereview/codereview.py +++ b/lib/codereview/codereview.py @@ -1247,24 +1247,8 @@ def MatchAt(ctx, pats=None, opts=None, globbed=False, default='relpath'): ####################################################################### # Commands added by code review extension. -# As of Mercurial 2.1 the commands are all required to return integer -# exit codes, whereas earlier versions allowed returning arbitrary strings -# to be printed as errors. We wrap the old functions to make sure we -# always return integer exit codes now. Otherwise Mercurial dies -# with a TypeError traceback (unsupported operand type(s) for &: 'str' and 'int'). -# Introduce a Python decorator to convert old functions to the new -# stricter convention. -\n def hgcommand(f):\n-\tdef wrapped(ui, repo, *pats, **opts):\n-\t\terr = f(ui, repo, *pats, **opts)\n-\t\tif type(err) is int:\n-\t\t\treturn err\n-\t\tif not err:\n-\t\t\treturn 0\n-\t\traise hg_util.Abort(err)\n-\twrapped.__doc__ = f.__doc__\n-\treturn wrapped +\treturn f
この変更により、
hgcommand
デコレータは、元の関数f
をそのまま返すだけのシンプルな関数になりました。以前のバージョンで存在した、エラーコードの変換やhg_util.Abort
の呼び出しを行うラッパー関数wrapped
が削除されています。 -
各コマンド関数内のエラーハンドリングの変更:
change
,code_login
,clpatch
,undo
,release_apply
,download
,file
,gofmt
,mail
,pending
,submit
,sync
,upload
など、Mercurialコマンドとして登録されている多くの関数で、エラーメッセージ文字列をreturn
していた箇所が、raise hg_util.Abort(...)
に変更されています。例:
change
関数の一部--- a/lib/codereview/codereview.py +++ b/lib/codereview/codereview.py @@ -1293,42 +1277,42 @@ def change(ui, repo, *pats, **opts):\n """\n \n if codereview_disabled:\n- return codereview_disabled + raise hg_util.Abort(codereview_disabled) \n dirty = {}\n if len(pats) > 0 and GoodCLName(pats[0]):\n name = pats[0]\n if len(pats) != 1:\n- return "cannot specify CL name and file patterns" + raise hg_util.Abort("cannot specify CL name and file patterns") pats = pats[1:]\n cl, err = LoadCL(ui, repo, name, web=True)\n if err != '':\n- return err + raise hg_util.Abort(err) if not cl.local and (opts["stdin"] or not opts["stdout"]):\n- return "cannot change non-local CL " + name + raise hg_util.Abort("cannot change non-local CL " + name) else:\n name = "new"\n cl = CL("new")\n if repo[None].branch() != "default":\n- return "cannot create CL outside default branch; switch with 'hg update default'" + raise hg_util.Abort("cannot create CL outside default branch; switch with 'hg update default'") dirty[cl] = True\n files = ChangedFiles(ui, repo, pats, taken=Taken(ui, repo))\n \n if opts["delete"] or opts["deletelocal"]:\n if opts["delete"] and opts["deletelocal"]:\n- return "cannot use -d and -D together" + raise hg_util.Abort("cannot use -d and -D together") flag = "-d"\n if opts["deletelocal"]:\n flag = "-D"\n if name == "new":\n- return "cannot use "+flag+" with file patterns" + raise hg_util.Abort("cannot use "+flag+" with file patterns") if opts["stdin"] or opts["stdout"]:\n- return "cannot use "+flag+" with -i or -o" + raise hg_util.Abort("cannot use "+flag+" with -i or -o") if not cl.local:\n- return "cannot change non-local CL " + name + raise hg_util.Abort("cannot change non-local CL " + name) if opts["delete"]:\n if cl.copied_from:\n- return "original author must delete CL; hg change -D will remove locally" + raise hg_util.Abort("original author must delete CL; hg change -D will remove locally") PostMessage(ui, cl.name, "*** Abandoned ***", send_mail=cl.mailed)\n EditDesc(cl.name, closed=True, private=cl.private)\n cl.Delete(ui, repo)\n @@ -1338,7 +1322,7 @@ def change(ui, repo, *pats, **opts):\n s = sys.stdin.read()\n clx, line, err = ParseCL(s, name)\n if err != '':\n- return "error parsing change list: line %d: %s" % (line, err) + raise hg_util.Abort("error parsing change list: line %d: %s" % (line, err)) if clx.desc is not None:\n cl.desc = clx.desc;\n dirty[cl] = True\n @@ -1360,7 +1344,7 @@ def change(ui, repo, *pats, **opts):\n cl.files = files\n err = EditCL(ui, repo, cl)\n if err != "":\n- return err + raise hg_util.Abort(err) dirty[cl] = True
コアとなるコードの解説
このコミットの核心は、Mercurialのコマンド処理におけるエラー伝達の責任を、デコレータから個々のコマンド関数へと移した点にあります。
変更前の hgcommand
デコレータは、以下のような役割を担っていました。
- デコレータがラップする関数(実際のMercurialコマンド)を実行する。
- ラップされた関数がエラーを示す文字列を返した場合、それを捕捉する。
- 捕捉した文字列を
hg_util.Abort
例外として再スローする。 - Mercurial 2.1 で導入された、コマンドが整数型の終了コードを返す必要があるという要件に対応するため、返り値が整数型でない場合に
hg_util.Abort
を発生させる。
しかし、このデコレータのラッパー機能が、Mercurialがコマンド関数の引数シグネチャを正しく検査するのを妨げていました。Mercurialは、コマンドの引数検証を行う際に、デコレータによって隠蔽された元の関数のシグネチャではなく、ラッパー関数のシグネチャを見てしまうため、引数の数が間違っている場合でも適切なエラーメッセージを出力できませんでした。
このコミットでは、この問題を解決するために、hgcommand
デコレータを以下のように変更しました。
def hgcommand(f):
return f
これにより、hgcommand
はもはやラッパーではなく、単に元の関数をそのまま返すだけの「パススルー」デコレータとなりました。結果として、Mercurialはコマンド関数の実際の引数シグネチャを直接参照できるようになり、引数の検証を正確に行えるようになりました。
デコレータがエラー変換の役割を放棄したため、各コマンド関数は自身でエラーを適切に処理する必要があります。そこで、以前はエラーメッセージ文字列を return
していた箇所が、Mercurialのエラー処理の標準的な方法である raise hg_util.Abort("エラーメッセージ")
に変更されました。hg_util.Abort
例外がスローされると、Mercurialのフレームワークがそれを捕捉し、指定されたエラーメッセージをユーザーに表示し、適切な非ゼロの終了コードでコマンドを終了させます。
この変更は、Mercurialのバージョンアップに伴う互換性の問題に対処しつつ、より堅牢で情報量の多いエラーハンドリングを実現するための重要なリファクタリングと言えます。
関連リンク
- Mercurial 公式サイト: https://www.mercurial-scm.org/
- Go プロジェクトの Mercurial リポジトリ (過去): https://go.googlesource.com/go/ (Goプロジェクトは現在Gitに移行しています)
- Mercurial 2.1 リリースノート (関連情報が含まれる可能性): https://www.mercurial-scm.org/wiki/WhatsNew
参考にした情報源リンク
- Google Search (Mercurial 2.1 command error handling changes python decorators hgcommand):
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGYnWIqLDlaxyMzJNx17hwmkyUsWSXn3G5vdCUWxk_kQvYjOs1BZTgRBtd0YEAxdjPpzhtSn8sbvQNWrqDVxPXH_A_x7k6ptTQqnR1pDQ14Xf6QMxUPvmvxI4hY3-9-_2cb3lkAgFg_oiJ8PxIPJXQYJg2yObPLarsSRj53wXUu7LfZqxFioaIV1y8SiYXNi-1QyDZGhboIBUFd
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGOuEZevxbvSvHDetZbP41WT6mpR9TTPvSavqCJzvC-_CMGwaSnDGqUTkobcEZeY8F9mEYlpF_RiU_ta2VB-8Ki1bfkN0HrdRvBUaaIhnA3UFqVesaaq-qHBVUOtQjrUOO6yUvavF9-HZfYumL54v-fYI2F9meazb93n12BlLeNplfHHm67xPQEoaKp16zsmZGT2CiBhTRWdb5QAR4gAK_o5WeNtZ-GkWpkChVl3E7XYqtOmq5c
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFN7cx5QP-tSfztxQtVF4ZFPkx_FNVH48F3_l10Ba9ET1fAiEHYm_GmSbpw4T6HQXGS-LSMkw52eeRpQ-pT6fkbQORMT6F9HiOQSKHxIQS1Z88xPuUBeEN_Ji3gxfi7ywbLJLhcRLg0Ndk=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFxaKL0j4aTO6uQnXRTui-xuM5GLWEyddRMXJZfxB8atJK-FGEJpv4WYmhPksbM710iQeiXojmMyv90SwkVf8J_ps1Z_oGylHFHK7vJnaNrRK5qY9F1URVSBbpCTFi1MqaXfTq4y7kCZ1Wftf8gsyb4rij3cY5cSkwA8Q==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGyWsUqlwBtPjbSkXB8XoY8uDuH_pVxbT2WQmNMJnzSEvdc8d9C5h2H56rQTIM9ZbGiDhy5wTarrECfuetECBVM2ek2njGSJ_AtsVl1pTzjFoU74ZpfaJP5UyLnvNxbC7QWQ5KnUqXQFTpb5s7DLA==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQG2x0UM1MVvEr6l7iWHLikDUen6Sty9WiPYvKAHdTpRecLFWrRh6bJQYFxI4iA40sZMY6QK9kDXD7Ld6SReCRgKjXn26HVrCw4nOPDmOu8j1_3S79_OUHhDu9o0ti-zDidXtG-bV1WAxd3T9frlBqA5wCZNoKs3nvE_-iwafYlRzPCYn3Eui6lIVcLJsv5fRl9BMQ==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGjwvuYdsKFG1LkWnvyHXQ5BOcnyV984w0zBJlatsNEb0v5zKgL4bdtl79MfZt9RE_7MByGJWgUtzpgRp1zSjn76VJzH0dkE9oORt1PjaK952ToyTZnVhFqR0I1suSYTcx5OrMtzAOybiet5CE=
- Python Decorators (公式ドキュメントなど): https://docs.python.org/ja/3/glossary.html#term-decorator
- Mercurial
hg_util.Abort
(Mercurialソースコード内の関連箇所): Mercurialのソースコードリポジトリ内でhg_util.Abort
を検索することで、その定義と使用例を確認できます。