Git インタラクティブリベースのガイド、実践例付き

Gitによるバージョン管理は、現代の開発者全員のツールベルトに標準的なものとなっています。commitpushpullのようなコマンドは、指の筋肉記憶に刻まれています。しかし、Gitの「より高度な」機能について知っている開発者は比較的少なく、それらがどれほど非常に貴重であるかを理解している人はさらに少ないでしょう。この記事では、Gitの中で最も強力なツールの一つである「インタラクティブリベース」について探ります。

インタラクティブリベースが全ての開発者のツールキットに含まれるべき理由

要するに、大げさではなく、インタラクティブリベースはプロジェクトでクリーンで構造化されたコミット履歴を作成することによって、より良い開発者になる手助けをしてくれます。

構造化されたコミット履歴がなぜ重要なのでしょうか?その逆を想像してみてください:同僚たちが最近の変更で実際に何をしたのか分からない、読みにくいコミット履歴。そのようなプロジェクトでは、どんどん「暗いコーナー」が現れ、自分自身が取り組んだ小さな部分だけを理解することしかできません。

これに対し、クリーンで構造化されたコミット履歴を見比べてみましょう:それはプロジェクトのコードベースをより読みやすく理解しやすくします。これは、健全で長く続くプロジェクトのための不可欠な要素です!

インタラクティブリベースがあなたにできること

インタラクティブリベースは、あなたのコミット履歴を最適化し、クリーンアップする手助けをします。それは多くの異なるユースケースをカバーし、次のようなことができるいくつかの機能があります:

  • 古いコミットメッセージを編集する
  • コミットを削除する
  • 複数のコミットを圧縮/結合する
  • コミットを並べ替える
  • 古いコミットの修正
  • 古いコミットの分割/編集のための再開

インタラクティブリベースを使用するタイミング(そして使用しないタイミング!)

他のいくつかのGitツールのように、インタラクティブリベースは「履歴を書き換える」。これは、インタラクティブリベースを使用して一連のコミットを操作すると、その部分のコミット履歴が書き換えられることを意味します。コミットのSHA-1ハッシュは変更されています。言い換えれば、完全に新しいコミットオブジェクトです。

この事実から、単純で重要なルールが生まれます。リモートリポジトリで同僚と共有したコミットにインタラクティブリベース(または履歴を書き換える他のツール)を使用しないでください。代わりに、チームブランチにマージする前に、独自のローカルコミット(たとえば、独自の機能ブランチ内のもの)をクリーンアップするために使用します。

インタラクティブリベース操作の基本メカニズム

インタラクティブリベースが使用できる多くの異なる用途がありますが、基本ワークフローは常に同じです。この基本メカニズムをしっかり理解すれば、インタラクティブリベースの「複雑な謎」は消え、あなたのツールベルトにおいて貴重でアクセス可能なアイテムになります。

ステップ1:セッションを開始する場所はどこか?

最初に答えなければならない質問は、「どの部分のコミット履歴を操作したいか?」です。これは、インタラクティブリベースセッションを開始する場所を教えてくれます。実践的な例を挙げて、古いコミットメッセージを編集したいとしましょう(実際にすぐに実行することになります)。

以下に示すのが、私たちの出発状況です。ここでは、インタラクティブなリベースを通じて古いコミットメッセージを編集しています。

コミットC2のメッセージを変更できるようにするためには、その親コミット(またはそれ以前)からインタラクティブなリベースセッションを開始する必要があります。この例では、インタラクティブなリベースセッションの開始点としてC1を使用します。

ステップ2: 実際のセッションを開始!

実際のセッションを開始するのは非常に簡単です:

$ git rebase -i HEAD~3

ここでは、git rebaseコマンドを使用し、-iフラグを付けて(インタラクティブであることを示すため)、ステップ1で選んだベースコミットを指定します。この例では、「HEADコミットから3つ後ろのコミット」を指定するためにHEAD~3を使用しました。また、特定のSHA-1ハッシュを提供することもできます。

ステップ3: Gitに何をして欲しいか伝える

インタラクティブなリベースセッションを開始すると、Gitがシリーズのコミットをリストしたエディターウィンドウが表示されます。これは、最新のコミットからステップ1で選んだベースコミットを除いたものまでです。

このステップでは、2つの重要な点に注意してください:

  1. コミットは逆順にリストされます! 最も新しいコミットは、上部に表示されると期待されるものの、リストの下部に表示されます。心配することはありません。あなたのGitリポジトリは健全です!🥳 インタラクティブなリベース操作を実行していることを思い出してください。これには、操作の最後に最も古いコミットから最も新しいコミットへとコミットを再適用する必要があります。
  2. このエディターウィンドウで実際の変更を行わないでください! このエディターウィンドウでコミットメッセージを変更しようとしているかもしれませんが(結局、それが実際にやりたいことですが…)、少しの忍耐が必要です。ここでは、Gitに何をしたいかを伝えるだけで、実際の変更は行いません。この点をすぐに実践で示します!

この理論的な概要を終えて、実践的なケースに一緒に飛び込みましょう!

古いコミットメッセージの編集

インタラクティブリベースの非常に一般的な使用例の1つは、事後に古いコミットメッセージを編集できることです。git commit --amendもコミットのメッセージを変更できることをご存知かもしれませんが、それは最も新しいコミットだけです。それより古いコミットの場合、インタラクティブリベースを使用する必要があります!

具体的なシナリオを見てみましょう。以下は、修正が必要な悪いコミットメッセージの画像です。

注:より良い概観と明確な視覚化のために、私はTower Gitデスクトップクライアントをいくつかのスクリーンショットで使用しています。このチュートリアルを進めるためにTowerは必要ありません。

例として、現在のタイトルが「インデックスのマークアップ構造を最適化…」であるコミットのメッセージを編集したいとしましょう。

最初のステップは、このインタラクティブなリベースセッションを開始するためのベースコミットを決定することです。「悪いリンゴ」コミットの親に(少なくとも)戻る必要があるため、セッションをHEAD~3(「見出しを変更…」というタイトルのコミットに対して3つ前のコミット)で開始します:

$ git rebase -i HEAD~3

このコマンドを実行した直後、お気に入りのエディタが開き、選択したコミットのリスト(ベースコミットを提供することによって)が表示されます。

念のため:そうしたい誘惑に駆られるかもしれませんが、ここではコミットメッセージを変更しないでください。私たちは、それぞれの行に「アクションキーワード」をマークアップするだけです。この場合、コミット(コミットメッセージを変更したいが、コミットの残りの部分はそのままにしたい)をrewordしたいと考えています。

非常に実用的に、利用可能なアクションキーワードはこのウィンドウの下部に記載されているため、何も心に留めておく必要はありません!

標準のpickキーワード(「コミットをそのまま受け取る」という意味)をお好みのアクションキーワードに置き換えたら、ウィンドウを保存して閉じるだけです。

そうすると、現在のコミットメッセージが含まれる新しいエディタウィンドウが開きます。最後に、最初にやろうとしていたことを許可されます:この古いコミットのメッセージを編集する!

変更を加えた後、エディタウィンドウを保存して閉じると、インタラクティブなリベースセッションが完了します—そして、コミットメッセージが更新されます!🎉

不要なコミットの削除

インタラクティブなリベースを使用することで、不要になった古いコミットを削除できます。たとえば、最近のコミットに誤って個人のパスワードを含めてしまったと想像してください。このような機密情報は、ほとんどの場合、コードベースに含めるべきではありません。

また、情報を削除して再コミットするだけでは、問題を本当に解決したことにはなりません。これは、パスワードが古いコミットの形でリポジトリに保存されていることを意味します。本当に望むのは、このデータをリポジトリから完全に削除することです!

まず、インタラクティブなリベースセッションのベースコミットを決定しましょう。悪いコミットの親から少なくとも始める必要があるため、「マークアップ構造を最適化…」コミットをベースに使用します。

$ git rebase -i 6bcf266b

今回は、git rebase -iコマンドで具体的なSHA-1ハッシュを使用していることに注意してください。もちろん、コミットハッシュの代わりにHEAD~2を使用してそのコミットを指定することもできます。

このコマンドを実行すると、再びコミットのリストが表示されます。

今回は、不要なコミットを取り除くためにdropアクションキーワードを使用します。または、この特別な場合には、エディタから行全体を削除するだけでもかまいません。エディタのウィンドウを保存して閉じる際に行(コミットを表す)が存在しなくなると、Gitは対応するコミットを削除します。

どちらの方法を選択しても、エディタのウィンドウを保存して閉じた後、そのコミットはリポジトリの履歴から削除されます!

複数のコミットを1つに統合する

インタラクティブなリベースの別の使用例は、複数の個別のコミットを結合して単一のコミットにする場合です。それがどのように機能するのかについて詳しく見る前に、いつまたはなぜこれが価値があるのかについて少し話しましょう。

一般的に言って、複数のコミットを一つに結合してコミットを「大きく」することは、ほとんどの場合良い戦略ではありません。経験則として、コミットはできるだけ小さく保つべきであり、「小さい」ことは「読みやすく理解しやすい」ことを意味します。しかし、それでも理にかなった状況もあります。次の2つの例を考えてみましょう:

  • 古いコミットで問題が発見されたと想像してください。その後、その問題を修正する新しいコミットを作成するかもしれません。このような状況で、これらのコミットを単一のものに結合できることは非常に理にかなっています。結局、新しいコミットは元々存在していなかった問題に対する「バンドエイド」に過ぎませんでした。これらのコミットを結合することで、最初から問題が存在していなかったかのように見えます!
  • もう一つの例は、コミットを少し細かく分割しすぎたことに気付いた場合です。小さなコミットを作成することは良いことですが、コミット履歴に多くの不必要に小さなコミットを散らばすことは、目標を超えてしまったことを意味します。

どちらの例においても、理由は同じです。最初から単一のものであるべきだった2つ(または複数)のコミットを結合することで、よりクリーンで読みやすいコミット履歴を生成しています!

一緒に実践例を見ていき、以下に示す状況を出発点としましょう。

これらの2つのコミットを単一のものとして意味的により合理的にすることを想定しましょう。squashツールを使ったインタラクティブリベースで、実際にそれらを結合することができます:

$ git rebase -i HEAD~3

これまでのところ、次に何が起こるかに慣れているはずです:エディターウィンドウが開き、コミットのリストが表示されます。

I already mentioned that we’re going to use the squash action keyword in this case. There’s an important thing to know about how squash works: the line you mark up with the keyword will be combined with the line directly above! This explains why I marked line 2 with the squash keyword in our example.

このウィンドウを保存して閉じると、新たなウィンドウが開きます。複数のコミットを結合することで新しいものを作成しているため、他のコミットと同様にコミットメッセージが必要だからです!

上記のスクリーンショットでGitが準備してくれたものは、元のコミットのコミッセージを組み合わせたものに加えて、いくつかのコメントが含まれています。古いメッセージを削除して新たに始めることも、それらを残してさらに情報を追加することも自由です。

このエディターウィンドウを保存して閉じた後、これまで2つの別々のコミットであったものが今では単一のコミットであると誇らしげに言えます!

インタラクティブリベースの力を活用

I hope you agree that Git’s interactive rebase tools can be very valuable! As developers, it’s important for us to strive for a clean and clear commit history. It’s a crucial ingredient in keeping a codebase healthy and easy to understand (both for your teammates, and yourself, after some time has passed).

さらに学びたい場合は、「Gitの応急手当」を強くお勧めします。Gitでのクリーンアップやミスの取り消し方を示す短い動画のコレクションです(無料)。

楽しんでください!

Gitインタラクティブリベースに関するよくある質問(FAQ)

Git RebaseとGit Mergeの違いは何ですか?

Git RebaseとGit Mergeは、それぞれ異なる方法で一つのブランチから別のブランチへ変更を統合するための手段です。Git Mergeは、二つの異なるブランチのコードを組み合わせるための直接的な方法であり、歴史の中に新しいコミットを作成し、コミットの時系列順序を保存します。一方、Git Rebaseは、一連のコミットを新しいベースコミットに移動または結合する方法であり、「他の全員が行ったことに基づいて自分の変更を行いたい」という意味になります。言い換えれば、それは現在のブランチの変更を別のブランチの上に置くことを可能にします。

Git Rebaseをどうやって取り消すか?

Git Rebaseを取り消したい場合、git reflogコマンドを使用して戻りたいコミットを見つけ、そしてgit reset --hard HEAD@{number}コマンドを使用することができます。git reflogコマンドはHEADに対して行われたすべての変更のリストを表示し、git resetコマンドは現在のHEADを指定された状態に設定することを可能にします。

Git Interactive Rebaseの目的は何ですか?

Git Interactive Rebaseは、コミットを編集、削除、圧縮するなど、多くの方法で変更することができます。コミットメッセージだけでなく、間違いがあれば実際のコードも変更できます。それはプロジェクトのコミット履歴を完全に制御できる強力なツールです。

Git Interactive Rebaseを使ってコミットをどのように圧縮すればよいですか?

コミットの統合とは、複数のコミットを1つにまとめる行為です。Gitでは、git rebase -iコマンドに続けて統合したいコミットのハッシュを指定することで、コミットを統合できます。開くテキストエディタでは、picksquashまたはsに置き換えることで、統合したいコミットをマークできます。

Gitインタラクティブリベースを使用するリスクは何ですか?

Gitインタラクティブリベースは強力なツールですが、正しく使用されない場合は危険です。コミット履歴を書き換えるため、他の人も作業しているパブリックブランチでは問題になる可能性があります。まだプッシュされていないローカルブランチで使用することをお勧めします。

Gitリベース中に競合が発生した場合、どのように解決すればよいですか?

リベース中に競合が発生することがあります。Gitは一時停止し、競合を解決して再開するまで待ちます。競合を解決するには、競合する変更を修正するためにファイルを編集し、解決済みのファイルをgit addで追加します。競合をすべて解決した後、git rebase --continueでリベースを再開できます。

Gitインタラクティブリベースを使用してコミットを分割できますか?

はい、Gitインタラクティブリベースを使用してコミットを小さなものに分割できます。1つのコミットで複数の変更を行った後、それらが別々のコミットであるべきだと判断した場合に役立ちます。

Gitインタラクティブリベースを使用してコミットメッセージを編集するにはどうすればよいですか?

インタラクティブリベース中にコミットメッセージを編集できます。コミットのリストで、編集したいコミットの隣にあるpickrewordまたはrに置き換えてください。続行すると、Gitはrewordでマークされた各コミットに対してテキストエディタを開き、コミットメッセージを変更できるようになります。

Git RebaseとGit Pullの違いは何ですか?

Git Pullは、リモートリポジトリから変更を取得し、現在のブランチにマージするコマンドです。一方、Git Rebaseは、一連のコミットを新しいベースコミットに移動または結合するコマンドです。どちらのコマンドも変更を統合するために使用されますが、それらは異なる方法で行います。

Git Interactive Rebaseを使用してコミットの順序を変更できますか?

はい、Git Interactive Rebaseを使用してコミットの順序を変更できます。コミットのリストで、行の順序を変更するだけでコミットの順序を変更できます。コミット履歴をより論理的または明確にするために役立つ場合があります。

Source:
https://www.sitepoint.com/git-interactive-rebase-guide/