foozy@708: \chapter{A tour of Mercurial: merging work} foozy@708: \label{chap:tour-merge} foozy@708: foozy@708: 前章においては、 foozy@708: リポジトリの複製、 foozy@708: リポジトリでのチェンジセットの生成、 foozy@708: ならびに \hgcmd{push} および \hgcmd{pull} foozy@708: によるリポジトリ間でのチェンジセットの授受を見てきました。 foozy@708: 次の段階として、別々のリポジトリにおける変更の\emph{マージ} foozy@708: (merge)について見てみましょう。 foozy@708: foozy@708: \section{Merging streams of work} foozy@708: foozy@708: 分散構成管理ツールにおいて、マージは作業の基本です。 foozy@708: foozy@708: \begin{itemize} foozy@708: \item Alice と Bob が、 foozy@708: 共同作業しているプロジェクトのリポジトリから複製した、 foozy@708: 個人的なリポジトリを持っているものとします。 foozy@708: Alice は自分のリポジトリにおいてバグを修正しました。 foozy@708: Bob は自分のリポジトリにおいて機能を追加しました。 foozy@708: 二人は、 foozy@708: バグフィックスと新機能の両方を含むリポジトリを共有したいと思うでしょう。 foozy@708: foozy@708: \item 筆者は、 foozy@708: 個別のリポジトリによって、 foozy@708: お互いが安全に隔離された複数の異なる作業を、 foozy@708: 同一プロジェクトにおいて同時に実施することが頻繁にあります。 foozy@708: この形式での作業では、 foozy@708: あるリポジトリにおける成果を、 foozy@708: 他のリポジトリに対して頻繁にマージする必要があります。 foozy@708: foozy@708: \end{itemize} foozy@708: foozy@708: マージは必要に応じて実施するありふれた作業ですので、 foozy@708: Mercurial では簡単に行えるようになっています。 foozy@708: それでは、マージ手順を見て行きましょう。 foozy@708: もう一度リポジトリの複製を行い(もう何度も複製しましたよね?)、 foozy@708: そのリポジトリにおいて変更を行います。 foozy@708: foozy@708: \interaction{tour.merge.clone} foozy@708: foozy@708: この時点で、 foozy@708: 内容の異なる2つの \filename{hello.c} のコピーが存在するはずです。 foozy@708: 2つのリポジトリの履歴は、 foozy@708: 図~\ref{fig:tour-merge:sep-repos} に示すように、 foozy@708: 枝分かれしています。 foozy@708: foozy@708: \interaction{tour.merge.cat} foozy@708: foozy@708: \begin{figure}[ht] foozy@708: \centering foozy@708: \grafix{tour-merge-sep-repos} foozy@708: \caption{Divergent recent histories of the \dirname{my-hello} and foozy@708: \dirname{my-new-hello} repositories} foozy@708: \label{fig:tour-merge:sep-repos} foozy@708: \end{figure} foozy@708: foozy@708: \hgcmd{pull} を行っても、 foozy@708: 作業領域ディレクトリには影響を及ぼさないことは既に説明したとおりですので、 foozy@708: \dirname{my-hello} から \hgcmd{pull} してみましょう。 foozy@708: foozy@708: \interaction{tour.merge.pull} foozy@708: foozy@708: 作業領域ディレクトリには影響を及ぼしていませんが、 foozy@708: \hgcmd{pull} コマンドは ``heads'' について何か警告しています。 foozy@708: foozy@708: \subsection{Head changesets} foozy@708: foozy@708: ``head'' とは、 foozy@708: リポジトリ中において、 foozy@708: 子孫(ないし子供)となるチェンジセットが存在しないチェンジセットのことです。 foozy@708: リポジトリにおける最も最新のリビジョンは、 foozy@708: 一切の子チェンジセットを持ちませんから、 foozy@708: 従って tip リビジョンは head となりますが、 foozy@708: 1つのリポジトリには複数の head が存在しえます。 foozy@708: foozy@708: \begin{figure}[ht] foozy@708: \centering foozy@708: \grafix{tour-merge-pull} foozy@708: \caption{Repository contents after pulling from \dirname{my-hello} into foozy@708: \dirname{my-new-hello}} foozy@708: \label{fig:tour-merge:pull} foozy@708: \end{figure} foozy@708: foozy@708: \dirname{my-hello} から \dirname{my-new-hello} への foozy@708: \hgcmd{pull} による影響を、 foozy@708: 図~\ref{fig:tour-merge:pull} で見ることができます。 foozy@708: 既に \dirname{my-new-hello} に存在していた履歴には手が付けられていませんが、 foozy@708: 新しいリビジョンが追加されています。 foozy@708: 図~\ref{fig:tour-merge:pull} からは、 foozy@708: 新しいリポジトリ(\dirname{my-new-hello})において、 foozy@708: \emph{チェンジセット識別子}は同じままでも、 foozy@708: \emph{リビジョン番号}が異なる様が読み取れます foozy@708: (そして、図らずも、チェンジセットについて話をする際に、 foozy@708: リビジョン番号を使用するのが良くない、という好例になっています)。 foozy@708: \hgcmd{heads} コマンドにより、 foozy@708: リポジトリの head を見ることができます。 foozy@708: foozy@708: \interaction{tour.merge.heads} foozy@708: foozy@708: \subsection{Performing the merge} foozy@708: foozy@708: 作業領域ディレクトリを、 foozy@708: (\dirname{my-hello} から取り込んだ)新たな tip リビジョンに更新するために、 foozy@708: いつものように \hgcmd{update} コマンドを実行すると、 foozy@708: どうなるでしょう? foozy@708: foozy@708: \interaction{tour.merge.update} foozy@708: foozy@708: Mercurial から、 foozy@708: \hgcmd{update} コマンドではマージが行われない旨が通達されます。 foozy@708: マージの実施が必要と思われる場合、 foozy@708: 強制的な実行をしない限りは foozy@708: \hgcmd{update} コマンドによる作業領域ディレクトリの更新は行われません。 foozy@708: \hgcmd{update} コマンドの代わりに、 foozy@708: \hgcmd{merge} コマンドを用いて2つの head をマージします。 foozy@708: foozy@708: \interaction{tour.merge.merge} foozy@708: foozy@708: \begin{figure}[ht] foozy@708: \centering foozy@708: \grafix{tour-merge-merge} foozy@708: \caption{Working directory and repository during merge, and foozy@708: following commit} foozy@708: \label{fig:tour-merge:merge} foozy@708: \end{figure} foozy@708: foozy@708: \hgcmd{merge} コマンドによって、 foozy@708: \hgcmd{parents} コマンドの出力、 foozy@708: および \filename{hello.c} の内容の変更という形で、 foozy@708: \emph{両方}の head の変更内容が作業領域ディレクトリに反映されます。 foozy@708: foozy@708: \interaction{tour.merge.parents} foozy@708: foozy@708: \subsection{Committing the results of the merge} foozy@708: foozy@708: 結果を \hgcmd{commit} するまでは、 foozy@708: \hgcmd{parents} はマージの際には常に2つの親(チェンジセット)を表示します。 foozy@708: foozy@708: \interaction{tour.merge.commit} foozy@708: foozy@708: これで、新しい tip リビジョンが作成されました。 foozy@708: 先述した2つの head の\emph{両方}を親に持つ点に注意してください。 foozy@708: これらは、先に \hgcmd{parents} で表示したリビジョンと一致します。 foozy@708: foozy@708: \interaction{tour.merge.tip} foozy@708: foozy@708: 作業領域ディレクトリがマージの際にどのようになっているのか、 foozy@708: そしてコミットによってどのようにリポジトリに作用するのかを、 foozy@708: 図~\ref{fig:tour-merge:merge} から読み取ることができます。 foozy@708: マージの際に作業領域ディレクトリの親であった2つのチェンジセットは、 foozy@708: コミットの際には新たなチェンジセットにとっての親チェンジセットとなります。 foozy@708: foozy@708: \section{Merging conflicting changes} foozy@708: foozy@708: 殆どのマージ作業は簡単に済みますが、 foozy@708: 時にはマージ対象のチェンジセット同士が、 foozy@708: 同じファイルの同じ部位を変更している場合があります。 foozy@708: 両者の変更内容が同一で無ければ、 foozy@708: マージは\emph{衝突}(conflict)を生じるため、 foozy@708: 両者の異なる変更内容を両立させて foozy@708: 何らかの一貫性の取れた状態にするための決断が必要です。 foozy@708: foozy@708: \begin{figure}[ht] foozy@708: \centering foozy@708: \grafix{tour-merge-conflict} foozy@708: \caption{Conflicting changes to a document} foozy@708: \label{fig:tour-merge:conflict} foozy@708: \end{figure} foozy@708: foozy@708: 文書に対する2つの変更の衝突の例を、 foozy@708: 図~\ref{fig:tour-merge:conflict} が図示しています。 foozy@708: 両者はファイルの同じ版を元にしていますが、 foozy@708: 一方が変更を行う傍ら、 foozy@708: 他方が同じ段落に対して異なる変更をしてしまいます。 foozy@708: 変更の衝突を解消する作業とは、 foozy@708: そのファイルがどのようになっているべきかを決定することに他なりません。 foozy@708: foozy@708: Mercurial には衝突を扱う機能が組み込まれていません。 foozy@708: その代わりに、 foozy@708: \command{hgmerge} と呼ばれる外部プログラムを実行します。 foozy@708: このプログラムは、 foozy@708: Mercurial に添付されるシェルスクリプト\footnote{訳注: foozy@708: \command{/bin/sh} 向けだから、ということなのでしょうが、 foozy@708: Windows のバイナリ版には添付されていません。}ですが、 foozy@708: 別なプログラムを起動させることもできます。 foozy@708: \command{hgmerge} の基底動作では、 foozy@708: 幾つかの著名なマージツールのうち、 foozy@708: 稼働環境においてインストールされていると思われるものを探します。 foozy@708: まず始めに、 foozy@708: 非対話的マージツール\footnote{訳注: foozy@708: \command{diff3} や \command{merge} など}を実行してみますが、 foozy@708: (人手によって解決する必要性があるために)それが失敗した場合や、 foozy@708: そもそもそれらのツールが提供されていない場合、 foozy@708: 他のグラフィカルなマージツールの起動を試みます foozy@708: \footnote{訳注: 例えば、 foozy@708: \command{diff3} によるマージを行い、 foozy@708: 衝突が検出された場合はそのファイルごとに、 foozy@708: \envar{EDITOR} 環境変数で定義されるエディタ(ないし \command{vi}) foozy@708: を起動して、 foozy@708: それぞれのチェンジセットに由来する変更の間での調停を要求してきます。 foozy@708: }。 foozy@708: foozy@708: \envar{HGMERGE} foozy@708: 環境変数に起動対象プログラムないしスクリプト名を設定することで、 foozy@708: Mercurial に \command{hgmerge} 以外を起動させる事もできます foozy@708: foozy@708: \subsection{Using a graphical merge tool} foozy@708: foozy@708: 著者のお薦めのグラフィカルなマージツールは \command{kdiff3} なので、 foozy@708: グラフィカルなファイルマージツールに求められる機能について、 foozy@708: これを題材に説明しようと思います。 foozy@708: 作業中の画面イメージが図~\ref{fig:tour-merge:kdiff3}にあります。 foozy@708: 着目している1つのファイルに対して、 foozy@708: 3つの異なるリビジョンが存在することから、 foozy@708: マージ方法は\emph{3方向マージ}(three-way merge)と呼ばれています。 foozy@708: それゆえ、マージツールはウィンドウ上部を3つの区画に分割しています。 foozy@708: foozy@708: \begin{itemize} foozy@708: \item 左端に表示されているのは、 foozy@708: ファイルの\emph{元}(base)の版、 foozy@708: つまりマージ対象としている2つの版にとって、 foozy@708: 最も新しい分岐元となっている版です。 foozy@708: foozy@708: \item 中央に表示されているのは、 foozy@708: マージ``先''の版\footnote{訳注: 原文では「``our'' version」}ですので、 foozy@708: 作業領域ディレクトリにおける変更内容が表示されます。 foozy@708: foozy@708: \item 右端に表示されているのは、 foozy@708: マージ``元''\footnote{訳注: 原文では「``their'' version」}ですので、 foozy@708: マージしようとしているチェンジセットに由来する内容が表示されます。 foozy@708: foozy@708: \end{itemize} foozy@708: foozy@708: これらの区画の下方に表示されているのは、 foozy@708: 現時点でのマージ\emph{結果}です。 foozy@708: マージにおける作業とは、 foozy@708: 画面上に赤字で表示された\footnote{訳注: foozy@708: \command{diff3} が行単位での衝突表示であるのに比べて、 foozy@708: GUI である利点が生きています。}、 foozy@708: 慎重なファイルのマージが必要とされる未解決の衝突を、 foozy@708: 妥当な内容で置き換えることです。 foozy@708: foozy@708: これら4つの区画は\emph{互いに固定}されているので、 foozy@708: いずれかの区画をスクロールさせた場合には、 foozy@708: 他の区画も相応の場所を表示するように更新されます。 foozy@708: foozy@708: \begin{figure}[ht] foozy@708: \centering foozy@708: \grafixL{kdiff3} foozy@708: \label{fig:tour-merge:kdiff3} foozy@708: \caption{Using \command{kdiff3} to merge versions of a file} foozy@708: \end{figure} foozy@708: 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: 中には特定のファイルフォーマット(一般には XML) foozy@708: に特化したものもあります。 foozy@708: foozy@708: \subsection{A worked example} foozy@708: foozy@708: 本節での例では、 foozy@708: 前述の図~\ref{fig:tour-merge:conflict} foozy@708: におけるファイル更新の履歴を再現します。 foozy@708: 元となる版のファイルを格納したリポジトリを作成することから始めましょう foozy@708: \footnote{訳注: 実行例では、 foozy@708: 新規のリポジトリである \dirname{scam} の \hgcmd{init} が抜けています。}。 foozy@708: foozy@708: \interaction{tour-merge-conflict.wife} foozy@708: foozy@708: 次に、リポジトリを複製し、ファイルを変更します。 foozy@708: foozy@708: \interaction{tour-merge-conflict.cousin} foozy@708: foozy@708: もう一つリポジトリを複製し、 foozy@708: 他の利用者によるファイルへの変更を模擬的に再現します foozy@708: (この模擬的な実行は、 foozy@708: タスクごとに隔離したリポジトリの間でのマージどころか、 foozy@708: それらのマージの際の衝突を解消することですら、 foozy@708: 決して珍しいことではない、 foozy@708: ということを暗示しています)。 foozy@708: foozy@708: \interaction{tour-merge-conflict.son} foozy@708: foozy@708: 同一ファイルに2つの異なる版ができたので、 foozy@708: マージ実施の環境が整いました。 foozy@708: foozy@708: \interaction{tour-merge-conflict.pull} foozy@708: foozy@708: マージにおける対話的な処理の部分が、 foozy@708: 本書における実行例の自動実行機構~ref{sec:automated-example-running foozy@708: }を損ねるため、 foozy@708: この例では Mercurial の \command{hgmerge} を使用しません。 foozy@708: その代わりに、 foozy@708: \envar{HGMERGE} を設定することで、 foozy@708: Mercurial に非対話的な \command{merge} コマンドを実行させます。 foozy@708: このコマンドは多くの Unix 的なシステムに同梱されています。 foozy@708: 以下の例を実際に試す際には、 foozy@708: \envar{HGMERGE} をわざわざ設定する必要はありません。 foozy@708: foozy@708: \interaction{tour-merge-conflict.merge} foozy@708: foozy@708: \command{merge} コマンドは衝突を解消せずに、 foozy@708: どの行における変更が衝突していて、 foozy@708: その変更がどのチェンジセットに由来するのかを示す\emph{マージマーク}を、 foozy@708: 衝突が検出されたファイルに書き込みます。 foozy@708: foozy@708: Mercurial は、 foozy@708: \command{merge} の終了コードがマージ処理\footnote{訳注: foozy@708: より正確には「マージにおける衝突の解消」}失敗を示す場合、 foozy@708: マージ処理を再実行する手順を表示します。 foozy@708: ここで提示される手順は、 foozy@708: マージ作業の途中で混乱してしまったり、 foozy@708: 間違ってしまったことに気付いて、 foozy@708: グラフィカルなマージツールを中途終了させた場合などに役立ちます。 foozy@708: foozy@708: 自動ないし手動のマージが失敗した場合であっても、 foozy@708: 関連の有るファイルを直接``修正''した上で、 foozy@708: マージ結果をコミットすることも可能です。 foozy@708: foozy@708: \interaction{tour-merge-conflict.commit} foozy@708: foozy@708: \section{Simplifying the pull-merge-commit sequence} foozy@708: \label{sec:tour-merge:fetch} foozy@708: foozy@708: ここまでに述べてきた変更マージの手順は単純なものですが、 foozy@708: 3つのコマンドを順に実行する必要があります。 foozy@708: foozy@708: \begin{codesample2} foozy@708: hg pull foozy@708: hg merge foozy@708: hg commit -m 'Merged remote changes' foozy@708: \end{codesample2} foozy@708: foozy@708: 最後のコミットの際には、 foozy@708: 通常は面白くも無い``決まりきった''内容にならざるを得ませんが、 foozy@708: コミットメッセージを入力する必要があります。 foozy@708: foozy@708: 可能であれば、必要とされる手順を低減させたいものです。 foozy@708: 実際に Mercurial は、これを可能とする \hgext{fetch} foozy@708: と呼ばれるイクステンションが同梱されています。 foozy@708: foozy@708: Mercurial は、 foozy@708: 取り扱いの利便性上から中核機能を小さく簡潔に保つ一方で、 foozy@708: 機能追加を可能にするための柔軟な拡張(イクステンション)機構を提供しています。 foozy@708: コマンドラインから利用できる foozy@708: Mercurial コマンドを追加するイクステンションもあれば、 foozy@708: 例えばサーバ機能を拡張するような、 foozy@708: ``舞台裏''で機能するイクステンションもあります。 foozy@708: foozy@708: \hgext{fetch} イクステンションは、 foozy@708: 予想したこととは思いますが、 foozy@708: \hgcmd{fetch} と呼ばれる新しいコマンドを追加します。 foozy@708: \hgcmd{fetch} コマンドは、 foozy@708: \hgcmd{pull}/\hgcmd{update}/\hgcmd{merge}/\hgcmd{commit} foozy@708: の組み合わせのように振舞います。 foozy@708: まずは他のリポジトリから作業中のリポジトリへ変更を取り込みます。 foozy@708: 取り込んだチェンジセットによる新たな head の追加が検知\footnote{訳注: foozy@708: 他のリポジトリからの取り込みにより、 foozy@708: 3つ以上の head がリポジトリに存在するようになった場合は、 foozy@708: マージ対象の特定ができないため、 foozy@708: 取り込みのみで処理を中断します。}された場合、 foozy@708: マージを開始し、 foozy@708: 自動的に生成されたコミットメッセージを使ってコミットを行います。 foozy@708: 新たな head の追加が無かった場合、 foozy@708: \hgcmd{fetch} コマンドは作業領域ディレクトリを foozy@708: tip リビジョンで更新します。 foozy@708: foozy@708: \hgext{fetch} イクステンションは簡単に有効化できます。 foozy@708: \sfilename{.hgrc} ファイルを編集し、 foozy@708: \rcsection{extensions} セクション foozy@708: (無い場合は作成してください)に移動し、 foozy@708: ``\Verb+fetch +'' で始まる行を追加します。 foozy@708: foozy@708: \begin{codesample2} foozy@708: [extensions] foozy@708: fetch = foozy@708: \end{codesample2} foozy@708: foozy@708: (通常は、 foozy@708: ``\texttt{=}'' の右辺にイクステンションの位置を指定しますが、 foozy@708: \hgext{fetch} イクステンションは標準の配布物に同梱されているので、 foozy@708: Mercurial は \hgext{fetch} を探し出すことができます) foozy@708: foozy@708: %%% Local Variables: foozy@708: %%% mode: latex foozy@708: %%% TeX-master: "00book" foozy@708: %%% End: