git 管理下にある指定したディレクトリ内に存在する全てのファイルの最終更新日を取得する

# はじめに

この記事は [2023 年の MDN 翻訳 Advent Calendar](https://qiita.com/advent-calendar/2023/mdn-translation-community-japan) 向けに作成したものです。

こんにちは。debiru です。好きなシェルは Bash です。

今日は、「[MDN翻訳ステータス一覧表](https://mdn.lavoscore.org/?sort=date-desc)」で使っているシェルスクリプトを紹介しようと思って来ました。

# 更新日降順で MDN 日本語記事を一覧する

上記と同じリンクですが、https://mdn.lavoscore.org/?sort=date-desc こちらで最も右側の列を見ると確認できます。

# 全ての日本語記事の最終更新日を取得したい

MDN のローカライズ版の記事は、日本語を含めて以下のリポジトリで管理されています。

https://github.com/mdn/translated-content/tree/main/files

この `ja` ディレクトリ下にある全てのファイルの最終更新日を取得することを考えます。

## `git log` コマンドの結果を利用する

上記リポジトリを `clone` して、`files/ja` ディレクトリに移動した上で、`git log .` のようなコマンドを打ってみます。

### `git log .` の結果

```
commit 1e2dc02459f274158b62814706ab1ca93da9c458
Author: Masahiro FUJIMOTO <mfujimot@gmail.com>
Date:   Fri Dec 1 15:24:05 2023 +0900

    2023/02/19 時点の英語版に同期

commit f6dad23930b874d53398a8800c9f00542b1dadd6
Author: Masahiro FUJIMOTO <mfujimot@gmail.com>
Date:   Fri Dec 1 14:37:17 2023 +0900

    2023/02/19 時点の英語版に同期

commit 7c70e40d45a7a455e340f93e7ca7b757fe93858e
Author: Masahiro FUJIMOTO <mfujimot@gmail.com>
Date:   Fri Dec 1 14:51:08 2023 +0900

    重複していたイベントの記事を削除

...
```

### `git log --name-status .` の結果

`--name-status` オプションを追加すると、変更ステータスとファイルパスが追加されます。

`M` は Modified の `M` です。`D` は Deleted の `D` です。他に新規追加された場合は Added の `A` が付いたりします。

```
commit 1e2dc02459f274158b62814706ab1ca93da9c458
Author: Masahiro FUJIMOTO <mfujimot@gmail.com>
Date:   Fri Dec 1 15:24:05 2023 +0900

    2023/02/19 時点の英語版に同期

M       files/ja/web/api/webgl_api/data/index.md

commit f6dad23930b874d53398a8800c9f00542b1dadd6
Author: Masahiro FUJIMOTO <mfujimot@gmail.com>
Date:   Fri Dec 1 14:37:17 2023 +0900

    2023/02/19 時点の英語版に同期

M       files/ja/web/api/webgl_api/types/index.md

commit 7c70e40d45a7a455e340f93e7ca7b757fe93858e
Author: Masahiro FUJIMOTO <mfujimot@gmail.com>
Date:   Fri Dec 1 14:51:08 2023 +0900

    重複していたイベントの記事を削除

M       files/ja/_redirects.txt
M       files/ja/_wikihistory.json
D       files/ja/conflicting/web/api/htmlelement/drag_event_b6e067958f5eb7e5b6c19e73c98fc256/index.md
D       files/ja/conflicting/web/api/htmlelement/dragend_event_2823bae8fed3efe03a09d01597d1cde8/index.md
D       files/ja/conflicting/web/api/htmlelement/dragenter_event_2dcc7160dcc0241687dce4442238bbf3/index.md
D       files/ja/conflicting/web/api/htmlelement/dragleave_event_0d933fa977f195f1bf9fd28b1f8051ee/index.md
D       files/ja/conflicting/web/api/htmlelement/dragover_event_6ef4663f0003f0c263a08969c008baf7/index.md
D       files/ja/conflicting/web/api/htmlelement/dragstart_event_be9da5ef68a2920309b168752ea03d20/index.md
D       files/ja/conflicting/web/api/htmlelement/drop_event_21b7ebf04561093fe90276329e9bddff/index.md

...
```

### `git log --name-only .` の結果

ファイルパスのみ追加する場合は `--name-only` オプションが使えます。

```
commit 1e2dc02459f274158b62814706ab1ca93da9c458
Author: Masahiro FUJIMOTO <mfujimot@gmail.com>
Date:   Fri Dec 1 15:24:05 2023 +0900

    2023/02/19 時点の英語版に同期

files/ja/web/api/webgl_api/data/index.md

commit f6dad23930b874d53398a8800c9f00542b1dadd6
Author: Masahiro FUJIMOTO <mfujimot@gmail.com>
Date:   Fri Dec 1 14:37:17 2023 +0900

    2023/02/19 時点の英語版に同期

files/ja/web/api/webgl_api/types/index.md

commit 7c70e40d45a7a455e340f93e7ca7b757fe93858e
Author: Masahiro FUJIMOTO <mfujimot@gmail.com>
Date:   Fri Dec 1 14:51:08 2023 +0900

    重複していたイベントの記事を削除

files/ja/_redirects.txt
files/ja/_wikihistory.json
files/ja/conflicting/web/api/htmlelement/drag_event_b6e067958f5eb7e5b6c19e73c98fc256/index.md
files/ja/conflicting/web/api/htmlelement/dragend_event_2823bae8fed3efe03a09d01597d1cde8/index.md
files/ja/conflicting/web/api/htmlelement/dragenter_event_2dcc7160dcc0241687dce4442238bbf3/index.md
files/ja/conflicting/web/api/htmlelement/dragleave_event_0d933fa977f195f1bf9fd28b1f8051ee/index.md
files/ja/conflicting/web/api/htmlelement/dragover_event_6ef4663f0003f0c263a08969c008baf7/index.md
files/ja/conflicting/web/api/htmlelement/dragstart_event_be9da5ef68a2920309b168752ea03d20/index.md
files/ja/conflicting/web/api/htmlelement/drop_event_21b7ebf04561093fe90276329e9bddff/index.md

...
```

### `git log --pretty="format:Commit: %H%nSubject: %s" --name-only .` の結果

`--pretty=format:...` オプションを指定すると、`git log` のコミット情報部分が `...` に指定したフォーマットに変換されます。`--name-only` オプションを併記すると、ファイルパスが追加で出力されます。

```
Commit: 1e2dc02459f274158b62814706ab1ca93da9c458
Subject: 2023/02/19 時点の英語版に同期
files/ja/web/api/webgl_api/data/index.md

Commit: f6dad23930b874d53398a8800c9f00542b1dadd6
Subject: 2023/02/19 時点の英語版に同期
files/ja/web/api/webgl_api/types/index.md

Commit: 7c70e40d45a7a455e340f93e7ca7b757fe93858e
Subject: 重複していたイベントの記事を削除
files/ja/_redirects.txt
files/ja/_wikihistory.json
files/ja/conflicting/web/api/htmlelement/drag_event_b6e067958f5eb7e5b6c19e73c98fc256/index.md
files/ja/conflicting/web/api/htmlelement/dragend_event_2823bae8fed3efe03a09d01597d1cde8/index.md
files/ja/conflicting/web/api/htmlelement/dragenter_event_2dcc7160dcc0241687dce4442238bbf3/index.md
files/ja/conflicting/web/api/htmlelement/dragleave_event_0d933fa977f195f1bf9fd28b1f8051ee/index.md
files/ja/conflicting/web/api/htmlelement/dragover_event_6ef4663f0003f0c263a08969c008baf7/index.md
files/ja/conflicting/web/api/htmlelement/dragstart_event_be9da5ef68a2920309b168752ea03d20/index.md
files/ja/conflicting/web/api/htmlelement/drop_event_21b7ebf04561093fe90276329e9bddff/index.md

...
```

### `git log --pretty=format:%cs --name-only .` の結果

|プレースホルダー|説明|
|:---:|:---|
|%H|コミットハッシュ（ロング）|
|%h|コミットハッシュ（ショート）|
|%s|コミットメッセージ（Subject）|
|%n|改行文字|
|%cs|コミット日時のショートフォーマット (`YYYY-MM-DD`)|

プレースホルダーの詳細は https://git-scm.com/docs/pretty-formats を参照ください。

```
2023-12-05
files/ja/web/accessibility/aria/attributes/aria-activedescendant/index.md

2023-12-05
files/ja/web/api/file_system_api/index.md
files/ja/web/api/file_system_api/origin_private_file_system/index.md

2023-12-05
files/ja/web/api/webgl_api/index.md
files/ja/web/api/webgl_api/matrix_math_for_the_web/index.md
files/ja/web/api/webgl_api/tutorial/adding_2d_content_to_a_webgl_context/index.md
files/ja/web/api/webgl_api/webgl_model_view_projection/camera_view_frustum.svg
files/ja/web/api/webgl_api/webgl_model_view_projection/clip_space_graph.svg
files/ja/web/api/webgl_api/webgl_model_view_projection/index.md
files/ja/web/api/webxr_device_api/cameras/index.md

...
```

この出力結果を使って、ファイルパスごとの最終更新日を取得したいと思います。

今回、[MDN翻訳ステータス一覧表](https://mdn.lavoscore.org/?sort=date-desc)では、時間情報までは不要と考えて、日付情報のみを使うことにしました。

## ファイルパスごとに最終更新日を記録していく



```
2021-01-01
file-A
file-B

2021-02-01
file-C

2021-03-01
file-A
```

こんな結果が得られたとき、これらのファイルの最終更新日は次のようになります。

|ファイル|最終更新日|
|:---:|:---:|
|file-A|2021-03-01|
|file-B|2021-01-01|
|file-C|2021-02-01|

この結果が得られるようなシェルスクリプトを書けば完成です。

## `git log` の結果を日時昇順で整形する

日時昇順にするには `--reverse` オプションを使います。

`git log --pretty=format:%cs --name-only --reverse .` で目的の `git log` が手に入ります。

## `git log` の結果から、ファイルごとに最終更新日を記録する

```bash
lines=$(git log --pretty=format:%cs --name-only --reverse .)

declare -A map
date=''

IFS=$'\n'
for line in $lines; do
    if [[ $line =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
        date="$line"
    else
        map[$line]="$date"
    fi
done

for key in "${!map[@]}"; do
    echo -e "${key}\t${map[${key}]}"
done
```

- 連想配列 `map` に `map[ファイルパス] = 日付` の形で情報を記録していく
- `${!map[@]}` と書くと、`map` のキーがリストとして展開される

```
files/ja/web/api/webglrenderingcontext/drawingbufferwidth/index.md	2023-12-04
files/ja/web/api/mutationevent/initmutationevent/index.md	2023-11-22
files/ja/web/api/rtcpeerconnection/localdescription/index.md	2023-11-02
files/ja/conflicting/web/api/xsltprocessor_7eb90412a7159e9ebdecfc78996d1ac5/index.md	2023-09-06
files/ja/web/api/nodelist/keys/index.md	2023-08-01
...
```

タブ区切りで、ファイルパスと日付（最終更新日）が得られます。

この情報を使って、[MDN翻訳ステータス一覧表](https://mdn.lavoscore.org/?sort=date-desc)では日本語記事に対する最終更新日を表示しています。

実際に使っているスクリプトファイルはこちらです。

https://github.com/debiru/mdn-translation-docs/blob/2c5d93a804554749e21a5797fd2eb2500c5b55a3/.core/git-updated-at-files.sh

## さいごに

[2023 年の MDN 翻訳 Advent Calendar](https://qiita.com/advent-calendar/2023/mdn-translation-community-japan) の参加者が絶望的にいなくて寂しいですが、私がもう少しだけ頑張って見ようと思います。つまらない記事になってしまったら恐縮ですが、もし少しでも面白いと思ってもらえたら嬉しいです。

MDN のネタ、もう少しないかな…。いや、あるにはあるのですが、MDN Community に私が質問を投げかけているものがあって、でも返事が貰えていないという状況なのです。もし良い返事が貰えたら情報をまとめて記事にしたいなと思っています。

というわけで、だいぶシンプルな内容でしたがシェルスクリプトに関するお話でした。

おわり。