このページは RepositoryCorruption の日本語訳です。
リポジトリ/作業領域状態(dirstate)破損への対応
Contents
破損の原因
Mercurial の実行は、格納領域に対する保護無しに、通常のユーザ権限で行われます。そのため、ユーザ、ツール、実行環境が、特別な権限等を持たなくても、うっかりリポジトリを破損してしまう可能性があります。破損の原因には、以下のようなものも含まれます:
- 利用者の不注意 (.hg/ 配下のファイルに対する破損/削除)
- ハードウェアの問題 (メモリエラー、ディスク破損、停電/瞬断等の電源不良)
- オペレーティングシステム上の問題 (ファイルシステムの不良、カーネルクラッシュ、環境間互換問題)
- ツールの問題 (サードパーティ製品ツールによる .hg/ 配下の破損)
Mercurial 自身の問題 (Mercurial のバグによる破損 - 非常に稀です)
多くの場合、利用者の不注意によるものが大半です。Mercurial はリポジトリ破損に対して、以下のような多段階に渡る防護策を講じています:
- 全ての基本的な履歴操作を追加に限定
- 全てのデータに対して暗号強度の高いハッシュ値を算出
- トランザクションに対するジャーナルの記録
- clone/push/pull 操作による頻繁な履歴の複製
- verify 操作による軽量な整合性チェック
例えば clone 操作などによって、重要なデータを常時バックアップするのは、他のデータと同様に良い安全策と言えます。
破損の分類
破損は大きく2つに分類されます:
dirstate の破損 (Mercurial の working directory 状態に対する認識に不整合が生じます)
repository の破損 (hg verify 実行でリポジトリの履歴記録に対するエラーが報告されます)
正確には、履歴情報に対する被害のみが 'リポジトリ破損' と呼ばれるべきですが、とりあえずここでは、両者に関して説明します。
Dirstate の破損
この破損は、管理対象ファイルに関する、現在実施されている操作の情報を記録したものが、被害を受けている状況です。ここで重要なのは .hg/dirstate ファイルです。このファイルには、親リビジョンの情報や、作業領域中の全管理対象ファイルに関する情報が記録されています。このファイルが破損した場合、以下のような挙動が見られます:
$ hg st M foo A bar $ hg id 58745409d2e2+ tip # ※ 以下は意図的に破損させるための操作です $ echo fdsj..適当な文字列..goo > .hg/dirstate $ hg st abort: unknown revision '6664736a666b67736a64666867736b6466686b67'!
dirstate ファイルの破損を復旧するのは、多くの場合、単純です。現時点での作業領域の親リビジョンが分かっているなら、以下の実行で復旧できます:
$ hg debugrebuildstate -r tip # tip で作業していたと仮定した場合の状態再構築 $ hg id 58745409d2e2+ tip $ hg st M foo ? bar
状態再構築しても、Mercurial は foo が改変されたことは認識できますが、bar が追加登録されたことは忘れてしまいます。
debugrebuildstate コマンドで復旧できない場合は、現在使用中のリポジトリから、別なリポジトリへと複製(clone)を行い、そちらで作業を再開する方法があります (--pull オプション指定は、潜在的なハードリンクに関する問題を回避します):
$ cd .. # ※ repo は被害を受けた『元』リポジトリ $ hg clone --pull repo fixed-repo requesting all changes adding changesets adding manifests adding file changes added 2 changesets with 2 changes to 1 files updating to branch default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd fixed-repo $ hg id 58745409d2e2+ tip
あとは、被害を受けた元リポジトリ(repo)から、修復済みのリポジトリ (fixed-repo)へと、変更を加えたファイル等を複製すれば良い訳です。
リポジトリの破損
この破損は、履歴記録、特に .hg/store 配下のファイルが被害を受けている状況です。この破損には様々な原因がありますが、多くは利用者の不注意によるものです (例: パス名に 'foo.c' を含むファイルの削除)。具体的な例で見てみると:
$ hg verify checking changesets checking manifests crosschecking files in changesets and manifests checking files 1213 files, 8591 changesets, 17158 total revisions $ rm .hg/store/data/mercurial/error.py.i # うっかり削除! $ hg verify checking changesets checking manifests crosschecking files in changesets and manifests checking files data/mercurial/error.py.i@7633: missing revlog! 7633: empty or missing mercurial/error.py mercurial/error.py@7633: f6aad9f78a08 in manifests not found mercurial/error.py@7636: 4fb29207f327 in manifests not found mercurial/error.py@7637: 3bfff9613e8b in manifests not found mercurial/error.py@7640: 40ee894622ad in manifests not found mercurial/error.py@7641: e640820306d6 in manifests not found mercurial/error.py@7643: f43c616251f5 in manifests not found mercurial/error.py@7644: 455d738b74c7 in manifests not found mercurial/error.py@7646: a3128b43b03f in manifests not found mercurial/error.py@7947: b4a8b72bfa1c in manifests not found mercurial/error.py@8144: 1f996809c441 in manifests not found mercurial/error.py@8225: e1537136a8d0 in manifests not found mercurial/error.py@8226: 5f91269779c0 in manifests not found mercurial/error.py@8227: 6706abc98ab2 in manifests not found 1213 files, 8591 changesets, 17145 total revisions 15 integrity errors encountered! (first damaged changeset appears to be 7633)
リポジトリの修復を行う場合、必ずバックアップをとってから実施しましょう!
基本的な修復
以下の例では、リポジトリの被害が、リビジョン 7633 以降と仮定します。履歴の記録方式が、追記に限定されている Mercurial の特性を生かすことで、問題のリビジョン以前の履歴に関しては、以下の方法で破損したリポジトリから回収することができます:
$ cd .. $ hg clone -r 7632 damage fixed requesting all changes adding changesets adding manifests adding file changes added 7633 changesets with 14944 changes to 1126 files updating to branch default 964 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd fixed $ hg verify checking changesets checking manifests crosschecking files in changesets and manifests checking files 1126 files, 7633 changesets, 14944 total revisions
整合性が保たれている部位からの再構築
'pull -r' を用いて、整合性が保たれている履歴の部位ごとに、ファイルの履歴が失われたリポジトリへと、少しずつ取り込むことも可能です。あらかじめ複製しておいたリポジトリがあれば、失われたファイルの履歴を含む、完全な履歴が複製できるかもしれません。
失われた履歴ファイル(revlog)の再構築
単一の履歴を持つ履歴ファイル(revlog)が時々破損することがあります。該当するリビジョン時点の内容と 全く同じ ファイルが、まだ手元にある場合、他の場合に比べれば、履歴ファイルの手動修復は容易です。例えば、"broken" という名のリポジトリで、作業領域直下の "f2.i" ファイルが失われたケースの例を見ていきましょう:
$ hg verify checking changesets checking manifests crosschecking files in changesets and manifests checking files data/f2.i@1: missing revlog! 1: empty or missing f2 f2@1: 5266937d3e5f in manifests not found 3 files, 3 changesets, 2 total revisions 3 integrity errors encountered! (first damaged changeset appears to be 1)
"@1" という表示は、失われた履歴ファイルがリビジョン 1 から参照されていた、ということを意味します。このファイルを修復するには、コミット実施を再現するための、新たなリポジトリが必要です。(新規リポジトリと既存リポジトリの repository format が同一となるように、リポジトリ複製の際には、適宜オプション指定等を行ってください)
$ cd .. $ hg clone -r 0 broken fix # damaged revision minus 1 $ hg up 0 # parent of damaged changeset 1 $ cp ../broken/f2 f2 $ hg add f2 $ hg ci -m fix f2
以上の操作によって、リビジョン 1 から参照される履歴情報が格納された "f2.i" が新規作成されました。後は、このファイルを "broken" リポジトリへとコピーします:
$ cp .hg/store/data/f2.i ../broken/.hg/store/data/f2.i $ cd ../broken $ hg verify checking changesets checking manifests crosschecking files in changesets and manifests checking files 3 files, 3 changesets, 3 total revisions
verify 実施時に、追加されたファイルが参照されるようにするためには、 .hg/store/fncache を編集する必要があるかもしれません。
この手順は、単一ファイルの履歴修復以外にも、履歴情報そのものの破損の修復にも応用可能です。
パッチキュー由来の履歴ファイルによるリビジョン参照の修復
修復されたリビジョンが、適用中のパッチキューのパッチの一部だった場合、修復作業を行ったリポジトリから複製した履歴ファイル(*.i ファイル、『インデックスファイル』とも呼ぶ)が、不適切なリビジョンを参照している可能性があるため、もう少々複雑な手順を踏む必要があります:
$ hg verify checking changesets checking manifests crosschecking files in changesets and manifests checking files data/f2.i@3: missing revlog! f2@?: rev 0 points to unexpected changeset 2 (expected 3) 3 files, 3 changesets, 2 total revisions 2 integrity errors encountered! (first damaged changeset appears to be 3)
この問題は、履歴ファイルにおけるリビジョン参照を、以下の Python スクリプトを使って、直接書き換えることで、解決することができます:
import struct from mercurial import revlog f = open('.hg/store/data/f2.i', 'r+') raw = f.read() unpacked_index = list(struct.unpack(revlog.indexformatng, raw[:64])) unpacked_index[4] = 3 # replace with your own "expected changeset number" new_raw = struct.pack(revlog.indexformatng, *unpacked_index) f.seek(0) f.write(new_raw) f.close()
備考: インデックスファイルの形式は以下に文書化されています: http://selenic.com/repo/hg-stable/file/tip/mercurial/revlog.py#l156
convert エクステンションによる修復
他の手段として、convert エクステンションによるリポジトリの強制的な再構築、という方法もあります。あらかじめ注意しておきますが、この方法でリポジトリを再構築した場合、全てのリビジョンのハッシュ値が変わってしまうため、全ユーザが修復後リポジトリから再度 clone を実施 する必要があります。
はじめに、convert エクステンションを有効にします。ここでは、リポジトリが REPO というディレクトリにあるものとします:
$ vim REPO/.hg/hgrc ... [extensions] hgext.convert= ...
ここですぐに変換しても良いのですが、安全のために、変換先の空のディレクトリを作成しておくのが良いでしょう:
$ mkdir REPOFIX
convert エクステンションによる修復を行います:
$ hg convert --config convert.hg.ignoreerrors=True REPO REPOFIX scanning source... sorting... converting... [多数のメッセージが表示されますが、多くはコミットメッセージです] ignoring: data/.DS_Store.i@26a47e9188c: no match found [更に多くのコミットメッセージ] .hgtags@78ff9079978f, line 1: tag '1.0b1' refers to unknown node updating tags
変換コマンド実行時により大量の出力がありますが、重要なのは、破損したファイルが無視されつつ、残りのファイルは新しいリポジトリに格納される、ということです。
これでも処理が中断される場合、.hg/store/data/{破損したファイル}.i ファイルを削除することで、上手く行く可能性があります。このような操作をする際には、あらかじめバックアップを取っておきましょう。
高度な手法: リポジトリデータ構造の監査
Mercurial の内部データ構造は、理解が容易です。詳細は Design や FileFormats に書かれています。Mercurial には debugindex, debugdata, debugstateといった、内部情報を直接表示するようなコマンドもあります (DebuggingFeatures 参照)。