Git

マージコミットをrevertした後の再適用

GitLab flowで運用していて、リリースしたものに問題があったのでproductionブランチ上でマージコミットをrevertしてリリースしたら、その後の修正リリースでどうすればよいか悩むことになりました。対処した方法と、そもそもロールバックリリースの方法が間違っていたということを説明します。

課題

  • 前提:GitLab flowで運用
    • masterブランチからfeature branchを分岐して開発
    • productionブランチのコミットがデプロイ対象
  • 状況
    • feature A, Bをマージしてリリースした
    • 問題があったので、productionブランチでrevertを行いロールバックした
  • やりたいこと
    • feature Aは問題ないことがわかったので、まずそれのみ再リリースしたい
    • feature Bは問題があったので再修正の上で次にリリースしたい

準備

説明のためのサンプルリポジトリをセットアップします。

リポジトリ新規作成

revert_and_revert$ git init
Initialized empty Git repository in /Users/yoichi/prog/git-study/revert_and_revert/.git/
revert_and_revert(master)$ git commit --allow-empty -m "initialize repository with empty commit"
[master (root-commit) a48347d] initialize repository with empty commit
revert_and_revert(master)$ git branch production

各featureを開発し、masterにマージ

revert_and_revert(master)$ git branch feat_a
revert_and_revert(master)$ git branch feat_b
revert_and_revert(master)$ git checkout feat_a && echo feat_a > a.txt && git add a.txt && git commit -m 'implement feat_a'
Switched to branch 'feat_a'
[feat_a 304db62] implement feat_a
 1 file changed, 1 insertion(+)
 create mode 100644 a.txt
revert_and_revert(feat_a)$ git checkout feat_b && echo feat_b_with_defect > b.txt && git add b.txt && git commit -m 'implement feat_b'
Switched to branch 'feat_b'
[feat_b 5465c73] implement feat_b
 1 file changed, 1 insertion(+)
 create mode 100644 b.txt
revert_and_revert(feat_b)$ git checkout master
Switched to branch ‘master'
revert_and_revert(master)$ git merge --no-ff --no-edit feat_a
Merge made by the 'recursive' strategy.
 a.txt | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 a.txt
revert_and_revert(master)$ git merge --no-ff --no-edit feat_b
Merge made by the 'recursive' strategy.
 b.txt | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 b.txt
revert_and_revert(master)$ cat a.txt
feat_a
revert_and_revert(master)$ cat b.txt
feat_b_with_defect

リリース

revert_and_revert(master)$ git checkout production
Switched to branch 'production'
revert_and_revert(production)$ git merge --no-ff --no-edit master
Merge made by the 'recursive' strategy.
 a.txt | 1 +
 b.txt | 1 +
 2 files changed, 2 insertions(+)
 create mode 100644 a.txt
 create mode 100644 b.txt

ロールバックリリース

revert_and_revert(production)$ git revert --no-edit -m1 HEAD
[production 9feb753] Revert "Merge branch 'master' into production"
 Date: Sun Jun 17 06:12:20 2018 +0900
 2 files changed, 2 deletions(-)
 delete mode 100644 a.txt
 delete mode 100644 b.txt

うまくいかなかったやり方

masterブランチでA以外の機能をロールバック

revert_and_revert(production)$ git checkout master
Switched to branch 'master'
revert_and_revert(master)$ git log --first-parent --oneline
57f9496 (HEAD -> master) Merge branch 'feat_b'
5871e26 Merge branch 'feat_a'
a48347d initialize repository with empty commit
revert_and_revert(master)$ git revert --no-edit -m1 57f9496
[master 38d6b46] Revert "Merge branch 'feat_b'"
 Date: Sun Jun 17 06:13:22 2018 +0900
 1 file changed, 1 deletion(-)
 delete mode 100644 b.txt

productionブランチへマージ→おや?何も入らないぞ。

revert_and_revert(master)$ git checkout production
Switched to branch ‘production'
revert_and_revert(production)$ git merge --no-ff master
Merge made by the 'recursive' strategy.

うまくいったやり方

productionブランチからpre_productionブランチを分岐し、マージコミットのrevertのrevertをした上でmasterをマージ

revert_and_revert(production)$ git checkout -b pre_production
Switched to a new branch 'pre_production'
revert_and_revert(pre_production)$ git revert --no-edit HEAD
[pre_production 64d00fe] Revert "Revert "Merge branch 'master' into production""
 Date: Sun Jun 17 06:16:17 2018 +0900
 2 files changed, 2 insertions(+)
 create mode 100644 a.txt
 create mode 100644 b.txt
revert_and_revert(pre_production)$ git merge --no-edit --no-ff master
Removing b.txt
Merge made by the 'recursive' strategy.
 b.txt | 1 -
 1 file changed, 1 deletion(-)
 delete mode 100644 b.txt

productionブランチにそれをマージ→Aのみが入っているのでこれで再リリースできる。

revert_and_revert(pre_production)$ git checkout production
Switched to branch 'production'
revert_and_revert(production)$ git merge --no-edit --no-ff pre_production
Merge made by the 'recursive' strategy.
 a.txt | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 a.txt
revert_and_revert(production)$ ls
a.txt
revert_and_revert(production)$ cat a.txt
feat_a

次のリリースに向けて、masterから分岐して、Bのrevertをrevertの上、修正をコミット。

revert_and_revert(production)$ git checkout master
Switched to branch 'master'
revert_and_revert(master)$ git checkout -b fix_b
Switched to a new branch 'fix_b'
revert_and_revert(fix_b)$ git log --first-parent --oneline
38d6b46 (HEAD -> fix_b, master) Revert "Merge branch 'feat_b'"
57f9496 Merge branch 'feat_b'
5871e26 Merge branch 'feat_a'
a48347d initialize repository with empty commit
revert_and_revert(fix_b)$ git revert --no-edit 38d6b46
[fix_b 0efb2fb] Revert "Revert "Merge branch 'feat_b'""
 Date: Sun Jun 17 06:19:52 2018 +0900
 1 file changed, 1 insertion(+)
 create mode 100644 b.txt
revert_and_revert(fix_b)$ cat b.txt
feat_b_with_defect
revert_and_revert(fix_b)$ echo feat_b > b.txt
revert_and_revert(fix_b)$ git commit -a -m "fix feat_b"
[fix_b 56146b3] fix feat_b
 1 file changed, 1 insertion(+), 1 deletion(-)

masterブランチにマージ

revert_and_revert(fix_b)$ git checkout master
Switched to branch 'master'
revert_and_revert(master)$ git merge --no-edit --no-ff fix_b
Merge made by the 'recursive' strategy.
 b.txt | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 b.txt
revert_and_revert(master)$ cat b.txt
feat_b

productionブランチにマージすれば、修正版のBを含む次のリリースができる。

revert_and_revert(master)$ git checkout production
Switched to branch 'production'
revert_and_revert(production)$ git merge --no-edit --no-ff master
Merge made by the 'recursive' strategy.
 b.txt | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 b.txt
revert_and_revert(production)$ cat b.txt
feat_b

そもそも

GitLab flow ではマージを masterブランチ→productionブランチ の方向のみに限定するのが原則なので、それに反して production ブランチのみにあるコミット(マージコミット)をrevertした時点で負けで、単純なマージでproductionブランチに変更が入らない状態になってしまいました。

正解は、変更は常にmasterブランチの側で行うこと、つまりロールバックリリースの際もproductionブランチではなくmasterから分岐したブランチでロールバックし、それをproductionブランチへマージしてリリース、としていればよかったのだと思います。