プログラムなどのバージョン管理を行える、無料のソースコード管理 (SCM: software configuration management) ソフト Git で、gc (garbage collect) 機能により、どのコミットからも到達不能になり削除される対象になった dangling コミットを、削除される前に復旧する手順の例を紹介します。

※Git-2.49.0 (64 bit) を使用します。
前回の記事
前回は TortoiseGit を用いて、 git reset –hard と同様の Hard オプションを指定したリセットを行い、最新の3回目のコミットから、作業ツリー(HEAD, インデックス, 作業ディレクトリ)を2回目のコミットの状態にリセットしました。
hard リセットしたコミットより後のコミットに戻すにはreflogを使用
hard オプションで指定したコミットの状態に作業ツリーをリセットすると、HEAD・インデックス・作業ディレクトリのファイル群などがそのコミットの状態に戻されるため、そのコミットよりも後のコミットを復元する際に reflog を用いる必要があります。
Git – リセットコマンド詳説
--hard
オプションを使った場合に限り、reset
コマンドは危険なコマンドになってしまうことを覚えておいてください。Git がデータを完全に削除してしまう、数少ないパターンです。reset
コマンドの実行結果は簡単に取り消せますが、--hard
オプションに限ってはそうはいきません。作業ディレクトリを強制的に上書きしてしまうからです。 ここに挙げた例では、v3 バージョンのファイルは Git のデータベースにコミットとしてまだ残っていて、reflog
を使えば取り戻せます。ただしコミットされていない内容については、上書きされてしまうため取り戻せません。
reflog からも削除されたコミットは gc 削除対象になります
どのコミットからも到達不能になったコミット(例では最新の3回目のコミット)は、 reflog から一定期間後 (デフォルトでは 90 日後)に消されて、その結果 dangling commit (Google 翻訳:
ぶら下がりコミット) になり、 gc により削除されます。
それまでは、まだそのコミット自体は存在します。
–expire=<time>
Prune entries older than the specified time. If this option is not specified, the expiration time is taken from the configuration setting
gc.reflogExpire
, which in turn defaults to 90 days.--expire=all
prunes entries regardless of their age;--expire=never
turns off pruning of reachable entries (but see--expire-unreachable
).指定された時間より古いエントリをプルーニングします。このオプションが指定されていない場合、有効期限は構成設定 gc.reflogExpire から取得され、デフォルトでは 90 日になります。 --expire=all は、経過時間に関係なくエントリを削除します。 --expire= は、到達可能なエントリのプルーニングをオフにすることはありません (ただし、--expire-unreachable を参照してください)。Git – git-reflog Documentation
reflog からも消されたコミットのハッシュ値を得て復旧するには fsck
reflog からも削除されたコミット (dangling commit) は、自動的に行われる gc auto の dangling commit の削除の際に、削除されます。
その削除の前に git fsck –full コマンドで検出された dangling commit のハッシュ値を用いることで、コミットを復旧することができます。
git fsck –full コマンドでも検出されなくなった場合は、そのコミットは gc によって削除された後なので、復旧はおそらくできません。
git fsck
ユーティリティーを使用してデータベースの完全性をチェックする方法があります。--full
オプションを付けて実行すると、他のどのオブジェクトからも指されていないオブジェクトをすべて表示します。このケースでは、 “dangling commit” という文字列の後に失われたコミットが表示されています。 前と同様にこのSHA-1ハッシュを指すブランチを作成すれば、失われたコミットを取り戻せます。
Git – メンテナンスとデータリカバリ
サンプルリポジトリのあるフォルダを作業ディレクトリにしてコマンドプロンプトで git fsck –full コマンドを実行しても、まだ dangling commit は検出されていません。
G:\Dev\StudyTortoiseGit\Repo1ResetHard>git fsck --full
Checking object directories: 100% (256/256), done.
gc から削除される対象の dangling commit の作成
dangling commit の復旧を行う前に、最新のコミット(例では3回目のコミット)を dangling commit にします。
手順としては、以下の2段階で、最新のコミットが dangling commit になります。
※ 手順2は行わなくても git fsck –full で dangling commit として認識されるかもしれません。
- HEAD を最新のひとつ前のコミット( 2 回目のコミット)に移動
- 移動した2回目のコミットから、新たな3回目のコミットを行い、最新のコミットを他のコミットから到達不能にする
- reflog に残っている最新のコミット(3回目のコミット)の情報を、「到達不能なコミットの削除」によって削除する
HEAD をひとつ前のコミットに移動
最新のコミット(例では3回目のコミット)を dangling commit にするために、1つ前のコミット(例では2回目のコミット)に HEAD (現在のコミット位置)を移動します。
今回は、 HEAD 以外の作業ツリーの要素であるインデックスと作業ディレクトリも、1つ前のコミットの状態に戻すため Hard オプションのリセットを行います。
サンプルのリポジトリフォルダをエクスプローラで開いて、右クリックして表示されるメニュー「Git コミット”」を選択します。
※ Git コマンドを使用する場合は、メッセージだけのコミットなどを行ってください。

1つ前のコミットから新たなコミットを行う
追記:1つ前のコミットから新たなコミットを行わなくても、削除したいコミットよりも古いコミットに git reset で移動して、 reflog から到達不能なコミットを削除するだけで dangling commit になりました。その手順については以下の記事を参照してください。
https://compota-soft.work/wp1/wp-admin/post.php?post=53952&action=edit
以下この章では、文章の流れとして、1つ前のコミットから、新たなコミットをメッセージだけで行う手順を残します。
「コミット」ダイアログが表示されたら、「メッセージ」テキストエリアにコミットの説明文を書いて、左下の「メッセージのみ」チェックボックスにチェックをして、「コミット」ボタンを押して、特にファイルの変更などがない状態でメッセージだけでコミットを行います。

これで、以前とは異なる3回目のコミットが行われました。

reflog の到達不能なコミットを削除 & dangling commit の検出
新たな3回目のコミットを行い、以前の3回目のコミットが2回目のコミットとのつながりがなくなっても(全てのコミットから到達不能になっても)、 git fsck –full コマンドで dangling commit は検出されません。
G:\Dev\StudyTortoiseGit\Repo1ResetHard>git fsck --full
Checking object directories: 100% (256/256), done.
G:\Dev\StudyTortoiseGit\Repo1ResetHard>git log --reflog --oneline
f960404 (HEAD -> master) 最新のコミットの1つ前のコミットから、新たに別のコミットを行います。
fa795f0 file.txt に 2 行目を追加
1f1f4dd file.txt に hello, world を追記。
e2829b8 file.txt をバージョン管理に追加します。
dangling commit にならない原因は、 reflog にまだ、以前の3回目の記録が残っているからでした。
reflog は到達不能になったコミットも、デフォルトでは 90 日間記録を保ちます。
これは、うっかりコミットを消しても復旧するための猶予期間かもしれません。
今回は、 dangling commit の状態にして、そこから復旧する手順を確認するため、
git reflog expire --expire-unreachable=now --all
で reflog の到達不能なコミットを削除するコマンドを実行して、猶予期間に関係なく到達不能なコミットを削除します。
G:\Dev\StudyTortoiseGit\Repo1ResetHard>git reflog expire --expire-unreachable=now --all
G:\Dev\StudyTortoiseGit\Repo1ResetHard>git log --reflog --oneline
f960404 (HEAD -> master) 最新のコミットの1つ前のコミットから、新たに別のコミットを行います。
1f1f4dd file.txt に hello, world を追記。
e2829b8 file.txt をバージョン管理に追加します。
その結果、もう一度 git fsck –full コマンドで dangling commit を調べると、以前の3回目のコミットのハッシュ値 (SHA-1) が表示されました。
G:\Dev\StudyTortoiseGit\Repo1ResetHard>git fsck --full
Checking object directories: 100% (256/256), done.
dangling commit fa795f083559ba016c00be644bd24601ac587f41
これで、自動的に行われる gc auto が実行されれば、この dangling commit として検出された以前の3回目のコミットは削除されます。
今回は、gc に削除される前に dangling commit をすぐに復旧します。

dangling commit の復旧
前の章で git fsck –full コマンドで検出された dangling commit のハッシュ値 (SHA-1) を用いて、ハッシュ値が指している以前の3回目のコミットを復旧します。
G:\Dev\StudyTortoiseGit\Repo1ResetHard>git fsck --full
Checking object directories: 100% (256/256), done.
dangling commit fa795f083559ba016c00be644bd24601ac587f41
git checkout ハッシュ値
を実行して、作業ツリーをハッシュ値の指す、以前の3回目のコミットの状態に上書きします。
G:\Dev\StudyTortoiseGit\Repo1ResetHard>git checkout fa795f083559ba016c00be644bd24601ac587f41
Note: switching to 'fa795f083559ba016c00be644bd24601ac587f41'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
HEAD is now at fa795f0 file.txt に 2 行目を追加
その結果、以下のような説明が表示されました。
この dangling コミットの情報を保持するためには、 swtich コマンドで新たなブランチを作り、そこにコミットを割り当てる必要があります。
「切り離された HEAD」状態です。周りを見回したり、実験したりすることができます 変更を加えてコミットすると、この中で行ったコミットは破棄できます。 ブランチに切り替えることで、ブランチに影響を与えることなく状態を維持できます。 作成したコミットを保持するために新しいブランチを作成したい場合は、次のようにします。 これを (今すぐまたは後で) switch コマンドで -c を使用して行います。例:dangling commit のハッシュ値を指定して git checkout を行った際のメッセージと Google 翻訳
git switch -c 新しいブランチ名
を入力して、復旧したいコミットをチェックアウトした現在の作業ツリーを新しいブランチに切り替えます。
G:\Dev\StudyTortoiseGit\Repo1ResetHard>git switch -c new-branch
Switched to a new branch 'new-branch'
dangling commit の復旧の確認
作成した新しいブランチに、そこに dangling commit から復旧したコミットが割り当てられていることを確認します。
サンプルのリポジトリフォルダをエクスプローラで開いて、右クリックで表示されるメニュー「TortoiseGit」→「リビジョングラフ」を選択して、「リビジョングラフ」ダイアログを表示します。
「リビジョングラフ」ダイアログには、先ほど追加した新しいブランチもありますが、先に、最初からある master ブランチのログを確認します。
master ブランチの枠を右クリックして表示されるメニュー「ログを表示」を選択します。

こちらは、先ほど Hard オプションのリセットで作業ツリー全体を2回目のコミットに戻した後に行った新しい3回目のコミットが確認できました。

次に、先ほど dangling commit を checkout した後に、そのコミットの情報を保持するために switch -c で新しく作成したブランチ new-branch のログを確認します。
new-branch の枠を右クリックして表示されるメニュー「ログを表示」を選択します。

一度は、他のコミットから到達不能になり、 reflog からも削除されて dangling commit になって削除されるのを待っていたコミットが、新たなブランチに、コミット時のメッセージも含めて復旧されました。

まとめ
今回は、プログラムなどのバージョン管理を行える、無料のソースコード管理 (SCM: software configuration management) ソフト Git で、gc (garbage collect) 機能により、どのコミットからも到達不能になり削除される対象になった dangling コミットを、削除される前に復旧する手順の例を紹介しました。
参照サイト Thank You!
- Git
- TortoiseGit – Windows Shell Interface to Git
- Git – リセットコマンド詳説
- リビジョングラフ
- リビジョンログダイアログ
- Git – git-log Documentation
- Git – git-reset Documentation
- Git – git-checkout Documentation
- Git – git-reflog Documentation
- Git – gitcli Documentation
- Git – メンテナンスとデータリカバリ
記事一覧 → Compota-Soft-Press
コメント