2018年以降の記事はGitHub Pagesに移行しました

Pro Gitと入門Gitと入門gitと実用GitとGit道場#1でGitの復習 rebase編

参考

rebaseとは

入門git P126
土台の置き換え(リベース)にはgit rebaseコマンドを使う。

実用Git P176
一連のコミットのもととなるもの(基点)を変更する際に使います。

Git道場 講義
コミットをかぶせる事*1

コミットの基を変更すると。

パターン1 一般的な?場合(前方移植(forward-port))

Git道場で繰り返したパターンに近いかな。こういうブランチを作る。

       1---2---3(topic)
      /
A---B---C---D(master)

まずは1-5行目にそれぞれ1-5と入力したファイルを用意。

$ touch num
$ git add num
$ git commit -am"first commit"
[master (root-commit) fa9eabc] first commit
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 num

$ vi num
$ cat num
$ git commit -am "Add line number"
[master 15f636f] Add line number
 1 files changed, 5 insertions(+), 0 deletions(-)

$ git g
* 15f636f  (HEAD, master) Add line number
* fa9eabc  first commit

次にtopicブランチを作成しを育てる。

$ vi num
$ cat num
1 git add
2
3
4
5
$ git commit -am "Add message line 1"
[topic 29d901e] Add message line 1
 1 files changed, 1 insertions(+), 1 deletions(-)

$ vi num
$ cat num
1 git add
2 git commit
3
4
5
$ git commit -am "Add message line 2"
[topic 0b89cc2] Add message line 2
 1 files changed, 1 insertions(+), 1 deletions(-)

$ vi num
$ cat num
1 git add
2 git commit
3 git rebase
4
5
$ git commit -am "Add message line 3"
[topic 2588a24] Add message line 3
 1 files changed, 1 insertions(+), 1 deletions(-)


$ git g
* 2588a24  (HEAD, topic) Add message line 3
* 0b89cc2  Add message line 2
* 29d901e  Add message line 1
* 15f636f  (master) Add line number
* fa9eabc  first commit

masterに戻り、同じくファイルを編集。

$ git chm
Switched to branch 'master'

$ vi num
$ cat num
1
2
3
4
5 git add from_master
$ git commit -am "Add message line 5"
[master 0b91a9f] Add message line 5
 1 files changed, 1 insertions(+), 1 deletions(-)

$ vi num
$ cat num
1
2
3
4 git commit from_master
5 git add from_master
$ git commit -am "Add message line 4"
[master 93b8e52] Add message line 4
 1 files changed, 1 insertions(+), 1 deletions(-)

$ git g
D  * 93b8e52  (HEAD, master) Add message line 4
C  * 0b91a9f  Add message line 5
 3 | * 2588a24  (topic) Add message line 3
 2 | * 0b89cc2  Add message line 2
 1 | * 29d901e  Add message line 1
   |/
B  * 15f636f  Add line number
A  * fa9eabc  first commit

各コミットをABCD123とすると、こう。

       1---2---3(topic)
      /
A---B---C---D(master)

そして、topicブランチの変更をmasterブランチへ"かぶせる"

$ git ch topic
Switched to branch 'topic'

$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: Add message line 1
Using index info to reconstruct a base tree...
<stdin>:11: trailing whitespace.
1 git add
warning: 1 line adds whitespace errors.
Falling back to patching base and 3-way merge...
Auto-merging num
Applying: Add message line 2
Using index info to reconstruct a base tree...
<stdin>:12: trailing whitespace.
2 git commit
warning: 1 line adds whitespace errors.
Falling back to patching base and 3-way merge...
Auto-merging num
Applying: Add message line 3
Using index info to reconstruct a base tree...
<stdin>:13: trailing whitespace.
3 git rebase
warning: 1 line adds whitespace errors.
Falling back to patching base and 3-way merge...
Auto-merging num
CONFLICT (content): Merge conflict in num
Failed to merge in the changes.
Patch failed at 0003 Add message line 3

When you have resolved this problem run "git rebase --continue".
If you would prefer to skip this patch, instead run "git rebase --skip".
To restore the original branch and stop rebasing run "git rebase --abort".

途中でコンフリクトしたけど、内部ではこうなっている。HEADは今ここ。

  • topicブランチの一つ目の変更をmasterブランチに適用
       1---2---3(topic)
      /
A---B---C---D(master)---1'(HEAD)
  • topicブランチの二つ目の変更をmasterブランチに適用
       1---2---3(topic)
      /
A---B---C---D(master)---1'---2'(HEAD)
  • topicブランチの三つ目の変更をmasterブランチに適用しようとしてコンフリクト
       1---2---3(topic)
      /
A---B---C---D(master)---1'---2'---3'(HEAD)

コンフリクト発生時、グラフはこうなっている。

$ git g
* cf6c3d6  (HEAD) Add message line 2
* 38f8aff  Add message line 1
* 93b8e52  (master) Add message line 4
* 0b91a9f  Add message line 5
| * 2588a24  (topic) Add message line 3
| * 0b89cc2  Add message line 2
| * 29d901e  Add message line 1
|/
* 15f636f  Add line number
* fa9eabc  first commit

Add message line 1と2がmasterブランチから生えてる、イコールmasterブランチにtopicブランチの変更(二つ)がコミットされている。一個ずつ変更を適用してコミットとしているので、もとのtopicブランチとSHA1値が変わっている。

最後の変更をコミットしようとしたらコンフリクトしたので、直す。

$ vi num
$ git add num
$ git rebase --continue
Applying: Add message line 3

rebaseが無事終了。グラフを見てみると…。

$ git g
* 7ab5db8  (HEAD, topic) Add message line 3
* cf6c3d6  Add message line 2
* 38f8aff  Add message line 1
* 93b8e52  (master) Add message line 4
* 0b91a9f  Add message line 5
* 15f636f  Add line number
* fa9eabc  first commit

masterブランチとtopicブランチが一直線になった。旧topicブランチは変更が適用されたという事で削除されている。最終的にはこうなる。

A---B---C---D(master)---1'---2'---3'(HEAD, topic)

一連の流れのreflogはこんな感じ。

7ab5db8 HEAD@{0}: rebase: Add message line 3           # 一つずつ変更を適用
cf6c3d6 HEAD@{1}: rebase: Add message line 2           # 一つずつ変更を適用
38f8aff HEAD@{2}: rebase: Add message line 1           # 一つずつ変更を適用
93b8e52 HEAD@{3}: checkout: moving from topic to 93b8e52... # rebaseが勝手に無名ブランチ(masterの所)へcheckout
2588a24 HEAD@{4}: checkout: moving from master to topic     # rebaseのため自力でcheckoutした
パターン2 rebaseしたいブランチに依存しているブランチがある場合

これを見てまたrebase怖いと思ってしまった。内容を見てみる。

           x---y(subdev)
          /
   1---2---3(dev)
  /
A---B---C(master)

こういうブランチがあった時に、devをrebaseするとどうなるかという事。なるかどうかは別にして、git rebase master devで下記のようになってくれるとうれしい。

                             x'---y'(subdev)
                            /
A---B---C(master)---1'---2'---3'(dev)

だが、実態はそうではない。以下で試してみる。まず準備。

#devブランチを作成
$ git chb dev
$ touch newfeature
$ git add newfeature
$ git commit -am"Add newfeature"
$ echo "feature is ..." >> newfeature
$ git commit -am "Add message line 1"
#devブランチにサブのブランチを作成し育てる
$ git chb subdev
$ echo "sub module now" >> newfeature
$ git commit -am "Add message line 2(sub module)"
$ echo "sub module now 2" >> newfeature
$ git commit -am "Add message line 3(sub module)"
#devブランチに戻り育てる
$ git ch dev
$ echo "new feature end" >> newfeature
$ git commit -am "Add message line 2"
#masterブランチに戻り育てる
$ git chm
$ echo "feature add" >> num
$ git commit -am "Modify num"
$ echo "feature add 2" >> num
$ git commit -am "Modify num 2"

で、今のグラフ。

$ git g
  y * ebdc7e7  (subdev) Add message line 3(sub module)
C   | * 18ac45f  (HEAD, master) Modify num 2
B   | * a43a699  Modify num
 3  | | * a3f65de  (dev) Add message line 2
  x * | | f473f19  Add message line 2(sub module)
    | |/
    |/|
 2  * | cf36abe  Add message line 1
 1  * | 43b3504  Add newfeature
    |/
A   * 3aec83c  (nobra) Add message nobra

rebase開始。

$ git rebase master dev
First, rewinding head to replay your work on top of it...
Applying: Add newfeature
Applying: Add message line 1
Applying: Add message line 2

グラフはこうなる。

$ git g
 3'  * 948e1a4  (HEAD, dev) Add message line 2
 2'  * 0bc3e36  Add message line 1
 1'  * ae15a29  Add newfeature
   y | * ebdc7e7  (subdev) Add message line 3(sub module)
C    * | 18ac45f  (master) Modify num 2
B    * | a43a699  Modify num
   x | * f473f19  Add message line 2(sub module)
 2   | * cf36abe  Add message line 1
 1   | * 43b3504  Add newfeature
     |/
A    * 3aec83c  (nobra) Add message nobra

(ブランチが)すごく…多いです。こうなった。

           x---y(subdev)
          /
   1---2--
  /
A---B---C(master)---1'---2'---3'(dev)

dev(1,2,3)はきちんと適用された。が、subdev(x,y)は適用されていない。ので1,2,x,yは残っている。(3はdevをrebaseし、subdevから到達できないのでいつもどおり削除されている)

むむむ。しかし、修正内容さえ分かっていればコンフリクトしても冷静に直せばきちんとrebaseできるか。dev(3')にsubdev(y)をrebaseする。

$ git rebase dev subdev
First, rewinding head to replay your work on top of it...
Applying: Add message line 2(sub module)
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
Auto-merging newfeature
CONFLICT (content): Merge conflict in newfeature
Failed to merge in the changes.
Patch failed at 0001 Add message line 2(sub module)

When you have resolved this problem run "git rebase --continue".
If you would prefer to skip this patch, instead run "git rebase --skip".
To restore the original branch and stop rebasing run "git rebase --abort".

$ vi newfeature
$ git add newfeature
$ git rebase --continue
Applying: Add message line 2(sub module)
Applying: Add message line 3(sub module)
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
Auto-merging newfeature

$ git g
* 0fe8a57  (HEAD, subdev) Add message line 3(sub module)
* 71f976a  Add message line 2(sub module)
* 948e1a4  (dev) Add message line 2
* 0bc3e36  Add message line 1
* ae15a29  Add newfeature
* 18ac45f  (master) Modify num 2
* a43a699  Modify num
* 3aec83c  (nobra) Add message nobra

rebase対merge

  • 1人1リポジトリとかで作業している場合は、あまり意識せずに自由にやってもいい
    • mergeの方が何をしていたかの履歴が残るので良いかもしれない
  • rebaseを使う時の問題は、複数人複数リポジトリで作業している場合
    • rebaseすると新しいコミットになる(SHA1も変わる)
    • 公開した(pushした)後にrebaseするのはまずい
  • 上記のようにrebaseしたいブランチに依存しているブランチがある場合も注意かもしれない
    • うまくrebaseしていかないとグラフがどうなっているのかわからなくなりそう…
    • マージを含むブランチがある場合のrebaseも同様

*1:Git道場で実施したのはpull --rebaseだけど基本は同じはず