52020


[Git のドキュメント](https://git-scm.com/docs/merge-strategies)によると、`git merge` は内部で
3-way merge を行います。

2-way merge だと不都合があるのは分かるのですが、ではこの 3-way merge は具体的にどのようなアルゴリズムでマージをするのでしょうか？
厳密には 3-way merge と recursive 3-way merge というものがあるようですが、ここでは Git
が内部で使っているものの詳細を教えて頂きたいです。


CC BY-SA 4.0
2019-01-16T09:24:27.420
52019
2021-01-28T03:30:18.357
19110
question
git アルゴリズム
Git の 3-way merge とは具体的にどのようなアルゴリズムですか？


## 「way」とは

ここでの「way」とは、マージする際に "見て" いる場所のこと。3-way merge は 3 つの場所を見ている。

  * 2-way merge：「マージの起点コミット」「マージさせたいコミット」を見てマージする
  * 3-way merge：「マージの起点コミット」「マージさせたいコミット」「2 つのコミットの最近共通祖先となるコミット」を見てマージする

## アルゴリズムの概略

ここでは Git のデフォルト・マージ戦略である「recursive」にしたがった 3-way merge
のアルゴリズムを書きます。簡単のために省略して書いています。

  * **入力** : コミットグラフ。マージの起点としたいコミット `X`。`X` にマージしたいコミット `Y`。

  * **出力** : マージできるかどうか。マージできるなら、マージ済みコミット `Z` を含む新しいコミットグラフ。マージできないなら、コンフリクト箇所。

  * **手順**

    1. **`X` と `Y` の共通祖先となるコミット `B` (base) を見つける**。このとき、`X` と `Y` に最も近い共通祖先を見つける。そのような祖先が一意に定まらない場合があるが、後述するように上手く処理される。
    2. **`X` と `B` の diff、`Y` と `B` の diff をそれぞれ求める**。
    3. **2つの diff 結果をそれぞれの変更箇所ごとに比較しつつマージする** 。両方が同じ変更を行っている場合、そのまま採用する。片方が変更して他方が何もしていない場合、変更している方を採用する。同じ箇所で異なる変更が行われている場合、自動マージできないのでその部分のコンフリクトを報告する。
    4. **結果を出力する** 。自動マージがすべて成功した場合、マージコミットを `Z` を作って付け足したコミットグラフを作る。そうでない場合、コンフリクト箇所を示す (手動マージに任せる)。

上の手順ではひとつ説明していない部分があるので、以下それを説明します。

### 共通祖先 `B` が一意に定まらない場合について

`X` と `Y` の共通祖先が複数あり、どれが最も近いか決められない場合があります。

[code]

    ( )----( )----(P)-----( )----(X)
            |      |       |
            |      |   +---+
            |      |   |
            |      +-------+
            |          |   |
            |      +---+   |
            |      |       |
            +-----(Q)-----(Y)
    
    親コミット <--       --> 子コミット
    
[/code]

たとえば上のように交差したコミットグラフでは、`P` と `Q` は共に `X` と `Y` に近い共通祖先です。`P` と `Q`
のどちらがより近いのかは、グラフからは比較不能です。共通祖先 `B` の候補が 2 つ出てきてしまいました。

この場合 recursive 3-way merge では、共通祖先たちをマージした **仮想的なコミット** を作り、その仮想コミットを `B`
だと思って次の手順に移ります。なお共通祖先が 3 つ以上あった場合は先頭の 2 つから順番に「2
つのコミットをマージする」を繰り返します。祖先のマージをする際にも同じ問題が起こる可能性があるため、このマージは再帰的に (recursive に)
行われます。

こうすることで共通祖先が複数あってもひとつだと思うことができます。この手法は、実験する限りは良い感じだと考えられています。

> This has been reported to result in fewer merge conflicts without causing
> mismerges by tests done on actual merge commits taken from Linux 2.6 kernel
> development history.
>
> <https://git-scm.com/docs/git-merge#git-merge-recursive>

## この回答で説明されていないこと

  * [Fast-forward merge](https://git-scm.com/docs/git-merge#_fast_forward_merge)
  * recursive 以外の[マージ戦略](https://git-scm.com/docs/git-merge#_merge_strategies) (resolve, octopus, ours, subtree)
  * recursive 戦略の[細かいオプション](https://git-scm.com/docs/git-merge#git-merge-recursive)
  * ファイルの削除やリネームも合わせて考えたマージ
  * ファイルのパーミッションだけ変わった場合の処理

## 参考

  * [How does 'git merge' work in details?](https://stackoverflow.com/q/14961255/5989200) \-- Stack Overflow
  * Plastic SCM のブログ記事 
    * [Three-way merging: A look under the hood](http://blog.plasticscm.com/2016/02/three-way-merging-look-under-hood.html)
    * [The basics: elements of a merge](http://blog.plasticscm.com/2011/09/merge-recursive-strategy.html)
    * [More on recursive merge strategy](http://blog.plasticscm.com/2012/01/more-on-recursive-merge-strategy.html)
  * Git ドキュメント 
    * [git-merge-base](https://git-scm.com/docs/git-merge-base)
    * [git-merge](https://git-scm.com/docs/git-merge)
  * Git のソースコード 
    * [merge-recursive.c](https://github.com/git/git/blob/master/merge-recursive.c)
  * [Merge (version control)](https://en.wikipedia.org/wiki/Merge_\(version_control\)) \-- Wikipedia 英語版
  * [7.8 Git のさまざまなツール - 高度なマージ手法](https://git-scm.com/book/ja/v2/Git-%E3%81%AE%E3%81%95%E3%81%BE%E3%81%96%E3%81%BE%E3%81%AA%E3%83%84%E3%83%BC%E3%83%AB-%E9%AB%98%E5%BA%A6%E3%81%AA%E3%83%9E%E3%83%BC%E3%82%B8%E6%89%8B%E6%B3%95) \-- Pro Git


CC BY-SA 4.0
2019-01-16T09:24:27.420
52020
2021-01-28T03:30:18.357
2021-01-28T03:30:18.357
19110
19110
52019
answer