🐷

コピペで学ぶチュートリアル: GitHub Pull Request の merge conflict 解決手法を試す

2022/09/25に公開

導入

GitHub の pull request で merge conflict が発生した際、その解決には 1.GitHub の Web UI 上で行うものと、2.ローカルのコンピュータで行うものがあります。

  1. GitHub 公式: GitHub でのマージ コンフリクトを解決する
  2. GitHub 公式: コマンド ラインを使用してマージ コンフリクトを解決する

解決手法の違いについて手を動かして動作確認すると、知識の整理もできると思いますので、本記事ではほぼコピペで貼り付けるだけで簡単に手順を再現できるようにしています。

merge conflict がない場合の update 手法との区別

本記事で対象とするのは pull request の merge conflict を解決する手法なので、merge conflict が発生していないときに pull request を update する手法とは区別してください。後者については以下を参考にしてください。

pull request のマージ手法との区別

本記事で対象とするのは pull request『内』で merge conflict を解決する手法なので、pull request 自体を base ブランチへと merge する手法とは区別してください。後者については以下を参考にしてください。

準備: GitHub レポジトリの作成

まずはローカル環境で git レポジトリを作成します。

コピペして実行
mkdir pull-req-conflict-experiments
cd pull-req-conflict-experiments
git init

GitHub 上にレポジトリを作成しましょう。Web ブラウザではなくコマンドを使えば一発で終わります。

コピペして実行
gh repo create pull-req-conflict-experiments --public --source=. --remote=origin
上記の gh コマンドは何?

gh コマンド(GitHub CLI)を使えば、GitHub 上でのリポジトリ作成など作業をローカルからコマンドひとつで行えます。まだインストールしていない方は、ぜひインストールを検討してください。gh repo create サブコマンドの説明にもありますが、上記のコマンドのオプションと引数の意味はこちらです。

  • pull-req-udpate-experiments: GitHub 上のレポジトリ名
  • --public: public レポジトリとして作成
  • --source=.: ローカルレポジトリのパスはカレントディレクトリ .
  • --remote=origin リモートレポジトリを origin に指定

Merge conflict が発生しない場合

本記事は merge conflict 解決手法の動作確認が目的ですが、対比のためにまずは merge conflict が発生しない場合を見ていきましょう。

コピペして実行
cat << EOF > experiment1.txt
a

b

c
EOF
git add --all
git commit -m "create experiment1.txt"
git push origin main

ファイルの内容はこちらです。

experiment1.txt
a

b

c

次に、pull request を作成します。

コピペして実行
git switch -c pr-1

sed -i 's/a/aaaaa/' experiment1.txt # ファイル中のaをaaaaaに置き換え
git add --all
git commit -m "update a in pr-1"

git push --set-upstream origin pr-1
gh pr create --title pr-1 --body "" --base main --head pr-1

base ブランチに直接変更を加え、push しましょう。

コピペして実行
git switch main

# merge conflict が発生しないよう、`pr-1`とは別のファイル行を変更しています。
sed -i 's/b/bbbbb/' experiment1.txt # ファイル中のbをbbbbbに置き換え
git add --all
git commit -m "update b in main"

git push origin main
base ブランチの push が pull request の file diff に反映されない?

base ブランチの変更を push しただけでは、pull request の file diff は更新されません。下画像のように、3 行目がb1 文字のままであることがわかります。

pr-file-diff-1a

これは、GitHub では pull request の file diff は base ブランチの変更を取り込まないようになっているからです。base ブランチの変更を取り込むには、別記事「コピペで学ぶチュートリアル: GitHub Pull Request の update 手法 2 種類を試す」の解説している update(rebase) branch の操作が必要です。

Pull request を見るとThis branch has no conflicts with the base branchと表示されているので、Merge pull request ボタンを押しましょう。

merge pull request button

ターミナルから pull request をマージしたい場合
コピペして実行
gh pr merge pr-1 --merge --delete-branch

Merge conflict を GitHub UI 上で解決

Merge conflict の中でも、同一ファイルの同一行での conflict は GitHub UI 上で解決できます。

GitHub UI 上で解決できる merge conflict と、できない conflict

GitHub 公式: GitHub でのマージ コンフリクトを解決する

GitHub で解決できるマージコンフリクトは、Git リポジトリの別々のブランチで、同じファイルの同じ行に異なる変更がなされた場合など、互いに矛盾する行変更を原因とするもののみです。 その他すべての種類のマージ コンフリクトについては、コマンド ラインでコンフリクトをローカルに解決する必要があります。

変更対象のテキストファイルを作成します。

コピペして実行
cat << EOF > experiment2.txt
a

b

c
EOF
git add --all
git commit -m "create experiment2.txt"
git push origin main

ファイルの内容はこちらです。

experiment2.txt
a

b

c

Pull request を作成します。

コピペして実行
git switch -c pr-2

sed -i 's/a/aaaaa/' experiment2.txt # ファイル中のaをaaaaaに置き換え
git add --all
git commit -m "update a in pr-2"

git push --set-upstream origin pr-2
gh pr create --title pr-2 --body "" --base main --head pr-2

base ブランチに直接変更を加え、push しましょう。

コピペして実行
git switch main

# わざとmerge conflict が発生するよう、`pr-2`と同一のファイル行を変更しています
sed -i 's/a/aaa/' experiment2.txt # ファイル中のaをaaaに置き換え
git add --all
git commit -m "update b in main"

git push origin main

Pull request を見ると merge conflict が発生し、Merge pull requst ボタンは押せなくなっています。代わりに Resolve conflicts ボタンを押しましょう。

merge conflict

このような画面が表示されるので、merge conflict を解決していきます。

resolve conflict 1

<<<<<<<=======>>>>>>> を消せば、右上の Mark as resolved ボタンが押せるようになります。

resolve conflict 2

続いて、Commit merge ボタンを押しましょう。

resolve conflict 3

Resolve conflicts が完了すると、Merge branch 'main' into pr-2 と pull request の Commits に表示されています。

pr git log 1

Pull request を見るとThis branch has no conflicts with the base branchと表示されているので、Merge pull request ボタンを押しましょう。

merge pull request button

ターミナルから pull request をマージしたい場合
コピペして実行
gh pr merge pr-1 --merge --delete-branch

Merge pull request 後に main ブランチの git log をみるとこうなっています。

main git log

上画像では履歴が一直線に見えて分岐がわからないので、git log コマンドで分岐の様子を確認しましょう。

コピペして実行
git switch main
git pull origin main
git log --oneline --decorate --graph
git log
* ba28bc0 (HEAD -> main, origin/main, origin/HEAD) Merge pull request #1 from richardimaoka/pr-2
|\
| *   e734fc2 Merge branch 'main' into pr-2
| |\
| |/
|/|
* | 9b3f23c update b in main
| * 70888e8 update a in pr-2
|/
* e22e7cc create experiment2.txt

Merge conflict をローカルで解決

例えば、pull request の base ブランチでファイルを更新し、同じファイルを head ブランチで削除した場合の merge conflict は GitHub UI からは解決ができず、ローカルで解決する必要があります。

GitHub UI 上で解決できる merge conflict と、できない conflict

GitHub 公式: GitHub でのマージ コンフリクトを解決する

GitHub で解決できるマージコンフリクトは、Git リポジトリの別々のブランチで、同じファイルの同じ行に異なる変更がなされた場合など、互いに矛盾する行変更を原因とするもののみです。 その他すべての種類のマージ コンフリクトについては、コマンド ラインでコンフリクトをローカルに解決する必要があります。

変更対象のテキストファイルを作成します。

コピペして実行
cat << EOF > experiment3.txt
a

b

c
EOF
git add --all
git commit -m "create experiment3.txt"
git push origin main

次に、pull request を作成します。

コピペして実行
git switch -c pr-3

rm experiment3.txt
git add --all
git commit -m "delete experiment3.txt in pr-3"

git push --set-upstream origin pr-3
gh pr create --title pr-3 --body "" --base main --head pr-3

main ブランチに直接 commit

コピペして実行
git switch main

sed -i 's/a/aaa/' experiment3.txt # ファイル中のaをaaaに置き換え
git add --all
git commit -m "update a to aaa in main"

git push origin main

Pull request を見ると Merge pull request ボタンも、Resolve conflicts ボタンも押せなくなっています。

merge conflict unresolvable

上画像に表示されているUse the comand lineのところ(字が小さくて見えづらいですが)をクリックすると、以下の説明が出てきます。

how to resolve

Step 1 から 3 までを実行しましょう。Pull request で発生された merge conflict がローカルでも再現されます。

コピペして実行
git pull origin main # そもそものpush元なので、今回に関しては実行しても何も起きない
git checkout pr-3    # もしくはgit switch pr-3
git merge main       # ここでmerge conflict発生

上記 3 つめのgit mergeコマンドの結果以下が表示されます。

実行結果
CONFLICT (modify/delete): experiment3.txt deleted in HEAD and modified in main.
Version main of experiment3.txt left in tree.
Automatic merge failed; fix conflicts and then commit the result.

git statusで merge conflict の状態を確認しましょう。

コピペして実行
git status
実行結果
Unmerged paths:
        deleted by us:   experiment3.txt

experiment3.txtはこのようになっています。

コピペして実行
cat experiment3.txt
experiment3.txt
aaa

b

c

このままmainブランチでのexperment3.txtの内容を取捨選択する形で conflict を解決しましょう。

コピペして実行
git add --all
git commit -m "resolve conflict by taking main"
git push origin pr-3
experment3.txt を delete して conflict を解決する場合

直前のコマンドの代わりに、こちらを実行してください。

コピペして実行
rm experment3.txt
git add --all
git commit -m "resolve conflict by taking main"
git push origin pr-3

Pull request の Commits はこのようになります。

pr git log 2

コミットresolve conflict by taking mainは、直前のコミットで削除したファイルを復元しています。

pr diff

Pull request を見るとThis branch has no conflicts with the base branchと表示されているので、Merge pull request ボタンを押しましょう。

merge pull request button

ターミナルから pull request をマージしたい場合
コピペして実行
gh pr merge pr-3 --merge --delete-branch

最後に、git log で分岐の様子を確認しましょう。

コピペして実行
git switch main
git pull origin main
git log --oneline --decorate --graph
git log
* bb0ee29 (HEAD -> main, origin/main, origin/HEAD) Merge pull request #2 from richardimaoka/pr-3
|\
| * 6c39372 (origin/pr-3) resolve conflict by taking main
| |\
| |/
|/|
* | b2d25a9 update a to aaa in main
| * f878dae delete experiment3.txt in pr-3
|/
* e383322 create experiment3.txt
* ba28bc0 Merge pull request #1 from richard
GitHubで編集を提案

Discussion