GitHubのプルリクエストで、実際にマージされるのと異なる内容が表示される謎現象を解き明かす

## はじめに

複雑なブランチ構成をしたプロジェクトのgithubでプルリクエストを作ったとき、なぜかブランチで行っていない筈の修正が変更ファイルのタブに表示されてしまうことはないだろうか？

単純なファイルdiffを取るとこの修正は差分表示されなかったり、実際にマージを実行するときこの修正は悪影響を及ぼさなかったりするので、この謎の差分は何者なのか気になっていたが、こいつについて調査してみた。


## 現象・原因

### 現象

複雑なブランチ構成のgithub管理プロジェクトで、プルリクエストの Files changed タブ にマージ元ブランチ以外の修正内容（既にマージ済みの他ブランチ修正内容）が表示される場合がある。

（変更ファイルとして表示はされるが）実際にマージするときは、既にマージ済みの他ブランチ修正内容を再度マージしようとして衝突（コンフリクト）が発生することは無い。

### 原因

githubのプルリクエストでは、gitのトリプルドットdiffによって変更ファイルを表示している。

この現象の原因はトリプルドットdiffが「共通祖先」と「マージ元ブランチ」の差分を表示することにある。
トリプルドット差分は通常であればマージ元ブランチで行われた修正のみを差分表示する。
しかし、複雑な状況下では期待しない差分表示されてしまうことがある。

実際にマージするときは、「共通祖先」と「マージ元ブランチ」と「マージ先ブランチ」を元に3-way mergeアルゴリズムでマージされるので、上記差分とは異なる内容がマージされる。
（ちなみにこの状況で`git diff main...featureX` のコマンドを打つと、`multiple merge bases, using 《コミットID》` 警告が出る。）


## 具体例

### 現象の発生手順

git-flowでリリースブランチに緊急でfeatureをマージした場合などに、この現象は発生する。
たとえば以下のような手順で操作をおこなったとき、現象が発生する。

1. developからrelease1.0.0ブランチ、feature1ブランチ、feature2ブランチを作成。
2. feature1ブランチ、feature2ブランチにそれぞれ修正をかけて、修正内容をdevelopとrelease1.0.0へマージ。
3. release1.0.0に修正を加えてdevelopにマージするプルリクエストを作成する。

このとき、プルリクエストのFiles changed タブは、以下のようになる。

- **期待するFiles changed タブ**
  release1.0.0で行った修正のみが表示される
  （「feature1、feature2がマージされたdevelop」と「release1.0.0ブランチ」の差分）

- **実際のFiles changed タブ**
  release1.0.0で行った修正＋feature1で行った修正が表示される
  （「feature2」と「release1.0.0ブランチ」の差分）

### 現象の発生するコミットグラフ

コミットグラフは以下のようになる。
見るとわかるように **共通祖先コミットは feature1修正を含まない** 。

そのため `git diff develop...release1.0.0` を表示すると、共通祖先とrelease1.0.0の差分である「release1.0.0で行った修正＋feature1で行った修正」が表示される。

```mermaid
%%{init: { 'gitGraph': {'mainBranchName': 'develop', 'showCommitLabel': false}} }%%
gitGraph
    commit
    commit
    branch release1.0.0
    branch feature1
    branch feature2
    
    checkout feature1
    commit tag: "この修正内容が表示されてしまう"
    checkout develop
    merge feature1
    checkout release1.0.0
    merge feature1
    
    checkout feature2
    commit tag: "共通祖先"
    checkout develop
    merge feature2
    checkout release1.0.0
    merge feature2
    
    checkout release1.0.0
    commit
    commit type: HIGHLIGHT tag: "developへのPR"
```

### 別パターンの現象発生コミットグラフ

以下のようなパターンでも、Files changed タブ にマージ元ブランチ以外の修正内容が表示されてしまう。（マージ先ブランチ=developで行った修正なのに表示されてしまう）

原因はやはりgitのトリプルドットdiffによって変更ファイルを表示していることである。

```mermaid
%%{init: { 'gitGraph': {'mainBranchName': 'develop', 'showCommitLabel': false}} }%%
gitGraph
    commit
    commit
    branch feature1 order: 2
    
    checkout develop
    commit tag: "この修正内容が表示されてしまう"
    commit
    branch release1.0.0 order: 1
    
    checkout feature1
    commit tag: "共通祖先"
    checkout develop
    merge feature1
    
    checkout release1.0.0
    merge feature1
    
    checkout release1.0.0
    commit
    commit type: HIGHLIGHT tag: "developへのPR"
```

## 対策

### 根本的な対策はおそらく無い

この現象はgitの仕様から来ているものだと思うので、根本的な対策はおそらく無い。
気にせず無視するのがベターな対策だと思われる。

release1.0.0ブランチにdevelopをマージしても問題ない状況（developにリリースしたくない余計なコミットが入っていない）なら、developをrelease1.0.0にマージする事で、変更ファイルをrelease1.0.0で行った修正のみ表示させることができる。

ただし **developからrelease1.0.0へ意図しない修正をマージしてしまう危険性があるのでお勧めしない** 。

### お勧めしない対策をした後のコミットグラフ

```mermaid
%%{init: { 'gitGraph': {'mainBranchName': 'develop', 'showCommitLabel': false}} }%%
gitGraph
    commit
    commit
    branch release1.0.0
    branch feature1
    branch feature2

    checkout feature1
    commit
    checkout develop
    merge feature1
    checkout release1.0.0
    merge feature1

    checkout feature2
    commit
    checkout develop
    merge feature2 tag: "共通祖先"
    checkout release1.0.0
    merge feature2

    checkout release1.0.0
    commit

    checkout develop
    commit tag: "何か修正があると、取り込まれてしまう"
    checkout release1.0.0
    merge develop type: HIGHLIGHT tag: "developへのPR"
```

## 参考url

PR Diff shows different content from the actual file content in Bitbucket Datacenter
https://confluence.atlassian.com/bitbucketserverkb/pr-diff-shows-different-content-from-the-actual-file-content-in-bitbucket-datacenter-1384450058.html