誤ってpushしないためのbashrc

# はじめに

push、怖いですよね。
mainやdevelopにプッシュしてしまったら…と考えただけでストレスを感じます。
これでは精神衛生上良くないので、予防策を講じようと思います。

※bashと.bashrcの説明は省きます。
※例外は考慮していません。

### 追記

コメントを頂いて知ったのですが、一般的にはフック機能というものを使うらしいです。

https://git-scm.com/book/ja/v2/Git-%E3%81%AE%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA-Git-%E3%83%95%E3%83%83%E3%82%AF

リポジトリごとに設定できるのが良いですね。

# 筆者の環境

環境によっては動かないかもしれないので、私の環境を書いておきます。
Windows 11 Pro + WSL(Ubuntu 22.04.4 LTS)

# 手順

1. git関数の追加
2. ブランチの保護
3. ブランチの保護を解除
4. 保護されたブランチの一覧
5. pushの制限

という流れでやっていきます。

### 1. git関数を追加

```bash:bashrc
git() {}
```

試しに、`git push`でHello World!を出力してみます。

```bash:bashrc
git() {
  case $1 in
  "push")
    echo "Hello World!"
    ;;
  esac
}
```

実行してみます。

```bash:bash
~/hoge: bash

~/hoge: git push
Hello World!
```

動きました！
動作確認出来たので、pushの部分は削除しておきます。

```diff:bashrc
git() {
  case $1 in
- "push")
-   echo "Hello World!"
-   ;;
  esac
}
```

### 2. ブランチの保護

ブランチ名を直接条件に追加しても良いですが、
保護されたブランチが増えてくると管理が面倒です。
`git protect ブランチ名`で保護出来るようにします。

まず、保護したブランチを保存するためのファイルを作成します。
好きなディレクトリに作成してください。

```bash:bash
~/hoge: touch protected_branches.txt
```

次に、作成したファイルを読み込みます。

```bash:bashrc
protected_branches_file="/hoge/protected_branches.txt"
```

次に、保護するブランチをファイルに書き出します。

```bash:bashrc
protect_branches() {
  for arg in "$@"; do
	# 一致する行がない場合
	if ! grep -xq "$arg" "$protected_branches_file"; then
      echo "$arg" >> "$protected_branches_file"
	  echo "$arg を保護しました"
	else
	  echo "$arg は既に保護されています"
	fi
  done
}
```

最後に、`git protect ブランチ名`で保護できるようにします。

```bash:bashrc
git() {
  case $1 in
  "protect")
	shift
	protect_branches "$@"
    ;;
  esac
}
```

実行してみます。

```bash:bash
~/hoge: bash

~/hoge: git protect hoge fuga
hoge を保護しました
fuga を保護しました

~/hoge: git protect hoge fuga
hoge は既に保護されています
fuga は既に保護されています
```

```diff:protected_branches.txt
+ hoge
+ fuga
```

動きました！ファイルも更新されています！

### 3. ブランチの保護を解除

`git protect -rm ブランチ名`で解除出来るようにします。

まず、ファイルから保護を解除したいブランチを除けるようにします。

```bash:bashrc
unprotect_branches() {
  for arg in "$@"; do
	# 一致する行がある場合
	if grep -xq "$arg" "$protected_branches_file"; then
      # 行完全一致
	  sed -i "/^$arg$/d" "$protected_branches_file"
	  echo "$arg の保護を解除しました"
	else
	  echo "$arg は保護されていません"
	fi
  done
}
```

次に、`git protect -rm hoge`で保護を解除できるようにします。

```bash:bashrc
git() {
  case $1 in
  "protect")
	if [[ $2 = "-rm" ]]; then
	  shift
	  shift
	  unprotect_branches "$@"
	else
	  shift
	  protect_branches "$@"
	fi
    ;;
  esac
}
```

実行してみます。

```bash:bash
~/hoge: bash

~/hoge: git protect -rm hoge fuga
hoge の保護を解除しました
fuga の保護を解除しました

~/hoge: git protect -rm hoge fuga
hoge は保護されていません
fuga は保護されていません
```

```diff:protected_branches.txt
- hoge
- fuga
```

保護と解除が出来るようになりました！

### 4. 保護されたブランチの一覧

`git protect -a`で一覧表示できるようにします。

まず、保護されたブランチを表示できるようにします。

```bash:bashrc
show_protected_branches() {
  readarray -t protected_branches < "$protected_branches_file"
  echo "保護されているブランチの一覧"
  # 連番: ブランチ名
  # !で添字を参照
  for i in "${!protected_branches[@]}"; do
    printf "%d: %s\n" $((i + 1)) "${protected_branches[$i]}"
  done
}
```

次に、`git protect -a`で一覧を表示できるようにします。

```bash:bashrc
git() {
  case $1 in
  "protect")
	if [[ $2 = "-rm" ]]; then
	  shift
	  shift
	  unprotect_branches "$@"
	elif [[ $2 = "-a" ]]; then
	  show_protected_branches
	else
	  shift
	  protect_branches "$@"
	fi
    ;;
  esac
}
```

実行してみます。

```bash:bash
~/hoge: bash

~/hoge: git protect -a
保護されているブランチの一覧
1: hoge
2: fuga
```

あとはpushを制限するだけです！

### 5. pushの制限

まず、指定したブランチが保護されているかを判定できるようにします。

```bash:bashrc
is_protected_branch() {
  if grep -xq "$1" "$protected_branches_file"; then
	return 0
  else
	return 1
  fi
}
```

次に、`git push 保護されたブランチ名`の場合にメッセージを表示し、pushを拒否します。

```bash:bashrc
git() {
  case $1 in
  "push")
    # "push -f"のようにオプションを付ける場合を考慮して、最後の引数だけ渡す
	if is_protected_branch "${@: -1}"; then
	  echo "このブランチは保護されています"
	  echo "pushを実行するには、保護を解除してください"
    else
      command git "$@"
	fi
	;;
  "protect")
    # 省略
	;;
  esac
}
```

このままだとpushとprotect以外が実行できません。
他のコマンドも実行できるようにします。

```bash:bashrc
git() {
  case $1 in
  "push")
    # 省略
	;;
  "protect")
    # 省略
	;;
  *)
	command git "$@"
	;;
  esac
}
```

実行してみます。

```bash:bash
~/hoge: bash

~/hoge: git push hoge
このブランチは保護されています
pushを実行するには、保護を解除してください
```

pushとprotect以外が実行できるかも試してみましょう。

```bash:bash
~/hoge: git status
On branch main
nothing to commit, working tree clean
```

完成です！
pushに怯える日々ともこれでおさらば！

# おわりに

初めてbashという言語に触れたのですが、
ifやcaseの終わりがfi, esacになっていて、お洒落だな～と思いました。

# 参考記事

https://www.tohoho-web.com/ex/shell.html

https://qiita.com/takech9203/items/b96eff5773ce9d9cc9b3

https://atmarkit.itmedia.co.jp/ait/articles/1905/22/news004.html

https://genzouw.com/entry/2021/06/30/120014/2685/

https://git-scm.com/book/ja/v2/Git-%E3%81%AE%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA-Git-%E3%83%95%E3%83%83%E3%82%AF

# 余談

今回、`wsl/Ubuntu/etc/bash.bashrc`を使ったのですが、

1. Windowsのbashrc
2. WSLのbashrc
3. Git for Windowsのbashrc

がPC上に存在していて、最初はどれが適用されているのか分かりませんでした。
bashrcの先頭でファイルパスをechoすると迷わないのでおすすめです。