Git と Mercurial - 比較と対照 質問する

Git と Mercurial - 比較と対照 質問する

しばらくの間、私は個人的なプロジェクトに Subversion を使用しています。

Git や Mercurial、そして DVCS 全般について素晴らしい話を耳にすることが多くなってきています。

DVCS を全部試してみたいと思っていますが、どちらのオプションにもあまり詳しくありません。

Mercurial と Git の違いは何ですか?

注:どれが「最良」か、あるいはどれから始めるべきかを調べようとしているわけではありません。実装と哲学の面でどう違うのかを知りたいので、主に、似ている点と異なる点の主要な領域を探しています。

ベストアンサー1

免責事項: 私は Git を使用しており、git メーリング リストで Git の開発をフォローし、Git に少し貢献しています (主に gitweb)。私はドキュメントから Mercurial を知り、FreeNode の #revctrl IRC チャネルでの議論からも少し知りました。

この記事を書くにあたり、Mercurial について助言をくれた #mercurial IRC チャンネルの皆さんに感謝します。



まとめ

ここで、PHPMarkdown / MultiMarkdown / MarukuのMarkdown拡張機能のような、テーブル用の構文があると便利です。

  • リポジトリ構造: Mercurial では、オクトパスマージ (親が 2 つ以上) や非コミット オブジェクトのタグ付けは許可されません。
  • タグ: Mercurial は.hgtags、リポジトリごとのタグに特別なルールを適用したバージョン管理されたファイルを使用します。また、 内のローカル タグもサポートしています.hg/localtags。Git では、タグは名前空間内に存在する参照でありrefs/tags/、デフォルトではフェッチ時に自動的にフォローされ、明示的なプッシュが必要です。
  • ブランチ: Mercurial では、基本的なワークフローは匿名ヘッドに基づいています。Git は軽量の名前付きブランチを使用し、リモート リポジトリ内のブランチに従う特別な種類のブランチ (リモート追跡ブランチ) を備えています。
  • リビジョンの命名と範囲: Mercurial はリポジトリにローカルなリビジョン番号を提供し、相対リビジョン (先端、つまり現在のブランチから数える) とリビジョン範囲をこのローカル番号に基づいて決定します。Git はブランチの先端を基準にしてリビジョンを参照する方法を提供し、リビジョン範囲はトポロジカルです (リビジョンのグラフに基づく)。
  • Mercurialは名前変更追跡を使用し、Gitは名前変更検出を使用してファイルの名前変更を処理します。
  • ネットワーク: Mercurial は SSH および HTTP の「スマート」プロトコルと静的 HTTP プロトコルをサポートしています。最新の Git は SSH、HTTP、GIT の「スマート」プロトコルと HTTP(S) の「ダム」プロトコルをサポートしています。どちらもオフライン転送用のバンドル ファイルをサポートしています。
  • Mercurial は拡張機能(プラグイン) と確立された API を使用します。Git はスクリプト機能と確立された形式を備えています。

MercurialとGitにはいくつか異なる点がありますが、似ている点もあります。両方のプロジェクトはお互いのアイデアを借りています。たとえば、hg bisectMercurial(以前は二等分拡張) は Git の コマンドからヒントを得ましたgit bisectが、 のアイデアgit bundleは からヒントを得ましたhg bundle

リポジトリ構造、リビジョンの保存

Git のオブジェクト データベースには、ファイルの内容を含むBLOBオブジェクト、ファイル名やファイル権限 (ファイルの実行権限、シンボリック リンク) の関連部分を含むディレクトリ構造を格納する階層ツリーオブジェクト、作成者情報、コミットによって表されるリビジョンでのリポジトリの状態のスナップショットへのポインタ (プロジェクトのトップ ディレクトリのツリー オブジェクト経由)、および 0 個以上の親コミットへの参照を含むコミットオブジェクト、および他のオブジェクトを参照し、PGP/GPG を使用して署名できるタグオブジェクトの 4 種類のオブジェクトがあります。

Git は、オブジェクトを保存する 2 つの方法を使用します。1 つは、各オブジェクトが別々のファイルに保存されるルーズフォーマット (これらのファイルは 1 回書き込まれ、変更されることはありません)、もう 1 つは、多数のオブジェクトが 1 つのファイルにデルタ圧縮されて保存されるパックフォーマットです。操作のアトミック性は、オブジェクトの書き込み後に新しいオブジェクトへの参照が書き込まれる (create + rename トリックを使用してアトミックに) という事実によって実現されます。

Git リポジトリは、git gcディスク領域を削減し、パフォーマンスを向上させるために を使用した定期的なメンテナンスが必要ですが、最近では Git が自動的にそれを行います。(この方法により、リポジトリの圧縮が向上します。)

Mercurial は (私が理解している限りでは)、ファイルの履歴をファイルログに保存します(名前変更の追跡などの追加のメタデータや、いくつかのヘルパー情報も一緒に保存されると思います)。ディレクトリ構造を保存するためにマニフェストと呼ばれるフラットな構造を使用し、コミット メッセージや 0、1、または 2 つの親を含む変更セット (リビジョン) に関する情報を保存するためにchangelogと呼ばれる構造を使用します。

Mercurial はトランザクション ジャーナルを使用して操作の原子性を実現し、操作が失敗または中断された後のクリーンアップにはファイルの切り捨てを使用します。Revlog は追加のみです。

Git と Mercurial のリポジトリ構造を比較すると、Git はオブジェクト データベース (またはコンテンツ アドレス指定ファイルシステム) に似ており、Mercurial は従来の固定フィールド リレーショナル データベースに似ていることがわかります。

相違点:
Git ではツリーオブジェクトが階層構造を形成しますが、Mercurial のマニフェストファイルはフラット構造です。Git のBLOBオブジェクトはファイルの内容の1 つのバージョンを格納しますが、Mercurial のファイル ログは単一ファイルの全履歴を格納します(名前の変更に伴う複雑さを考慮しない場合)。つまり、他のすべての条件が同じであれば、Git の方が Mercurial よりも高速になる操作領域 (マージやプロジェクトの履歴の表示など) と、Mercurial の方が Git よりも高速になる領域 (パッチの適用や単一ファイルの履歴の表示など) が異なります。この問題は、エンド ユーザーにとっては重要ではない可能性があります。

Mercurial の変更ログ構造の固定レコード構造のため、Mercurial のコミットは最大 2 つの親しか持つことができませんが、Git のコミットは 3 つ以上の親を持つことができます (いわゆる「オクトパス マージ」)。理論上は、オクトパス マージを一連の 2 つの親のマージに置き換えることができますが、Mercurial リポジトリと Git リポジトリ間の変換時に複雑化する可能性があります。

私の知る限り、Mercurial には Git の注釈付きタグ(タグオブジェクト) に相当するものはありません。注釈付きタグの特別なケースは署名付きタグ(PGP / GPG 署名付き) です。Mercurial で同等のことは次のようにして実行できます。Gpg拡張機能この拡張機能は Mercurial と一緒に配布されています。Gitのように Mercurial で非コミット オブジェクトにタグを付けることはできませんが、これはあまり重要ではないと思います (一部の Git リポジトリでは、署名されたタグを検証するために使用する公開 PGP キーを配布するために、タグ付き BLOB を使用しています)。

参照: ブランチとタグ

Git では、参照 (ブランチ、リモート追跡ブランチ、タグ) はコミットの DAG の外側にあります (そうあるべきです)。refs/heads/名前空間 (ローカル ブランチ) 内の参照はコミットを指し、通常は "git commit" によって更新されます。これらはブランチの先端 (ヘッド) を指しているため、このような名前になっています。refs/remotes/<remotename>/名前空間 (リモート追跡ブランチ) 内の参照はコミットを指し、リモート リポジトリ内のブランチに従い<remotename>、"git fetch" または同等の機能によって更新されます。refs/tags/名前空間 (タグ) 内の参照は通常、コミット (軽量タグ) またはタグ オブジェクト (注釈付きおよび署名付きタグ) を指し、変更されることを意図していません。

タグ

Mercurial では、タグを使用してリビジョンに永続的な名前を付けることができます。タグは、無視パターンと同様に保存されます。つまり、グローバルに表示されるタグは、.hgtagsリポジトリ内のリビジョン管理されたファイルに保存されます。これには 2 つの結果があります。まず、Mercurial はこのファイルに対して特別なルールを使用して、すべてのタグの現在のリストを取得し、そのようなファイルを更新する必要があります (たとえば、現在チェックアウトされているバージョンではなく、ファイルの最も最近コミットされたリビジョンを読み取ります)。次に、新しいタグを他のユーザーや他のリポジトリに表示するには、このファイルへの変更をコミットする必要があります (私が理解している限り)。

Mercurialは に保存されるローカルタグhg/localtagsもサポートしており、これは他の人には見えません(もちろん転送もできません)。

Git では、タグは名前空間に保存されている他のオブジェクト (通常はコミットを指すタグ オブジェクト) への固定 (定数) の名前付き参照です。デフォルトでは、リビジョン セットをフェッチまたはプッシュするときに、git はフェッチまたはプッシュされるリビジョンを指すタグを自動的にフェッチまたはプッシュします。ただし、フェッチまたはプッシュされるタグをある程度制御refs/tags/できます。

Git は、軽量タグ (コミットを直接指す) と注釈付きタグ (タグ オブジェクトを指し、タグ メッセージ (オプションで PGP 署名を含む) を含み、コミットを指す) を若干異なる方法で扱います。たとえば、デフォルトでは、「git describe」を使用してコミットを記述するときに注釈付きタグのみを考慮します。

Git には Mercurial のローカル タグと厳密に同等のものはありません。ただし、Git のベスト プラクティスでは、準備ができた変更をプッシュし、他のユーザーがクローンして取得するための、別のパブリック ベア リポジトリを設定することを推奨しています。つまり、プッシュしないタグ (およびブランチ) は、リポジトリ専用になります。一方、たとえばローカル タグには、、headsまたはremotes以外の名前空間を使用することもできます。tagslocal-tags

個人的な意見:私の意見では、タグはリビジョン グラフの外部にあるため (リビジョン グラフへのポインターであるため)、リビジョン グラフの外側に存在する必要があります。タグはバージョン管理されないが、転送可能である必要があります。Mercurial がファイルを無視するためのメカニズムに似たメカニズムを使用するという選択は、.hgtags特別に扱う必要がある (ツリー内のファイルは転送可能だが、通常はバージョン管理される)、またはローカルのみのタグを持つ必要がある (.hg/localtagsバージョン管理されないが、転送できない) ことを意味します。

支店

Git のローカル ブランチ(ブランチ チップ、またはブランチ ヘッド) は、コミットへの名前付き参照であり、新しいコミットを成長させることができます。ブランチは、アクティブな開発ライン、つまりブランチ チップから到達可能なすべてのコミットを意味することもあります。ローカル ブランチはrefs/heads/名前空間に存在するため、たとえば 'master' ブランチの完全修飾名は 'refs/heads/master' になります。

Git の現在のブランチ (チェックアウトされたブランチ、および新しいコミットが配置されるブランチ) は、HEAD 参照によって参照されるブランチです。HEAD は、シンボリック参照ではなく、コミットを直接指すことができます。匿名の名前のないブランチにいるこの状況は、デタッチされた HEADと呼ばれます(「git ブランチ」は、'(ブランチなし)' にいることを示します)。

Mercurialには匿名ブランチ(ブランチヘッド)があり、ブックマーク(ブックマーク拡張機能)。このようなブックマーク ブランチは完全にローカルであり、それらの名前は (バージョン 1.6 までは) Mercurial を使用して転送できませんでした。rsync または scp を使用して、ファイルを.hg/bookmarksリモート リポジトリにコピーできます。また、を使用して、hg id -r <bookmark> <url>ブックマークの現在の先端のリビジョン ID を取得することもできます。

1.6以降、ブックマークはプッシュ/プルできるようになりました。ブックマーク拡張機能このページにはリモートリポジトリの操作違いとしては、Mercurial ではブックマーク名がグローバルであるのに対し、Git の「リモート」の定義では、リモート リポジトリ内の名前からローカルのリモート追跡ブランチの名前へのブランチ名のマッピングも記述されます。たとえば、refs/heads/*:refs/remotes/origin/*マッピングとは、リモート リポジトリ内の「master」ブランチ (「refs/heads/master」) の状態を「origin/master」リモート追跡ブランチ (「refs/remotes/origin/master」) で見つけることができることを意味します。

Mercurial には、いわゆる名前付きブランチもあります。ブランチ名がコミット (変更セット内) に埋め込まれます。このような名前はグローバルです (フェッチ時に転送されます)。これらのブランチ名は、変更セットのメタデータの一部として永続的に記録されます。最新の Mercurial では、「名前付きブランチ」を閉じて、ブランチ名の記録を停止できます。このメカニズムでは、ブランチの先端がオンザフライで計算されます。

Mercurial の「名前付きブランチ」は、コミット ラベルと呼ぶべきだと思います。それが名前付きブランチの本来の意味です。「名前付きブランチ」に複数のヒント (子のない複数のコミット) がある場合や、リビジョン グラフの複数の分離した部分で構成される場合もあります。

Git には Mercurial の「埋め込みブランチ」に相当するものはありません。さらに、Git の哲学では、ブランチに何らかのコミットが含まれているとは言えますが、コミットが何らかのブランチに属しているという意味ではありません。

Mercurial のドキュメントでは、少なくとも長期間存続するブランチ (リポジトリ ワークフローごとに 1 つのブランチ)、つまりクローンによるブランチ作成には、個別のクローン (個別のリポジトリ) を使用することが依然として提案されていることに注意してください。

プッシュ中のブランチ

Mercurial は、デフォルトですべてのヘッドをプッシュします。単一のブランチ (単一のヘッド)をプッシュする場合は、プッシュするブランチのチップ リビジョンを指定する必要があります。ブランチ チップは、リビジョン番号 (リポジトリに対してローカル)、リビジョン識別子、ブックマーク名 (リポジトリに対してローカル、転送されない)、または埋め込みブランチ名 (名前付きブランチ) で指定できます。

私の理解する限りでは、Mercurial の用語で「名前付きブランチ」としてマークされたコミットを含むリビジョンの範囲をプッシュすると、プッシュ先のリポジトリにこの「名前付きブランチ」が含まれることになります。つまり、このような埋め込みブランチ (「名前付きブランチ」) の名前は、(特定のリポジトリ/プロジェクトのクローンに関して)グローバルです。

デフォルトでは (設定変数により異なりますがpush.default)、「git push」または「git push < remote >」を使用すると、Git は一致するブランチ、つまりプッシュ先のリモート リポジトリに既に存在する同等のブランチのみをプッシュします。git --all-push のオプション (「git push --all」) を使用してすべてのブランチをプッシュしたり、「git push < remote > < branch >」を使用して特定の単一のブランチをプッシュしたり、「git push < remote > HEAD」を使用して現在のブランチをプッシュしたりできます。

上記のすべては、Git がremote.<remotename>.push構成変数を介してプッシュするブランチを構成していないことを前提としています。

フェッチ中のブランチ

注:ここでは Git の用語を使用します。ここでの「フェッチ」とは、変更をローカル作業と統合せずにgit fetchリモート リポジトリから変更をダウンロードすることを意味します。これが「 」と「hg pull」の機能です。

私の理解が正しければ、デフォルトでは Mercurial はリモート リポジトリからすべてのヘッドを取得しますが、" hg pull --rev <rev> <url>" または " " を使用して取得するブランチを指定して単一のブランチhg pull <url>#<rev>を取得できます。<rev> は、リビジョン識別子、"名前付きブランチ" 名 (変更ログに埋め込まれたブランチ)、またはブックマーク名を使用して指定できます。ただし、ブックマーク名は (少なくとも現時点では) 転送されません。取得した "名前付きブランチ" リビジョンが属するすべての "名前付きブランチ" リビジョンが転送されます。"hg pull" は、取得したブランチのヒントを匿名の名前なしヘッドとして保存します。

Git では、デフォルトで (「git clone」によって作成された 'origin' リモート、および「git remote add」を使用して作成されたリモートの場合)、「git fetch」(または「git fetch <remote>」) は、リモート リポジトリ (名前空間)からすべてのブランチを取得し、名前空間に保存します。つまり、たとえば、リモート 'origin' の 'master' という名前のブランチ (フル ネーム: 'refs/heads/master') は、'origin/master'リモート追跡ブランチ(フル ネーム: 'refs/remotes/origin/master')として保存されます。refs/heads/refs/remotes/

Git で単一のブランチを取得するにはgit fetch <remote> <branch>、次の操作を実行します。Git は要求されたブランチを FETCH_HEAD に保存します。これは、Mercurial の名前のないヘッドに似ています。

これらは、強力なrefspec Git 構文のデフォルトのケースの例にすぎません。refspec を使用すると、取得するブランチとその保存場所を指定および/または構成できます。たとえば、デフォルトの「すべてのブランチを取得する」ケースは、ワイルドカード refspec '+refs/heads/*:refs/remotes/origin/*' で表され、「単一のブランチを取得する」は 'refs/heads/<branch>:' の省略形です。refspec は、リモート リポジトリのブランチ名 (refs) をローカル refs 名にマップするために使用されます。ただし、Git を効果的に操作するために refspec について (あまり) 知る必要はありません (主に「git remote」コマンドのおかげです)。

個人的な意見:私は個人的に、Mercurial の「名前付きブランチ」(ブランチ名が変更セット メタデータに埋め込まれている) は、グローバル名前空間では誤った設計であると考えています。特に分散バージョン コントロール システムではそうです。たとえば、Alice と Bob の両方がリポジトリに「for-joe」という「名前付きブランチ」を持っている場合を考えてみましょう。これらのブランチには共通点はありません。しかし、Joe のリポジトリでは、この 2 つのブランチは 1 つのブランチとして扱われます。つまり、ブランチ名の衝突を防ぐための規則を何らかの方法で考案したことになります。これは Git では問題ではありません。Joe のリポジトリでは、Alice の「for-joe」ブランチは「alice/for-joe」になり、Bob のブランチは「bob/for-joe」になります。こちらも参照してください。ブランチ名とブランチIDを分離するMercurial wiki で提起された問題。

Mercurial の「ブックマーク ブランチ」には現在、コア内配布メカニズムがありません。

相違点:
この部分はMercurialとGitの主な違いの1つです。ジェームズ・ウッディアットそしてスティーブ・ロッシュと回答で述べられています。Mercurial はデフォルトで、匿名の軽量コードラインを使用します。これは、Mercurial の用語では「ヘッド」と呼ばれます。Git は軽量の名前付きブランチを使用し、リモート リポジトリのブランチ名をリモート追跡ブランチ名にマッピングする注入マッピングを使用します。Git はブランチに名前を付けることを「強制」します (まあ、単一の名前のないブランチ (分離された HEAD と呼ばれる状況) は例外です) が、これはトピック ブランチ ワークフローなどのブランチを多用するワークフロー、つまり単一リポジトリ パラダイムでの複数のブランチでより適切に機能すると思います。

命名の改訂

Gitではリビジョンに名前を付ける方法が数多くあります(例えば、git rev-parseマニュアルページ):

  • 完全な SHA1 オブジェクト名 (40 バイトの 16 進文字列)、またはリポジトリ内で一意となる部分文字列
  • シンボリック参照名、例: 'master' ('master' ブランチを参照)、'v1.5.0' (タグを参照)、'origin/next' (リモート追跡ブランチを参照)
  • リビジョンパラメータのサフィックスは、^コミットオブジェクトの最初の親、^nつまりマージコミットのn番目の親を意味します。~nリビジョンパラメータのサフィックスは、最初の親の直系におけるコミットのn番目の祖先を意味します。これらのサフィックスを組み合わせて、シンボリック参照からのパスに従うリビジョン指定子を形成できます。例: 'pu~3^2~3'
  • 「git describe」の出力。つまり、最も近いタグ、オプションでダッシュとコミット数が続き、その後にダッシュ、'g'、短縮されたオブジェクト名が続きます (例: 'v1.6.5.1-75-g5bf8097')。

ここでは触れられていないが、reflog に関連するリビジョン指定子もあります。Git では、コミット、タグ、ツリー、または BLOB の各オブジェクトには SHA-1 識別子があります。指定されたリビジョンのツリー (ディレクトリ) または BLOB (ファイル コンテンツ) を参照するための、たとえば 'next:Documentation' や 'next:README' などの特別な構文があります。

Mercurialにはチェンジセットの命名方法も数多くあります(例えば、HGマニュアルページ):

  • 単純な整数はリビジョン番号として扱われます。リビジョン番号は特定のリポジトリに対してローカルであり、他のリポジトリでは異なる可能性があることを覚えておく必要があります。
  • 負の整数は、先端からの連続オフセットとして扱われ、-1 は先端、-2 は先端の前のリビジョンなどを示します。これらはリポジトリに対してローカルでもあります。
  • 一意のリビジョン識別子 (40 桁の 16 進文字列) またはその一意のプレフィックス。
  • タグ名 (指定されたリビジョンに関連付けられたシンボリック名)、またはブックマーク名 (拡張子付き: 指定されたヘッドに関連付けられたシンボリック名、リポジトリにローカル)、または「名前付きブランチ」(コミット ラベル。「名前付きブランチ」によって指定されたリビジョンは、指定されたコミット ラベルを持つすべてのコミットの先端 (子のないコミット) であり、そのような先端が複数ある場合は、リビジョン番号が最も大きいもの)
  • 予約名「tip」は、常に最新のリビジョンを識別する特別なタグです。
  • 予約名「null」は、ヌル リビジョンを示します。
  • 予約名「.」は作業ディレクトリの親を示します。

違い
上記のリストを比較するとわかるように、Mercurialはリポジトリにローカルなリビジョン番号を提供しますが、Gitは提供しません。一方、Mercurialは、リポジトリにローカルな「tip」(現在のブランチ)からの相対オフセットのみを提供します(少なくとも親revspecExtension) ですが、Git では任意のチップに続く任意のコミットを指定できます。

最新のリビジョンは、Git では HEAD と名付けられ、Mercurial では「tip」と名付けられます。Git には null リビジョンはありません。Mercurial と Git はどちらも、多くのルートを持つことができます (親のないコミットを複数持つことができます。これは通常、以前は別々だったプロジェクトが結合した結果です)。

参照: さまざまな種類のリビジョン指定子Elijah のブログ (newren's) の記事。

個人的な意見:リビジョン番号は過大評価されていると思います(少なくとも分散開発や非線形/分岐履歴の場合)。まず、分散バージョン管理システムの場合、リビジョン番号はリポジトリに対してローカルであるか、中央の番号付け機関として特別な方法でリポジトリを処理する必要があります。次に、より長い履歴を持つ大規模なプロジェクトでは、5 桁の範囲でリビジョン番号を持つことができるため、6 文字から 7 文字に短縮されたリビジョン識別子よりもわずかな利点しかなく、リビジョンが部分的にしか順序付けされていないのに厳密な順序付けを意味します (ここで、リビジョン n と n+1 が親と子である必要がないことを意味します)。

改訂範囲

Git では、リビジョン範囲は位相的です。よく見られるA..B構文は、線形履歴の場合、リビジョン範囲が A (ただし A は除く) で始まり、B (つまり範囲はより下から開いている) で終わることを意味しますが、は の省略形 (「糖衣構文」) であり、履歴をたどるコマンドの場合、これは A から到達可能なコミットを除く、B から到達可能なすべてのコミットを意味します。つまり、A が B の祖先でなくても、範囲^A Bの動作は完全に予測可能 (かつ非常に便利) です。つまり、 は、A と B (マージ ベース) の共​​通の祖先からリビジョン B までのリビジョンの範囲を意味します。A..BA..B

Mercurial では、リビジョン範囲はリビジョン番号の範囲に基づいています。範囲はA:B構文を使用して指定され、Git の範囲とは対照的に、閉じた区間として機能します。また、範囲 B:A は範囲 A:B の逆順ですが、これは Git では当てはまりません (ただし、A...B構文については下記の注記を参照してください)。ただし、このような単純さには代償が伴います。リビジョン範囲 A:B は、A が B の祖先であるか、またはその逆、つまり線形履歴の場合のみ意味を持ちます。それ以外の場合 (そうだと思います)、範囲は予測不可能であり、結果はリポジトリに対してローカルです (リビジョン番号はリポジトリに対してローカルであるため)。

This is fixed with Mercurial 1.6, which has new topological revision range, where 'A..B' (or 'A::B') is understood as the set of changesets that are both descendants of X and ancestors of Y. This is, I guess, equivalent to '--ancestry-path A..B' in Git.

Git also has notation A...B for symmetric difference of revisions; it means A B --not $(git merge-base A B), which means all commits reachable from either A or B, but excluding all commits reachable from both of them (reachable from common ancestors).

Renames

Mercurial uses rename tracking to deal with file renames. This means that the information about the fact that a file was renamed is saved at the commit time; in Mercurial this information is saved in the "enhanced diff" form in filelog (file revlog) metadata. The consequence of this is that you have to use hg rename / hg mv... or you need to remember to run hg addremove to do similarity based rename detection.

Git is unique among version control systems in that it uses rename detection to deal with file renames. This means that the fact that file was renamed is detected at time it is needed: when doing a merge, or when showing a diff (if requested / configured). This has the advantage that rename detection algorithm can be improved, and is not frozen at time of commit.

Both Git and Mercurial require using --follow option to follow renames when showing history of a single file. Both can follow renames when showing line-wise history of a file in git blame / hg annotate.

In Git the git blame command is able to follow code movement, also moving (or copying) code from one file to the other, even if the code movement is not part of wholesome file rename. As far as I know this feature is unique to Git (at the time of writing, October 2009).

Network protocols

Both Mercurial and Git have support for fetching from and pushing to repositories on the same filesystem, where repository URL is just a filesystem path to repository. Both also have support for fetching from bundle files.

Mercurial support fetching and pushing via SSH and via HTTP protocols. For SSH one needs an accessible shell account on the destination machine and a copy of hg installed / available. For HTTP access the hg-serve or Mercurial CGI script running is required, and Mercurial needs to be installed on server machine.

Git supports two kinds of protocols used to access remote repository:

  • SSH 経由のアクセスやカスタム git:// プロトコル ( による) 経由のアクセスを含む「スマート」プロトコルgit-daemonでは、サーバーに git がインストールされている必要があります。これらのプロトコルでの交換は、クライアントとサーバーが共通するオブジェクトについてネゴシエートし、パックファイルを生成して送信することで構成されます。最新の Git には、「スマート」HTTP プロトコルのサポートが含まれています。
  • HTTP および FTP (フェッチのみ)、および HTTPS (WebDAV 経由でプッシュ) などの「ダム」プロトコルでgit update-server-infoは、サーバーに git がインストールされている必要はありませんが、リポジトリに (通常はフックから実行される) によって生成された追加情報が含まれている必要があります。交換は、クライアントがコミット チェーンをたどり、必要に応じてルーズ オブジェクトとパック ファイルをダウンロードすることで行われます。欠点は、厳密に必要な量よりも多くダウンロードされること (たとえば、コーナー ケースでは、パック ファイルが 1 つしかない場合、数個のリビジョンのみをフェッチする場合でも、パック ファイル全体がダウンロードされる)、および完了するまでに多数の接続が必要になる可能性があることです。

拡張: スクリプト機能と拡張機能 (プラグイン)

Mercurial はPythonで実装されていますが、パフォーマンス向上のため、一部のコア コードは C で記述されています。機能追加手段として拡張機能(プラグイン) を作成するための API を提供しています。「ブランチのブックマーク」やリビジョンの署名など​​の機能の一部は、Mercurial とともに配布される拡張機能で提供されており、有効にする必要があります。

Git はCPerl、およびシェル スクリプトで実装されています。Git は、スクリプトで使用するのに適した多くの低レベル コマンド (配管) を提供します。新しい機能を導入する通常の方法は、それを Perl またはシェル スクリプトとして記述し、ユーザー インターフェイスが安定したら、パフォーマンス、移植性、およびシェル スクリプトの場合はコーナー ケースを回避するために C で書き直すことです (この手順は組み込みと呼ばれます)。

Git は [リポジトリ] 形式と [ネットワーク] プロトコルに依存し、それらを中心に構築されています。言語バインディングの代わりに、他の言語での Git の (部分的または完全な)再実装があります(その一部は部分的に再実装され、git コマンドを部分的にラッパーにしたものです)。JGit (Java、EGit、Eclipse Git プラグインで使用)、Grit (Ruby)、Dulwich (Python)、git# (C#)。


要約

おすすめ記事