ソフトウェアパッケージをソースからインストールする必要があるのに、パッケージ使用前に修正しておかなければならないバグをソー ス中に発見してしまう、というような事態はよくあることです。変更の後、暫くパッケージのことを忘れていると、数ヵ月後にパッケージを新しい版で更新する必要が出てきたとします。パッケージの新しい版が未だにバグを残していたなら、古い版のソースツリーから 修正内容を抽出して、新しい版に適用しなければなりません。このような作業は退屈で間違いを起こしやすいものです。
これは “パッチ管理” 問題の単純なケースです。自分では変更することができない “上流” のソースツリーがあるとします。上流のソースツリーの上でローカルな修正を行う必要があるなら、上流ソースの新しい版に対してローカルな修正を適用できるように、そう いった修正を別途管理したいと思うでしょう。
パッチ管理問題はさまざまな状況で発生します。オープンソースソフトウェアプロジェクトのユーザが、プロジェクト のメンテナンス担当へ、バグ修正や新規機能をパッチ形式で送付する状況が、おそらく最もわかりやすい状況でしょう。
オープンソースソフトウェアを含むオペレーティングシステムの配布者は、配布するパッケージに対する変更を頻繁に行うので、自分たちの環境においてビルドを行うのは当然のことです。
整備の上で幾つか変更を行いたい場合、標準的なdiff およびpatch プログラム(これらのツールに関する議論は12.4 節を参照のこと)を使用して、単一のパッチを管理することは簡単です。しかし、一旦変更の数が増え始めると、単一のパッチの管理は関連性の無 い “成果の塊” に感じ始めるため、例えば、単一のパッチは単一のバグ修正のみを含む(パッチは複数のファイルを修正するかもしれませんが、 “単一の事” しか行わない)ようになるでしょうから、異なるバグやローカルな修正に必要とされるパッチを、いくつも抱えることになるかもしれません。このような状況で、上流のパッケージ保守担当者にバグ修正のパッチを送ったとすると、彼らはその後のリ リースにおいてその修正を取り込むでしょうから、新しい版への更新の際には、そのパッチの適用を取りやめることができます。
上流のソースツリーに対して単一のパッチを保守することは、退屈で間違いやすいですが難しくはありません。しかし、保守しなければならないパッチの数が増えるにしたがい、問題の複雑さはすみやかに増加します。すくなからぬパッチを抱え込むこと で、適用の有無を把握したり、それらを保守することが、「面倒なこと」から「圧倒されること」へと変化するでしょう。
幸いなことに、 Mercurial は Mercurial Queues (あるいは単に “MQ”)と呼ばれる、パッチ管理問題を簡素化する強力な拡張機能を持っています。
1990 年代後半、何人かの Linux カーネル開発者達は、 Linux カーネルの挙動を変える “パッチ系列” の保守を始めていました。幾つかの系列は安定性に、幾つかは網羅性に、その他の系列はより実験的な部分に焦点を当てていまし た。
これらのパッチのサイズは速やかに巨大化しました。 2002 年、 Andrew Morton が、自分のパッチキュー管理作業を自動化するのに用いていた、幾つかのシェルスクリプトを発表しました。 Andrew は、 Linux カーネルソース上での数百(時には数千)のパッチの管理に、これらのスクリプトを上手に利用していました。
2003 年の初頭、 Andreas Gruenbacher と Martin Quinson は、 Andrew によるスクリプトの手法を取り入れて、
“patchwork quilt” [AG] あるいは単に “quilt” (これについて述べた論文は [Gru05]を参照のこと) と呼ばれるツールを発表しました。パッチ管理が大幅に自動化されることから、 quilt はオープンソース開発者の間で瞬く間に大きな支持を得ました。
quilt は、最上位のディレクトリにおいてパッチのスタックを管理します。管理開始の際には、 quilt に対してディレクトリツリーを管理する旨と、どのファイルを管理したいのかを伝えます。 quilt はこれらのファイルの名前と内容を別な場所に保存します。バグの修正の際には、新しいパッチを(単一のコマンドを使用して)作成し、修正する必要の有るファイルの編集を行い、パッチを “refresh” します。
refresh の段階で quilt はディレクトリツリーを走査します。 quilt は実施された全ての変更でパッチを更新します。最上位のディレクトリにおいて作成した別なパッチを用いることで、 “1つのパッチが適用されたツリー” から “2つのパッチが適用されたツリー” へ と変化させるために必要な変更を、追跡することができます。
ツリーに対するパッチの適用状況を変更することもできます。パッチを “pop” すると、そのパッチによる変更はディレクトリツリーから取り除かれます。しかし、 quilt はどのパッチが取り除かれたのかを覚えているので、取り除かれたパッチを再び “push” することができ、ディレクトリツリーには当該パッチによる変更が復元されます。最も重要な点は、 “refresh” コマンドの実行と、それによる最 上位のパッチの内容更新が任意の時点にできることです。これは、パッチの適用状況と、そのパッチによる変更内容の両方を、任意の時 点で変更できることを意味します。
quilt は変更制御ツールを意識しないため、展開された tarball の最上位ディレクトリにおいても、 Subversion リポジトリにおいて も同等に機能します。
2005 年中旬、 quilt 的な振る舞いを Mercurial に追加するための、 Mercurial Queues と呼ばれる拡張機能が、 Chris Mason により 実装されました。
quilt と MQ の大きな違いは、 quilt が変更制御システムを意識しないのに対して、 MQ が Mercurial に統合されていることで す。 push される個々のパッチは、 Mercurial のチェンジセットとして表現されます。パッチを pop することで、チェンジセットは取 り除かれます。
変更制御システムを意識しないことから、 Mercurial と MQ を利用できない状況について知る上で、依然として quilt は非常に有 用なソフトウェアです。
パッチと変更管理の統一を通して MQ が提供するものの価値を、誇張し過ぎることはありません。
フリーソフトウェアおよびオープンソースの世界でパッチが利用され続けるのは、変更管理ツールが年々その機能を向上させている にも関わらず、パッチが軽快さを持っていることが大きな理由の一つです。
伝統的な変更制御ツールは、実施したことに関する全てを、永続的で取り消しの出来ないものとして記録します。この振る舞いに大 きな価値がある一方で、幾分堅苦しくもあります。過激な実験を行おうとする場合、自分が行おうとすることに慎重になるか、必要とさ れない〜なお悪いことには、誤解や不安定の元となる〜失敗と間違いの記録を、永続的な履歴記録中に残す危険を冒す必要がありま す。
対照的に、 MQ における分散履歴管理とパッチの結合により、あなたの作業を容易に隔離することができます。あなたのパッチは通 常の変更履歴の上で存続し続け、望む時にそれらの実施/取り消しを行うことが出来ます。そのパッチが気に入らない場合、それを取り やめることができます。そのパッチが完全には望むものでない場合、望む姿に洗練させるまで、必要なだけ何度でも修正することが出来 ます。
例えば、パッチと変更管理の統合により、パッチの理解とその効果〜および元になったコードとの連携〜のデバッグが、非常に簡単 になります。全ての適用済みパッチが関連したチェンジセットを持っているので、どのチェンジセットとパッチがそのファイルに影響を 及ぼしているのかを、“hg log filename ” によって見ることが出来ます。bisect 拡張を用いることで、バグが持ち込まれたり修正さ れた時点を見るために、全てのチェンジセットと適用済みパッチを通しての二分探索を行うことができます。“hg annotate” コマンド を用いることで、ソースファイルの特定の行を変更したのが、どのチェンジセットやパッチであるかを見ることが出来ま す。
MQ は、それがパッチ指向の特性を持つことを表に出しているため、パッチがどういったものであるかや、パッチとともに機能する ツールに関することがらを理解する手助けになります。
伝統的な Unix のdiff コマンドは、2つのファイルを比較し両者の違いを表示します。patch コマンドは、この違いをファイルに 対する変更とみなします。これらのコマンドの簡単な動作例として、図 12.4を見てください。
diff が生成する(そして、patch が入力する)ファイルの形式は “パッチ(patch)” ないし “差分(diff)” と呼ばれます。パッチと差分の間に違いはありません(以後は、より一般的に使用される “パッチ” という呼称を使用しま す)。
パッチファイルは、任意のテキストから始めることができます。patch コマンドはこのテキストを無視しますが、 MQ はチェンジ セットを生成する際のコミットメッセージとみなします。パッチ内容を開始を見つけるために、patch は “diff -” で始まる最初の行 を探します。
MQ は unified 差分と共に機能します(patch はそれ以外の何種類かの差分形式でも機能しますが、 MQ は unified 差分でないと 機能しません)。 unified 差分は2種類のヘッダを持っています。ファイルヘッダ headerには、変更対象となるファイルのファイル名 が記述され、patch コマンドが新規のファイルヘッダを見つけた際には、変更を行うために当該する名前のファイルを探しま す。
ファイルヘッダに続いて、 hunk 列が記述されます。それぞれの hunk はヘッダで開始され、その hunk により変更される対象の、 ファイルにおける行番号の範囲を識別します。ヘッダに続く hunk は、ファイルの改変されない部分からなる数行のテキストが前後に付 加されます。これらの改変されない部分のことを、 hunk に対するコンテキストと呼びます。後続の hunk との間に少量のコンテキスト しかない場合、diff は新たな hunk ヘッダを表示しません。変更内容の間に数行のコンテキスト行を置いて、 hunk をそのまま続けま す。
コンテキストの個々の行は空白文字で始まります。 hunk 内部では、 “-” で始まる行は “削除される行” を、 “+” で始まる行は “挿 入される行” を意味します。例えば、変更される行は、1行の削除と1行の挿入で表現されます。
パッチのより微妙な側面に関しては後ほど( 12.6節にて)説明しますが、 MQ を利用するに当たってはここまでの知識で十分で す。
MQ は Mercurial の拡張として実装されているので、利用の前に明示的に有効化する必要があります(ダウンロードの必要はありませ ん。 MQ は通常の Mercurial の配布物に含まれています)。 MQ を有効にするには、~/.hgrc ファイルを編集し、 12.5 に示す行を 追加してください。
拡張が有効化されると、いくつかの新しいコマンドが有効化されます。“hg help” を使って“hg qinit” コマンドの利用可否を見ることで、拡張が機能することを確認できます。 12.3 の例を参照してください。
1 $ hg help qinit
2 hg qinit [-c] 3 4 init a new queue repository 5 6 The queue repository is unversioned by default. If 7 -c/--create-repo is specified, qinit will create a separate nested 8 repository for patches (qinit -c may also be run later to convert 9 an unversioned patch repository into a versioned one). You can use 10 qcommit to commit changes to this queue repository. 11 12 options: 13 14 -c --create-repo create queue repository 15 16 use "hg -v help qinit" to show global options
|
MQ は全ての Mercurial リポジトリで利用でき、コマンドはそのリポジトリにしか作用しません。利用開始の際には、“hg qinit” コマンドによりリポジトリの準備を行います( 12.4 参照)。このコマンドは、.hg/patches と呼ばれる空のディレクトリを作成し、 MQ はこのディレクトリにメタデータを格納します。多くの Mercurial コマンドと同様、“hg qinit” コマンドは実行が正常に終了し た場合には、特に何も表示しません。
1 $ hg init mq-sandbox
2 $ cd mq-sandbox 3 $ echo ’line 1’ > file1 4 $ echo ’another line 1’ > file2 5 $ hg add file1 file2 6 $ hg commit -m’first change’ 7 $ hg qinit
|
1 $ hg tip
2 changeset: 0:5ddbf74425a0 3 tag: tip 4 user: Bryan O’Sullivan <bos@serpentine.com> 5 date: Mon Jul 20 21:58:51 2009 +0000 6 summary: first change 7 8 $ hg qnew first.patch 9 $ hg tip 10 changeset: 1:a3c1798530eb 11 tag: qtip 12 tag: first.patch 13 tag: tip 14 tag: qbase 15 user: Bryan O’Sullivan <bos@serpentine.com> 16 date: Mon Jul 20 21:58:51 2009 +0000 17 summary: [mq]: first.patch 18 19 $ ls .hg/patches 20 first.patch series status
|
新しいパッチで作業を開始するには、“hg qnew” コマンドを使います。このコマンドは作成するパッチの名前を引 数に取ります。例 12.5に示すように、 MQ はこれを.hg/patchesディレクトリ中の実ファイルの名前とみなしま す。
.hg/patches ディレクトリ配下にはそれ以外にも、series とstatusという2つの新しいファイルが作成され ます。series は、そのリポジトリにおいて MQ が管理する全てのパッチの一覧を、1行1パッチで保持していま す。status はそのリポジトリにおいて MQ が適用した全てのパッチを追跡するための、内部帳簿的な用途に使用されま す。
新しいパッチを作成したならば、普段と同じように作業領域ディレクトリのファイルを編集できます。“hg diff” や“hg annotate” といった、 Mercurial の全ての通常コマンドはそれ以前と全く同様に機能します。
作業内容を保存する段階になったなら、作業中のパッチを更新するために“hg qrefresh” を使用します(図 12.5参照)。このコマン ドは、作業領域ディレクトリでの変更内容をパッチへと格納し、対応するチェンジセットを、それらの変更内容を保持するように更新し ます。
1 $ echo ’line 2’ >> file1
2 $ hg diff 3 diff -r a3c1798530eb file1 4 --- a/file1 Mon Jul 20 21:58:51 2009 +0000 5 +++ b/file1 Mon Jul 20 21:58:52 2009 +0000 6 @@ -1,1 +1,2 @@ 7 line 1 8 +line 2 9 $ hg qrefresh 10 $ hg diff 11 $ hg tip --style=compact --patch 12 1[qtip,first.patch,tip,qbase] b353a00003e1 2009-07-20 21:58 +0000 bos 13 [mq]: first.patch 14 15 diff -r 5ddbf74425a0 -r b353a00003e1 file1 16 --- a/file1 Mon Jul 20 21:58:51 2009 +0000 17 +++ b/file1 Mon Jul 20 21:58:52 2009 +0000 18 @@ -1,1 +1,2 @@ 19 line 1 20 +line 2 21
|
“hg qrefresh” コマンドはいつでも何度でも実行できるので、作業の “チェックポイント” として利用するのも良いでしょう。都合 の良い時にパッチの refresh を実施することで、実験的な作業を行ってみて、それがうまく機能しない場合には、直近の refresh 時点ま での変更を、“hg revert” コマンドにより取り消すことができます。
1 $ echo ’line 3’ >> file1
2 $ hg status 3 M file1 4 $ hg qrefresh 5 $ hg tip --style=compact --patch 6 1[qtip,first.patch,tip,qbase] 9eb545de58b2 2009-07-20 21:58 +0000 bos 7 [mq]: first.patch 8 9 diff -r 5ddbf74425a0 -r 9eb545de58b2 file1 10 --- a/file1 Mon Jul 20 21:58:51 2009 +0000 11 +++ b/file1 Mon Jul 20 21:58:52 2009 +0000 12 @@ -1,1 +1,3 @@ 13 line 1 14 +line 2 15 +line 3 16
|
パッチに対する作業を終えるか、他のパッチに対する作業が必要になったなら、再度“hg qnew” コマンドを実行することで、新しい パッチを作成します。 Mercurial は、新規に作成したこのパッチを、既存のパッチの最上位に適用します。図 12.8を参照してくださ い。先に作業していたパッチに含まれる変更は、この新しいパッチの文脈の一部として含まれます(“hg annotate” 出力を見れば、こ のことは明らかです)。
1 $ hg qnew second.patch
2 $ hg log --style=compact --limit=2 3 2[qtip,second.patch,tip] f44a4270d8a8 2009-07-20 21:58 +0000 bos 4 [mq]: second.patch 5 6 1[first.patch,qbase] 9eb545de58b2 2009-07-20 21:58 +0000 bos 7 [mq]: first.patch 8 9 $ echo ’line 4’ >> file1 10 $ hg qrefresh 11 $ hg tip --style=compact --patch 12 2[qtip,second.patch,tip] 90c5baabf0e2 2009-07-20 21:58 +0000 bos 13 [mq]: second.patch 14 15 diff -r 9eb545de58b2 -r 90c5baabf0e2 file1 16 --- a/file1 Mon Jul 20 21:58:52 2009 +0000 17 +++ b/file1 Mon Jul 20 21:58:53 2009 +0000 18 @@ -1,3 +1,4 @@ 19 line 1 20 line 2 21 line 3 22 +line 4 23 24 $ hg annotate file1 25 0: line 1 26 1: line 2 27 1: line 3 28 2: line 4
|
これまでは、“hg qnew” と“hg qrefresh” を除いて、 Mercurial の通常コマンドのみを使用するように注意してきました。しかし、図 12.5.3 に示すように、パッチに関する作業を行う際により便利な多くのコマンドを、 MQ は提供しています。
“管理されている” パッチと “適用されている” それの間に違いがあることを、先の記述では暗に示していますが、実際に両者の間には違 いがあります。 MQ は適用すること無しに、パッチをリポジトリ中で管理することができます。
適用されたパッチは、リポジトリ中に対応するチェンジセットを持ち、パッチとチェンジセットの効果は作業領域 ディレクトリにおいて見ることができます。“hg qpop” コマンドを使用して、パッチの適用を取り消すこともできま す。
MQ は取り除かれたパッチを管理し続けますが、そのパッチはもはやリポジトリ中に対応するチェンジセットを持たず、作業領域 ディレクトリにはパッチによる変更の痕跡は残されていません。図 12.10に、適用されたパッチと追跡されているそれの違いを示しま す。
“hg qpush” コマンドを使用することで、未適用パッチの再適用、ないし取り除きを行うことができます。この操作によりパッチに 対応する新しいチェンジセットが作成され、パッチによる変更は再び作業領域ディレクトリに現れます。図 12.11に、“hg qpop” およ び “hg qpush” の実施例を示します。図のように1つないし2つのパッチを一度取り除いても、“hg qseries” の出力は変化しません が、その一方で“hg qapplied” の出力は変化します。
1 $ hg qapplied
2 first.patch 3 second.patch 4 $ hg qpop 5 now at: first.patch 6 $ hg qseries 7 first.patch 8 second.patch 9 $ hg qapplied 10 first.patch 11 $ cat file1 12 line 1 13 line 2 14 line 3
|
“hg qpush” および“hg qpop” のそれぞれが、デフォルトでは一度に一つのパッチに対して処理を行う一方で、一度 に複数のパッチの適用や取り消しを行うこともできます。“hg qpush” に-a オプションを指定することにより、全 ての未適用パッチの適用が、“hg qpop” に-a オプションを指定することにより、全ての適用済みパッチの取り消 しを行うことができます。(それ以外の複数パッチの適用/取り消しの方法に関しては、 12.7 節を参照してくださ い。)
1 $ hg qpush -a
2 applying second.patch 3 now at: second.patch 4 $ cat file1 5 line 1 6 line 2 7 line 3 8 line 4
|
いくつかの MQ コマンドは、処理の前に作業領域ディレクトリの確認を行い、何らかの改変が検出された場合には処理を中断します。 この確認は、パッチに取り込まれていない変更内容を失わないために行われます。図 12.13 に例を示します。“hg qnew” コマンドは 未取り込みの変更(このケースでは file3 の“hg add” に起因するもの)がある場合、新しいパッチを生成しませ ん。
1 $ echo ’file 3, line 1’ >> file3
2 $ hg qnew add-file3.patch 3 $ hg qnew -f add-file3.patch 4 abort: patch "add-file3.patch" already exists
|
作業領域ディレクトリを確認するコマンドは、すべて “了解済み” オプションを取ることができ、そのオプションは常に -f と名づけられています。 -f オプションの厳密な意味はコマンドごとに異なります。例えば、“hg qnew -f” は新たに生成されるパッチに未取り込みの変更を全て取り込みますが、 “hg qpop -f” は取り消されるパッチが影響を及ぼすファイルに対する変更を元に戻しま す1 。利用 する前に各コマンドの -f オプションのドキュメントを確認しましょう!
“hg qrefresh” コマンドは、常に最上位の適用済みパッチを更新します。これは、あるパッチに対する操作を(refresh することで) 中断し、取り消し(pop)ないし適用(push)により別のパッチを最上位に持ってくることで、そのパッチに対して作業することができ ることを意味します。
この機能によって可能になることを例によって示します。2つのパッチによって新しい機能を開発しているものとしましょう。1つ 目のパッチはソフトウェアの中核機能の変更を、そして2つ目のパッチは — 1つ目のパッチの上で — 中核機能の変更を 使用するためのユーザーインタフェース (UI) の変更を行います。 UI へのパッチの作業中に、中核機能へのパッチ にバグを見つけたとしても、それを修正するのは簡単なことです。 UI へのパッチに対する“hg qrefresh” により 作業中の変更を保存した後に、“hg qpop” により操作対象パッチを中核機能へのそれに変更します(パッチスタッ クを下へと移動します)。中核機能へのパッチのバグを修正し、“hg qrefresh” によってパッチへの反映を行った 後に、“hg qpush” により操作対象パッチを UI へのパッチに戻すことで、やりかけの作業を継続することができま す。
MQ はパッチの適用に GNU patch コマンドを使用しますので、patch コマンドの動作とパッチそのものに関して、より詳細な情報を 知ることは有用です。
パッチのファイルヘッダを見ると、実際のパス名には現れない余分な要素を先頭に持っていることに気が付くでしょう。これは 以前にパッチが生成されていた方法の名残です(今でもこの方法を用いていますが、近年の構成管理ツールでは稀で す)。
Alice が tarball を展開してファイルを編集した後で、パッチを作成しようと考えたとします。作業領域ディレクトリを改名し、再 度 tarball を展開(この展開のために改名することが必要になります)し、diff コマンドに-r および-N オプションを 指定することで、改変前のディレクトリと改変後のディレクトリの間で再帰的にパッチを生成します。一方には改変 前のディレクトリ名が全てのファイルのパス冒頭に付加され、他方には改変後のディレクトリ名が同様に付加されま す。
Alices からパッチを受け取った人物の環境に、改変前と改変後ディレクトリの両方と厳密に一致する名前のディレクト リがある、というのはありそうもない事ですから、patch コマンドは、パッチ適用時にパス名要素の何番目までを 取り除くかを指す-p オプションを持っています。このオプションに指定される数を除去数(strip count)と呼びま す。
“-p1” オプションは、 “除去数を1とみなす” ことを意味します。patch コマンドが、ファイルヘッダにおいてファイル名 foo/bar/baz を検知した場合、foo 部分を除去したbar/baz というファイルに対してパッチをあてます(厳密なことを言えば、除去数 は除去されるパス区切り(およびそれに付随する要素)の数を指します。除去数1は、foo/bar を bar にしますが、/foo/bar(先頭 のスラッシュに注意)はfoo/bar になります)。
パッチにおける “標準の” 除去数は1ですので、ほとんど全てのパッチは取り除かれる先頭要素を1つ含んでいます。 Mercurial の “hg diff” コマンドはこの形式でパス名を生成しますので、“hg import” コマンドや MQ は除去数1のパッチを期待していま す。
除去数が1ではないパッチをパッチキューに追加しようとした場合、現時点で -p オプションを持っていない“hg qimport” ( Mercurial バグ番号 311 参照のこと)では取り込むことができません。その場合、“hg qnew” で新規パッチを MQ 上に作成し、 “patch -pN ” によりパッチを適用、“hg addremove” でパッチにより追加/削除されたファイルを特定し、“hg qrefresh” を行うのが最善の方法です。このような面倒な手順はいずれ不要になるかもしれません。詳細は Mercurial バグ番号 311 を参照してくだ さい。
patch が hunk を適用する際には、 it tries a handful of (successively はどこに掛かる?) successively less accurate strategies to try to make the hunk apply XXXXX 用心深いこの方法により、古い版のファイルで生成されたパッチであっても、新しい版のファイルに 適用することが、多くの場合で可能となります。
patch コマンドは、最初は hunk における行番号、コンテキストおよび変更対象テキストの厳密一致を試みます。厳密一致ができな い場合、行番号に関する情報を無視し、コンテキストのみの厳密一致を試みます。これが成功した場合、patch コマンドは、 hunk が 適用されたことと、元の行番号からオフセット分ずれていることを表示します。
コンテキストのみによる一致が失敗した場合、patch は冒頭および末尾行を取り除いたコンテキストを用いて、縮小コンテキストの みによる一致を試みます。縮小コンテキストによる hunk 適用が成功した場合、あいまいな要因を元に hunk が適用された ことを表示します(この時示される数値は、patch コマンドがパッチ適用前にコンテキストから取り除いた行数で す)。
これらのどの技法でも適用できない場合、patch コマンドは争点となっている hunk が却下された旨を表示します。patch コマンド は却下された hunk (単に “reject” とも呼ばれます)を同名で.rej 拡張子を持つファイルに保存します。更にその上で、パッチ適用 前のファイルのコピーを.orig 拡張子付きで保存します。拡張子無しのファイルは、適切にの適用された hunk による変更を含んでい ます。ファイル foo を変更する6つの hunk を持つパッチがあり、そのうちの1つが適用できなかった場合、変更前の内容を持つ foo.orig、適用できなかった hunk を1つ持つ foo.rej および適用できた5つの hynk による変更を含む fooの3つのファイルがで きます。
patch コマンドのファイルへの作用を知る上で、有用な事がいくつかあります。
オフセット付きや、あいまいな要因を元にしている場合であっても、パッチの適用は完全に成功することが多いのですが、一方でこのよ うな厳密性を欠いた適用手法は、おのずとファイルへのパッチ適用が不完全である可能性を残してしまいます。最も典型的な 事例は、パッチを2度適用してしまうことや、不適切な位置に適用してしまうことです。patch や“hg qpush” が オフセットやあいまい要因に関して言及した際には、ファイルが適切に変更されていることを後から確認してくださ い。
オフセット付きや、あいあまいな要因を元に適用されたパッチを refresh するのが、多くの場合においておすすめなのは、パッチの refresh が、パッチを綺麗に適用するための新しいコンテキスト情報を生成するからです。ただし、パッチを refresh す ることで、元ファイルの異なる版に対してパッチの適用が失敗するようになる場合があるため、 “多くの場合” おす すめですが、 “常に” ではありません。ソースツリーの複数の版に対して適用可能なパッチを保守するような場合、 パッチ適用処理の結果を検証する機会を得ることが出来るので、パッチにあいまい要因を持たせておくのは許容範囲で す。
パッチの適用に失敗すると、“hg qpush” はエラーメッセージを表示して終了します。.rej ファイルが残されている場 合、それ以上のパッチを push したり他の作業をする前に、却下された hunk の修正を行うことが一般的には最善で す。
パッチの適用対象であるソースの更新により、それまではきちんと適用できていたパッチが適用できなくなった場合の Mercurial Queues の使い方の詳細に関しては、 12.8 節を参照してください。
残念なことに、却下された hunk を扱うための決定的な技法は存在しません。多くの場合、.rej ファイルを参照しながら、対象 ファイルを編集し、却下された hunk を手動で適用しなければなりません。
思い切った事も辞さないのであれば、パッチの適用に関してはpatch よりも強力な、wiggle [Bro] と呼ばれるツールが、 Linux カーネルハッカーの Neil Brown により書かれています。
patch により却下された hunk の適用を自動化するために、簡便な手法を用いるmpatch [Mas] と呼ばれるツールも、別の Linux カーネルハッカーの Chris Mason (Mercurial Queues の作者です)により書かれています。mpatch は、4つのよくある理由で却下 された hunk の適用を助けることができます。
wiggle ないしmpatch を使用する際には、実施結果に対して二重に注意を払う必要があります。実のところmpatch は、処理の完 了時に自動的にマージプログラムへと誘導することで、ツール出力の二重確認の手法を強要していますので、mpatch の実行結果を確認 し、残されたマージ処理を完了させることが出来ます。
MQ は大量のパッチの取り扱いを効率よく実施します。 2006 EuroPython conference [O’S06] での講演のために、 2006 年中旬に性 能実験を実施しました。適用パッチとして、 1,738 個のパッチを持つ Linux 2.6.17-mm1 パッチ系列を使用しています。 Linux 2.6.12-rc2 から Linux 2.6.17 にかけての、 27,472 のリビジョン全てを持つ Linux カーネルリポジトリに対して、これらのパッチを適 用したのです。
旧式の遅いラップトップ PC 上で、 1,738 個のパッチ全てを“hg qpush -a” するのに 3.5 分、それらを“hg qpop -a” するの に 30 秒かかりました(新しいラップトップなら、全てのパッチを push する時間は2分まで下がりました)。最も大 きなパッチの1つ(22,779 行の変更を 287 のファイルに対して行います)を 6.6 秒で“hg qrefresh” できていま す。
MQ が巨大なソースツリーで作業するのに適しているのは明らかですが、最高の性能を出すために知っておいたほうが良い幾つかの コツがあります。
最初のコツは、 “一括” 操作を行うことです。“hg qpush” および“hg qpop” の実行の際には、何ら変更がされていないこと と、“hg qrefresh” し忘れがないことを確認するために、常に作業領域ディレクトリを走査しています。小さなソースツリーの場合 は、この走査に要する時間は気になりません。しかし、中程度(10,000 ファイル程度)のソースツリーでは、1秒からそれ以上の時間 が必要です。
“hg qpush” および“hg qpop” コマンドでは、複数パッチを一括して push および pop する際に、作業を切り上げる “到達パッ チ” を指定することができます。到達パッチ指定付きで実行することで、“hg qpush” は指定したパッチが適用スタックの最上位になる までパッチの適用を行います。“hg qpop” の場合は、到達パッチが適用スタックの最上位になるまでパッチの取り消しを行いま す。
到達パッチの指定には、パッチの名前か数値が使用できます。数値指定の場合、パッチは0から数え始めるため、最初のパッチは 0、次のパッチの1となります。
直接変更することのできないリポジトリに対して、パッチスタックを持つことはよくある事です。第三者のソースに対す る変更や、元ソースの更新頻度よりも開発に時間の掛かる機能を実装している場合、元ソースの更新との同期や、適 用できなくなったパッチの hunk を修正する必要があります。このような作業は、パッチ系列のリベースと呼ばれま す。
リベースの一番単純な方法は、パッチに対して“hg qpop -a” を行い、“hg pull” で元ソースの変更をリポジトリに取り込み、 最後に“hg qpush -a” でパッチを再適用します。 MQ によるパッチ適用では、衝突が検出されている間は適用できないパッチの適用 を止めることで、衝突の解消とパッチの“hg qrefresh” を行う機会を設けつつ、パッチスタック中の全てのパッチを更新し終わるまで パッチの適用を継続します。
元ソースの変更がパッチの適用具合に悪影響を及ぼす心配が無いのであれば、この手法は手軽で且つ上手く機能するでしょう。しか しながら、元ソースで頻繁に更新される部分に触れるようなパッチスタックの場合、却下された hunk の手動での修正は、すぐにでも面 倒な作業と化すでしょう。
リベース処理を部分的に自動化する事は可能です。元ソースの幾つかのリビジョンに対してきちんと適用できるパッチであれば、 異なるリビジョンとパッチとの間での衝突に対して、事前の適用情報を用いた解消を MQ により行うことができま す。
手順は少々込み入っています。
“hg qpush -m” 実施の際には、seriesファイルに列挙されたそれぞれのパッチは通常通り適用されます。あ いまい要因を元にパッチが適用されたり、パッチの適用が却下された場合、 MQ は“hg qsave” により保存された パッチキューを参照し、パッチに対応するチェンジセットを用いた 3-way マージを行います。このマージ処理には Mercurial の通常のマージ機構が利用されますので、衝突の解消の際には GUI マージツールが起動されるかもしれませ ん。
パッチの影響を解消し終えると、マージ結果を元に MQ によるパッチの refresh が行われます。
この手順を終えたリポジトリには、古いパッチキューに相当するチェンジセットを元にした余分な head と、.hg/patches.N に保 存された古いパッチキューが残ります。余分な head の削除は、“hg qpop -a -n patches.N ” ないし“hg strip” で行 うことができます。バックアップとしての必要性がなくなったなら、.hg/patches.N も削除してしまって構いませ ん。
パッチを操作する MQ コマンドにおけるパッチの指定は、パッチの名前か数値で行います。名前による指定は非常にわかりやすいで しょう。例えば、“hg qpush” コマンドへのfoo.patch の指定により、foo.patch が適用されるまでパッチの適用が繰り返されま す。
短縮形式として、名前と数値オフセットの両方を指定することもできます。foo.patch-2 は “foo.patch パッチの2つ前” を、bar.patch+4 は “bar.patch パッチの4つ後ろ” を意味します。
数値によるパッチの指定はそれほど難しくありません。“hg qseries” により最初に表示されるパッチは0、2番目は1、となって います(そう、0から数え始める仕組みです)。
MQ は、通常の Mercurial コマンドの利用時におけるパッチ操作も簡便にします。チェンジセット識別子を受け付ける全てのコマン ドは、適用済みのパッチ名も受け付けます。リポジトリ中に元々あった通常のタグに加えて、パッチ適用の際の起点となるリビジョンに タグ2 が付 与されます。それに加えて、qbase およびqtip タグにより、最下位および最上位の適用ずみパッチをそれぞれ指定できま す。
Mercurial の通常タグに対するこれらの拡張は、パッチの取り扱いをより簡便にします。
(“パッチ爆弾” については14.4 節を参照してください)
パッチの名前を利用可能にするために、 MQ は Mercurial の持つ内部タグ機能を使用しているので、パッチを名前で指定する場合には、その名前を全て入力する必要はありません。
1 $ hg qapplied
2 first.patch 3 second.patch 4 $ hg log -r qbase:qtip 5 changeset: 1:2c9f9a14ed72 6 tag: first.patch 7 tag: qbase 8 user: Bryan O’Sullivan <bos@serpentine.com> 9 date: Mon Jul 20 21:48:24 2009 +0000 10 summary: [mq]: first.patch 11 12 changeset: 2:1b8fcc30cf3e 13 tag: qtip 14 tag: second.patch 15 tag: tip 16 user: Bryan O’Sullivan <bos@serpentine.com> 17 date: Mon Jul 20 21:48:24 2009 +0000 18 summary: [mq]: second.patch 19 20 $ hg export second.patch 21 # HG changeset patch 22 # User Bryan O’Sullivan <bos@serpentine.com> 23 # Date 1248126504 0 24 # Node ID 1b8fcc30cf3e3d7640a082057402061e7832301f 25 # Parent 2c9f9a14ed7225ad61f5178a726f1f72da46528e 26 [mq]: second.patch 27 28 diff -r 2c9f9a14ed72 -r 1b8fcc30cf3e other.c 29 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 30 +++ b/other.c Mon Jul 20 21:48:24 2009 +0000 31 @@ -0,0 +1,1 @@ 32 +double u;
|
パッチの名前をタグで実現することで、“hg log” コマンドの実行時に、その出力の一部としてタグとしてのパッチ名が表示される、という副作用も得られます。このことにより、適用済みのパッチと “通常の” リビジョンを、視覚的に識別することを容易にします。適用済みパッチと連携する Mercurial の通常コマンドの実行例を図 12.14 に示しま す。
MQ の利用に関して、独立した節を設ける程ではないものの、知っておいたほうが良い事柄が幾つかあります。ここでは、そういった 事柄を集めてみました。
MQ が利用する.hg/patches ディレクトリが Mercurial の作業領域ディレクトリの外にあるため、 MQ の “下にある”Mercurial のリ ポジトリは、パッチの管理や存在に関して何も認識していません。
このことは、パッチディレクトリの内容をそれ自身の Mercurial リポジトリを用いて管理できる、という興味深い可能性を もたらします。例えば、パッチに関する作業を行い、“hg qrefresh” をした後で、パッチの現状を“hg commit” することで、後からその状態へとパッチを “巻き戻す” (roll back)することができるなど、有用な機能を提供しま す。
複数のリポジトリの間で、同一パッチスタックの異なる版を共有することも出来ます。筆者は Linux カーネル機能の開発の際にこの 手法を使用しています。複数の CPU アーキテクチャごとにそれぞれ真新しいカーネルソースのコピーを用意し、それぞれに作業中の パッチを含むリポジトリを複製します。別なアーキテクチャで変更内容の試験を行う際には、対応するカーネルソースのパッチリポジト リへ現時点のパッチを push し、全てのパッチを最適用(pop 後に push)した後に、そのカーネルのビルドおよび試験を行いま す。
リポジトリ形式の上でパッチを管理することで、適用対象のソースに対する制御の可否に関わり無く、開発者同士でお互いに衝突す ること無しに、同じパッチ系列に対する作業を実施できます
MQ は.hg/patches ディレクトリを自身のリポジトリとして、パッチ操作を補助しますが、“hg qinit” での初期化の際に -c オプションを指定することで、.hg/patches ディレクトリを Mercurial リポジトリとして作成することが出来ま す。
利便性上、.hg/patches ディレクトリが Mercurial リポジトリである場合、 MQ は作成・取り込みを行ったパッチの全てを自動的 に“hg add” します。
最後になりますが、 MQ は.hg/patches において“hg commit” を実行する短縮コマンド“hg qcommit” を提供していますので、(ディレクトリ移動等の)煩わしいキー入力が省略できます。
MQ によるパッチのリポジトリ管理のサポートは、限定的なものです。
MQ は、パッチディレクトリに対して行われた変更を、自動的に検出することはできません。“hg pull” の実行や、手動での編 集、あるいは“hg update” の実行によるパッチやseries の変更を行った場合、パッチ適用対象のリポジトリにおいて“hg qpop -a” の後に“hg qpush -a” を行って、それらの変更を有効にする必要があります。この作業を忘れた場合、 MQ は適用されている パッチがどれなのか混乱してしまうでしょう。
暫くの間、パッチを使った作業をしていると、扱っているパッチの解釈や操作を補助するツールが、欲しくてたまらなくなっているに違 いありません。
diffstat コマンド [Dic] は、パッチによって各ファイルがどれだけ変更されるかを表すヒストグラムを生成します。どのファイル が、どの程度の影響を受けるのか、といった全体的な “感覚を掴む” には良い方法です(diffstat の-p オプション利用は勿論良いので すが、ファイル名の前置詞に対して行う-p オプションの巧妙な処理は、少なくとも筆者にとってはわかりにくいで す)。
1 $ diffstat -p1 remove-redundant-null-checks.patch
2 drivers/char/agp/sgi-agp.c | 5 ++--- 3 drivers/char/hvcs.c | 11 +++++------ 4 drivers/message/fusion/mptfc.c | 6 ++---- 5 drivers/message/fusion/mptsas.c | 3 +-- 6 drivers/net/fs_enet/fs_enet-mii.c | 3 +-- 7 drivers/net/wireless/ipw2200.c | 22 ++++++---------------- 8 drivers/scsi/libata-scsi.c | 4 +--- 9 drivers/video/au1100fb.c | 3 +-- 10 8 files changed, 19 insertions(+), 38 deletions(-) 11 $ filterdiff -i ’*/video/*’ remove-redundant-null-checks.patch 12 --- a/drivers/video/au1100fb.c~remove-redundant-null-checks-before-free-in-drivers 13 +++ a/drivers/video/au1100fb.c 14 @@ -743,8 +743,7 @@ void __exit au1100fb_cleanup(void) 15 { 16 driver_unregister(&au1100fb_driver); 17 18 - if (drv_info.opt_mode) 19 - kfree(drv_info.opt_mode); 20 + kfree(drv_info.opt_mode); 21 } 22 23 module_init(au1100fb_init);
|
patchutils パッケージ [Wau] は貴重な存在です。このパッケージは、 “Unix の理念” に従って、それぞれがパッチに対して単一の処理を行う小さなツールの集まりです。 patchutils の中で筆者が最も利用しているのは、パッチファイルから一部を展開する filterdiff です。例えば、あるパッチが数ダースのディレクトリに渡って数百のファイルを変更する場合、filterdiff を起動する ことで、指定したパターンに名前が合致するファイルにだけ変更を行う、小さなパッチを生成することが出来ます。それ以外の例につい ては、 13.9.2 節を参照してください。
一連のパッチが、フリーソフトウェアやオープンソースプロジェクトへ送付するものであろうと、あなたの作業にお ける定期的な変更手続きとみなされるものであろうとも、より良く作業するための、簡単に利用できる手法がありま す。
まずは、パッチに説明的な名前をつけましょう。例えば rework-device-alloc.patch といった名前は、そのパッチが何を行うも のかというヒントをすばやく与えてくれるので、良い名前と言えるでしょう。名前は長くても問題にはなりません。名前を入力すること はそれほど多くはないでしょうが、“hg qapplied” や“hg qtop” といったコマンドは、何度も何度も実行するものですから。多くの パッチを扱う場合や、多くの異なるタスクに手一杯でパッチに多くの注意を割けないような場合、名前の適切さはとりわけ重要で す。
次に、どのパッチに対して作業しているのかに注意しましょう。“hg qtop” コマンドを — 例えば、“hg tip -p” を指定しつつ — 使用して頻繁にパッチの名前を見ることで、どんな作業をしているのかを確認しましょう。筆者は作業中に何度も意図しないパッチに対 して“hg qrefresh” を実行してしまったことがありますが、間違ったパッチに取り込んでしまった変更を正しいパッチに移動させるの は、往々にして手のかかるものです。
上記の理由から、 12.12 節で紹介しているdiffstat やfilterdiffのようなサードパーティー製ツールの学習に、少しでも良いの で時間を費やすべきです。前者はパッチの及ぼす変更に関してすばやい見解を得ることが、後者はパッチ中の hunk を選択的に継ぎ合わ せて異なるパッチに組み上げることができます。
真新しい Mercurial リポジトリにファイルを投入するのは、非常にオーバーヘッドが低いので、単にダウンロードした ソース tarball に対して変更を加えるのだとしても、 MQ によりパッチ管理を行うことは非常に理にかなっていま す。
まずはソース tarball のダウンロードと展開を行い、 Mercurial リポジトリに投入します。
次にパッチスタックを作成し、変更を行います。
数週間から数ヵ月経ってから、そのパッケージの著者が新しい版をリリースしたとします。まずはリポジトリに変更を取り込みま す。
上記手順で“hg locate” により始まるパイプラインは、作業領域ディレクトリ中の全てのファイルを削除しますので、“hg commit” の--addremove オプションは、新しい版においてどのファイルが本当に追加/削除されたのかを判定できま す。
最後に、新しくなったソースツリーの最上位でパッチを適用します。
MQ はパッチ全体を結合する“hg qfold” コマンドを提供しています。このコマンドは、名前を指定したパッチを指定した順序で、最 上位の適用済みパッチへと “結合” し、それらの説明文を最上位パッチの説明文末尾へ追加します。結合対象のパッチは、結合の時点で 未適用でなければなりません。
パッチの結合順序は重要です。最上位の適用済みパッチが foo で、そこに“hg qfold” と quux を“hg qfold” する場合、順に foo、bar そして quuxと適用するのと同じ効果を持つパッチができあがります。
パッチの一部を他のパッへ併合するのは、パッチ全体を結合するよりも面倒です。
あるファイル(群)に対する変更全体を移動したい場合、filterdiff の-i および-x オプションを用いることで、パッチから切り 出す変更点を選択して、その結果を併合先パッチへと取り込むことでができます。通常は取り込み元となったパッチそのものは変更した くないものです。そこで、 MQ は取り込み元パッチを“hg qpush” する際に、取り込まれた分の hunk が拒否され たことが報告されますから、“hg qrefresh” でパッチを更新することで、重複した hunk を取り除くことができま す。
1つのファイルに対する複数の hunk を持つパッチの一部だけが欲しい場合、事態はもう少し厄介ですが、それ でも部分的に自動化することができます。“lsdiff -nvv” を使うことで、パッチに関するメタデータを表示させま す。
このコマンドは、3つの異なる数値の類を表示します。
必要なファイル番号や hunk 番号を特定するためには、視覚的な精査やパッチの読解が必要とされますが、それらの数値を filterdiff の--files や--hunks といったオプションに指定することで、ファイルや hunk を正確に選択することができま す。
一度 hunk を取り出してしまえば、結合先パッチの末尾に結合して 12.14.2 節の残りの作業を再開することができま す。
既に quilt を熟知しているのであれば、 MQ は同様のコマンド群を持っていますが、その働きにはいくらかの違いがありま す。
殆どの quilt コマンドに対して、 “q” で始まる対応する MQ のコマンドがあることに気付くことでしょう。但し、 quilt の add お よび remove コマンドに対応するのが、 Mercurial の通常の“hg add” および“hg remove” であるのが例外です。また、 MQ には quilt の edit に対応するコマンドはありません。