はじめに
この実験では、特定の Git コミットが現在の HEAD から到達可能かどうかを判断する方法を学びます。これは、リポジトリの履歴を理解し、アクティブな開発ラインの一部であるコミットを特定するための基本的なスキルです。
2 つの主要な方法を探ります。2 つのポイント間のコミットの系譜を視覚化するための git log --ancestry-path コマンドの使用と、祖先関係を直接チェックするための git merge-base --is-ancestor コマンドの使用です。複数のブランチとコミットを持つサンプルリポジトリを設定し、一部のコミットを意図的に HEAD から到達不可能にすることで、これらの技術を練習し、理解をテストして確認することができます。
git log --ancestry-path を使用する
このステップでは、git log --ancestry-path コマンドの使い方を探ります。このコマンドは、2 つのコミット間の特定のパスに沿ったコミット履歴を表示するのに便利です。変更の系譜を理解するのに役立ちます。
まず、簡単な Git リポジトリを作成し、いくつかのコミットを行って --ancestry-path を使用するシナリオを設定しましょう。
プロジェクトディレクトリに移動します。
cd ~/project
この実験用の新しいディレクトリを作成し、Git リポジトリを初期化します。
mkdir ancestry-lab
cd ancestry-lab
git init
空の Git リポジトリが初期化されたことを示す出力が表示されるはずです。
Initialized empty Git repository in /home/labex/project/ancestry-lab/.git/
次に、ファイルを作成し、最初のコミットを行いましょう。
echo "Initial content" > file1.txt
git add file1.txt
git commit -m "Initial commit"
コミットが確認される出力が表示されます。
[master (root-commit) <commit-hash>] Initial commit
1 file changed, 1 insertion(+)
create mode 100644 file1.txt
次に、別のコミットを行いましょう。
echo "Adding more content" >> file1.txt
git add file1.txt
git commit -m "Add more content"
2 番目のコミットの出力が表示されます。
[master <commit-hash>] Add more content
1 file changed, 1 insertion(+)
次に、新しいブランチを作成し、そのブランチでコミットを行いましょう。
git branch feature
git checkout feature
echo "Feature work" > file2.txt
git add file2.txt
git commit -m "Add feature file"
ブランチの作成、切り替え、および新しいコミットの出力が表示されます。
Switched to a new branch 'feature'
[feature <commit-hash>] Add feature file
1 file changed, 1 insertion(+)
create mode 100644 file2.txt
マスターブランチに戻り、別のコミットを行いましょう。
git checkout master
echo "More master work" >> file1.txt
git add file1.txt
git commit -m "More master content"
ブランチの切り替えと新しいコミットの出力が表示されます。
Switched to branch 'master'
[master <commit-hash>] More master content
1 file changed, 1 insertion(+)
これで、ブランチを持つコミット履歴ができました。git log を使って全履歴を見てみましょう。
git log --all --decorate --oneline
次のようなログが表示されます(コミットハッシュと順序は異なる場合があります)。
<commit-hash> (HEAD -> master) More master content
<commit-hash> Add more content
<commit-hash> (feature) Add feature file
<commit-hash> Initial commit
次に、git log --ancestry-path を使いましょう。このコマンドには 2 つのコミット参照が必要です。2 番目のコミットの祖先で、1 番目のコミットの子孫であるコミットが表示されます。
「Initial commit」と「More master content」のコミットハッシュを見つけましょう。これらは git log --all --decorate --oneline の出力から取得できます。<initial-commit-hash> と <master-commit-hash> を実際のハッシュに置き換えます。
git log --ancestry-path <initial-commit-hash> <master-commit-hash> --oneline
このコマンドは、初期コミットからマスターブランチの最新コミットまでのパス上のコミットを表示します。「Initial commit」、「Add more content」、および「More master content」のコミットが表示されるはずです。
--ancestry-path オプションは、履歴内の 2 つのポイント間の直接的な開発の流れを理解するのに便利で、後でマージされた可能性のある他のブランチのコミットは無視します。
git merge-base --is-ancestor を実行する
このステップでは、git merge-base --is-ancestor について学びます。このコマンドは、あるコミットが別のコミットの祖先であるかどうかを確認するために使用されます。これは単純なチェックで、コミット情報を出力するのではなく、ステータスコード(真の場合は 0、偽の場合は 1)を返します。これは、スクリプトでの使用や迅速なチェックに特に便利です。
前のステップで作成した ancestry-lab リポジトリを引き続き使用します。正しいディレクトリにいることを確認してください。
cd ~/project/ancestry-lab
前のステップのコミット履歴を思い出してください。master ブランチと feature ブランチの両方にコミットがあります。
「Initial commit」と master ブランチの最新コミット(「More master content」)のコミットハッシュを見つけましょう。最近のコミットを見るには、git log --oneline を使用できます。
git log --oneline
出力は次のようになります。
<master-commit-hash> (HEAD -> master) More master content
<commit-hash> Add more content
<initial-commit-hash> Initial commit
次に、git merge-base --is-ancestor を使用して、「Initial commit」が master の最新コミットの祖先であるかどうかを確認します。<initial-commit-hash> と <master-commit-hash> を実際のハッシュに置き換えてください。
git merge-base --is-ancestor <initial-commit-hash> <master-commit-hash>
echo $?
echo $? コマンドは、前のコマンドの終了ステータスを出力します。最初のコミットが 2 番目のコミットの祖先である場合、終了ステータスは 0 になります。そうでない場合は 1 になります。
「Initial commit」は確かに master の最新コミットの祖先であるため、echo $? の出力は 0 になるはずです。
次に、「Initial commit」が feature ブランチの最新コミットの祖先であるかどうかを確認しましょう。まず、「Add feature file」コミットのコミットハッシュを見つけます。
git log --all --decorate --oneline
出力は次のようになります。
<master-commit-hash> (HEAD -> master) More master content
<commit-hash> Add more content
<feature-commit-hash> (feature) Add feature file
<initial-commit-hash> Initial commit
次に、git merge-base --is-ancestor を使用して、「Initial commit」が「Add feature file」コミットの祖先であるかどうかを確認します。<initial-commit-hash> と <feature-commit-hash> を実際のハッシュに置き換えてください。
git merge-base --is-ancestor <initial-commit-hash> <feature-commit-hash>
echo $?
再び、「Initial commit」は両方のブランチの起点であるため、echo $? の出力は 0 になるはずです。
最後に、feature の最新コミットが master の最新コミットの祖先であるかどうかを確認しましょう。<feature-commit-hash> と <master-commit-hash> を実際のハッシュに置き換えてください。
git merge-base --is-ancestor <feature-commit-hash> <master-commit-hash>
echo $?
この場合、feature の最新コミットは master の最新コミットの祖先ではありません(初期の分岐後、異なるブランチにあります)。したがって、echo $? の出力は 1 になるはずです。
コミット間の祖先関係を理解することは、Git が履歴を追跡する方法や、マージやリベースなどの操作がどのように機能するかを理解するための基本です。--is-ancestor フラグは、この関係を確認する簡単な方法を提供します。
到達不能なコミットをテストする
このステップでは、Git における「到達不能(unreachable)」なコミットの概念を探ります。到達不能なコミットとは、どのブランチ、タグ、またはその他の参照からも到達できないコミットのことです。これらのコミットは、git log のような標準コマンドで見られる現在のプロジェクト履歴の一部ではありません。
ancestry-lab リポジトリを引き続き使用します。正しいディレクトリにいることを確認してください。
cd ~/project/ancestry-lab
現在、すべてのコミットは master または feature ブランチから到達可能です。コミットが到達不能になるシナリオを作成しましょう。
まず、master ブランチに新しいコミットを作成します。
echo "Temporary commit" >> file1.txt
git add file1.txt
git commit -m "Temporary commit"
この新しいコミットの出力が表示されます。
[master <commit-hash>] Temporary commit
1 file changed, 1 insertion(+)
次に、master ブランチを前のコミットにリセットします。これにより、「Temporary commit」は master ブランチから到達不能になります。git reset --hard HEAD~1 を使用します。HEAD~1 は現在の HEAD の直前のコミットを指します。
git reset --hard は変更を破棄するので注意してください! この場合、意図的に master ブランチの履歴から「Temporary commit」を破棄しています。
git reset --hard HEAD~1
HEAD が現在前のコミットにあることを示す出力が表示されます。
HEAD is now at <previous-commit-hash> More master content
次に、標準の git log を見てみましょう。
git log --oneline
「Temporary commit」が master ブランチのログ出力にもう含まれていないことがわかります。
<previous-commit-hash> (HEAD -> master) More master content
<commit-hash> Add more content
<initial-commit-hash> Initial commit
「Temporary commit」は依然として Git データベースに存在しますが、どのブランチやタグからも参照されていません。これが「到達不能」なコミットです。
到達不能なコミットをどうやって見ることができるでしょうか?Git には reflog と呼ばれる特別な参照があり、ブランチの先端やその他の参照の更新を記録します。--walk-reflogs オプション付きの git log または単に git reflog を使用して、これらのコミットを見ることができます。
git reflog を使用しましょう。
git reflog
行われたアクションのログが表示され、作成してからリセットしたコミットも含まれます。
<master-commit-hash> (HEAD -> master) master@{0}: reset: moving to HEAD~1
<temporary-commit-hash> master@{1}: commit: Temporary commit
<previous-commit-hash> master@{2}: commit: More master content
<commit-hash> master@{3}: commit: Add more content
<initial-commit-hash> master@{4}: commit (initial): Initial commit
「Temporary commit」のエントリに注目してください。reflog 内の master@{1} を介して到達可能です。ただし、現在の HEAD やどのブランチの先端からは到達不能です。
到達不能なコミットは、最終的に Git のガベージコレクション(git gc)によって削除されますが、デフォルトの期間(通常は 30 日または 90 日)は reflog を介してアクセス可能です。誤ってコミットをリセットまたは削除した場合、これは大きな助けになります。
到達不能なコミットを理解することで、Git の内部オブジェクトデータベースと、そのデータベース内のコミットを指す参照(ブランチ、タグ、HEAD)の違いを理解するのに役立ちます。
まとめ
この実験では、2 つの主要な方法を使って、Git のコミットが HEAD から到達可能かどうかを確認する方法を学びました。まず、git log --ancestry-path コマンドを調べました。このコマンドを使うと、指定された 2 つのコミット間のパスに沿ったコミット履歴を視覚化できます。複数のブランチとコミットを持つシンプルなリポジトリをセットアップし、このコマンドが血統を理解し、あるコミットが別のコミットの祖先であるかどうかを特定するのにどのように役立つかを示しました。
次に、git merge-base --is-ancestor コマンドの使い方を学びます。このコマンドは、あるコミットが別のコミットの祖先であるかどうかをより直接的かつプログラム的に判断する方法を提供します。最後に、到達不能なコミットでこれらの方法をテストし、Git リポジトリ内で到達可能性を検証する方法を理解を深めます。



