Gitを簡単に:rebaseで履歴を変更する—パートII
Or Kamara
2020年6月17日
0 分で読めます「Gitを簡単に使う」シリーズの第2回へようこそ!
前回の記事では、Rebaseの基本と、Gitを使う際によく遭遇する2つのシナリオについて説明しました。パートIはこちらからご覧いただけます。今回は、よくある4つのシナリオと、まとめをご紹介します。
さっそく始めましょう!
よくあるシナリオ(とその対処法!)—パートII
前回の記事の続きとして、コードの履歴を変更したい場面と、Gitを使った変更方法を見ていきましょう。
1. 過去のコミットを2つに分割する(edit)
2つのコミットをまとめた際にミスをしたことに気づき(詳しくはパートIを参照)、17a73c1をe2952e7にsquashした状態を元に戻し、新しいファイルを別のコミットにしたいとします。この場合は、編集したい位置までコミットを適用して停止し、変更を加えてから続行します。つまり、editオプションを使います。
もう一度git rebase -i masterを実行します。今回は、分割したいコミット(2つ目のコミット、`6871683`)にeditオプションを指定します。
rebase画面を閉じると、次のように表示されます。
Gitは1つ目と2つ目のコミットを適用して停止しました。ここからは自分で変更を加えられます。
現時点のGitグラフは次のとおりです。
先ほど説明したとおり、2つ目のコミットをファイルごとに2つのコミットへ分割します。まずgit reset --soft HEAD^を実行し、2つ目のコミットによる変更をリセットします。resetコマンドは、現在HEADが指しているブランチのコミットを変更します。--softオプションを指定すると、インデックスと作業ディレクトリはそのまま維持され、元のHEADからそのコミットまでに変更されたすべてのファイルがステージされます。
新しいGitのステータスは次のとおりです。両方のファイルがステージされています。
次に、git restore --staged b2.txtを実行して、ステージ済みファイルの一覧からb2.txtを外します。
ステージ済みファイルがb.txtだけになったので、2つ目のコミットの前半をコミットします。次にb2.txtをステージして、2つ目のコミットの後半をコミットします。
Gitグラフは次のようになります。
最後にrebaseを続行して、残りのコミットを適用します。次を実行してください。
git rebase --continue。最終的なGitグラフは次のようになります。
2. 特定のコミット内のすべてのファイルを修正する(edit)
次のシナリオには覚えがある方も多いでしょう。コードを複数のコミットにきれいに分けてプッシュした後で、リンターを実行し忘れたことに気づいたとします。先ほど説明したように、fix lintingという名前のコミットを追加するのではなく、すべてのコミットでリンティングを修正するのが適切です。
小さな変更ならsquashオプションを使えますが、複数のコミットにまたがる変更がある場合は、各コミット(または修正したいコミット)に対してリンティングスクリプトを1つずつ実行するのが最善です。そのために、前の例と同様、editオプションを再度使います。
再びgit commit rebase -iを実行し、複数のファイルを含む1つ目のコミットにeditオプションを指定します。
先ほどと同じく、Gitは1つ目のコミットを適用したところで停止します。
ここでリンター(例:npm run lint)を実行して、リポジトリの状態を確認します。
ご覧のとおり、リンタースクリプトによって6つのファイル(a*.txt)の内容が変更されました。あとは、変更をステージしてコミットし、現在の変更を amend してからrebaseを続行するだけです。
Gitグラフは次のようになります。
3. すべてのコミットでテストが通ることを確認する(exec)
コードを複数のコミットに分ける場合は、それぞれのコミットが単独で成立することが大切です。つまり、複数のコミットをメインブランチのいずれかにマージした後、それぞれのコミットに戻っても、コードがコンパイルでき、すべてのテストが通る状態であるべきです。
では、コミットをマージする前に、どうやって確認すればよいでしょうか?
すべてのコミットでテストを手動実行しましょう!……もちろん冗談です。自動化すべきですね :) そのために、execオプションを使います。
このオプションを使うと、rebaseの処理中にシェルスクリプトを実行できます。実行したい箇所にexecコマンドの行を追加するだけです。たとえば、もう一度git rebase -i masterを実行してみましょう。
では、楽しい作業に取りかかりましょう。各コミットの後にスクリプト./run_tests.shを実行して、テストを動かします。
rebase画面を閉じると、次のように出力されます。
ご覧のとおり、Gitは各コミットの適用後にテストスクリプトを実行しました。つまり、feat: 2nd commitにテストが失敗する原因があるとわかります。次のように実行すれば、各コミットの後にexecコマンドを1つずつ追加することもできます。
$ git rebase -i --exec "./run_tests.sh"
最後に(実は最初に)知っておきたいこと—中止する方法
これはシナリオというより、注意点です。rebaseコマンドは危険な場合があります!各コマンドの影響を理解せずに使うと、作業を台無しにする可能性があります。そこで、まず2つのことをおすすめします。
コミットを操作する前に、Gitリポジトリのバックアップを必ず作成してください。私が手軽に行っているのは、すべての変更をリモートブランチにプッシュすることです。何か問題が起きても、
git resetを使って元のコードに戻せます。各コマンドの動作を理解しておきましょう。実際のコードで初めて使うのではなく、まず一時的なGitリポジトリで試してください。
rebaseの処理はいつでも中止して、元の状態に戻せることを覚えておきましょう。
最初のrebase画面では、コミットの行をすべて削除するだけで、何も変更されません。
rebaseの途中で道に迷い、どうすればよいかわからなくなった場合は、いつでも
git rebase --abortを実行できます。このコマンドはrebaseを中止し、リポジトリを元の状態(git rebase -iの実行前の状態)に戻します。
まとめ
ここまでの例で見てきたように、git rebaseは強力なコマンドです。特にインタラクティブモードで使うと、その力を発揮します。活用できるケースは数え切れないほどありますが、各オプションがどのような場面で役立つのか、基本を理解する一助になれば幸いです。
この記事を通してお伝えしたいのは、git rebaseを賢く、そして臆せず使ってほしいということです。最初は少し怖く感じるでしょうし、Enterキーを押すときに誰かに見守ってほしいと思うかもしれません。でも、それで大丈夫です。練習を重ねれば、次第にいつもの作業になります。また、皆さんの具体的な用途に合わせた、ほかにもたくさんの活用シナリオがあるはずです。
それでは、Gitを楽しんでください!
Capture the Flagを始めよう
オンデマンドのバーチャル入門ワークショップを見て、Capture the Flagの課題の解き方を学びましょう。



