高度な Git コミット操作

GitGitBeginner
今すぐ練習

💡 このチュートリアルは英語版からAIによって翻訳されています。原文を確認するには、 ここをクリックしてください

はじめに

お帰りなさい、Gitの冒険者!あなたはバージョン管理の世界に最初の一歩を踏み出しました。そして今、あなたのスキルを一段階上げる時が来ました。この実験では、Gitのより高度なコミット操作のいくつかを探っていきます。これらの技術は、あなたがプロジェクトの履歴をよりコントロールできるようにし、間違いを修正し、作業を再編成し、より効果的にコラボレートすることを可能にします。

この実験を、あなたのタイムマシンをアップグレードするように考えてください。時間を旅することができるだけでなく、今ではタイムライン自体をどのように変更するかを学ぶことができます!これが難しそうに聞こえたら心配しないでください。私たちがあなたを各ステップに案内し、これらの操作をどのように行うかだけでなく、なぜ現実世界のシナリオで役立つのかを説明します。

この実験が終わるとき、あなたはコミットの修正、変更の元に戻し、特定のコミットをチェリーピックし、インタラクティブなリベースを行い、コミットをまとめることができるようになります。これらは、プロの開発者が毎日使ってクリーンで整理されたプロジェクト履歴を維持する強力なツールです。一緒に入り込んで、あなたのGitスキルを次のレベルに引き上げましょう!


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL git(("Git")) -.-> git/SetupandConfigGroup(["Setup and Config"]) git(("Git")) -.-> git/BranchManagementGroup(["Branch Management"]) git(("Git")) -.-> git/BasicOperationsGroup(["Basic Operations"]) git(("Git")) -.-> git/DataManagementGroup(["Data Management"]) git/SetupandConfigGroup -.-> git/init("Initialize Repo") git/BasicOperationsGroup -.-> git/add("Stage Files") git/BasicOperationsGroup -.-> git/commit("Create Commit") git/DataManagementGroup -.-> git/reset("Undo Changes") git/BranchManagementGroup -.-> git/branch("Handle Branches") git/BranchManagementGroup -.-> git/checkout("Switch Branches") git/BranchManagementGroup -.-> git/log("Show Commits") git/BranchManagementGroup -.-> git/cherry_pick("Cherry Pick") git/BranchManagementGroup -.-> git/rebase("Reapply Commits") subgraph Lab Skills git/init -.-> lab-387471{{"高度な Git コミット操作"}} git/add -.-> lab-387471{{"高度な Git コミット操作"}} git/commit -.-> lab-387471{{"高度な Git コミット操作"}} git/reset -.-> lab-387471{{"高度な Git コミット操作"}} git/branch -.-> lab-387471{{"高度な Git コミット操作"}} git/checkout -.-> lab-387471{{"高度な Git コミット操作"}} git/log -.-> lab-387471{{"高度な Git コミット操作"}} git/cherry_pick -.-> lab-387471{{"高度な Git コミット操作"}} git/rebase -.-> lab-387471{{"高度な Git コミット操作"}} end

作業環境のセットアップ

高度な操作を始める前に、新しい作業環境をセットアップしましょう。新しいディレクトリを作成し、その中にGitリポジトリを初期化します。これにより、既存のプロジェクトに影響を与えることなく、実験するクリーンな空間が得られます。

ターミナルを開き、次のコマンドを入力して、各行の後に「Enter」キーを押します。

cd ~/project
mkdir git-advanced-lab
cd git-advanced-lab
git init

これらのコマンドが何をするかを順に解説しましょう。

  1. cd ~/projectcdは「ディレクトリを変更する」という意味です。~/projectは、通常、ホームディレクトリ(~)内の「プロジェクト」フォルダを指すパスです。このコマンドにより、ターミナルがそのディレクトリに移動します。もしホームディレクトリに「プロジェクト」ディレクトリが存在しない場合、エラーが発生するかもしれません。その場合は、まずmkdir ~/projectを使って作成し、その後再度cd ~/projectを試してください。
  2. mkdir git-advanced-labmkdirは「ディレクトリを作成する」という意味です。このコマンドは、現在のディレクトリ(前のコマンドの後は~/projectになるはず)内に新しいフォルダ「git-advanced-lab」を作成します。この新しいディレクトリが、この実験用のGitリポジトリのルートになります。
  3. cd git-advanced-lab:このコマンドは、再度現在のディレクトリを変更し、今度は新しく作成された「git-advanced-lab」ディレクトリ内に移動します。これ以降、実行するすべてのコマンドはこのディレクトリ内で実行されます。
  4. git init:これは、リポジトリを初期化するための重要なGitコマンドです。git initは、現在のディレクトリ(git-advanced-lab)内にすべての必要なGit構造をセットアップし、それをGitリポジトリに変えます。git-advanced-lab内に名前が.gitの隠しフォルダが作成されるのが見えます。この.gitフォルダは、Gitリポジトリの核心であり、すべてのバージョン履歴と設定を保存します。

これでGitリポジトリが初期化されたので、作業用の簡単なファイルを作成して最初のコミットを行いましょう。

echo "Hello, Advanced Git" > hello.txt
git add hello.txt
git commit -m "Initial commit"

これらのコマンドで何が起こっているかを説明します。

  1. echo "Hello, Advanced Git" > hello.txtechoは、テキストを表示するコマンドです。"Hello, Advanced Git"は表示したいテキストです。>はリダイレクト演算子です。これは、echoコマンドの出力を受け取り、それを「hello.txt」という名前のファイルにリダイレクトします。「hello.txt」が存在しない場合は作成されます。既に存在する場合は、新しい内容で上書きされます。つまり、このコマンドは「Hello, Advanced Git」という内容の新しいファイル「hello.txt」を作成します。
  2. git add hello.txt:Gitがファイルの変更を追跡する前に、明示的にGitにそれを追跡するように指示する必要があります。git add hello.txtは、「hello.txt」ファイルをステージングします。ステージングとは、次のコミットに含めるためにファイルを準備することを意味します。ステージングエリアを、コミットの準備ゾーンと考えてください。
  3. git commit -m "Initial commit"git commitは、現在ステージングされているすべての変更(今回の場合は「hello.txt」の追加)を取り込み、それをリポジトリの履歴に新しいコミットとして保存します。-m "Initial commit"は、コミットメッセージを追加します。"Initial commit"自体がメッセージであり、コミットしている変更の短い説明になります。明確で簡潔なコミットメッセージを書くのは良い習慣です。

素晴らしい!これで1つのコミットがあるリポジトリができました。すべてが正しくセットアップされていることを確認するために、ステータスを確認しましょう。

git status

git statusを実行した後、ターミナルに次のようなメッセージが表示されるはずです。

On branch master
nothing to commit, working tree clean

このメッセージは、作業ツリーがクリーンであることを示しています。「作業ツリー」とは、プロジェクトファイルが配置されているディレクトリを指します。「クリーン」とは、まだコミットされていない作業ディレクトリに変更がないことを意味します。これは、クリーンな状態で高度な操作を始める準備ができていることを確認しています!

最後のコミットの修正

コミットをした直後に、あるファイルの変更を含め忘れてしまったり、コミットメッセージに小さな誤字があったりしたとしましょう。そのような小さな修正のために新しいコミットを作成する代わりに、Gitは--amendオプションを使って最新のコミットを修正することができます。これは、少しだけ時間を戻って最後の操作を調整するようなものです。

試してみましょう。まず、「hello.txt」ファイルに別の行を追加することで修正します。

echo "This is an important file." >> hello.txt

このコマンドは、「hello.txt」ファイルに新しい行を追加します。>>演算子について理解しましょう。

  • >は、新しい内容でファイル全体を上書きします。
  • >>は、新しい内容を既存のファイルの末尾に追加し、元の内容を保持します。

このコマンドを実行した後、「hello.txt」には2行が含まれるようになります。

Hello, Advanced Git
This is an important file.

今度は、最初のコミット自体にこの「重要なファイル」のメモを含めるべきだったことに気付いたとしましょう。この変更を含めて前のコミットを修正し、追加内容を反映するようにコミットメッセージを更新することができます。

git add hello.txt
git commit --amend -m "Initial commit with important note"

これらのコマンドの解説をします。

  1. git add hello.txt:「hello.txt」を修正したので、再びgit addを使って変更をステージングする必要があります。これにより、Gitに対して「hello.txt」の現在の状態をコミットに含めることを伝えます。
  2. git commit --amend -m "Initial commit with important note":ここでマジックが起こります。
    • git commit --amend--amendフラグは、新しいコミットを作成する代わりに、最後のコミットを修正することをGitに伝えます。
    • -m "Initial commit with important note":これは新しいコミットメッセージを提供します。-mオプションを省略すると、Gitはデフォルトのテキストエディタを開き、元のコミットメッセージを編集できるようにします。

何が起こったのでしょうか?

Git履歴に新しいコミットを作成する代わりに、Gitは以前の「Initial commit」を新しく改良されたバージョンに置き換えました。古いコミットは消え、新しいコミットにはステージングした変更と更新されたコミットメッセージが含まれます。

コミットログを確認することでこれを確認しましょう。

git log --oneline

出力にはただ1つのコミットが表示されるはずですが、コミットメッセージは「Initial commit with important note」になっているはずです。

<commit_hash> Initial commit with important note

ログビューが自動的に閉じない場合は、qキーを押してログビューを終了します。

修正時の重要な留意点

  • まだプッシュしていないコミットのみを修正すること:共有リモートリポジトリ(GitHub、GitLabなど)にまだプッシュしていないコミットのみを修正する必要があります。既にプッシュされたコミットを修正すると、他の人が依存している履歴が書き換えられ、共同作業環境で重大な問題や混乱を引き起こす可能性があります。
  • ローカル履歴のクリーンアップ:修正は主に、作業を共有する前にローカルのコミット履歴をクリーンアップするために役立ちます。たとえば、まだ機能ブランチで作業している間にコミットを微調整するのに便利な方法です。

コミットの元に戻す

時々、コミットをしてしまった後に、バグが導入されたことに気付いたり、単にそれらの変更を元に戻したいと思うことがあります。時を戻ってコミットを削除するためにgit resetを使うかもしれません。しかし、git resetは破壊的な場合があります。特に、変更を既にプッシュしている場合や、他の人が同じブランチで作業している場合にはそうです。変更を元に戻すより安全で協調的な方法は、git revertを使うことです。

git revertは、特定のコミットによって導入された変更を取り消す新しいコミットを作成します。履歴から元のコミットを消去するわけではありません。代わりに、不要なコミットの影響を実際に逆転させる新しいコミットを追加します。これにより履歴が保存され、共有リポジトリにとってははるかに安全です。

それでは、戻すコミットを作成しましょう。

echo "This line will be reverted" >> hello.txt
git add hello.txt
git commit -m "Add line to be reverted"

これらのコマンドは、以前に行ったものと似ています。

  1. echo "This line will be reverted" >> hello.txt:「hello.txt」ファイルに新しい行を追加します。
  2. git add hello.txt:変更されたファイルをステージングします。
  3. git commit -m "Add line to be reverted":「この行は元に戻されます」というメッセージで新しいコミットを作成し、この新しい行を「hello.txt」に追加します。

今度は、「この行は元に戻されます」を追加することが間違いだったと判断し、それを削除したいとしましょう。最後のコミットを次のコマンドを使って元に戻すことができます。

git revert HEAD

このコマンドを解説しましょう。

  • git revert:これは変更を元に戻すコマンドです。
  • HEAD:Gitでは、HEADは現在のブランチの現在のコミットへのポインタです。ほとんどの場合、HEADは最新のコミットを指します。したがって、git revert HEADは「最新のコミットを元に戻す」という意味になります。古いコミットを元に戻すには、特定のコミットハッシュを指定することもできます。

git revert HEADを実行すると、Gitは次のことを行います。

  1. 変更を分析するHEADコミット(今回の場合、「この行は元に戻されます」を追加するコミット)によって導入された変更を見ます。
  2. 逆の変更を作成する:それらの変更をどのように元に戻すかを考えます(今回の場合、「この行は元に戻されます」という行を「hello.txt」から削除すること)。
  3. 新しいコミットを作成する:これらの逆の変更を適用する新しいコミットを自動的に作成します。
  4. テキストエディタを開く(オプション):Gitは通常、元に戻すコミット用の事前に入力されたコミットメッセージ付きでデフォルトのテキストエディタ(Vim、Nano、VS Codeのエディタなど)を開きます。デフォルトのメッセージは通常、「Revert 'Add line to be reverted'」のようなものです。デフォルトのメッセージを受け付けるか、より詳細なコンテキストを提供するために修正することができます。

git revert中のテキストエディタの操作

デフォルトのエディタが開き、それに慣れていない場合(特にVimの場合)は心配しないでください!プロセスは通常簡単です。

  • デフォルトのメッセージを受け付けて元に戻しを完了するには
    • Vimの場合Escキーを押してコマンドモードに入り、次に:wq(保存して終了)と入力し、Enterキーを押します。
    • Nanoの場合Ctrl + X(終了)を押し、次にY(変更を保存)を押し、Enter(ファイル名を確認)を押します。
    • その他のエディタの場合:「保存」と「閉じる」または「終了」のオプションを探します。

エディタを保存して閉じた後(開いた場合)、Gitは元に戻すコミットを完了します。

今、コミット履歴を確認しましょう。

git log --oneline

ログには、逆順(最新のものが最初)で3つのコミットが表示されるはずです。

  1. 「Revert」で始まるコミット(先ほど作成した元に戻すコミット)。
  2. 「この行は元に戻されます」を追加するコミット(元に戻したコミット)。
  3. 「重要なメモ付きの初期コミット」(元の修正済みコミット)。
<commit_hash_revert> Revert "Add line to be reverted"
<commit_hash_original> Add line to be reverted
<commit_hash_initial> Initial commit with important note

今、「hello.txt」の内容を見ると、「この行は元に戻されます」という行が消えており、「この行は元に戻されます」を追加するコミットの変更が実際に元に戻されているはずです。

共有リポジトリではなぜgit revertgit resetより好まれるのか?

  • 破壊的でない履歴git revertは履歴を書き換えません。変更を元に戻すために新しいコミットを追加するので、協調作業に安全です。誰もが履歴の中に元の間違いと修正を見ることができます。
  • 強制プッシュを回避する:コミットを既にプッシュしている場合、git resetはしばしばリモートリポジトリに強制プッシュ(git push --force)が必要になります。強制プッシュは非常に混乱を招き、他の人の作業を上書きする可能性があります。git revertはこの問題を回避します。
  • 明確な監査トレイルgit revertは、いつ、なぜ変更が元に戻されたかを明確に記録します。これは、プロジェクトの進化と問題のデバッグを理解するために非常に重要です。

コミットのチェリーピック

Gitにおけるチェリーピックは、木からチェリーを摘むようなものです。つまり、特定のコミットを1つのブランチから選択して、現在のブランチに適用するということです。これは、特定の機能や修正を別のブランチから取り込みたいが、そのブランチ全体をマージしたくない場合に便利です。

別のブランチで開発された機能をメインブランチに持ち込みたいシナリオをシミュレートしましょう。

まず、新しいブランチを作成してその上でコミットを行います。

git checkout -b feature-branch
echo "This is a new feature" >> feature.txt
git add feature.txt
git commit -m "Add new feature"

これらのコマンドを解説します。

  1. git checkout -b feature-branch
    • git checkout:このコマンドは、ブランチを切り替えたり新しいブランチを作成したりするために使用されます。
    • -b feature-branch-bフラグは、git checkoutに対して「feature-branch」という名前の新しいブランチを作成し、そのブランチに直ちに切り替えるように指示します。したがって、このコマンドは新しいブランチを作成し、それをアクティブブランチにします。
  2. echo "This is a new feature" >> feature.txt:「feature-branch」内に、「This is a new feature」という内容の新しいファイル「feature.txt」を作成します。
  3. git add feature.txt:新しく作成された「feature.txt」ファイルをステージングします。
  4. git commit -m "Add new feature":「feature-branch」上に、「Add new feature」というメッセージでコミットを作成し、「feature.txt」の追加を含めます。

今度は、この機能についてはこれで終了し、メインブランチ(master)に統合したいとしましょう。まず、masterブランチに戻ります。

git checkout master

このコマンドは、アクティブブランチを「master」に簡単に切り替えます。

次に、「feature-branch」の「Add new feature」コミットをmasterブランチに適用します。このためにgit cherry-pickを使用します。

git cherry-pick feature-branch

待ってください。なぜfeature-branchでコミットハッシュではないのでしょうか?この簡単なケースでは、コミットとしてのfeature-branchは、feature-branch上の最新のコミットに解決されます。feature-branch上で1つのコミットしか作成していないので、これは実際にそのコミットをチェリーピックします。feature-branch上に複数のコミットがあり、特定のコミットをピックしたい場合は、代わりにその特定のコミットのコミットハッシュを使用します。たとえば、git log feature-branch --onelineを使用してコミットハッシュを見つけ、その後git cherry-pick <commit_hash>を使用することができます。

git cherry-pick feature-branchを実行すると、Gitは次のことを行います。

  1. コミットを見つける:「feature-branch」上の最新のコミット(またはハッシュで指定したコミット)を特定します。
  2. 変更を適用する:そのコミットによって導入された変更を、現在のブランチ(master)に適用しようとします。
  3. 新しいコミットを作成する:成功した場合(コンフリクトがない場合)、Gitはmasterブランチ上に、チェリーピックしたコミットと同じ変更と通常同じコミットメッセージを持つ新しいコミットを作成します。

masterブランチのコミットログを確認しましょう。

git log --oneline

masterブランチ上に、「Add new feature」というメッセージの新しいコミットが表示されるはずです。重要なことは、これはfeature-branch上のコミットとは異なるコミットハッシュを持つ新しいコミットであり、変更は同じであることです。

また、masterブランチ内のファイルを一覧表示すると(たとえばlsコマンドを使用して)、新しいファイル「feature.txt」も表示されるはずです。

チェリーピックの重要な留意点

  • 新しいコミット:チェリーピックは常に新しいコミットを作成します。元のコミットを移動またはコピーしません。コミット履歴を考える際にこれを覚えておくことが重要です。
  • 潜在的なコンフリクト:チェリーピックは時々コンフリクトを引き起こす場合があります。特に、2つのブランチのコードベースが大幅に分岐している場合です。コンフリクトが発生した場合は、Gitはチェリーピックプロセスを一時停止し、マージコンフリクトと同様に解決するように求めます。
  • 慎重に使用する:強力ではありますが、チェリーピックは慎重に使用する必要があります。チェリーピックを過度に使用すると、履歴を追跡するのが難しくなります。特に、後で機能ブランチ全体をマージする場合です。完全なマージが望ましくないか適切でない場合、1つのブランチから別のブランチに特定の孤立した変更や修正を適用する際に最も効果的です。

インタラクティブなリベース

インタラクティブなリベースは、Gitの最も強力で、潜在的に複雑な機能の1つです。これにより、非常に柔軟な方法でコミット履歴を書き換えることができます。コミットの順序を入れ替えたり、コミットを結合(スクエッシュ)したり、コミットメッセージを編集したり、コミット自体を完全に削除したりすることができます。コミット履歴に対して微調整できるエディタのようなものです。

警告:インタラクティブなリベースは、特に共有ブランチでは、履歴を書き換えるため、リスクがあります。既に共有リポジトリにプッシュされたコミットをリベースすることは決してしないでください。ただし、その影響を完全に理解し、チームと調整した場合を除きます。 リベースは、共有する前にローカルのコミットを整理するために、一般的にはより安全で適切です。

実際にインタラクティブなリベースを見てみるために、いくつかのコミットを作成しましょう。

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」ファイルに1行ずつ追加しながら、3つの新しいコミットを連続して作成します。git commit -am-amショートハンドは、すべての変更されたファイルと削除されたファイルをステージング(-a)し、コミット(-m)することを組み合わせています。既にファイルを追跡しており、それらに対する変更をコミットしたい場合に便利なショートカットです。

今度は、これらの3つのコミットを整理したいとしましょう。たとえば、「First change」と「Second change」は実際には同じ論理的な変更の一部であり、それらを1つのコミットに結合したい場合があります。また、「Third change」のコミットメッセージを改善したい場合もあります。インタラクティブなリベースはこれに最適です。

次のコマンドを使用してインタラクティブなリベースセッションを開始します。

git rebase -i HEAD~3

このコマンドを解説しましょう。

  • git rebase -igit rebaseはリベースするためのコマンドです。-iフラグは「インタラクティブ」を意味し、Gitに対してインタラクティブなリベースを実行したいことを伝えます。
  • HEAD~3:これはリベースするコミットの範囲を指定します。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.

インタラクティブなリベースエディタの理解

  • コミット一覧:ファイルの上部には、リベース操作の対象となるコミットが一覧表示されます。デフォルトでは、逆順(一番上が古い、一番下が最新の)で表示されます。
  • コマンド:コミット一覧の下には、各コミットを変更するために使用できる利用可能なコマンドの一覧がGitによって提供されます。最も一般的なものは以下の通りです。
    • pick(またはp):コミットをそのまま使用します。これがデフォルトです。
    • reword(またはr):コミットを使用しますが、コミットメッセージを変更できるようにします。
    • edit(またはe):コミットを使用しますが、このコミットでリベースプロセスを停止して、さらなる変更(たとえば、ファイルを修正したり、追加の変更を加えたり)を行うことができます。
    • squash(またはs):このコミットを一覧の前のコミットに結合します。このコミットのコミットメッセージは、前のコミットのメッセージに追加されます(後で結合されたメッセージを編集できます)。
    • fixup(またはf):squashと同様ですが、このコミットのコミットメッセージを破棄し、前のコミットのメッセージのみを使用します。
    • drop(またはd):このコミットを履歴から完全に削除します。

インタラクティブなリベースの計画

以下のことを行いたいとします。

  1. 「Second change」を「First change」に結合する
  2. 「Third change」のコミットメッセージをより良いものに変更する

これを達成するには、エディタファイルを以下のように変更します。

pick abc1234 First change
squash def5678 Second change
reword ghi9101 Third change

エディタファイル内のコミットハッシュを変更しないでください。

Vim(エディタがVimの場合)での編集の手順

  1. 挿入モードに入るiキーを押します。これにより、ファイルを入力および編集できるようになります。
  2. 変更を行う:矢印キーを使って行に移動し、上記のように「pick」を「squash」と「reword」に変更します。
  3. 挿入モードを終了するEscキーを押します。
  4. 保存して終了する:wqと入力し、Enterキーを押します。

エディタファイルを保存して閉じた後、Gitは指示に基づいてリベース操作を開始します。

まず、コミットを結合する

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.
#
## Date:      Tue Oct 24 10:30:00 2023 +0000
#
## interactive rebase in progress; onto <base_commit_hash>
#
## Last commands done (2 commands done):
##    pick abc1234 First change
##    squash def5678 Second change
## Next command to do (1 remaining command):
##    reword ghi9101 Third change
## You are currently rebasing branch'master' on '<base_commit_hash>'.

このエディタは、結合されるコミットの元のコミットメッセージを表示します。これで、結合されたコミットに対して1つの統一的なコミットメッセージを作成するために、このテキストを編集できます。たとえば、次のように変更できます。

Combined first and second changes: Initial setup of hello.txt

メッセージを編集した後、エディタを保存して閉じます。

次に、コミットメッセージを変更する

その後、Gitは「Third change」の「reword」コマンドに移ります。再びエディタを開き、今度は「Third change」のコミットメッセージを編集できるようにします。元のメッセージが表示されます。

Third change

## Please enter the commit message for your changes. Lines starting
## with '#' will be ignored, and an empty message aborts the commit.
#
## Date:      Tue Oct 24 10:35:00 2023 +0000
#
## interactive rebase in progress; onto <base_commit_hash>
#
## Last commands done (3 commands done):
##    pick abc1234 First change
##    squash def5678 Second change
##    reword ghi9101 Third change
## Next command to do (0 remaining commands):
##    (finish)
## You are currently rebasing branch'master' on '<base_commit_hash>'.

メッセージをより詳細なものに変更します。たとえば:

Improved third change: Added a more descriptive line to hello.txt

エディタを保存して閉じます。

リベースの完了

インタラクティブなリベースの指示に含まれるすべてのコマンドを処理した後、Gitはリベースプロセスを完了します。通常、「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を使用してリベース操作をキャンセルし、リベースを開始する前の状態にブランチを戻すことができます。これは、何かが悪くなった場合にリベースを取り消す安全な方法です。

ルートからのリベース

ステップ5では、git rebase -i HEAD~3を使って最後の3つのコミットをリベースしました。しかし、インタラクティブなリベースはさらに強力です。git rebase -i --rootを使うと、リポジトリの履歴の最初のコミット(ルートコミット)から始まって、すべてのコミットをインタラクティブにリベースできます。これにより、プロジェクト全体の履歴を再構築するための究極のコントロールが与えられます。

極めて注意:ルートリベースは非常に強力な操作であり、その力だけに大きな責任が伴います(誤用すると重大な問題が発生する可能性があります)。共有リポジトリでのルートリベースはほとんどの場合悪い考えであり、例外的で十分に理解された理由があり、かつチーム全体と慎重に調整した後にのみ行うべきであることを避けてください。これは履歴全体を完全に書き換え、ルート以降のすべてのコミットのコミットハッシュを変更します。他の人が同じリポジトリで作業している場合、これは大きな混乱を引き起こします。

この実験では、ローカルの孤立したリポジトリで作業しているため、ルートリベースを試してその仕組みを学ぶのは安全です。ただし、本番の共同作業プロジェクトでは警告を忘れないでください!

ルートリベースを試してみましょう。

git rebase -i --root

このコマンドは、前のリベースと非常に似ていますが、HEAD~3の代わりに--rootを使っています。--rootは、インタラクティブなリベースのために、リポジトリの履歴の最初からすべてのコミットを対象にするようGitに指示します。

これを実行すると、デフォルトのテキストエディタが再び開きますが、今回は最初のコミット(「重要なメモ付きの初期コミット」またはそれを修正したもの)から始まって、リポジトリ内のすべてのコミットが一覧表示されます。前のステップよりも長い一覧が表示されるはずです。

次のようなものが表示されるかもしれません(コミットハッシュとメッセージは、前のステップをどのように行ったかによって異なります)。

pick <hash_initial> Initial commit with important note
pick <hash_reverted> Add line to be reverted
pick <hash_revert_commit> Revert "Add line to be reverted"
pick <hash_new_feature> Add new feature
pick <hash_combined> Combined first and second changes: Initial setup of hello.txt
pick <hash_third_revised> Improved third change: Added a more descriptive line to hello.txt

ルートリベースの計画

この演習では、このルートリベースでいくつかのことを行いましょう。

  1. 最初のコミットのメッセージを変更する。初期コミットのメッセージをさらに微調整したいとしましょう。
  2. 「Revert」コミットを、元に戻した行を導入したコミットに結合する。これは多少工夫した例ですが、履歴を操作する方法を示しています。「Revert」コミットは本当は初期の間違いを整理するための一部に過ぎないと考えられるので、間違いを導入したコミットに結合できます。(実際のシナリオでは、リバートをスクエッシュすることが常に望ましいわけではありません。なぜなら、変更を元に戻す明確な記録が提供されるからです)。

この計画を実現するには、エディタファイルを次のように変更します。

reword <hash_initial> Initial commit with important note
squash <hash_reverted> Add line to be reverted
pick <hash_revert_commit> Revert "Add line to be reverted"
pick <hash_new_feature> Add new feature
pick <hash_combined> Combined first and second changes: Initial setup of hello.txt
pick <hash_third_revised> Improved third change: Added a more descriptive line to hello.txt

最初のコミットに対しては「pick」を「reword」に、2番目のコミット(「この行は元に戻されます」を追加するコミット)に対しては「pick」を「squash」に変更しました。今は「Revert」コミットを「pick」のままにしておきます。

エディタを保存して閉じます。

まず、初期コミットのメッセージを変更する

Gitは最初に一時停止し、初期コミットのメッセージを変更できるようにします。エディタが開き、現在のメッセージ「重要なメモ付きの初期コミット」(またはステップ2で修正したもの)が表示されます。これを「初期セットアップとファイル作成」のように変更します。エディタを保存して閉じます。

次に、「この行は元に戻されます」を追加するコミットを初期コミットに結合する

その後、Gitは「squash」コマンドを処理します。「この行は元に戻されます」を追加するコミットの変更を、前のコミット(今は「初期セットアップとファイル作成」)に結合します。エディタが再び開き、結合されたコミットメッセージを編集できるようになります。両方のコミットの元のメッセージが表示されます。結合された変更を反映する新しいメッセージを作成できます。たとえば、メッセージを次のように変更できます。

Initial setup and file creation with subsequent line removal

エディタを保存して閉じます。

リベースの完了

Gitは残りのコミット(残りのコミットに対する「pick」)の処理を続け、ルートリベースを完了します。

ルートリベースされた履歴を確認する

最後にコミットログを確認しましょう。

git log --oneline

ルートリベースで指示した変更が反映された、コミット履歴全体が表示されるはずです。

  • 最初のコミットには、新しいメッセージ「初期セットアップとファイル作成、その後の行の削除」(または類似のもの)が表示されるはずです。
  • 「この行は元に戻されます」を追加するコミットと「Revert」コミットはまだ表示されるかもしれません。または、リベースの指示によって結合されているかもしれません。(上記の例の指示では、「Revert」を「pick」のままにしているので、まだ表示されるはずです)。
  • ルートコミット以降の履歴全体でコミットハッシュが変更されています。

再び、特に共有リポジトリでは、ルートリベースには極めて注意してください! このステップは主に教育目的で、インタラクティブなリベースの力と柔軟性を示すためのものです。

まとめ

おめでとうございます、Gitの達人!あなたはこれまで以上にバージョン管理のスキルを高めました。この実験で学んだ強力な技術を振り返りましょう。

  1. コミットの修正:これまでのコミットに小さな誤りを修正したり、小さな変更を加えることができます。新しいコミットを作成することなく、小さな調整のために履歴をクリーンに保つことができます。
  2. コミットの元に戻す:前のコミットを逆転させる新しいコミットを作成することで、安全に変更を元に戻す方法を学びました。これは、共同作業環境での誤りを修正するための破壊的でない方法です。
  3. チェリーピック:特定のコミットを1つのブランチから別のブランチに適用できるようになりました。どの変更を取り込むかを細かくコントロールできるため、機能や修正を選択的に持ち込むのに最適です。
  4. インタラクティブなリベース:履歴を書き換える技術を習得しました。他人と共有する前に、コミットを整理して整えることができます。これには、コミットの順序を入れ替えたり、スクエッシュしたり、コミットメッセージを編集して、より洗練された履歴を作成することが含まれます。
  5. ルートリベース:最初のコミットから始まって、プロジェクト全体の履歴を再構築する方法を学びました。リポジトリの物語に対する究極のコントロールが与えられます。これに関連する力とリスクを忘れないでください!

これらの高度なGit操作は、開発者のツールキットにおいて非常に強力なツールです。クリーンで整理されたコミット履歴を維持することができ、大規模なプロジェクトを行ったり、チームと共同作業したりする際に重要です。整ったコミット履歴は、美観のためだけでなく、コードレビュー、デバッグ、およびプロジェクトの経時的な進化の理解を大幅に改善します。