🐷

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

2022/08/21に公開

導入

GitHub の Pull Request をマージする方法には 3 種類、Merge pull request(Create a merge commit)、Squash and merge、Rebase and merge があります。

Create a merge commit

それぞれの違いについては公式ドキュメントでも、Qiita などでも解説されています。

すでに様々な場所で解説されている内容ですが、あらためて手を動かして動作確認すると、新しい発見があり、知識の整理もできると思います。そこで本記事では、これら 3 種類の違いを説明しつつ、コピペで貼り付けるだけで簡単に手順を再現できるようにしています。

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

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

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

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

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

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

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

Merge pull request(Create a merge commit)

まずは 3 種類のマージ手法のうち、Merge commit から説明します。

Create a merge commit

最初のコミットで編集対象のファイル pull-req-merge-commit.txtを作成します。

コピペして実行 (全部一度にコピペできます)
cat << EOF > pull-req-merge-commit.txt
a

b

c
EOF
git add --all
git commit -m "create pull-req-merge-commit.txt"
git push origin main

1 つめの Pull Request を作成しましょう。

コピペして実行
git switch -c pr-merge-commit-1
sed -i 's/a/aaaaa/' pull-req-merge-commit.txt # ファイル中のaをaaaaaに置き換え
git add --all
git commit -m "update a in pr-merge-commit-1"

# GitHubにPull Requestを作成
git push --set-upstream origin pr-merge-commit-1
gh pr create --title pr-merge-commit-1 --body "" --base main --head pr-merge-commit-1
1 つめの Pull Request の File diff

branch-merge-commit-1

次に 1 つめの Pull Request とコンフリクトを起こさないように 2 つめの Pull Request を作成します。

コピペして実行
git switch -c pr-merge-commit-2 main
sed -i 's/b/bbbbb/' pull-req-merge-commit.txt # ファイル中のbをbbbbbに置き換え
git add --all
git commit -m "update b in pr-merge-commit-2"
sed -i 's/c/ccccc/' pull-req-merge-commit.txt # ファイル中のcをcccccに置き換え
git add --all
git commit -m "update c in pr-merge-commit-2"

# GitHubにPull Requestを作成
git push --set-upstream origin pr-merge-commit-2
gh pr create --title pr-merge-commit-2 --body "" --base main --head pr-merge-commit-2
2 つめの Pull Request の File diff

branch-merge-commit-2

両方の Pull Request の状態を図解

git-pr-merge-4

これで merge commit の動作を確認するための Pull Request が 2 つ作成されたので、1 つめ Pull Request をマージします。

コピペして実行
gh pr merge pr-merge-commit-1 --merge --delete-branch
1 つめの Pull Request マージ結果

これによって Merge commit が作成されます。

github-log-merge-commit-1

Merge commit の File diff は、1 つ目のブランチの直前のコミットの File diff と同じ内容です。

git-pr-merge-5.png

続いて 2 つめの Pull Request をマージします。

コピペして実行
gh pr merge pr-merge-commit-2 --merge --delete-branch

# GitHub 側で更新された main ブランチを pull
git switch main
git pull origin main
2 つめの Pull Request マージ結果

これによって 2 つめの Merge commit が作成されます。

github-log-merge-commit-2

しかし GitHub 上でここまでの git log を確認すると、本来は分岐が発生しているにも関わらず、一直線にコミットが並んでいるように見え、かつ Merge commit2 つが連続しています。

この場合、下記のコマンドで確認すると分岐の様子がわかりやすくなります。

コピペして実行
git log --oneline --decorate --graph
実行結果
*   012ef98 Merge pull request #2 from richardimaoka/pr-merge-commit-2
|\
| * 30d1b5b update 3 in pr-merge-commit-2
| * 474cc81 update 2 in pr-merge-commit-2
* |   4f7c52c Merge pull request #1 from richardimaoka/pr-merge-commit-1
|\ \
| |/
|/|
| * 93924f6 update 1 in pr-merge-commit-1
|/
* 5ae629f create pull-req-merge-commit.txt

2 つめの Merge commit の File diff は、2 つ目のブランチの全てのコミットの File diff を合わせた内容です。

git-pr-merge-6.png

ローカル で no-ff オプションを使ったマージを試す

GitHub Docs の「プルリクエストをマージする」を見ると、次の記述があります。これを確認してみましょう。

(create a merge commit では) プルリクエストは --no-ff オプションを使用してマージされますが…

ローカル環境で no-ff マージを試す手順

この no-ff オプションの動作を確認して、本当に GitHub の merge commit と同じ動作なのかを試します。

まずは最初のコミットで編集対象のファイルを作成します。先程の git レポジトリをそのまま利用できます。

コピペして実行
touch local-no-ff.txt
git add --all
git commit -m "create local-no-ff.txt"

作成したファイルを更新するコミットを行います。

コピペして実行
git switch -c local-no-ff-1
echo a > local-no-ff.txt
git add --all
git commit -m "add a in branch local-no-ff-1"
echo b >> local-no-ff.txt
git add --all
git commit -m "add b in branch local-no-ff-1"

図解にするとこのような感じです。

git-pr-merge-1.png
git コミットの結果

no-ffオプションを付けてgit mergeコマンドを実行しましょう

コピペして実行
git switch main
git merge --no-ff --no-edit local-no-ff-1
git branch -d local-no-ff-1
git push origin main

no-ffをつけてマージした結果はこちらのようになります。GitHub 上でのマージと同じように、Merge commit が作成されます。

git-pr-merge-3.png
git マージの結果

git log でも確認してみましょう。

コピペして実行
git log --oneline --decorate --graph
git log 実行結果
*   0d471f0 Merge branch 'local-no-ff-1'
|\
| * 4d651ad add 2 in branch local-no-ff-1
| * 47b1b49 add 1 in branch local-no-ff-1
|/
* d6035c2 create local-no-ff.txt
ローカル環境でデフォルトのマージを試す手順

no-ffとの比較とのため、fast-forward マージ、つまりオプションを付けない git でのデフォルトのマージを試してみます。

まずは最初のコミットで編集対象のファイルを作成します。先程の git レポジトリをそのまま利用できます。

コピペして実行
touch local-ff.txt
git add --all
git commit -m "create local-ff.txt"

作成したファイルを更新するコミットを行います。

コピペして実行
git switch -c local-ff-1
echo a > local-ff.txt
git add --all
git commit -m "add a in branch local-ff-1"
echo b >> local-ff.txt
git add --all
git commit -m "add b in branch local-ff-1"

図解にするとこのような感じです。

git-pr-merge-1
git コミットの結果

コピペして実行
git switch main
git merge local-ff-1
git branch -d local-ff-1
git push origin main

デフォルトのマージを行った結果はこちらのようになります。Merge commit は作成されず、main ブランチの先にもうひとつのブランチのコミットを連続してコミットしたような形になります。

git-pr-merge-2
git マージの結果

git log でも確認してみましょう。

コピペして実行
git log --oneline --decorate --graph
git log 実行結果
* 6b1858f add 2 in branch local-ff-1
* 2a85d20 add 1 in branch local-ff-1
* 07beb58 create local-ff.txt

Squash merge

2 つめのマージ手法 Squash merge を説明します。

Squash merge

編集対象のファイルを作成します。

コピペして実行 (全部一度にコピペできます)
cat << EOF > pull-req-squash-merge.txt
a

b

c
EOF
git add --all
git commit -m "create pull-req-squash-merge.txt"
git push origin main

1 つめの Pull Request を作成しましょう。

コピペして実行
git switch -c pr-squash-merge-1
sed -i 's/a/aaaaa/' pull-req-squash-merge.txt # ファイル中のaをaaaaaに置き換え
git add --all
git commit -m "update a in pr-squash-merge-1"

# GitHubにPull Requestを作成
git push --set-upstream origin pr-squash-merge-1
gh pr create --title pr-squash-merge-1 --body "" --base main --head pr-squash-merge-1
1 つめの Pull Request の File diff

branch-merge-commit-1

次に 1 つめの Pull Request とコンフリクトを起こさないように 2 つめの Pull Request を作成します。

コピペして実行
git switch -c pr-squash-merge-2 main
sed -i 's/b/bbbbb/' pull-req-squash-merge.txt # ファイル中のbをbbbbbに置き換え
git add --all
git commit -m "update b in pr-squash-merge-2"
sed -i 's/c/ccccc/' pull-req-squash-merge.txt # ファイル中のcをcccccに置き換え
git add --all
git commit -m "update c in pr-squash-merge-2"

# GitHubにPull Requestを作成
git push --set-upstream origin pr-squash-merge-2
gh pr create --title pr-squash-merge-2 --body "" --base main --head pr-squash-merge-2
2 つめの Pull Request の File diff

branch-merge-commit-2

両方の Pull Request の状態を図解

git-pr-merge-4

これで merge commit の動作を確認するための Pull Request が 2 つ作成されましたので、1 つめ Pull Request をマージします。

コピペして実行
gh pr merge pr-squash-merge-1 --squash --delete-branch
1 つめの Pull Request マージ結果

Merge commit は作成されません。また 1 つめのブランチには 1 つしか新たなコミットがなかったので、squash commit も行なわれません。

main ブランチの先に 1 つめのブランチのコミットを連続してコミットしたような形になります。

github-log-squash-merge-1

File diff とブランチはこのようになります。

git-pr-merge-7.png

続いて 2 つめの Pull Request をマージします。

コピペして実行
gh pr merge pr-squash-merge-2 --squash --delete-branch

# GitHub 側で更新された main ブランチを pull
git switch main
git pull origin main
2 つめの Pull Request マージ結果

2 つめのブランチには複数のコミットが行われたので、squash commit が作成されます。

GitHub 上で git log を確認すると、squash コミット のみが追加されたことがわかります。

github-log-squash-merge-2

ローカルでも git log を確認してみましょう。

コピペして実行
git log --oneline --decorate --graph
実行結果
* f54088c pr-squash-merge-2 (#4)
* 6a003d3 update 1 in pr-squash-merge-1 (#3)
* f205052 create pull-req-squash-merge.txt

File diff とブランチはこのようになります。

git-pr-merge-8.png

Rebase merge

3 つめのマージ手法 Rebase merge を説明します。

Rebase merge

編集対象のファイルを作成します。

コピペして実行 (全部一度にコピペできます)
cat << EOF > pull-req-rebase-merge.txt
a

b

c
EOF
git add --all
git commit -m "create pull-req-rebase-merge.txt"
git push origin main

1 つめの Pull Request を作成しましょう。

コピペして実行
git switch -c pr-rebase-merge-1
sed -i 's/a/aaaaa/' pull-req-rebase-merge.txt # ファイル中のaをaaaaaに置き換え
git add --all
git commit -m "update a in pr-rebase-merge-1"

# GitHubにPull Requestを作成
git push --set-upstream origin pr-rebase-merge-1
gh pr create --title pr-rebase-merge-1 --body "" --base main --head pr-rebase-merge-1
1 つめの Pull Request の File diff

branch-merge-commit-1

次に 1 つめの Pull Request とコンフリクトを起こさないように 2 つめの Pull Request を作成します。

コピペして実行
git switch -c pr-rebase-merge-2 main
sed -i 's/b/bbbbb/' pull-req-rebase-merge.txt # ファイル中のbをbbbbbに置き換え
git add --all
git commit -m "update b in pr-rebase-merge-2"
sed -i 's/c/ccccc/' pull-req-rebase-merge.txt # ファイル中のcをcccccに置き換え
git add --all
git commit -m "update c in pr-rebase-merge-2"

# GitHubにPull Requestを作成
git push --set-upstream origin pr-rebase-merge-2
gh pr create --title pr-rebase-merge-2 --body "" --base main --head pr-rebase-merge-2
2 つめの Pull Request の File diff

branch-merge-commit-2

両方の Pull Request の状態を図解

git-pr-merge-4

これで rebase merge の動作を確認するための Pull Request が 2 つ作成されましたので、1 つめ Pull Request をマージします。

コピペして実行
gh pr merge pr-rebase-merge-1 --rebase --delete-branch
1 つめの Pull Request マージ結果

main ブランチの先に 1 つめのブランチのコミットを連続してコミットしたような形になります。

github-log-rebase-merge-1

File diff とブランチはこのようになります。

git-pr-merge-10.png

続いて 2 つめの Pull Request をマージします。

コピペして実行
gh pr merge pr-rebase-merge-2 --rebase --delete-branch

# GitHub 側で更新された main ブランチを pull
git switch main
git pull origin main
2 つめの Pull Request マージ結果

main ブランチの先に 2 つめのブランチのコミットを連続してコミットしたような形になります。

github-log-squash-merge-2

ローカルでも git log を確認してみましょう。

コピペして実行
git log --oneline --decorate --graph
実行結果
* c17e7a9 update 3 in pr-rebase-merge-2
* 5cd3768 update 2 in pr-rebase-merge-2
* 49dce5e update 1 in pr-rebase-merge-1
* e9bc068 create pull-req-rebase-merge.txt

File diff とブランチはこのようになります。

git-pr-merge-11.png

GitHubで編集を提案

Discussion