図で分かるgit-rebase

世間的に「Gitはコミットログを書き換えられてキモい」と言われ、肩身が狭いので git-rebase の説明を書いてみた。

git help から引用

まずは基本に忠実に、ヘルプを読みましょう。

  • git help rebase
SYNOPSIS
       git rebase [-i | --interactive] [options] [--onto <newbase>]
               <upstream> [<branch>]
       git rebase [-i | --interactive] [options] --onto <newbase>
               --root [<branch>]

       git rebase --continue | --skip | --abort

DESCRIPTION
       If <branch> is specified, git rebase will perform an automatic git checkout <branch> before doing anything else. Otherwise it remains
       on the current branch.

       All changes made by commits in the current branch but that are not in <upstream> are saved to a temporary area. This is the same set
       of commits that would be shown by git log <upstream>..HEAD (or git log HEAD, if --root is specified).

       The current branch is reset to <upstream>, or <newbase> if the --onto option was supplied. This has the exact same effect as git
       reset --hard <upstream> (or <newbase>). ORIG_HEAD is set to point at the tip of the branch before the reset.

       The commits that were previously saved into the temporary area are then reapplied to the current branch, one by one, in order. Note
       that any commits in HEAD which introduce the same textual changes as a commit in HEAD..<upstream> are omitted (i.e., a patch already
       accepted upstream with a different commit message or timestamp will be skipped).

       It is possible that a merge failure will prevent this process from being completely automatic. You will have to resolve any such
       merge failure and run git rebase --continue. Another option is to bypass the commit that caused the merge failure with git rebase
       --skip. To restore the original <branch> and remove the .git/rebase-apply working files, use the command git rebase --abort instead.

ヘルプ読んで、「なるほど!」と思った方は、このままブラウザのタブを閉じて大丈夫です。
この先にgit-help 以上の情報はありません。


git-helpを貼っただけだと「git-rebaseキモい」の世論は変わらないと思うので、基本的な所を分かりやすく日本語で書いてみました。

git-rebase の概要

まずはgit-rebase の引数について説明します。

  • 基本的な構文*1
$ git rebase [--onto <newbase>] <upstream> [<branch>]

引数は3つあります。
--onto

  • 省略可。省略時はと同じ。
  • 指定はオブジェクト(ブランチ名、タグ名、ハッシュ値、HEAD@{n}・・・など)

  • 必須。
  • 指定はオブジェクト(ブランチ名、タグ名、ハッシュ値、HEAD@{n}・・・など)

  • 省略可。省略時はcurrent branch(今いるブランチ)と同じ。
  • 指定はオブジェクト(ブランチ名、タグ名、ハッシュ値、HEAD@{n}・・・など)

git-rebase の処理の流れ

  1. との共通の祖先を探す。
  2. <共通の祖先>からまでコミットと同じ変更をに追加していく。
  3. がブランチ名なら、ブランチの参照をHEADに再設定する。

1つずつ、図を交えて、順番に説明します。

0. 最初の状態

例として、こんなコミットログがあるとします。

このとき、こんなコマンドを打ったとする。

$ git rebase master topic
1. 共通の祖先を探す

git-rebase は、まず共通の祖先(図の赤い箇所)を探します。

2. 変更をに追加

次に、<共通の祖先>からまでの変更内容(図のオレンジの箇所)をに追加していきます。
今回は --onto オプションを指定していないため、が と同じになります。

  • 変更箇所を


  • に追加する


3. ブランチの参照の更新

最後に、 がブランチ名の場合はブランチの参照が更新されます。

がハッシュ値などの場合、ブランチの参照の更新はされず、HEADがブランチでない場所(無名ブランチ)を指すことになります。

以上がgit-rebase がやっている処理です。

--onto がある場合

上述した「git-rebase の処理の流れ」と同じです。
実際にコマンドを打って、各自で確認してみてください。

$ git rebase --onto master topicA topicB
$ git log --oneline --decorate --graph --all

たぶん、コミットログが↓のように変わるはずです。

  • git-rebase前


  • git-rebase後


gitk

git-rebase に慣れていない間は、gitk を常に起動させておき、git-rebase前後を確認した方が良いです。

$ gitk --all &

他のオプションについて

他のオプションの説明は色んな方が既にブログなどに書いているので、グーグル先生に聞いてください。

*1:私は「git rebase <ココに> <ここから> <ここまで>」で覚えてます。「ここから、ここまでの変更を、ココに追加する」という感じで