インタラクティブなリベース(Interactive Rebasing)
インタラクティブなリベースは、Git の中で最も強力で、かつ複雑になり得る機能の 1 つです。これを使用すると、コミット履歴を非常に柔軟に書き換えることができます。コミットの順序変更、結合(squash)、メッセージの編集、さらにはコミットの削除まで可能です。コミット履歴のためのきめ細かなエディタを持っているようなものです。
警告: インタラクティブなリベースは履歴を書き換えるため、特に共有ブランチで行う場合はリスクが伴います。共有リポジトリにすでにプッシュされたコミットに対しては、その影響を完全に理解し、チームと調整済みでない限り、決してリベースを行わないでください。 リベースは通常、自分の作業を他人に共有する前に、ローカルのコミットを整理するために使用するのが安全で適切です。
インタラクティブなリベースの動作を確認するために、一連のコミットを作成しましょう。
echo "First change" >> hello.txt
git commit -am "First change"
echo "Second change" >> hello.txt
git commit -am "Second change"
echo "Third change" >> hello.txt
git commit -am "Third change"
これらのコマンドは、hello.txt に行を追加する 3 つのコミットを立て続けに作成します。git commit -am という短縮記法は、変更および削除されたすべてのファイルのステージング(-a)とコミット(-m)を同時に行います。すでに追跡されているファイルに変更を加えてコミットする際に便利なショートカットです。
さて、これら 3 つのコミットを整理したいとします。例えば、「First change」と「Second change」は実際には 1 つの論理的な変更の一部であり、1 つのコミットにまとめたいとします。また、「Third change」のメッセージをもっと分かりやすく改善したいとします。インタラクティブなリベースはこれに最適です。
以下のコマンドでインタラクティブなリベースセッションを開始します。
git rebase -i HEAD~3
コマンドの意味を理解しましょう。
git rebase -i: git rebase はリベースを行うコマンドです。-i フラグは「インタラクティブ(対話型)」を意味し、リベース操作を対話的に行いたいことを Git に伝えます。
HEAD~3: リベースしたいコミットの範囲を指定します。HEAD は現在のコミットを指し、~3 は「HEAD から 3 つ遡る」ことを意味します。つまり、直近の 3 つのコミットを選択しています。特定のハッシュを指定して、履歴の特定の時点からリベースを開始することもできます。
このコマンドを実行すると、Git はデフォルトのテキストエディタを開き、直近 3 つのコミットのリストを表示します。このエディタ画面で、履歴をどのように変更するかを Git に指示します。以下のような内容が表示されるはずです(ハッシュ値は異なります)。
1 pick 63c95db First change
2 pick 68e7909 Second change
3 pick 5371424 Third change
4
5 ## Rebase 3bf348d..5371424 onto 3bf348d (3 commands)
6 #
7 ## Commands:
8 ## p, pick <commit> = use commit
9 ## r, reword <commit> = use commit, but edit the commit message
10 ## e, edit <commit> = use commit, but stop for amending
11 ## s, squash <commit> = use commit, but meld into previous commit
12 ## f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
13 ## commit's log message, unless -C is used, in which case
14 ## keep only this commit's message; -c is same as -C but
15 ## opens the editor
16 ## x, exec <command> = run command (the rest of the line) using shell
17 ## b, break = stop here (continue rebase later with 'git rebase --continue')
18 ## d, drop <commit> = remove commit
19 ## l, label <label> = label current HEAD with a name
20 ## t, reset <label> = reset HEAD to a label
21 ## m, merge [-C <commit> | -c <commit>] <label> [## <oneline>]
22 ## . create a merge commit using the original merge commit's
23 ## . message (or the oneline, if no original merge commit was
24 ## . specified); use -c <commit> to reword the commit message
25 #
26 ## These lines can be re-ordered; they are executed from top to bottom.
27 #
28 ## If you remove a line here THAT COMMIT WILL BE LOST.
29 #
30 ## However, if you remove everything, the rebase will be aborted.
インタラクティブ・リベース・エディタの理解:
- コミットリスト: ファイルの上部には、リベース対象のコミットがリストされています。デフォルトでは、古い順(上が古く、下が新しい)に並んでいます。
- コマンド: リストの下には、各コミットをどのように変更できるかのコマンド一覧があります。よく使われるものは以下の通りです。
pick (または p): コミットをそのまま使用します。これがデフォルトです。
reword (または r): コミットを使用しますが、コミットメッセージを変更します。
edit (または e): コミットを使用しますが、リベースを一時停止して、ファイルの修正や追加の変更(amend)ができるようにします。
squash (または s): このコミットをリスト上の前のコミットに結合します。このコミットのメッセージは前のコミットのメッセージに追記されます(後で結合後のメッセージを編集できます)。
fixup (または f): squash と似ていますが、このコミットのメッセージを破棄し、前のコミットのメッセージのみを使用します。
drop (または d): このコミットを履歴から完全に削除します。
今回のリベース計画:
やりたいことは以下の通りです。
- 「Second change」を「First change」に結合(squash)する。
- 「Third change」のメッセージをより良いものに書き換える(reword)。
これを実現するために、エディタの内容を以下のように書き換えます。
pick abc1234 First change
squash def5678 Second change
reword ghi9101 Third change
エディタ内のコミットハッシュは変更しないでください。
Vim での編集手順(Vim がエディタの場合):
- 挿入モードに入る:
i キーを押します。これでテキストの編集が可能になります。
- 変更を加える: 矢印キーで移動し、上記のように「pick」を「squash」や「reword」に書き換えます。
- 挿入モードを抜ける:
Esc キーを押します。
- 保存して終了:
:wq と入力して Enter を押します。
エディタを保存して閉じると、Git は指示に基づいてリベースを開始します。
まず、コミットの結合(Squash):
Git はまず「squash」コマンドを処理します。「Second change」の変更内容を「First change」に統合します。その後、統合されたコミットのメッセージを編集するために、再びエディタが開きます。以下のような内容が表示されます。
## This is a combination of 2 commits.
## This is the 1st commit message:
First change
## This is the 2nd commit message:
Second change
## Please enter the commit message for your changes. Lines starting
## with '#' will be ignored, and an empty message aborts the commit.
...
ここでは、結合されるコミットの元のメッセージが表示されています。これらを編集して、1 つのまとまったメッセージを作成します。例えば、以下のように変更します。
Combined first and second changes: Initial setup of hello.txt
編集後、エディタを保存して閉じます。
次に、コミットメッセージの書き換え(Reword):
次に Git は「Third change」に対する「reword」コマンドを処理します。またしてもエディタが開き、今度は「Third change」のメッセージを編集できます。元のメッセージが表示されているので、より説明的なものに変更しましょう。
Improved third change: Added a more descriptive line to hello.txt
保存して閉じます。
リベースの完了:
すべての指示が処理されると、リベースが完了します。通常、「Successfully rebased and updated refs/heads/master.」といったメッセージが表示されます。
リベース後の履歴の確認:
コミットログを再度確認しましょう。
git log --oneline
履歴は以下のようになっているはずです(ハッシュ値は異なります)。
<commit_hash_third_revised> Improved third change: Added a more descriptive line to hello.txt
<commit_hash_combined> Combined first and second changes: Initial setup of hello.txt
<commit_hash_cherry_pick> Add new feature
<commit_hash_revert> Revert "Add line to be reverted"
<commit_hash_original_reverted> Add line to be reverted
<commit_hash_initial_amended> Initial commit with important note
以下の点に注目してください。
- コミットの数が以前より減っています(2 つを 1 つに結合したため)。
- 「First change」と「Second change」だったものは、結合され、より説明的なメッセージになっています。
- 「Third change」だったもののメッセージが書き換えられています。
- 履歴を書き換えたため、リベースされたコミットのハッシュ値がすべて新しくなっています。
インタラクティブ・リベースに関する重要なリマインド:
- ローカル履歴用のツール: インタラクティブ・リベースは、主に自分のローカルなコミット履歴を整理するためのツールです。
- 共有ブランチでのリベースを避ける: 共有リポジトリにプッシュ済みのブランチに対してはリベースを行わないでください。共有された履歴を書き換えると、共同作業者に深刻な問題を引き起こします。
git rebase --abort: リベース中にミスをしたり、混乱したり、解決できない競合が発生したりした場合は、いつでも git rebase --abort を使用してリベース操作をキャンセルし、リベース開始前の状態に戻すことができます。これは、何かがうまくいかなかったときに安全に撤退する方法です。