foozy@708: \chapter{Behind the scenes} foozy@708: \label{chap:concepts} foozy@708: foozy@708: 多くの構成管理システムと異なり、 foozy@708: Mercurial が基にしている概念は非常に単純なので、 foozy@708: Mercurial のプログラムが実際にどのように動作するのかを理解するのは簡単です。 foozy@708: そのような知識は必要無いかもしれませんが、 foozy@708: 筆者は内情に関する``概念理解''が有用であると考えています。 foozy@708: foozy@708: 筆者自身は、内情を理解することで、 foozy@708: Mercurial が\emph{安全性}と\emph{効率}に留意して設計されている、 foozy@708: という確信を得ることができました。 foozy@708: また、 foozy@708: 構成管理操作を行った際にソフトウェアがどのように機能するのかを、 foozy@708: 容易に覚えておけるのであれば、 foozy@708: 構成管理ツールの振る舞いに驚かされる機会が減る、 foozy@708: という点も非常に重要です。 foozy@708: foozy@708: この章では、 foozy@708: 最初に foozy@708: Mercurial の設計における中核的な概念について説明した上で、 foozy@708: 実装における興味深い点に関する詳細を幾つか取り上げようと思います。 foozy@708: foozy@708: \section{Mercurial's historical record} foozy@708: foozy@708: \subsection{Tracking the history of a single file} foozy@708: foozy@708: ファイルの変更を追跡する場合、 foozy@708: Mercurial はファイルの履歴を foozy@708: \emph{filelog} と呼ばれるメタデータオブジェクト形式で保存します。 foozy@708: filelog に記録される個々の要素は、 foozy@708: 追跡対象ファイルの、 foozy@708: とあるリビジョンを再現するのに十分な情報を保持しています。 foozy@708: filelog は foozy@708: \sdirname{.hg/store/data} ディレクトリ配下にファイルとして保存されており、 foozy@708: 履歴情報と、 foozy@708: Mercurial のリビジョン検索を補助するインデックスの、 foozy@708: 2種類の情報を保持しています。 foozy@708: foozy@708: サイズが大きかったり変更履歴の多いファイルの場合、 foozy@708: filelog を履歴情報(拡張子 ``\texttt{.d}'') foozy@708: とインデックス(拡張子 ``\texttt{.i}'')の2つに分離して保存されます。 foozy@708: 変更履歴がそれほど無い小さなファイルの場合、 foozy@708: 履歴情報とインデックスは foozy@708: ``\texttt{.i}'' 拡張子を持つ単一のファイルに保存されます。 foozy@708: 作業領域ディレクトリ中のファイルと、 foozy@708: その変更履歴を追跡するためのリポジトリ中の filelog ファイルの対応を、 foozy@708: 図~\ref{fig:concepts:filelog}に示します。 foozy@708: foozy@708: \begin{figure}[ht] foozy@708: \centering foozy@708: \grafix{filelog} foozy@708: \caption{Relationships between files in working directory and foozy@708: filelogs in repository} foozy@708: \label{fig:concepts:filelog} foozy@708: \end{figure} foozy@708: foozy@708: \subsection{Managing tracked files} foozy@708: foozy@708: foozy@708: 追跡対象ファイルの情報をまとめるために、 foozy@708: Mercurial は \emph{manifest} と呼ばれる構造を使用しています。 foozy@708: manifest に記録される個々の要素は、 foozy@708: 当該チェンジセットにおけるファイルの一覧や、 foozy@708: 各ファイルのリビジョン、 foozy@708: 幾つかのファイルのメタデータといった、 foozy@708: 個々のチェンジセットごとのファイルに関する情報を保持しています。 foozy@708: foozy@708: \subsection{Recording changeset information} foozy@708: foozy@708: \emph{changelog} は、 foozy@708: チェンジセットのコミット主や、 foozy@708: コミット時のログメッセージ、 foozy@708: その他チェンジセットに関する幾つかの情報や、 foozy@708: manifest のリビジョンといった、 foozy@708: 個々のチェンジセットに関する情報を保持しています。 foozy@708: foozy@708: \subsection{Relationships between revisions} foozy@708: foozy@708: changelog、manifest ないし filelog における個々のリビジョンは、 foozy@708: 直接の親リビジョン foozy@708: (マージを行ったリビジョンの場合は、 foozy@708: マージ対象となった2つの親リビジョン) foozy@708: への参照を保持しています。 foozy@708: 今述べたように、 foozy@708: 各構造に\emph{またがった}関連性をもち、 foozy@708: それらは必然的に階層構造を持っています。 foozy@708: foozy@708: リポジトリ中の全てのチェンジセットに関して、 foozy@708: changelog には厳密に1つのリビジョンが保存されます。 foozy@708: changelog における各リビジョンは、 foozy@708: manifest 中のリビジョンへの参照を保持しています。 foozy@708: manifest 中の各リビジョンは、 foozy@708: チェンジセットが生成された際の各ファイルのリビジョンに対応する foozy@708: filelog 中のリビジョンへの参照を保持しています。 foozy@708: この関連性を図~\ref{fig:concepts:metadata}に示します。 foozy@708: foozy@708: \begin{figure}[ht] foozy@708: \centering foozy@708: \grafix{metadata} foozy@708: \caption{Metadata relationships} foozy@708: \label{fig:concepts:metadata} foozy@708: \end{figure} foozy@708: foozy@708: 図からもわかるように、 foozy@708: changelog、manifest および foozy@708: filelog が保持するリビジョン情報間の関係は、 foozy@708: 必ずしも``1対1''というわけではありません。 foozy@708: 2つのチェンジセットの間で foozy@708: manifest が変更されていない場合、 foozy@708: それらのチェンジセットに対応する changelog 要素は、 foozy@708: manifest 中の同じリビジョンを参照します。 foozy@708: 2つのチェンジセットの間で foozy@708: Mercurial が追跡するファイルが変更されていない場合、 foozy@708: それらのチェンジセットに対応する manifest 要素は、 foozy@708: filelog 中の同じリビジョンを参照します。 foozy@708: foozy@708: \section{Safe, efficient storage} foozy@708: foozy@708: changelog、manifest および filelog は、 foozy@708: \emph{revlog} と呼ばれる同じ構造により構成されています。 foozy@708: foozy@708: \subsection{Efficient storage} foozy@708: foozy@708: revlog は \emph{差分}手法という仕組みを使用して、 foozy@708: リビジョン情報を効率的に格納しています。 foozy@708: 差分手法では、 foozy@708: ファイルの各リビジョンごとに完全な複製を保持する代わりに、 foozy@708: 旧リビジョンから新リビジョンへの変形に必要な情報を保持します。 foozy@708: 多くのファイルでのデータ格納において、 foozy@708: 差分手法は一般的に完全な複製の場合の数パーセント程度のサイズになります。 foozy@708: foozy@708: 旧式の構成管理システムでは、 foozy@708: テキスト形式のファイルでしか差分手法が適用できないものもあります。 foozy@708: それらのシステムにおけるバイナリファイルの格納は、 foozy@708: 完全なスナップショットか、 foozy@708: テキスト表現形式への変換によって行われますが、 foozy@708: これらは共に不経済な手法です。 foozy@708: 任意のバイナリデータを含むファイルであっても、 foozy@708: Mercurial は差分を効率的に扱うことができますので、 foozy@708: テキストを特別扱いする必要はありません\footnote{訳注: foozy@708: cvs add における foozy@708: -kb 指定の欠落によるファイル内容の破損、 foozy@708: といった心配はありません。 foozy@708: 尤も、Mercurial の基底動作では、 foozy@708: キーワードの置換等を行いませんので、 foozy@708: そもそも心配する必要が無いのですが…。}。 foozy@708: foozy@708: \subsection{Safe operation} foozy@708: \label{sec:concepts:txn} foozy@708: foozy@708: Mercurial は revlog の末尾にデータを\emph{追加}するだけで、 foozy@708: 書き込まれた後からファイルの一部を改変するようなことは行いません。 foozy@708: 既存データの改変を必要とする仕組みと比較した場合、 foozy@708: この手法は堅牢且つ効率的です。 foozy@708: foozy@708: それに加えて、 foozy@708: Mercurial は複数のファイルにまたがった全ての書き込みを、 foozy@708: 単一の\emph{トランザクション}の一部として扱います。 foozy@708: トランザクションは\emph{不可分}なものとして扱われますので、 foozy@708: トランザクション全体が成功すれば結果の全てが利用者に見えるようになりますが、 foozy@708: トランザクションの一部でも失敗した場合には、 foozy@708: 全ての書き込み操作は取り消されます。 foozy@708: 一方はデータの読み込みを行い他方はデータの書き出しを行うような、 foozy@708: 2つの Mercurial プロセスを同時に実行した場合でも、 foozy@708: この不可分保証により、 foozy@708: 読み込みを混乱させるような部分的な書き込みデータを、 foozy@708: データ読み込み側のプロセスが読み込むことはありません foozy@708: \footnote{訳注: foozy@708: 厳密にはこの記述は正しくありません。 foozy@708: 詳細は \ref{sec:hook:carepretxn}~節を参照してください。}。 foozy@708: foozy@708: Mercurial がファイルへの追加しか行わないことが、 foozy@708: トランザクションの不可分性保証の提供を容易にしています。 foozy@708: トランザクション保証が容易である程、 foozy@708: それが正しく機能していることを確信できる筈です。 foozy@708: foozy@708: \subsection{Fast retrieval} foozy@708: foozy@708: 初期の構成管理システムが共に陥っていた\emph{非効率な復旧}問題の落とし穴を、 foozy@708: Mercurial は上手に回避しています。 foozy@708: 殆どの構成管理システムは、 foozy@708: ``スナップショット''に対する変更の追加的な連続として、 foozy@708: リビジョンの内容を保持していました。 foozy@708: この手法の場合、 foozy@708: 特定のリビジョンを再構築するには、 foozy@708: 最初にスナップショットを読み込み、 foozy@708: 続いて対象リビジョンとの間の全ての差分データを読み込む必要があります。 foozy@708: ファイルの履歴が積み重なるほど、 foozy@708: 差分データを読み込まなければ成らないリビジョンが増加し、 foozy@708: 特定のリビジョンの再構築に時間が必要となります。 foozy@708: foozy@708: \begin{figure}[ht] foozy@708: \centering foozy@708: \grafix{snapshot} foozy@708: \caption{Snapshot of a revlog, with incremental deltas} foozy@708: \label{fig:concepts:snapshot} foozy@708: \end{figure} foozy@708: foozy@708: Mercurial がこの問題の解決に使用している手法は、 foozy@708: 簡単なものですが効果的です。 foozy@708: 前回のスナップショット作成時点から、 foozy@708: 固定された閾値を超えて差分情報が蓄積された際には、 foozy@708: 差分情報の蓄積ではなく、 foozy@708: 新たなスナップショット(勿論圧縮は行います)を保存する、 foozy@708: というものです。 foozy@708: この手法は、 foozy@708: \emph{任意の}リビジョンにおけるファイルを素早く再構築できます。 foozy@708: この手法は非常に有効であるため、 foozy@708: 他の幾つかの構成管理システムにも取り込まれています。 foozy@708: foozy@708: 図~\ref{fig:concepts:snapshot}の概要が示すように、 foozy@708: Mercurial は、 foozy@708: revlog のインデックスファイルにおける各要素に、 foozy@708: 特定のリビジョンの再構築の際に読み込みが必要とされる、 foozy@708: データファイル中の要素の範囲を格納します。 foozy@708: foozy@708: \subsubsection{Aside: the influence of video compression} foozy@708: foozy@708: 動画圧縮を熟知しているか、 foozy@708: ケーブルないし衛星によるデジタルテレビ配信を視聴したことがあるならば、 foozy@708: たいていの動画圧縮形式において各動画フレームが、 foozy@708: 先行するフレームとの差分で保持されていることをご存知かもしれません。 foozy@708: 加えてそれらの形式では、 foozy@708: 圧縮率を向上させるために``非可逆''圧縮手法を用いていますので、 foozy@708: フレーム間差分の数に応じて視覚的エラーが蓄積されます。 foozy@708: foozy@708: 動画配信の場合、 foozy@708: 時折の信号異常による``欠落''が有り得ますし、 foozy@708: 可逆圧縮過程により生じる誤差の蓄積を制限する必要もあるため、 foozy@708: 動画圧縮側では定期的に完全なフレーム foozy@708: (``キーフレーム''と呼ばれます)を圧縮形式の中に挿入します。 foozy@708: これは動画信号が中断されても、 foozy@708: 次のキーフレームの到着時点からの再開が可能であることを意味します。 foozy@708: 符号化エラーの蓄積も、 foozy@708: 個々のキーフレームでクリアされます。 foozy@708: foozy@708: \subsection{Identification and strong integrity} foozy@708: foozy@708: 差分ないしスナップショット情報のデータに対して、 foozy@708: revlog 要素は暗号化に用いられるハッシュ値を計算して保持しています。 foozy@708: これにより、 foozy@708: リビジョンに関する情報の偽造を困難にすると同時に、 foozy@708: 不慮の破損の検出が容易になります。 foozy@708: foozy@708: ハッシュ値の算出は、 foozy@708: 単なる破損の検出以上のものをもたらします。 foozy@708: ハッシュ値は各リビジョンの識別子として使用されます。 foozy@708: Mercurial のエンドユーザとして目にするチェンジセット識別子のハッシュ値は、 foozy@708: changelog のリビジョンに由来する値です。 foozy@708: filelog や manifest でもハッシュ値を使用していますが、 foozy@708: Mercurial ではこれらは舞台裏のみで使用されています。 foozy@708: foozy@708: 特定リビジョンのファイルを再構築する場合や、 foozy@708: 他のリポジトリからチェンジセットを取り込んだ場合、 foozy@708: Mercurial はハッシュ値が正しいことを確認します。 foozy@708: 一貫性に問題があることが検出された場合、 foozy@708: 警告を発した上で、 foozy@708: 進行中の全ての処理を停止します。 foozy@708: foozy@708: Mercurial が定期的に差し込んでいるスナップショットは、 foozy@708: 特定リビジョンの再構築の際の効率に加えて、 foozy@708: 部分的なデータの破損に対する堅牢性をもたらしてます。 foozy@708: ハードウェアエラーやシステムのバグによって、 foozy@708: revlog が部分的に破損した場合、 foozy@708: 破損を免れた revlog のデータから、 foozy@708: 破損した部位の前後共に、 foozy@708: 一部(あるいは殆どの)リビジョンを復旧することが可能です。 foozy@708: 差分のみを保持するモデルを採用する構成管理システムでは、 foozy@708: このようなことはできません。 foozy@708: foozy@708: \section{Revision history, branching, and merging} foozy@708: foozy@708: 全ての Mercurial の revlog 要素は、 foozy@708: 通常は\emph{親}と言われる直前のリビジョンの識別子を保持しています。 foozy@708: 実際には、 foozy@708: 各 revlog 要素は1つではなく2つの親の情報を保持できます。 foozy@708: Mercurial は``空識別子''(null ID)と呼ばれる特別なハッシュ値を使って、 foozy@708: ``親不在''を表現します\footnote{訳注: foozy@708: つまり、多くの revlog 要素は、 foozy@708: 一方の親リビジョンとして空IDを保持しています。}。 foozy@708: このハッシュ値は単純に0が連続した文字列です。 foozy@708: foozy@708: revlog の概念図を図~\ref{fig:concepts:revlog}に見ることができます。 foozy@708: filelog や manifest、changelog の全てが同じ構造を持っており、 foozy@708: 個々の要素が保持している、 foozy@708: 差分やスナップショットといったデータの種別が異なるだけです。 foozy@708: foozy@708: revlog における最初のリビジョン foozy@708: (図における底位置のリビジョン)は、 foozy@708: 2つの親リビジョン格納領域の両方に空識別子を保持しています。 foozy@708: ``通常の''リビジョンでは、 foozy@708: 第1親の格納領域には親リビジョンの識別子が、 foozy@708: 第2親の格納領域には空識別子が格納され、 foozy@708: 親リビジョンが1つしかないことを表します。 foozy@708: 親リビジョンの識別子として同じ識別子を格納するリビジョン同士は、 foozy@708: 互いにブランチとなります。 foozy@708: ブランチをマージしたリビジョンは、 foozy@708: 統合された両方のリビジョンの識別子を親リビジョンの識別子として格納します。 foozy@708: foozy@708: \begin{figure}[ht] foozy@708: \centering foozy@708: \grafix{revlog} foozy@708: \caption{} foozy@708: \label{fig:concepts:revlog} foozy@708: \end{figure} foozy@708: foozy@708: \section{The working directory} foozy@708: foozy@708: Mercurial は、 foozy@708: リポジトリで構成管理されているファイルの、 foozy@708: 特定のリビジョンにおけるスナップショットを作業領域ディレクトリに保持します。 foozy@708: foozy@708: 作業領域ディレクトリは、 foozy@708: どのリビジョンのスナップショットを保持しているのかを``知っています''。 foozy@708: 作業領域ディレクトリを特定のリビジョンで更新しようとした場合、 foozy@708: Mercurial は (1) 相応しいリビジョンの manifest を参照し、 foozy@708: (2) 当該リビジョンのコミット時点での管理対象ファイルを特定し、 foozy@708: (3) 作業領域ディレクトリ中のファイルが保持すべき内容を決定します。 foozy@708: その上で、 foozy@708: 当該チェンジセットのコミット時点と同じ内容を持つように、 foozy@708: 作業領域ディレクトリ中に各ファイルの複製を再生成します。 foozy@708: foozy@708: dirstate 形式には、 foozy@708: 作業領域ディレクトリがどのチェンジセットで更新されているかとか、 foozy@708: 作業領域で Mercurial により構成管理されているファイルの一覧など、 foozy@708: 作業領域ディレクトリに関する foozy@708: Mercurial の管理情報が格納されています。 foozy@708: foozy@708: 個々のリビジョンに関する revlog 要素は、 foozy@708: 2つの親リビジョン識別子を格納する領域を持っていますので、 foozy@708: 通常のリビジョン(1つの親リビジョンだけを参照)も、 foozy@708: 2つのリビジョンをマージするリビジョンも表現可能ですが、 foozy@708: dirstate 形式も2つの親リビジョン識別子を格納する領域を持っています。 foozy@708: \hgcmd{update} コマンドを実行した際には、 foozy@708: 指定したチェンジセットは``第1親''(first parent)として保持され、 foozy@708: 第2親は空識別子を保持します。 foozy@708: チェンジセットとの \hgcmd{merge} を行った際には、 foozy@708: dirstate 形式が保持する第1親は変化しませんが、 foozy@708: 第2親は \hgcmd{merge} コマンドに指定されたチェンジセットに設定されます。 foozy@708: \hgcmd{parents} コマンドにより、 foozy@708: dirstate 形式が保持する親リビジョンの識別子を表示できます。 foozy@708: foozy@708: \subsection{What happens when you commit} foozy@708: foozy@708: dirstate 形式が親リビジョン情報を保持するのは、 foozy@708: 何も覚え書きのためだけではありません。 foozy@708: Mercurial は dirstate 形式の持つ親リビジョン情報を、 foozy@708: コミットの際の\emph{新規チェンジセットの親チェンジセット}として使用します。 foozy@708: foozy@708: \begin{figure}[ht] foozy@708: \centering foozy@708: \grafix{wdir} foozy@708: \caption{The working directory can have two parents} foozy@708: \label{fig:concepts:wdir} foozy@708: \end{figure} foozy@708: foozy@708: 図~\ref{fig:concepts:wdir}は、 foozy@708: 1つの親チェンジセットのみを持つ、 foozy@708: 通常の作業領域ディレクトリを表しています。 foozy@708: 図における作業領域ディレクトリの親チェンジセットは、 foozy@708: リポジトリにおける最新で且つ子を持たないチェンジセットですので、 foozy@708: \emph{tip} と呼ばれます。 foozy@708: foozy@708: \begin{figure}[ht] foozy@708: \centering foozy@708: \grafix{wdir-after-commit} foozy@708: \caption{The working directory gains new parents after a commit} foozy@708: \label{fig:concepts:wdir-after-commit} foozy@708: \end{figure} foozy@708: foozy@708: 作業領域ディレクトリそのものを、 foozy@708: ``コミットしようとしているチェンジセット'' foozy@708: と捉えるとわかりやすいでしょう。 foozy@708: Mercurial に対して追加/削除/改名ないし複製を指示したファイルは、 foozy@708: 既に Mercurial により構成管理されているファイルへの変更と同様に、 foozy@708: そのチェンジセットに反映されます。 foozy@708: その新たなチェンジセットには、 foozy@708: 作業領域ディレクトリと同じ親チェンジセットが設定されます。 foozy@708: foozy@708: コミットが完了したなら、 foozy@708: Mercurial や作業領域ディレクトリの親チェンジセットの情報を更新します。 foozy@708: 第1親にはコミットにより新たに生成されたチェンジセットの識別子が設定され、 foozy@708: 第2親には空識別子が設定されます。 foozy@708: コミット後の模式図を、 foozy@708: 図~\ref{fig:concepts:wdir-after-commit}に示します。 foozy@708: Mercurial はコミットの際に、 foozy@708: 作業領域ディレクトリ中のファイルには一切触れず、 foozy@708: 単に dirstate の親チェンジセット情報を書き換えるだけです。 foozy@708: foozy@708: \subsection{Creating a new head} foozy@708: foozy@708: 現時点での tip 以外のチェンジセットでの作業領域ディレクトリの更新は、 foozy@708: 良くあることです。 foozy@708: 例えば、 foozy@708: 先週火曜日時点でのプロジェクトの状態を調べたり、 foozy@708: どのチェンジセットがバグを持ち込んだのかを調べる、 foozy@708: といった状況です。 foozy@708: このような状況での自然な行為は、 foozy@708: 作業領域ディレクトリを希望のチェンジセットで更新し、 foozy@708: 当該チェンジセットをコミットした時点でのファイルの内容を、 foozy@708: 作業領域ディレクトリ中のファイルを参照して確認する、 foozy@708: というものでしょう。 foozy@708: この行為による影響を、 foozy@708: 図~\ref{fig:concepts:wdir-pre-branch}に示します。 foozy@708: foozy@708: \begin{figure}[ht] foozy@708: \centering foozy@708: \grafix{wdir-pre-branch} foozy@708: \caption{The working directory, updated to an older changeset} foozy@708: \label{fig:concepts:wdir-pre-branch} foozy@708: \end{figure} foozy@708: foozy@708: 作業領域ディレクトリを以前のチェンジセットで更新した場合、 foozy@708: 何らかの変更を行ってコミットしたなら、 foozy@708: Mercurial はどのように振舞うのでしょうか? foozy@708: Mercurial はこれまでに説明してきた場合と同じように振舞います。 foozy@708: 作業領域ディレクトリの親チェンジセットが、 foozy@708: 新規に作成されるチェンジセットの親になります。 foozy@708: 新規作成されるチェンジセットは子を持たず、 foozy@708: よって新たな tip チェンジセットとなります。 foozy@708: コミットの結果、 foozy@708: リポジトリには子を持たないチェンジセットが2つ存在し、 foozy@708: これらは \emph{head} と呼ばれます。 foozy@708: この状況を図~\ref{fig:concepts:wdir-branch} に示します。 foozy@708: foozy@708: \begin{figure}[ht] foozy@708: \centering foozy@708: \grafix{wdir-branch} foozy@708: \caption{After a commit made while synced to an older changeset} foozy@708: \label{fig:concepts:wdir-branch} foozy@708: \end{figure} foozy@708: foozy@708: \begin{note} foozy@708: Mercurial に馴染みの無い方は、 foozy@708: 引数無しで \hgcmd{pull} コマンドを実行した場合の、 foozy@708: 良くある「間違い」を気に留めて置いてください。 foozy@708: \hgcmd{pull} コマンドの基底動作は、 foozy@708: 作業領域ディレクトリの更新を\emph{行いません}ので、 foozy@708: リポジトリへの新規チェンジセットの取り込みは行われても、 foozy@708: 作業領域ディレクトリは \hgcmd{pull} コマンド実行前のままです。 foozy@708: 作業領域ディレクトリは当該時点での tip と同期していないため、 foozy@708: \hgcmd{pull} の実行後に何らかの変更を行いコミットした場合、 foozy@708: 結果として新たな head を生成することになります。 foozy@708: foozy@708: 括弧付きで「間違い」と述べたのは、 foozy@708: この状況を修復するのに必要なことが、 foozy@708: \hgcmd{merge} してから \hgcmd{commit} すれば良いだけだからです。 foozy@708: 言い換えるなら、 foozy@708: このようなケースは全然深刻な状況ではない、ということです。 foozy@708: Mercurial に慣れていない人はビックリするかもしれませんが…。 foozy@708: このような事態を回避する別の方法や、 foozy@708: 初心者にとって意外に感じるこのような振る舞いを Mercurial がとる理由について、 foozy@708: 後ほど説明したいと思います。 foozy@708: \end{note} foozy@708: foozy@708: \subsection{Merging heads} foozy@708: foozy@708: \hgcmd{merge} コマンド実行の際に、 foozy@708: Mercurial は作業領域ディレクトリの第1親は変更せずに、 foozy@708: 第2親をマージ対象として指定したチェンジセットに変更します。 foozy@708: この様子を図~\ref{fig:concepts:wdir-merge}に示します。 foozy@708: foozy@708: \begin{figure}[ht] foozy@708: \centering foozy@708: \grafix{wdir-merge} foozy@708: \caption{Merging two heads} foozy@708: \label{fig:concepts:wdir-merge} foozy@708: \end{figure} foozy@708: foozy@708: 2つのチェンジセットにおいて管理されるファイルをマージするため、 foozy@708: Mercurial は作業領域ディレクトリを変更します。 foozy@708: 多少簡便化して説明すると、 foozy@708: 両方のチェンジセットの manifest に含まれる全てのファイルに対して、 foozy@708: 概ね以下のようにマージ処理が実施されます。 foozy@708: foozy@708: \begin{itemize} foozy@708: \item どちらのチェンジセットでもファイルを変更していない場合、 foozy@708: そのファイルに対しては何も行われません。 foozy@708: foozy@708: \item 一方のチェンジセットが変更しているファイルを、 foozy@708: 他方が変更していない場合、 foozy@708: 変更内容を反映したファイルを作業領域ディレクトリに複製します。 foozy@708: foozy@708: \item 一方のチェンジセットが削除したファイルは、 foozy@708: 他方の削除に関わらず、 foozy@708: 作業領域ディレクトリから削除されます。 foozy@708: foozy@708: \item 一方のチェンジセットが削除したファイルを、 foozy@708: 他方が変更していた場合、 foozy@708: ファイルの変更と削除のどちらを採用するのか、 foozy@708: ユーザに対して問い合わせます。 foozy@708: foozy@708: \item 両方のチェンジセットがファイルを変更している場合、 foozy@708: 内容のマージ結果をファイルに保存するために、 foozy@708: 外部マージプログラムが起動されます。 foozy@708: この場合、ユーザによる対話的操作が必要になるかもしれません。 foozy@708: foozy@708: \item 一方のチェンジセットが変更しているファイルを、 foozy@708: 他方が改名したり複製したりしている場合、 foozy@708: 変更内容が新しいファイルにも伝播するようにします。 foozy@708: foozy@708: \end{itemize} foozy@708: foozy@708: 他にも細かい話---特にマージに関しては細かい話が沢山あります---がありますが、 foozy@708: マージに関連する一般的な振る舞いの種類はこの程度です。 foozy@708: ご覧の様に、殆どの状況が全く自動的に処理されますし、 foozy@708: 実際のマージでも殆どの場合、 foozy@708: 衝突解消のための対話的な入力無しに自動的に完了します。 foozy@708: foozy@708: マージ後のコミットの際に処理される事柄を考える場合は、 foozy@708: 先にも述べましたが、 foozy@708: 作業領域ディレクトリを foozy@708: ``コミットしようとしているチェンジセット'' foozy@708: と捉えるとわかりやすいでしょう。 foozy@708: \hgcmd{merge} コマンドが完了した後の作業領域ディレクトリは、 foozy@708: 親チェンジセットを2つ持ち、 foozy@708: コミットによって生成される新たなチェンジセットは、 foozy@708: これらを親チェンジセットとします。 foozy@708: foozy@708: Mercurial では繰り返しマージすることが可能ですが、 foozy@708: Mercurial はりビジョンおよび作業領域ディレクトリの両方に対して、 foozy@708: 一度に2つの親リビジョンしか追跡できないため、 foozy@708: 個々のマージの都度コミットする必要があります。 foozy@708: 複数のチェンジセットの一括マージは技術的には可能でしょうが、 foozy@708: ユーザが混乱したり、 foozy@708: ひどく乱雑なマージが行われるであろうことは目に見えています。 foozy@708: foozy@708: \section{Other interesting design features} foozy@708: foozy@708: これまでの節で、 foozy@708: Mercurial が信頼性と性能へ注意深く配慮を払っていることを説明するために、 foozy@708: 設計における最も重要な側面の幾つかに焦点を当ててきました。 foozy@708: しかし、 foozy@708: 詳細事項への配慮は、 foozy@708: これだけに留まりません。 foozy@708: Mercurial の構成において筆者の個人的な興味をそそる側面が多数あります。 foozy@708: これまでの``big ticket''な側面とは別に、 foozy@708: いくつかを選んで詳細を説明しようと思いますので、 foozy@708: これらに興味があれば、 foozy@708: 良い設計のシステムの考案の際に有用な、 foozy@708: より良い発想を得ることができるでしょう。 foozy@708: foozy@708: \subsection{Clever compression} foozy@708: foozy@708: Mercurial はスナップショットと差分のそれぞれに対して、 foozy@708: 圧縮が有効である場合には圧縮形式で保存します。 foozy@708: Mercurial は常にスナップショットないし差分の圧縮を\emph{試行}しますが、 foozy@708: 非圧縮な状態よりもサイズが小さい場合に限り、 foozy@708: 圧縮形式での保存を行います。 foozy@708: foozy@708: このことは、 foozy@708: 例えば \texttt{zip} アーカイブや JPEG 画像のように、 foozy@708: 元々圧縮形式の内容を持つファイルの格納の際に、 foozy@708: Mercurial が``適切な処置''を行うこと意味します。 foozy@708: これらのファイルは Mercurial による2度目の圧縮の際には、 foozy@708: 最初のサイズよりも大きくなるのが一般的ですので、 foozy@708: Mercurial は \texttt{zip} や JPEG ファイルをそのまま保存します。 foozy@708: foozy@708: 圧縮形式のファイルのリビジョン間の差分は、 foozy@708: 一般的にはスナップショットよりも大きくなりますので、 foozy@708: この場合でも Mercurial は``適切な処置''を行います。 foozy@708: ファイルのスナップショットそのものを保存する場合の許容範囲を、 foozy@708: 差分情報のサイズが超えることが判明した場合、 foozy@708: Mercurial はスナップショットを保存しますので、 foozy@708: 繰り返しになりますが、 foozy@708: 差分のみを保持するモデルよりもディスク容量が節約できます。 foozy@708: foozy@708: \subsubsection{Network recompression} foozy@708: foozy@708: Mercurial はディスクへの履歴保存の際に、 foozy@708: 性能に対する圧縮率がそこそこ良好でバランスの取れている``収縮'' foozy@708: (deflate)圧縮アルゴリズム foozy@708: (著名な \texttt{zip} アーカイブ形式が同等のものを使用しています) foozy@708: を使用しています。 foozy@708: しかし、 foozy@708: ネットワーク越しのデータ転送の際には、 foozy@708: Mercurial は履歴データを圧縮しません。 foozy@708: foozy@708: ネットワーク接続が HTTP 経由の場合、 foozy@708: Mercurial はデータ通信の経路全体を、 foozy@708: より良い圧縮率を得られる圧縮アルゴリズム foozy@708: (\texttt{bzip2} 圧縮として広く使用されている foozy@708: Burrows-Wheeler アルゴリズム)で再圧縮します。 foozy@708: リビジョン情報個別の圧縮ではなく、 foozy@708: \texttt{bzip2} アルゴリズムと通信経路全体の圧縮という組み合わせにすることで、 foozy@708: 転送データ量を大幅に低減することができますので、 foozy@708: 殆ど全てのネットワーク形態において良好な性能を発揮できます。 foozy@708: foozy@708: (\command{ssh} での接続の場合、 foozy@708: \command{ssh} 自身が圧縮を行うことができるので、 foozy@708: Mercurial は接続経路の再圧縮を\emph{行いません} foozy@708: \footnote{訳注: 訳者の経験では、 foozy@708: サーバ側の Python が zlib を使用できない場合、 foozy@708: ssh での push/pull が機能しなかったので、 foozy@708: Mercurial のサイトにも同様の記述がありますが、 foozy@708: この記述は少々辻褄が合わない気がします。}) foozy@708: foozy@708: \subsection{Read/write ordering and atomicity} foozy@708: foozy@708: 不完全な書き込み内容が利用されることのないように保証する上では、 foozy@708: ファイルへの追加書き込みだけが全てではありません。 foozy@708: もう一度、図~\ref{fig:concepts:metadata}を見ていただければわかるように、 foozy@708: changelog 中のリビジョン要素は manifest 中のリビジョン要素を、 foozy@708: manifest 中のリビジョン要素は filelog 中のリビジョン要素を指しています。 foozy@708: この階層構造は意図的なものなのです。 foozy@708: foozy@708: データ書き込みの際には、 foozy@708: filelog および manifest への書き込みでトランザクションが開始され、 foozy@708: これらへの書き込みが完了するまでは foozy@708: changelog への書き込みは行われません。 foozy@708: 読み込みの際には、 foozy@708: changelog を起点として manifest、filelog の順序で読み込みを行います。 foozy@708: foozy@708: changelog への書き込みに先立って、 foozy@708: 常に filelog および manifest への書き込みが完了しているので、 foozy@708: changelog からの不完全な manifest への参照を読み込むことも、 foozy@708: manifest からの不完全な filelog への参照を読み込むこともありません。 foozy@708: foozy@708: \subsection{Concurrent access} foozy@708: foozy@708: 読み書き手順と不可分性保証により、 foozy@708: 例え読み込みの最中に書き込みが行われるとしても、 foozy@708: Mercurial は読み込みにおけるリポジトリの\emph{排他}を必要としません。 foozy@708: この特性は大規模化の際に非常に影響があります。 foozy@708: 任意の数の Mercurial プロセスが、 foozy@708: 書き出しプロセスの有無に関わらず、 foozy@708: リポジトリに対して同時読み出しを安全に行うことができます。 foozy@708: foozy@708: 読み出しにおける排他不要の特性は、 foozy@708: 多ユーザシステム上でリポジトリを公開している際に、 foozy@708: 複製(\hgcmd{clone})や変更の取り込み(\hgcmd{pull})のために、 foozy@708: 他のユーザに(あなたの) foozy@708: リポジトリへの\emph{書き込み}を許可する必要\footnote{訳注: foozy@708: プロセス間で排他を行う場合、 foozy@708: 排他用のファイルを用いるか、 foozy@708: ディレクトリそのものに排他設定を行うのが一般的ですが、 foozy@708: そのためには書き込み権限が必要です。} foozy@708: が無いことを意味します。 foozy@708: 読み出しを行う他のユーザには、 foozy@708: \emph{読み出し}権限のみの公開で済みます foozy@708: (この性質は構成管理システムに共通の特性では\emph{ありません}ので、 foozy@708: 一般的なものだとは思わないでください。 foozy@708: 多くの構成管理システムでは、 foozy@708: 読み出しユーザであっても、 foozy@708: 安全な読み出しのためにはリポジトリを排他する権限が必要であり、 foozy@708: そのためには最低でも1つのディレクトリに対する書き込み権限が必要なため、 foozy@708: 安全性と管理上で面倒な問題の原因となり得ます。)。 foozy@708: foozy@708: Mercurial が排他を行うのは、 foozy@708: 一度に1つのプロセスのみがリポジトリに書き込むのを保証場合だけです foozy@708: (排他に適さないと言われる NFS のようなファイルシステム\footnote{訳注: foozy@708: 構成管理システムに限らず、 foozy@708: 排他の実現に creat(EXCL) foozy@708: で生成されるファイルを使用しているために、 foozy@708: NFS では適切に排他できないプログラムが多数存在します。 foozy@708: }であっても、 foozy@708: 安全に排他できる仕組みを用いています)。 foozy@708: リポジトリが他のプロセスにより排他されている場合、 foozy@708: 書き込みを行うプロセスは、 foozy@708: リポジトリの排他が解除されるまで暫く待って再度排他を試行しますが、 foozy@708: 長時間に渡って排他されたままの場合は、 foozy@708: 時間切れとみなされます。 foozy@708: そのため、 foozy@708: 例えば人知れずシステムが停止したとしても、 foozy@708: 自動化された日次処理が停止したままになったり、 foozy@708: 停止しない処理が次々と積み上がったりすることはありません。 foozy@708: foozy@708: \subsubsection{Safe dirstate access} foozy@708: foozy@708: dirstate 形式ファイルからのリビジョン情報の読み出しに際して、 foozy@708: Mercurial はファイルに対する排他を行ったりはせず、 foozy@708: 書き込みの際にのみ排他を行います。 foozy@708: 不完全な書き込みを foozy@708: dirstate 形式ファイルから読み出してしまうことを回避するため、 foozy@708: Mercurial は foozy@708: 対象 dirstate 形式ファイルと同じディレクトリに特有の名前でファイルを書き出し、 foozy@708: この一時ファイルを \filename{dirstate} foozy@708: ファイルへと不可分な操作で改名します。 foozy@708: そのため、 foozy@708: \filename{dirstate} という「名前の」ファイルは、 foozy@708: 不完全な書き込みを持たない完全な内容であることが保証されます。 foozy@708: foozy@708: \subsection{Avoiding seeks} foozy@708: foozy@708: 比較的大量のデータ読み込み処理に対してすら、 foozy@708: ディスクヘッドのシークは非常にコストが高くつくため、 foozy@708: Mercurial の性能確保の重要な点は、 foozy@708: ディスクヘッドのシークを極力回避することにあります。 foozy@708: foozy@708: 例えば dirstate 形式のようなデータが、 foozy@708: 単一のファイルに保存される理由がここにあります。 foozy@708: Mercurial により構成管理されるディレクトリごとに foozy@708: \filename{dirstate} ファイルが存在する場合は、 foozy@708: ディレクトリごとにディスクヘッドのシークが発生し得ます。 foozy@708: そのようなディスクヘッドのシークを回避するために、 foozy@708: Mercurial は一度に単一の foozy@708: \filename{dirstate} ファイル全体を読み込みます\footnote{訳注: foozy@708: ディスクの利用が進んで空きブロックが断片化された場合、 foozy@708: 不連続なブロックが割り当てられますから、 foozy@708: 必ずしも「単一ファイル」=「ヘッドのシークが回避可能」ではありませんが、 foozy@708: 少なくとも「ヘッドのシークを低減」することは可能です。}。 foozy@708: foozy@708: ローカルストレージにおけるリポジトリの複製の際には、 foozy@708: Mercurial は``書き出し時複製''の仕組みも使用します。 foozy@708: 複製元リポジトリから複製先に個々の revlog ファイルを複製する代わりに、 foozy@708: ``ハードリンク''を使用することで、 foozy@708: ``2つのファイル名が同一内容のファイルを参照'' foozy@708: することを手早く表明します。 foozy@708: 一方の revlog ファイルに書き込みを行う際には、 foozy@708: Mercurial は当該ファイルのハードリンクを確認します。 foozy@708: 当該ファイルが複数のリポジトリから参照されいている場合、 foozy@708: Mercurial は当該リポジトリ用に revlog の新たな複製を作成します。 foozy@708: foozy@708: 何人かの構成管理ツールの開発者により、 foozy@708: この方法--- foozy@708: 完全にリポジトリ固有のものとしてファイルを複製する--- foozy@708: がディスク使用量削減にそれほど効果的でないとの指摘を受けています。 foozy@708: それは事実ではありますが、 foozy@708: ディスク容量の確保は安価であり、 foozy@708: OS への複製要求を遅延することにより高い性能を得ることができます。 foozy@708: 別な仕組みを用いる場合、 foozy@708: 性能が低下しソフトウェアの複雑さが増しますので、 foozy@708: 日々の利用における``体感''に非常に影響を及ぼします\footnote{訳注: foozy@708: つまり、 foozy@708: Mercurial でのハードリンクの使用は、 foozy@708: 複製を行うことによるディスクヘッドのシークを低減するのが主眼で、 foozy@708: ディスク使用量の低減が主眼ではない、 foozy@708: ということです。}。 foozy@708: foozy@708: \subsection{Other contents of the dirstate} foozy@708: \label{sec:concepts:dirstate} foozy@708: foozy@708: ファイルの変更の際の Mercurial への通知が必要ないことから、 foozy@708: ファイル変更の有無を効率的に判定するために、 foozy@708: 特別な情報を格納した dirstate 形式ファイルを使用します。 foozy@708: 作業領域ディレクトリ中の全てのファイルに対して、 foozy@708: Mercurial はファイルの最終変更日時とその時点でのサイズを foozy@708: dirstate 形式ファイルに格納しています。 foozy@708: foozy@708: \hgcmd{add}、\hgcmd{remove}、\hgcmd{rename} ないし foozy@708: \hgcmd{copy} を明示的に使用した場合、 foozy@708: Mercurial はこの情報を更新しますので、 foozy@708: コミット時の振る舞いを特定できます。 foozy@708: foozy@708: Mercurial が作業領域ディレクトリ中のファイルを確認する場合、 foozy@708: 最初にファイルの変更日時を確認します。 foozy@708: 変更日時が同一ならば、ファイルは変更されていない筈です。 foozy@708: ファイルサイズが異なっているならば、ファイルは変更されている筈です。 foozy@708: 変更日時が異なっているのにファイルサイズが同一の場合にのみ、 foozy@708: ファイルの内容が異なっているか否かを判定するために foozy@708: Mercurial は実際にファイルの内容を読み込みます\footnote{訳注: foozy@708: Windows 環境での改行変換を行っているような場合、 foozy@708: バイナリ版とソース版でファイルサイズの算出手順に違いがあるらしく、 foozy@708: \hgcmd{diff} が何も出力しないのに、 foozy@708: \hgcmd{state} では「変更」扱いされることが稀にあります。}。 foozy@708: このように僅かな追加情報を格納することで、 foozy@708: Mercurial が必要とする読み込みデータ量を劇的に減らすことができ、 foozy@708: 他の構成管理システムと比較して大幅に性能が改善されています。 foozy@708: foozy@708: %%% Local Variables: foozy@708: %%% mode: latex foozy@708: %%% TeX-master: "00book" foozy@708: %%% End: