【Mac】スナップショット用のリポジトリにコミットさせるシェルスクリプト【Git】

# なんか 「差分」 って、圧強ない？

こんにちは、すライムです。
Ｎ予備校でプログラミングを学ぶ生徒のはしくれです。

なかなか Git の苦手意識が拭えません。

作業はこまめにコミットしたほうが良いというのは、みんなの共通認識としてあると思います。
けど、リポジトリ汚したくない気持ちもあって...コミットまとめるのも面倒ですし。

「差分」というカタイ言葉からは、「過不足なく」というニュアンスを感じるのは私だけでしょうか？
うまく動いたからといって、スナップショット的に気楽にコミットしちゃダメな感じというか...

そこで、教材のリポジトリとは別に、スナップショット用のリポジトリを作れないかな？ と思い実験してみました。
worspace 自体を Git 管理すれば、簡単に実現できそうな気がしていたのです。

![スクリーンショット 2022-12-04 22.12.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/219243/4c7a5764-f166-8d95-d5df-ce1b3718d372.png)


まず、定期的に workspace 全体のスナップショットを残すためのシェルスクリプトを書いてみました。

* キャッシュを削除することで .gitignore の変更に対応。
* 更新の有無の確認と、コミットメッセージ用に git status を使用。

```sh:失敗.sh
#!/bin/bash

git rm -r --cached .
git add --all

status=$(git status --porcelain)

if [ -n "$status" ]
then
git commit -m "$status"
fi
```

これを workspace ディレクトリに置いて、cron で定期的に実行すれば作業のログを残せそうですね。わくわく！

しかし、コミットされなかったのです...

すでに別のリポジトリで管理されているディレクトリがあると、管理対象から外されるようなのです。

## .git なんてあるから争いが起こるんだ

一時的に Git の管理から外す、何か良い方法は無いものでしょうか？
もし、サプディレクトリ以下に .git ディレクトリが無かったら？

そこで考えた作戦がこちら。

1. サブディレクトリ以下の .git を一時的にリネーム
1. 変更があればコミット
1. リネームしていたディレクトリを .git に戻す

怒られそうな雰囲気がありますが、あくまで実験ですので...

退避時のディレクトリ名は .retreat にしました。
ついでにコミットメッセージに日時も足しました。

なんだかんだあって、できたのがこちら。

ノートに書きなぐるイメージから、名前は .note.sh にしました。
ドットノート。なんか語呂が良くないですか？ あと、なんか戦艦っぽい？

```sh:.note.sh
#!/bin/bash

# リポジトリのディレクトリ名
git_dir=.git

# そもそも git 管理されていなければ git init
if [ ! -d $git_dir ]; then
git init &> /dev/null
fi

# 退避用のディレクトリ名
retreat_dir=.retreat

# ディレクトリ名を一括で変更する関数
rename_dir() {
  git_list=$(find ./* -name $1 2> /dev/null)
for el in $git_list; do
  directry=$(dirname $el)
  mv $el $directry/$2
done
}

# .git ディレクトリをリネームして退避
rename_dir $git_dir $retreat_dir

# キャッシュを削除（.gitignore の更新を反映させるため）
git rm -r --cached . &> /dev/null
git add --all

# git や date コマンドを使う準備
export PATH=$PATH:/usr/local/bin
export LANG=ja_JP.UTF-8
git config --local push.default current


# もし変更があればメッセージを作ってコミット
gstatus=$(git status --porcelain)

if [ -n "$gstatus" ]
then
  timestamp=$(date +"%Y %_m/%_d(%a) %_I:%M %p")
  message=$(echo "$timestamp > \n$gstatus" \
                        | sed 's/^M /💬/g' \ # 変更
                        | sed 's/^A /🐣/g' \ # 新規作成
                        | sed 's/^D /👻/g' \ # 削除
                        | sed 's/^R /📛/g' \ # リネーム
                        | sed 's/^C /🐑/g')  # コピー

  git commit -m "$message" &> /dev/null

# 手動で動かしたときに表示されます
  echo
  echo "$message"
  echo

else

  echo
  echo "変更はありませんでした";
  echo
fi

# 退避してた .git のディレクトリ名を元に戻します
rename_dir $retreat_dir $git_dir
```



.gitignore には、.git の退避時の名前のほかに、データベース永続化用のディレクトリ名も登録しておきました。

```sh:.gitignore
node_modules
.retreat
nn-chat-db
```

これらのファイルを workspace ディレクトリに置いて cron で動かします。

例えば...
```sh:crontab -e
0/5 * * * * cd /Users/ユーザ名/workspace && sh .note.sh
```
こんな感じに設定すれば、5分ごとにコミットしてくれます。

あとは、教材通りに学習を進めるだけで、5分ごとに作業のスナップショットが残ります。
いつでも戻せる安心感は Nintendo Switch Online の巻き戻し機能に通じるものを感じます。

## 使ってみた
ログを見ると、こんな感じになりました。

```shcommit 3064c6a71b977e0006c66b1550616f5952bf97b7 (HEAD -> ss4)
Author: すライム <s_lime@example.com>
Date:   Thu Dec 1 15:45:00 2022 +0900

    2022 12/ 1(木)  3:45 PM >
    💬 express-study/routes/index.js

commit 4bbb5faecdca8521247812342f62a82e87daa490
Author: すライム <s_lime@example.com>
Date:   Thu Dec 1 15:40:01 2022 +0900

    2022 12/ 1(木)  3:40 PM >
    💬 express-study/app.js
    💬 express-study/views/index.pug
    🐣 express-study/views/login.pug

commit 4c038badfcf6c58f0f754ee563dfb4f0832d839d
Author: すライム <s_lime@example.com>
Date:   Thu Dec 1 15:35:00 2022 +0900

    2022 12/ 1(木)  3:35 PM >
    💬 express-study/app.js

commit 9d3cbb259a6b09a36d12f38e628ec1feda725c78
Author: すライム <s_lime@example.com>
Date:   Thu Dec 1 15:30:01 2022 +0900

    2022 12/ 1(木)  3:30 PM >
    💬 express-study/app.js

commit e78ec51ab21cb74d80197953eeaa6aab4383d835
Author: すライム <s_lime@example.com>
Date:   Thu Dec 1 15:18:39 2022 +0900

    2022 12/ 1(木)  3:18 PM >
    💬 express-study/package.json
    💬 express-study/yarn.lock

commit 37cdce6aa191c96038ce1a8413f46612121b4ac4
Author: すライム <s_lime@example.com>
Date:   Thu Dec 1 15:10:01 2022 +0900

    2022 12/ 1(木)  3:10 PM >
    💬 express-study/app.js
    🐣 express-study/routes/photos.js

commit 0f3c8147146b7fd34d7593ebebd76ee944f404a4
Author: すライム <s_lime@example.com>
Date:   Thu Dec 1 14:55:00 2022 +0900

    2022 12/ 1(木)  2:55 PM >
    💬 express-study/package.json

commit bb2541b7203c1d869c20eecadebad2b257ca5620
Author: すライム <s_lime@example.com>
Date:   Thu Dec 1 14:35:00 2022 +0900

    2022 12/ 1(木)  2:35 PM >
    🐣 express-study/.gitignore
    🐣 express-study/Dockerfile
    🐣 express-study/app.js
    🐣 express-study/bin/www
    🐣 express-study/docker-compose.yml
    🐣 express-study/package.json
    🐣 express-study/public/stylesheets/style.css
    🐣 express-study/routes/index.js
    🐣 express-study/routes/users.js
    🐣 express-study/views/error.pug
    🐣 express-study/views/index.pug
    🐣 express-study/views/layout.pug
    🐣 express-study/yarn.lock
```
🐣 は新しく作られたファイル、💬 は変更のあったファイルです。

数分単位で戻せて、どんなに汚しても文句を言われないリポジトリが手に入りました。


ただしこのスクリプトが動いてると、Git コマンドが思った動作をしないこともあります。
教材のリポジトリ退避中は、workspace のリポジトリを操作することになるようです。
何か良い解決方法は無いものでしょうか？

## 最後に
慣れないシェルスクリプトの書き方を調べながら作ったので、こんなものでも4日くらいかかりました...

そもそも、もっとシンプルに実現する方法がありそうな気がします。
よりスマートな方法や改善点をコメントからご教授いただけると喜びます。

最後までお読みいただきありがとうございました。


## こちらの記事を参考にさせていただきました
定期的に自動でコミットさせるスクリプト
https://qiita.com/narikei/items/b4e1c035c778d4eb2fc9

.gitignore の設定を反映させる
https://qiita.com/Potof_/items/c75eba9cfa72819506de

&>/dev/null と >/dev/null の違い
https://qiita.com/kiyodori/items/a37cfc23f068a5c16b2c