git addした日時をしらべる

# TL;DR

`git add`だけされて未コミットのファイル群がある場合、
```shell:Macの場合
ls -lT .git/objects/ | sort -k6
```

これで列挙されるディレクトリ群で、タイムスタンプが最新のものが、「最後にgit addした日時」である可能性が高い。

各ディレクトリ内のファイルは`zlib`で圧縮されているので、最新時刻のディレクトリ内のファイルを解凍した中身と、現在`git add`済みのファイルを比較して一致すれば間違いない。

# 背景

複数のGitリポジトリを1つのエディタで開いて色々と作業していた時に、`git status`したら、`git add`だけされて未コミットの状態（ステージングエリアに入っているだけ）のファイルがいくつか見つかりました。

そのファイルは読むためにエディタで開いていましたが、編集した記憶はありません。
以下のどっちなのか分からず、不安になりました。

* 実は操作ミスで今日編集してaddしちゃった
* かなり前にaddだけしてcommit忘れて放置しちゃった

ところが、`git add`した時刻って、gitの標準コマンドでは調べられないっぽいんですよね。お助けコマンドの`git reflog`にも、`reset`記録は出てくるけど`add`はノーマーク。

でも、条件によってはなんとか調べることができたので、一応メモとして書いておきます。

# 実演

# `.git/objects` 内のディレクトリ群

`.git`配下のファイルを直接確認します。仕組みを理解しやすいために、`git init`から順番に実演します。
（シェルプロンプトはfish標準の「Informative Vcs」で、gitの状態が見やすくなっています。）

初期状態の`.git/objects`内には、`info` `pack`の2ディレクトリがある。

```shell
~ $ mkdir repo
~ $ cd repo
~/repo $ git init
Initialized empty Git repository in /Users/the_red/repo/.git/
~/repo (main|✔) $ ls -l .git/objects/
total 0
drwxr-xr-x  2 the_red  staff  64  8  4 11:32 info
drwxr-xr-x  2 the_red  staff  64  8  4 11:32 pack
```

空ファイルを作ってみるが、これだけでは変わらず。

```shell
~/repo (main|✔) $ touch .gitignore
~/repo (main|…) $ ls -l .git/objects/
total 0
drwxr-xr-x  2 the_red  staff  64  8  4 11:32 info
drwxr-xr-x  2 the_red  staff  64  8  4 11:32 pack
```

`git add`してみると、`e6`というフォルダが増えている！

```shell
~/repo (main|…) $ git add .
~/repo (main|●1) $ ls -l .git/objects/
total 0
drwxr-xr-x  3 the_red  staff  96  8  4 11:33 e6
drwxr-xr-x  2 the_red  staff  64  8  4 11:32 info
drwxr-xr-x  2 the_red  staff  64  8  4 11:32 pack
```

コミットしてみると、さらにフォルダが2つ増えた。
addとcommitのどちらの場合もフォルダが作られるようですね。
コミットのハッシュ値は`44becd9`なので、`44`ディレクトリがコミットに相当するよう。
```shell
~/repo (main|●1) $ git commit -m 'Initial commit'
INFO[0000] opening .                                    
INFO[0000] scan time: 301 microseconds                  
INFO[0000] No leaks found                               
[main (root-commit) 44becd9] Initial commit
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 .gitignore
~/repo (main|✔) $ ls -l .git/objects/
total 0
drwxr-xr-x  3 the_red  staff  96  8  4 11:33 44
drwxr-xr-x  3 the_red  staff  96  8  4 11:33 82
drwxr-xr-x  3 the_red  staff  96  8  4 11:33 e6
drwxr-xr-x  2 the_red  staff  64  8  4 11:32 info
drwxr-xr-x  2 the_red  staff  64  8  4 11:32 pack
```

しかし11:33のディレクトリが3つもあって、順番が分からない。秒まで知りたい。

Macでは`-lT`オプションを付けると秒まで出してくれる。
（Linuxでは`--full-time`らしい）

```shell
~/repo (main|✔) $ ls -lT .git/objects/
total 0
drwxr-xr-x  3 the_red  staff  96  8  4 11:33:52 2022 44
drwxr-xr-x  3 the_red  staff  96  8  4 11:33:52 2022 82
drwxr-xr-x  3 the_red  staff  96  8  4 11:33:25 2022 e6
drwxr-xr-x  2 the_red  staff  64  8  4 11:32:35 2022 info
drwxr-xr-x  2 the_red  staff  64  8  4 11:32:35 2022 pack
~/repo (main|✔) $ 
```

あとは、ディレクトリ順のソートを時系列にソートしたい。普通はディレクトリがもっともっと多いので、目視ではとても探せないので。
パイプで`sort -k6`を実行すると、スペース区切りで左から6番目の「8(月)」以降だけを見てソートしてくれる。

```shell
~/repo (main|✔) $ ls -lT .git/objects/ | sort -k6
total 0
drwxr-xr-x  2 the_red  staff  64  8  4 11:32:35 2022 info
drwxr-xr-x  2 the_red  staff  64  8  4 11:32:35 2022 pack
drwxr-xr-x  3 the_red  staff  96  8  4 11:33:25 2022 e6
drwxr-xr-x  3 the_red  staff  96  8  4 11:33:52 2022 44
drwxr-xr-x  3 the_red  staff  96  8  4 11:33:52 2022 82
```

いい感じ！
ためしに、ファイルの中身を書き換えてまたaddしてみる。

```shell
~/repo (main|✔) $ echo .env >> .gitignore
~/repo (main|✚1) $ git add .
~/repo (main|●1) $ ls -lT .git/objects/ | sort -k6
total 0
drwxr-xr-x  2 the_red  staff  64  8  4 11:32:35 2022 info
drwxr-xr-x  2 the_red  staff  64  8  4 11:32:35 2022 pack
drwxr-xr-x  3 the_red  staff  96  8  4 11:33:25 2022 e6
drwxr-xr-x  3 the_red  staff  96  8  4 11:33:52 2022 44
drwxr-xr-x  3 the_red  staff  96  8  4 11:33:52 2022 82
drwxr-xr-x  3 the_red  staff  96  8  4 11:40:27 2022 4c
```

最下部に、新しいディレクトリ`4c`ってのが出来てますね。

ここまでで、以下の2点が確認できたことになります。

* 最終のadd時刻は`8/4 11:40:27` (`4c`)
* コミット`44becd9`直前のadd時刻は`8/4 11:33:25` (`e6`)



```shell:Macの場合
ls -lT .git/objects/ | sort -k6
```

これで列挙されるディレクトリ群で、タイムスタンプが最新のものが、「最後にgit addした日時」である可能性が高い。


ここまで確認すればほぼ十分な気はするけど、さすがにタイムスタンプだけでは不安だったりもするので、もう少し深堀りする方法も次に書きます。

# `.git/objects/**/` 内のファイル群

もっと確実に調べたいなら、該当ディレクトリ（今回は`4c`）内をさらに確認してあげると良い。

```shell
~/repo (main|●1) $ ls -lT .git/objects/4c/
total 8
-r--r--r--  1 the_red  staff  20  8  4 11:40:27 2022 49bd78f1d08f2bc09fa0bd8191ed38b7dce5e3
```

ファイルが1つある。
`add`済みのファイルが複数ある場合は、ここにも複数ファイルができる。
`cat`で見てあげれば良いかと思いきや、、、

```shell
~/repo (main|●1) $ cat .git/objects/4c/49bd78f1d08f2bc09fa0bd8191ed38b7dce5e3
xK��OR0e�K�+�=v⏎                                                                                                                  
```

バイナリらしく、そのままでは見れない。
`file`コマンドで正体を確認。

```shell
~/repo (main|●1) $ file .git/objects/4c/49bd78f1d08f2bc09fa0bd8191ed38b7dce5e3 
.git/objects/4c/49bd78f1d08f2bc09fa0bd8191ed38b7dce5e3: zlib compressed data
```

`zlib`形式で圧縮されたデータとのこと。

`zip`とか`gzip`ならOS標準コマンドで解凍できるんだけど、
`zlib`を解凍するには、別途ツールが必要。僕はこれをインストールしました。

https://ebc-2in2crc.hatenablog.jp/entry/2017/10/24/220717
```shell
brew tap ebc-2in2crc/zlibcmd
brew install zlibcmd
```

これで`zlib`コマンドが使えるようになったので、解凍してみる。

```shell
~/repo (main|●1) $ cat .git/objects/4c/49bd78f1d08f2bc09fa0bd8191ed38b7dce5e3 | zlib --decompress
blob 5.env
```

* `blob`がファイルタイプ、
* `5`が容量（改行入れて5バイト）
* `.env`がファイルの中身

ですね。今回はこれで間違いない！
ちなみにファイル名`.gitignore`は、別の場所で管理されています。

もっとちゃんとやろうとすると色々とあるんだろうけど、今回はこれくらいで止めておきますｗ

## `git gc`には要注意

僕はほとんど使わないんですけど、`git gc`というコマンドで、
一時ファイルを綺麗に掃除してgitの動作を軽くすることができます。

これがまさに、今回調べた`.git/objects`配下を消すという行為なので、
`git gc`してしまったら、この記事の調査方法は使えなくなりますのでご注意！
（`git reflog`も使えなくなります）

```shell
~/repo (main|●1) $ ls -lT .git/objects/ | sort -k6
total 0
drwxr-xr-x  2 the_red  staff  64  8  4 11:32:35 2022 info
drwxr-xr-x  2 the_red  staff  64  8  4 11:32:35 2022 pack
drwxr-xr-x  3 the_red  staff  96  8  4 11:33:25 2022 e6
drwxr-xr-x  3 the_red  staff  96  8  4 11:33:52 2022 44
drwxr-xr-x  3 the_red  staff  96  8  4 11:33:52 2022 82
drwxr-xr-x  3 the_red  staff  96  8  4 11:40:27 2022 4c
~/repo (main|●1) $ git gc
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Writing objects: 100% (4/4), done.
Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
~/repo (main|●1) $ ls -lT .git/objects/ | sort -k6
total 0
drwxr-xr-x  4 the_red  staff  128  8  4 12:19:52 2022 info
drwxr-xr-x  4 the_red  staff  128  8  4 12:19:52 2022 pack
```


# 参考リンク

Gitのステージング領域の正体を探る
https://engineering.mercari.com/blog/entry/2017-04-06-171430/

ファイル時刻の秒単位まで取得
https://tech.withsin.net/2016/12/26/ls-full-time/


# おわりに

冒頭に書いた疑問

* 実は操作ミスで今日編集してaddしちゃった
* かなり前にaddだけしてcommit忘れて放置しちゃった

は、後者であることが分かりました。
add分は念の為別ブランチに退避してGitHubでブランチをプロテクトして、
メインのブランチは最終コミットに戻して、安心して開発を再開できました。

ごくまれにこういうケースあると思うので、誰かの役に立てたら嬉しいっす。
ではまた〜。