gitのコミット日時をファイルの更新日時と一致させる方法

# はじめに
ファイル整理をしていて、過去のプロジェクトをgitで管理したいと思った。
だけど、ファイルの更新日時がすべて管理した日になるのは、なんかもったいない。
なので、あくまでもコミットしたのはその過去更新した日にしたいと思ったのがきっかけでいろいろ調べて、うまくいったので投稿した。

今回使用したスクリプトファイルはすべて、[スクリプト集(github)](https://github.com/tama14142356/scripts_util)に管理しているので、ここの説明を見るのが億劫な場合は、いきなりコードを見て、実行してみてもいいです。

# 目次
1. [前準備](#前準備)
1. [最終的なコマンド](#最終的なコマンド)  
（細かい説明を見たくない場合は、ここだけ見てください。）
1. [試行錯誤](#試行錯誤)
1. [参考文献](#参考文献)

# 前準備
## 前提条件
### 試した環境
<dl>
 <dt> OS </dt>
 <dd> windows10 Pro </dd>
 <dt> 必須ソフトウェア </dt>
 <dd> git for windows(git bash付き) </dd>
</dl>

### おそらくできるであろう環境
<dl>
 <dt> OS </dt>
 <dd> linux, mac, windows10 </dd>
 <dt> 必須ソフトウェア </dt>
 <dd> 何らかのgit </dd>
 <dd> linuxコマンドのdateコマンドが機能するターミナル </dd>
</dl>

## gitのインストール方法
### windows10
今回のメインではないので、詳しい説明は省きますが、[いまさらGit for Windowsのインストール、GitHubに接続してみた。](https://qiita.com/manabu-watanabe/items/ecf1b434baf305adaa00)に書いてある通り、インストールすれば、git bashも一緒にインストールされるはずです。

### mac
触ったことがなく、あまり知らないので、ご容赦ください。
以下のサイトが参考になるかもしれません。
- [【Mac】Gitの環境構築をしよう！](https://prog-8.com/docs/git-env)
- [最新の Git を Mac にインストールする手順](https://qiita.com/normalsalt/items/f200ba50363ebfd46df0)

### linux(ubuntu18.04LTS)
大体はプリインストールされています。
インストールされていない場合は、以下のコマンドをターミナルで打てば、インストールできると思います。
他のディストリビューションに関しては、触ったことがないので、ご容赦ください。
インストールされていない場合は、すいませんが、検索してください。

```
$ sudo apt install git
```

## 使用するコマンドの説明
今回かなり回りくどく、普通の使い方ではない使い方をしようとしているので、いろいろとコマンドが複雑に絡み合い、難しくなってしまっているため、今回使用したコマンドの中で、意味が分かっていた方がいいと思うコマンドの意味を紹介します。

### `GIT_COMMITTER_DATE=\"<date>\" git commit --allow-empty -m "<comment>" --date "<date>"`
gitの初期コミット時に空のコミットをするためのコマンド
さらに、そのコミット日時を`<date>`にする。

### `GIT_COMMITTER_DATE=\"<date>\" git commit -m "<comment>" --date "<date>"`
gitのコミットをするためのコマンド
さらに、そのコミット日時を`<date>`にする。

### `date -r "<filename>" '+%Y-%m-%dT%H:%M:%S%z'`
ファイルの更新日のみを表示するコマンド
`ls`コマンドでも、ファイルの更新日は表示可能だが、その後につなげにくいので、`date`コマンドを使用

### `ls -1atrd $(find "$(pwd)" -type f) | head -1`
現在いるフォルダ配下のすべてのファイルの更新日の中で、一番古い更新日のファイルを出力するコマンド。
`head -1`は一行目を抜き出すだけのコマンド。
よって、`ls -1atrd $(find "$(pwd)" -type f)`がこのコマンドの重要な部分です。
これに関しては、以下の参考サイトに書いてあることを組み合わせたものです。
- [ls -lコマンドの結果をサブディレクトリ含めてフルパスで列挙したい](https://qiita.com/dwatarub/items/768c5eb263b49198952e) 
- [【 ls 】コマンド（並べ替え編）――表示結果を並べ替える](https://www.atmarkit.co.jp/ait/articles/1607/05/news023.html)

### `git rev-parse --show-toplevel`
gitレポジトリのルートディレクトリを表示してくれるコマンドです。
つまり、`.git`ディレクトリが存在するディレクトリの名前を示します。

### `git ls-files "<filename>" --full-name`
ファイル`<filename>`が、git管理化にあるファイルであれば、先ほどのルートディレクトリからの相対パスを表示します。
今回は、これを逆手に取り、`git add`できない.gitignoreで除外されたファイルを特定するのに使用しました。
つまり、このコマンドの表示がファイル名であれば、除外されていないファイルなので、コミットできるようにしました。


# 最終的なコマンド
基本方針はコミット日時をファイルの更新日に上書きしてしまうという方針です。
この方針で、ファイルの更新日にコミットの日時を合わせる場合は、以下のコマンドでできます。
また、以下のコマンドを利用したスクリプト(bash スクリプト)は[スクリプト集(github)](https://github.com/tama14142356/scripts_util)で管理しているので、よければ、参照してください。

## 複数ファイル(ディレクトリ)
以下のbashファイルは新規作成でも、既存レポジトリ使用する場合でも使えます。
gitで管理したいディレクトリのルートディレクトリでこのスクリプトを実行すれば完了
ただし、コミットの数は、ファイルの数だけになり、多くなってしまいます。

```bash
#!/bin/bash
IFS=$'\n'
root_dir=$(git rev-parse --show-toplevel 2>tmp.txt)
rm tmp.txt
if [ -z "$root_dir" ]; then
    git init
fi
pushd "$root_dir" || exit
file_list=$(find "$(pwd)" -type f -not -path "*/.git/*")
is_first_commit=$(git branch)
if [ -z "$is_first_commit" ]; then
    oldest_file_name=$(ls -1atrd $file_list | head -1)
    date_info=$(date -r "$oldest_file_name" '+%Y-%m-%dT%H:%M:%S%z')
    GIT_COMMITTER_DATE=\"$date_info\" git commit --allow-empty -m "first commit" --date "$date_info"
fi

old_file_list=$(ls -1atrd $file_list)
for file in ${old_file_list}; do
    echo "$file"
    base_name=$(basename "$file")
    dir_name=$(dirname "$file")
    pushd "$dir_name" || exit
    date_info=$(date -r "$base_name" '+%Y-%m-%dT%H:%M:%S%z')
    git add "$base_name"
    git_relative_path=$(git ls-files "$base_name" --full-name)
    popd || exit
    if [ -f "$git_relative_path" ]; then
        GIT_COMMITTER_DATE=\"$date_info\" git commit -m "feat: add $git_relative_path" --date "$date_info"
    fi
done
popd || exit
```

## 単一ファイル
### コミットに上書き

```bash 
GIT_COMMITTER_DATE=\"$(date -r "<filename>" '+%Y-%m-%dT%H:%M:%S%z')\" git commit --amend --date \"$(date -r "<filename>" '+%Y-%m-%dT%H:%M:%S%z')\"
```

### 新たなコミット作成

```bash 
GIT_COMMITTER_DATE=\"$(date -r "<filename>" '+%Y-%m-%dT%H:%M:%S%z')\" git commit --date \"$(date -r "<filename>" '+%Y-%m-%dT%H:%M:%S%z')\"
```
### 空コミットの場合（初期コミット）

```bash 
GIT_COMMITTER_DATE=\"$(date -r "<filename>" '+%Y-%m-%dT%H:%M:%S%z')\" git commit --allow-empty --date \"$(date -r "<filename>" '+%Y-%m-%dT%H:%M:%S%z')\"
```

### 空コミットを上書き（初期コミット）

```bash 
GIT_COMMITTER_DATE=\"$(date -r "<filename>" '+%Y-%m-%dT%H:%M:%S%z')\" git commit --allow-empty --amend --date \"$(date -r "<filename>" '+%Y-%m-%dT%H:%M:%S%z')\"
```

# 試行錯誤
ここは、僕の今回のコマンドにたどり着くまでの思考の変遷をメモしたものなので、本当に読み飛ばして構いません。
## システムの時刻をいじる
参考文献の[Gitがファイルの日付を管理しない問題を何とかしたい(1)](http://nahitafu.cocolog-nifty.com/nahitafu/2018/11/git1-f73b.html)
を最初に見つけ、まさにやりたいことはこれだったが、システムの時刻を変更するのは抵抗があったので、やりませんでした。
この参考文献にあったgitにだけ時刻をファイル更新日に認識できるようにしたいとかんがえました。

## gitにだけシステムの時刻が違うように見せたい
残念ながら、僕の検索能力、技術力では、直接それを行う方法は見つけられませんでした。
そこで、検索しているうちに偶然見つけた[Gitのコミット日時を上書きする２つの方法](https://qiita.com/yoichi22/items/b25d223b639621b834cb)というサイトから
発想を転換して、コミットした後に書き換えてしまえればいいのではないかと考えました。
なので、この記事で紹介するコマンドは端的に言えば、**ファイルの更新日になるようにコミット日時を上書きしたい**というのが基本方針です。

# 参考文献
- [Gitでコミットの日時を変更する](https://toshusai.hatenablog.com/entry/2020/07/06/231208)
- [Gitのコミット日時を上書きする２つの方法](https://qiita.com/yoichi22/items/b25d223b639621b834cb)
- [ls -lコマンドの結果をサブディレクトリ含めてフルパスで列挙したい](https://qiita.com/dwatarub/items/768c5eb263b49198952e)
- [Gitがファイルの日付を管理しない問題を何とかしたい(1)](http://nahitafu.cocolog-nifty.com/nahitafu/2018/11/git1-f73b.html)
- [Gitでignoreされてるか確認する](https://qiita.com/blue0513/items/1525a7f06d708e901690)
- [【 ls 】コマンド（並べ替え編）――表示結果を並べ替える](https://www.atmarkit.co.jp/ait/articles/1607/05/news023.html)