2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

git: どのブランチにも属していない迷子のコミットに関する備忘録

Last updated at Posted at 2023-09-03

LICENSE: CC BY 4.01, 0BSD2

git reflog の活用シーン

ブランチ名がひとつしか付いていないブランチを未マージにも関わらず強制削除したり git reset --hard をしたりすると、そこにあったコミットは「どのブランチにも属していないコミット」になってしまう。このようなコミットを本記事では 迷子コミット と呼ぶことにする。迷子コミットは git log--all オプションを付けても表示されなくなってしまうため消失してしまったかのように見えるが、内部的には残っている。消してはいけないブランチをうっかり消してしまったときなど、このような迷子コミットをサルベージしたい場合には git reflog を見て復旧するのがセオリーである。

git commit --amend したときや git rebase したときにも、実は修正前のコミットやリベース前のコミットが残っており、これらも「どのブランチにも属していないコミット」である。このようなコミットは迷子というよりは脱皮後の抜け殻という感じだろうか。

git reflog のグラフ表示

git reflog を見ると HEAD が以前に指していたコミットなどがわかるため迷子コミットをサルベージすることができる。しかし git log --graph によるグラフ表示のように、迷子コミットがどこから生えていたのかを目視で確認したい場合もある。そのような場合には git log--reflog オプションを付ければ、git reflog に載っているエントリを拾った git log を表示してくれる。

# 情報量が多くなるので適宜 oneline 表示などを使うとよい
$ git log --all --graph --oneline --reflog

紛らわしいのだが、git reflog showgit log --walk-reflogs --oneline のエイリアスである。git log において --walk-reflogs オプションは --graph オプションと同時に使うことができないため、git reflog--graph オプションを付けてもグラフ表示させることはできない。

最強の git log(のバグ修正版)

さきほどの git log --reflog でも出てこない 激しく迷子な コミットも拾う “最強の git log” なる手法がある。

ただしこちらの記事で紹介されているコマンドでは、もし git fsck が出力するハッシュ値が大量になるとコマンドライン引数の上限を回避するために xargs が自動で引数群を分割してしまうため、複数回に分けて git log が実行されてしまう。この問題を解決するには xargs を使うのではなく、git log--stdin オプションで標準入力からハッシュ値を読み込むようにすればよい。

further-git-log.sh
#!/bin/sh
git fsck --verbose 2>&1 | grep -o -E '[0-9A-Fa-f]{40}' | grep -v -E '0{40}' | sort -u | git log --stdin "$@"

.gitconfig で以下のようにエイリアスを設定すれば、git further-log と書くだけで一発で “最強の git log” を呼び出せるため便利である(便利だが、使わざるを得ない状況には陥りたくないものである)。

.gitconfig
[alias]
	further-log = "!sh -c 'git fsck --verbose 2>&1 | grep -o -E \"[0-9A-Fa-f]{40}\" | grep -v -E \"0{40}\" | sort -u | git log --stdin \"$@\"' DUMMY"

上のエイリアス設定では sh -c 'ほげほげ' DUMMY のように末尾に DUMMY が付いているが、これは必要なダミー引数である。'ほげほげ' の中で $@ を使っているのだが、sh-c オプションで実行する場合には普通にシェルスクリプトを実行する場合と違ってコマンドライン引数が $1 からではなく $0 から順番に格納される仕様になっているためである。dash や bash などでも同様。ダミーなので別に DUMMY という文字列である必要はない。本来の用途からすると sh という文字列にしておくのが正しい気がする。

sh -c [-abCefhimnuvx] [-o option]...
      [+abCefhimnuvx] [+o option]...
      command_string [command_name [argument...]]

-c : Read commands from the command_string operand. Set the value of special parameter 0 (see Section 2.5.2, Special Parameters) from the value of the command_name operand and the positional parameters ($1, $2, and so on) in sequence from the remaining argument operands. No commands shall be read from the standard input.
-- https://manpages.ubuntu.com/manpages/jammy/en/man1/sh.1posix.html

$ sh -c 'echo "{0: $0}, {@: $@}"'
{0: sh}, {@: }
$ sh -c 'echo "{0: $0}, {@: $@}"' aaa bbb ccc
{0: aaa}, {@: bbb ccc}
$ sh -c 'echo "{0: $0}, {@: $@}"' DUMMY aaa bbb ccc
{0: DUMMY}, {@: aaa bbb ccc}

git log --reflog と “最強の git log” の違い

このセクションの内容はちゃんと調べていないので不正確かもしれない。

git log --reflog では出てこなくて “最強の git log” では出てくる迷子コミットは、恐らく git fsck --unreachable で出てくるものだと思われる。このような git fsck で Unreachable 判定となっている 激しく迷子な コミットは git gc を実行するとガベージコレクトされ、完全に消滅する(多分)。つまり、風前の灯火な 迷子コミットである。

迷子コミットの完全削除

迷子コミットをまったくサルベージできなくなってしまうため実行する際には注意すること。

例えば GitHub のようなリモートリポジトリに機密情報をアップロードしてしまった事故に際しては、ローカルリポジトリで歴史改変作業を行う過程で必然的に機密情報が迷子コミットとして追いやられるはずだが、リモートリポジトリにおける機密情報の削除が完了するまではローカルリポジトリの迷子コミットを削除してはいけない。すべてが終わるまでは情報保全をしておくこと。

機密情報アップロード事故に際しては以下の記事やドキュメントが参考になるはずである(なお、本記事筆者はこの手の事故対応をしたことがないし以下のリンク先も斜め読みしかしていない)。

一方、すべてが終わった後ならば機密情報がサルベージされないようにするために迷子コミットは完全に削除されるべきだが、そのような重大な事案の場合には上で紹介したウェブページでも書かれているように、ローカルリポジトリを破棄してリモートから clone しなおしたほうが確実だと思われる。

迷子コミットを git reflogまだ残っているものも含めて完全に削除するには以下のふたつのコマンドを実行する。

$ git reflog expire --expire-unreachable=now --all
$ git gc --prune=now

-- https://stackoverflow.com/questions/3765234/listing-and-deleting-git-commits-that-are-under-no-branch-dangling

迷子コミットといえど単に git gc するだけではそうそう削除されない。そのため、まず git reflog expire で迷子コミットを reflog から一掃する。そうすると迷子コミットは 風前の灯火な 迷子コミットとなり、ガベージコレクトの対象となる(git gc する前ならまだ git fsck --unreachable で出てくる)。

  1. 本記事のライセンスは CC BY 4.0 とします。

  2. 前記1に関わらず、引用部分を除き、本記事内のコードスニペットのライセンスは 0BSD とします。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?