Gitで複数の修正を同時進行で行いたい場合に使えるGitコマンド

開発Divの藤原です。

レビュー待ちの間に、次の修正をしたい。だけど、次の修正も同じファイルだからレビュー中のコミットに修正が入ったらマージが面倒。。。という場面、開発していると出くわすことはあるかと思います。

こういったケースに使うGitコマンドを実例を交え、Step by Step(ステップ・バイ・ステップ)でご紹介します。

1つ目の修正

以下のような内容のREADME.mdを修正する例を元に説明していきます。

aaaaaaaaaa
bbbbbbbbbb
cccccccccc

まずは、featureブランチ作成。

/git-test (develop) $  ls
README.md
/git-test (develop) $ git checkout -b features_001
/git-test (features_001) $

最終行に一行追加します。

/git-test (features_001) $  vim README.md
aaaaaaaaaa
bbbbbbbbbb
cccccccccc
+dddddddddd

修正をコミットし、サーバーにプッシュ。

/git-test (features_001) $ git add .
/git-test (features_001) $ git commit -m '001'
/git-test (features_001) $ git push origin features_001

コミットログの確認。

/git-test (features_001) $ git log --oneline
b457454 (HEAD -> features_001, origin/features_001) 001

ここまでは通常の手順です。続いてこの修正に重ねて修正する方法について説明していきます。

2つ目の修正

features_001のから派生させたブランチを作ります。

/git-test (features_001) $ git checkout -b features_002
/git-test (features_002) $

こうすることで、features_001の修正が入った状態のファイルに修正を加えることが可能です。

先頭行を変更します。

/git-test (features_002) $  vim README.md
-aaaaaaaaaa
+1111111111
bbbbbbbbbb
cccccccccc
dddddddddd

あとは、1つ目と同じように修正をコミットし、サーバーにプッシュ。

/git-test (features_002) $ git add .
/git-test (features_002) $ git commit -m '002'
/git-test (features_002) $ git push origin features_002

コミットログの確認。

/git-test (features_002) $ git log --oneline
71525b4 (HEAD -> features_002, origin/features_002) 002
b457454 (origin/features_001, features_001) 001

ここまでで、1つ目の修正を取り込んだ2つ目の修正ができました。

1つ目の修正を変更

例えば、1つ目でレビュー指摘を受けた場合などにより修正が必要になった場合の対応方法です。

まずは、1つ目の修正のブランチへ切り替え。

/git-test (features_002) $ git checkout features_001
/git-test (features_001) $

最終行に入れた修正を変更します。

/git-test (features_001) $  vim README.md
aaaaaaaaaa
bbbbbbbbbb
cccccccccc
-dddddddddd
+dddddeeeee

コミットを上書きします。

/git-test (features_001) $ git add .
/git-test (features_001) $ git commit --amend
コミットのviが立ち上がるので、そのまま保存。

変更を確認。

/git-test (features_001) $ git show
aaaaaaaaaa
bbbbbbbbbb
cccccccccc
+dddddeeeee

コミットが上書きされていることを確認。

サーバーにプッシュ。

/git-test (features_001) $ git push -f origin features_001

サーバーに一度プッシュしている場合、-fオプション強制オプションを付ける必要があります。
これは文字通り強制的にローカルの内容をサーバーへ反映するものですので、利用の際には注意が必要です。他の人のコミットを消したりしないように気を付けてください。

ちなみに、このケースで-fオプションが必要なのは、一度サーバーに反映したコミットを書き換える(履歴を書き換える)ためだと思います。コミットの上書きではなく追加の別コミットをプッシュする場合は-fオプションは不要です。

コミットログの確認。

/git-test (features_001) $ git log --oneline
a762606 (HEAD -> features_001) 001

1つ目の修正の変更を2つ目の修正へ取り込む

この段階で2つ目の修正には、上記の1つ目の修正の変更は取り込まれていません。 しかし、あるコマンドを使うことで簡単に取り込むことができます。

まずは、2つ目の修正のブランチへ切り替え。

/git-test (features_001) $ git checkout features_002
/git-test (features_002) $

1つ目の修正の変更をマージ。※この記事はココが一番ポイントです。

/git-test (features_002) $ git pull --rebase origin features_001
From localhost:fuji/git-test
 * branch            features_001 -> FETCH_HEAD
First, rewinding head to replay your work on top of it...
Applying: 002
Using index info to reconstruct a base tree...
M       README.md
Falling back to patching base and 3-way merge...
Auto-merging README.md

自動でマージされました。

変更を確認。

/git-test (features_002) $ git show
-aaaaaaaaaa
+1111111111
 bbbbbbbbbb
 cccccccccc
 dddddeeeee

1つ目の修正の変更が反映されています。最後の行。

コミットログの確認。

/git-test (features_002) $ git log --oneline
cb1e694 (HEAD -> features_002) 002
a762606 (origin/features_001, features_001) 001

マージコミットも作られておらず、履歴も保たれています。

サーバーにプッシュ。

/git-test (features_002) $ git push -f origin features_002

ここでも-fオプション強制オプションが必要です。
これで、1つ目の修正の変更を2つ目の修正へ取り込むことができました。

developブランチに入れられた他の人のコミットを取り込む場合なども手順は同じです。その場合は、サーバーの情報を一度pullコマンドで取得する必要があります。

/git-test (features_002) $ git checkout develop
/git-test (develop) $ git pull origin develop
/git-test (develop) $ git checkout features_002
/git-test (features_002) $ git pull --rebase origin develop

1つ目の修正を変更を2つ目の修正へ取り込んでコンフリクト(衝突)したとき

1つ目の修正の変更を取り込む以下のコマンドで、修正内容によってはコンフリクトが発生することがあります。

/git-test (features_002) $ git pull --rebase origin features_001
From localhost:fuji/git-test
 * branch            features_001 -> FETCH_HEAD
First, rewinding head to replay your work on top of it...
Applying: 002
error: Failed to merge in the changes.
Using index info to reconstruct a base tree...
M       README.md
Falling back to patching base and 3-way merge...
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Patch failed at 0001 002
The copy of the patch that failed is found in: .git/rebase-apply/patch

Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".

/git-test (features_002|REBASE 1/1) $

このようなメッセージが表示された場合はコンフリクトが発生していますので、手動でマージする必要があります。

コンフリクトを発生させるため、1つ目の修正の先頭行に以下のような行を追加しています。

+xxxxxxxxxx
aaaaaaaaaa

手動マージをします。

/git-test (features_002|REBASE 1/1) $ vim README.md

ファイルはこんな感じになっています。

<<<<<<< HEAD
xxxxxxxxxx
aaaaaaaaaa
=======
1111111111
>>>>>>> 002
bbbbbbbbbb
cccccccccc
dddddeeeee

不要な行を削除し、以下のように変更し保存します。

xxxxxxxxxx
1111111111
bbbbbbbbbb
cccccccccc
dddddeeeee

修正したファイルをaddします。

/git-test (features_002|REBASE 1/1) $ git add README.md

手動マージを完了します。

/git-test (features_002|REBASE 1/1) $ git rebase --continue

変更を確認。

/git-test (features_002) $ git show
 xxxxxxxxxx
-aaaaaaaaaa
+1111111111
 bbbbbbbbbb
 cccccccccc
 dddddeeeee

期待通りの差分が表示されます。

コミットログの確認。

/git-test (features_002) $ git log --oneline
6d2bd26 (HEAD -> features_002) 002
b1ebd43 (origin/features_001, features_001) 001

マージコミットも作られておらず、履歴も保たれています。

後は、サーバーにプッシュするだけ。

/git-test (features_002) $ git push -f origin features_002

これでコンフリクトも解消できました。