CI/CDをkatacodaで体験(初心者向け) - Part5(Fixing Merge Conflicts)


CI/CD入門

このぺーじでは、katacodaと呼ばれる「ブラウザから無料で勉強用のインスタンスを起動できるWebサービス」を利用してCI/CDを実践します
内容は上記リンクに沿うので、不明点があればそちらへどうぞ

Gitのバージョン管理について - Scenario5 - Fixing Merge Conflicts

ここでは、CI/CDとして欠かせないGitによるバージョン管理について学習します
このシナリオで学習することをさっと確認する場合は概要を確認
理解に間違い等がございましたら、ぜひご指摘ください

概要

  • branchの分岐 × 分岐元と分岐先の同じファイル、同じ行に対して異なる変更 -> conflict発生
  • git checkout --ours/theirs <file>で分岐先、分岐元どちらを優先するか決定。その後優先した変更をcommitし劣後の変更を加える
  • branch統合の方法はgit mergegit rebaseがあるが、前者を用いた方がいい。詳しくはこちらもしくはこちら

Step 1 - Git Merge

step1では、リモートレジストリ上にあるファイルを数人で編集する際に発生する"conflict"というエラーの対処法について学習する
ローカルレジストリの状況は下記の通り

$ ls -a
.  ..  .git  staging.txt
$ cat .git/config
[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
[remote "origin"]
        url = /s/remote-project/1
        fetch = +refs/heads/*:refs/remotes/origin/*

リモートレジストリ"origin"が登録されており、同様に別の作業者もこのリモートレジストリを登録している
ローカルレジストリ上のmaster branchとremotes/origin/master branch(リモートレジストリからダウンロードした内容が保存されるbranch)のstaging.txtが一致していないため、conflictが発生

$ cat staging.txt   //master branch
Fixing Error, Let's Hope No-One Else Does
$ git branch
* ESC[32mmasterESC[m
$ git branch -a
* ESC[32mmasterESC[m
  ESC[31mremotes/origin/masterESC[m
$ git checkout remotes/origin/master
Note: checking out 'remotes/origin/master'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b <new-branch-name>

HEAD is now at 4b304c4 Fix for Bug #1234
$ git branch
* ESC[32m(HEAD detached at origin/master)ESC[m
  masterESC[m
$ cat staging.txt   //remotes/origin/master branch
Fixing Previous Error
$ git checkout master
Previous HEAD position was 4b304c4 Fix for Bug #1234
Switched to branch 'master'
$ git branch -a
* ESC[32mmasterESC[m
  ESC[31mremotes/origin/masterESC[m
$ git merge remotes/origin/master
fatal: refusing to merge unrelated histories   //関連性のないヒストリを持つbranch同士のマージはGit2.9からできなくなった※1
$ git merge --allow-unrelated-histories origin/master   //上記エラーの対応
Auto-merging staging.txt
CONFLICT (add/add): Merge conflict in staging.txt
Automatic merge failed; fix conflicts and then commit the result.
$ cat staging.txt
<<<<<<< HEAD
Fixing Error, Let's Hope No-One Else Does
=======
Fixing Previous Error
>>>>>>> origin/master

※1
<<<HEADから====の部分と====から>>>>origin/masterで競合発生

Step 2 - Viewing Conflict

マージ対象のstaging.txtの差分を確認する際の一例としてgit diff (branch_a)(branch_b)を用いる

$ git diff master remotes/origin/master staging.txt
ESC[1mdiff --git a/staging.txt b/staging.txtESC[m
ESC[1mindex 02a3604..9139e85 100644ESC[m
ESC[1m--- a/staging.txtESC[m
ESC[1m+++ b/staging.txtESC[m
ESC[36m@@ -1 +1 @@ESC[m
ESC[31m-Fixing Error, Let's Hope No-One Else DoesESC[m
ESC[32m+ESC[mESC[32mFixing Previous ErrorESC[m

異なるファイルの差分を取る際は、git diff br1:foo/bar.txt br2:hoge/fuga.txtのように記述する

視覚的にコンフリクトを解消する方法としてkdiff3という外部ツールもある

Step 3 - Resolving Conflict

コンフリクトが発生した場合の基本的対策は下記のいずれかです。

  • 自分の変更結果を優先しコミット。その後、相手(コンフリクト先)の変更を組み込む
  • 相手の変更結果を優先しコミット。その後、自分(コンフリクト先)の変更を組み込む

自分もしくは相手を優先する際の方法はgit checkout <option> <file/directry>のオプションに--oursもしくはtheirsを与える

$ git checkout --theirs staging.txt   //レポジトリ先の変更を優先
$ git status
On branch master
Unmerged paths:
  (use "git add <file>..." to mark resolution)

        both added:      staging.txt

$ cat staging.txt
Fixing Previous Error
$ git add staging.txt
$ git status
On branch master
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:

        modified:   staging.txt

$ git commit -m 'checkout --theirs'
[master 46801f2] checkout --theirs
$ git log --oneline
ESC[33m46801f2ESC[mESC[33m (ESC[mESC[1;36mHEAD -> ESC[mESC[1;32mmasterESC[mESC[33m)ESC[m checkout --th
ESC[33m3e7785fESC[m Fixing Error
ESC[33mc2080aeESC[mESC[33m (ESC[mESC[1;31morigin/masterESC[mESC[33m)ESC[m Fix for Bug #1234
ESC[33mae9e69bESC[m First Commit
$ cat staging.txt
Fixing Previous Error

最初のgit statusboth add: staging.txtと見慣れない記載がされている
これはこちらに記載されている通り、コンフリクトの発生を知らせてくれる
また、どのようなコンフリクトが発生しているかも大まかに教えてくれる

注意
katacodaでは、step1で$ git merge --allow-unrelated-histories remotes/origin/masterを実行するよう要求されない
この操作を実行せずにstep3を実行しても--ours/--theirsオプションが反映されずgit statusの結果が変わらない
これは、katacodaの環境では自分のbranchとremoteのbranchが関連性を持っていないため、git merge remotes/origin/masterでは、コンフリクトの発生を認知できず、その後の--ours/--theirsオプションが意味をなしていないためと思われる
おそらく、最初にこの演習を作成した時点では、2つのbranchの関連性があったにも関わらず途中で関連性がないbranchを別に用意したのだろう
(ちゃんと演習内容も修正してほしい、、、、初心者殺しやん、こんなの涙)

$ git merge remotes/origin/master
fatal: refusing to merge unrelated histories   //関連性のないヒストリを持つbranch同士のマージはGit2.9からできなくなった※1
$ git merge --allow-unrelated-histories origin/master   //上記エラーの対応
Auto-merging staging.txt
CONFLICT (add/add): Merge conflict in staging.txt
Automatic merge failed; fix conflicts and then commit the result.

Step 4 - Non-Fast Forward

step4では、以下の図のような場面を考える

$ ls -a
.  ..  .git  new-file-5a.txt  new-file-5.txt  new-file.txt  staging.txt
$ git log --oneline
ESC[33mc993ea3ESC[mESC[33m (ESC[mESC[1;36mHEAD -> ESC[mESC[1;32mmasterESC[mESC[33m)ESC[m Fix for Bu
ESC[33m1f30797ESC[m Merge remote-tracking branch 'origin/master'
ESC[33m7b6b4d3ESC[m Fixing Error
ESC[33m499f006ESC[mESC[33m (ESC[mESC[1;31morigin/masterESC[mESC[33m)ESC[m Fix for Bug #1234
ESC[33mf9fb9b3ESC[m First Commit
$ git pull --no-edit origin master
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 4 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (4/4), done.
From /s/remote-project/1
 * branch            master     -> FETCH_HEAD
   499f006..b35bfb6  master     -> origin/master
Merge made by the 'recursive' strategy.
 new-file-6.txt  | 1 +
 new-file-6a.txt | 1 +
 2 files changed, 2 insertions(+)
 create mode 100644 new-file-6.txt
 create mode 100644 new-file-6a.txt
$ git log --all --decorate --oneline
ESC[33m3833389ESC[mESC[33m (ESC[mESC[1;36mHEAD -> ESC[mESC[1;32mmasterESC[mESC[33m)ESC[m Merge bran
ESC[33mb35bfb6ESC[mESC[33m (ESC[mESC[1;31morigin/masterESC[mESC[33m)ESC[m Fix for Bug #58a
ESC[33mc993ea3ESC[m Fix for Bug #55
ESC[33m1b74e50ESC[m Fix for Bug #58
ESC[33m1f30797ESC[m Merge remote-tracking branch 'origin/master'
ESC[33m7b6b4d3ESC[m Fixing Error
ESC[33m499f006ESC[m Fix for Bug #1234
ESC[33mf9fb9b3ESC[m First Commit
$ ls -a
.   .git             new-file-5.txt   new-file-6.txt  staging.txt
..  new-file-5a.txt  new-file-6a.txt  new-file.txt

開発者が自分の変更を容易に確認できる方法としてstep5で紹介する

Step 5 - Git Rebase

git rebaseを用いると以下の図のようなログを生成

git mergegit rebaseの復習

  • 共通点:最新のbranchの状態は同じ(HEADの位置は異なる可能性あり)
  • 差異:

    • merge:コミットの履歴を出来事の通り記録
    • rebase:コミットの流れを見やすく記録
  • 使い分け
    基本的には、git merge一択
    master branchの変更が活発でgit熟練者が複数人で使う場合はgit rebase
    masterに統合->git mergemaster以外に統合->git rebase

詳しく知りたい方はこちらもしくはこちら