Git コミットの高度な操作

GitBeginner
オンラインで実践に進む

はじめに

おかえりなさい、Git アドベンチャーの皆さん!バージョン管理の世界への第一歩を踏み出した今、次はスキルをさらにレベルアップさせる時です。この実験では、Git のより高度なコミット操作について探求します。これらのテクニックを習得することで、プロジェクトの履歴をより自在にコントロールできるようになり、ミスの修正、作業の整理、そしてより効果的な共同作業が可能になります。

この実験は、あなたのタイムマシンをアップグレードするようなものだと考えてください。単に過去に遡るだけでなく、タイムラインそのものを書き換える方法を学びます!難しそうに聞こえるかもしれませんが、心配はいりません。各ステップを丁寧に進め、操作方法だけでなく、実際の開発現場でなぜそれが必要なのかという理由も併せて解説します。

この実験を終える頃には、コミットの修正(amend)、変更の打ち消し(revert)、特定のコミットの抽出(cherry-pick)、インタラクティブなリベース(interactive rebase)、コミットの集約(squash)ができるようになります。これらは、プロのエンジニアがクリーンで整理されたプロジェクト履歴を維持するために日々使用している強力なツールです。それでは、Git スキルを次のレベルへと引き上げましょう!

ワークスペースのセットアップ

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

ターミナルを開き、以下のコマンドを入力して、各行の後に Enter を押してください。

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

これらのコマンドが何を行っているか、順を追って説明します。

  1. cd ~/project: cd は「ディレクトリの移動(change directory)」を意味します。~/project は通常、ホームディレクトリ(~)内の「project」フォルダを指すパスです。このコマンドでそのディレクトリに移動します。もしホームディレクトリに「project」ディレクトリがない場合はエラーになることがあります。その場合は、まず mkdir ~/project で作成してから、再度 cd ~/project を試してください。
  2. mkdir git-advanced-lab: mkdir は「ディレクトリの作成(make directory)」を意味します。現在のディレクトリ(前のコマンドで移動した ~/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.txt: echo はテキストを表示するコマンドです。"Hello, Advanced Git" が表示したいテキストです。> はリダイレクト演算子で、echo コマンドの出力を「hello.txt」という名前のファイルに書き込みます。ファイルが存在しない場合は作成され、存在する場合は新しい内容で上書きされます。
  2. git add hello.txt: Git がファイルの変更を追跡できるようにするには、明示的に追跡を開始するよう指示する必要があります。git add hello.txt は「hello.txt」ファイルをステージング(staging)します。ステージングとは、次のコミットに含めるためにファイルを準備することを意味します。ステージングエリアは、コミットのための「準備ゾーン」だと考えてください。
  3. git commit -m "Initial commit": git commit は、現在ステージングされているすべての変更(今回は「hello.txt」の追加)を取り込み、リポジトリの履歴に新しいコミットとして保存します。-m "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.

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

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 は以前の「Initial commit」を新しい改良版に事実上置き換えました。古いコミットは消え、新しいコミットにはステージングした変更と更新されたメッセージが含まれています。

コミットログを確認して、これを確認しましょう。

git log --oneline

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

<commit_hash> Initial commit with important note

ログ表示が自動的に終了しない場合は、q を押して終了してください。

修正(Amend)時の重要な注意点:

  • プッシュ前のコミットのみを修正する: まだ共有のリモートリポジトリ(GitHub、GitLab など)にプッシュしていないコミットのみを修正するようにしてください。すでにプッシュされたコミットを修正すると、他の人が利用している履歴を書き換えることになり、共同作業において大きな混乱や問題を引き起こす可能性があります。
  • ローカル履歴の整理: 修正(amend)は、主に作業を共有する前に「ローカル」のコミット履歴をきれいにするために役立ちます。例えば、機能ブランチで作業中にコミットを洗練させるのに最適な方法です。

コミットを打ち消す(Revert)

コミットをした後で、それがバグを引き起こしたことに気づいたり、単に変更を取り消したくなったりすることがあります。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": この新しい行の追加を含む、「Add line to be reverted」というメッセージの新しいコミットを作成します。

さて、「This line will be reverted」という行を追加したのは間違いだったと判断し、削除したいとします。最新のコミットを打ち消すには、以下を使用します。

git revert HEAD

コマンドを分解してみましょう。

  • git revert: 変更を打ち消すためのコマンドです。
  • HEAD: Git において HEAD は現在のブランチの最新のコミットを指すポインタです。多くの場合、HEAD は直近のコミットを指します。したがって、git revert HEAD は「最新のコミットを打ち消す」ことを意味します。特定のコミットハッシュを指定して、より古いコミットを打ち消すこともできます。

git revert HEAD を実行すると、Git は以下の処理を行います。

  1. 変更の分析: HEAD コミット(今回は「Add line to be reverted」)によって導入された変更を確認します。
  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(ファイル名を確認)を押します。
    • その他のエディタ: 「保存(Save)」して「閉じる(Close)」または「終了(Exit)」するオプションを探してください。

エディタを保存して閉じると、Git はリバートコミットを確定させます。

現在のコミット履歴を確認しましょう。

git log --oneline

ログには、新しい順に 3 つ のコミットが表示されるはずです。

  1. 「Revert」で始まるコミット(今作成したリバートコミット)。
  2. 「Add line to be reverted」(打ち消された元のコミット)。
  3. 「Initial commit with important note」(最初に修正したコミット)。
<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 の内容を確認すると、「This line will be reverted」という行は消えており、「Add line to be reverted」コミットの変更が事実上取り消されていることがわかります。

なぜ共有リポジトリでは git reset よりも git revert が好まれるのでしょうか?

  • 非破壊的な履歴: git revert は履歴を書き換えません。変更を元に戻すための新しいコミットを追加するため、共同作業において安全です。履歴を見れば、誰でも元のミスとその修正を確認できます。
  • 強制プッシュの回避: git reset を使用してすでにプッシュ済みのコミットを消すと、リモートリポジトリへの強制プッシュ(git push --force)が必要になります。強制プッシュは非常に破壊的で、他人の作業を上書きしてしまう可能性があります。git revert はこの問題を回避します。
  • 明確な監査証跡: git revert は、いつ、なぜ変更が取り消されたかの明確な記録を提供します。これはプロジェクトの進化を理解し、問題をデバッグする際に非常に価値があります。

コミットのチェリーピック(Cherry-picking)

Git におけるチェリーピック(Cherry-picking)は、木からサクランボを摘むようなものです。あるブランチから特定のコミットを選び、それを現在のブランチに適用します。これは、別のブランチから特定の機能や修正だけを取り込みたい場合に便利です。

別のブランチで開発された機能があり、その機能だけをメインブランチに取り込みたいというシナリオをシミュレートしてみましょう。

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

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 フラグは、「feature-branch」という名前の新しいブランチを作成し、すぐにそのブランチに切り替えるよう指示します。
  2. echo "This is a new feature" >> feature.txt: 「feature-branch」ブランチで、「feature.txt」という新しいファイルを作成します。
  3. git add feature.txt: 新しく作成したファイルをステージングします。
  4. git commit -m "Add new feature": 「feature-branch」上に、このファイルの追加を含むコミットを作成します。

さて、この機能の開発が一旦完了し、これをメインブランチ(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」では 1 つしかコミットしていないため、事実上そのコミットがチェリーピックされます。もし複数のコミットがあり、特定のものをピックしたい場合は、そのコミットのハッシュを使用します。例えば、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」にあるコミットとは異なるハッシュを持つ新しいコミットであるということです。

また、ls コマンドなどでファイル一覧を確認すると、master ブランチにも「feature.txt」が存在していることがわかります。

チェリーピック時の重要な注意点:

  • 新しいコミット: チェリーピックは常に新しいコミットを作成します。元のコミットを移動したりコピーしたりするわけではありません。履歴を考える上でこれは重要です。
  • 競合の可能性: 2 つのブランチのコードベースが大きく異なっている場合、チェリーピックによって競合(コンフリクト)が発生することがあります。競合が発生した場合、Git は処理を一時停止し、マージ競合の時と同様に解決を求めます。
  • 控えめに使用する: チェリーピックは強力ですが、慎重に使用すべきです。多用しすぎると、後で機能ブランチ全体をマージする際に履歴が追いにくくなることがあります。フルマージが望ましくない、あるいは適切でない場合に、特定の独立した変更や修正を適用するのに最も効果的です。

インタラクティブなリベース(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): このコミットを履歴から完全に削除します。

今回のリベース計画:

やりたいことは以下の通りです。

  1. 「Second change」を「First change」に結合(squash)する。
  2. 「Third change」のメッセージをより良いものに書き換える(reword)。

これを実現するために、エディタの内容を以下のように書き換えます。

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 は指示に基づいてリベースを開始します。

まず、コミットの結合(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 を使用してリベース操作をキャンセルし、リベース開始前の状態に戻すことができます。これは、何かがうまくいかなかったときに安全に撤退する方法です。

ルートからのリベース

ステップ 5 では、git rebase -i HEAD~3 を使用して直近 3 つのコミットをリベースしました。しかし、インタラクティブ・リベースはさらに強力です。git rebase -i --root を使用すると、リポジトリの履歴にあるすべてのコミット(最初の「ルート」コミットから)を対象にリベースを行うことができます。これにより、プロジェクトの全履歴を再構築する究極のコントロールが可能になります。

細心の注意: ルートリベースは極めて強力な操作であり、大きな力には大きな責任(そして誤用による大きな問題の可能性)が伴います。共有リポジトリでのルートリベースは、例外的な理由があり、チーム全員と慎重に調整済みでない限り、ほぼ常に悪いアイデアであり、避けるべきです。 これは全履歴を完全に書き換え、ルート以降のすべてのコミットハッシュを変更してしまいます。同じリポジトリで作業している他の人に多大な混乱をもたらします。

この実験では、ローカルの隔離されたリポジトリで作業しているため、ルートリベースがどのように機能するかを学ぶために実験しても安全です。実際の共同プロジェクトでの警告は忘れないでください!

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

git rebase -i --root

このコマンドは前のリベースと似ていますが、HEAD~3 の代わりに --root を使用します。--root は、リポジトリの最初からすべてのコミットを対象にすることを Git に伝えます。

これを実行すると、デフォルトのエディタが開き、今回はリポジトリ内のすべてのコミット(最初の「Initial commit with important note」などから)がリストされます。

以下のようなリストが表示されるはずです。

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

今回のルートリベース計画:

この演習では、以下の 2 つのことを行います。

  1. 最初のコミットのメッセージを変更する。最初のコミットメッセージをさらに洗練させたいとします。
  2. 「Revert」コミットを、打ち消しの対象となった元のコミットに結合(squash)する。これは少し不自然な例ですが、履歴をどのように操作できるかを示すためのものです。「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

最初のコミットを「reword」に、2 番目のコミット(「Add line to be reverted」)を「squash」に変更しました。「Revert」コミットは「pick」のままにしておきます。

保存して閉じます。

まず、最初のコミットの書き換え:

Git はまず、最初のコミットのメッセージを書き換えるために一時停止します。エディタが開き、現在のメッセージが表示されるので、例えば「Initial setup and file creation」に変更して保存し、閉じます。

次に、「Add line to be reverted」を最初のコミットに結合:

次に Git は「squash」コマンドを処理します。「Add line to be reverted」の変更内容を、その前のコミット(新しく書き換えた最初のコミット)に統合します。エディタが開き、結合後のメッセージを編集できます。例えば、以下のように変更します。

Initial setup and file creation with subsequent line removal

保存して閉じます。

リベースの完了:

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

ルートリベース後の履歴の確認:

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

git log --oneline

ルートリベースで指示した通りの履歴が表示されるはずです。

  • 最初のコミットが新しいメッセージ「Initial setup and file creation with subsequent line removal」になっています。
  • 指示の出し方によっては、コミットが整理され、履歴がよりスッキリしているはずです。
  • ルートコミット以降、すべてのコミットハッシュが新しくなっています。

繰り返しますが、共有リポジトリでのルートリベースには細心の注意を払ってください! このステップは、インタラクティブ・リベースのパワーと柔軟性を実証するための教育目的のものです。

まとめ

おめでとうございます、Git マスター!バージョン管理スキルがさらに向上しました。この実験で学んだ強力なテクニックを振り返ってみましょう。

  1. コミットの修正(Amend): 些細なミスを修正したり、最新のコミットに小さな変更を加えたりする方法を学びました。新しいコミットを増やさずに履歴をきれいに保てます。
  2. 変更の打ち消し(Revert): 以前のコミットを打ち消す新しいコミットを作成することで、安全に変更を取り消す方法を学びました。これは共同作業環境において非破壊的な修正方法です。
  3. チェリーピック(Cherry-pick): 特定のコミットをあるブランチから別のブランチへ適用する方法を学びました。必要な変更だけを選択的に取り込むことができます。
  4. インタラクティブなリベース(Interactive Rebasing): 履歴を書き換える技術を習得しました。コミットの順序変更、結合、メッセージの編集を行い、洗練された履歴を作成できます。
  5. ルートリベース(Root Rebasing): プロジェクトの最初のコミットから履歴全体を再構築する方法を学びました。リポジトリの物語を完全にコントロールできる究極の手段です。その強力さとリスクを忘れないでください。

これらの高度な Git 操作は、開発者のツールキットにある非常に強力なツールです。これらを活用することで、クリーンで整理されたコミット履歴を維持できます。これは大規模なプロジェクトやチーム開発において極めて重要です。整理された履歴は、単に見栄えが良いだけでなく、コードレビューやデバッグ、そしてプロジェクトがどのように進化してきたかを理解する助けになります。