オプションを自作する(Gitのオプションを追加)

# やったこと
gitで自作のオプションが欲しいなと思い、追加した。

## 追加したオプション
`git pull -d`

**意味**： `git pull`を行いつつ、リモートブランチが存在せず、リモート追跡ブランチとローカルブランチが存在する場合にブランチを削除するオプション

**つまり**：　`git fetch -p`をローカルブランチまで削除するように拡張。

**制約**：
- リモートリポジトリの名称は*origin*である必要がある。
- リモートブランチの名前とローカルブランチの名前は同じである必要がある。

### 制約への補足
#### リモートリポジトリの名称が*origin*である必要がある
つまり、`git remote`のコマンドを打ったときに以下のようになる必要がある。
もし違う人はコードを変えるか、リモートリポジトリの名称を変更する必要があります。
```sh:terminal
$ git remote
origin
```
#### リモートとローカルのブランチの名前が同じ
これは、基本的には同じであると思います。
`git push origin ブランチ名`の場合は問題ないですが、`git push origin ブランチ名:別名`した場合には動かないです。
後者の場合は`git pull`の際にも後ろにブランチ名をつけないと失敗します。
```sh:terminal
＃ OK(↓こっちの方が一般的かな？)
$ git push origin ローカルブランチ名

# NG(こっちだと反応しなくなる。)
$ git push origin ローカルブランチ名:リモートブランチ名

# さらに詳細に
$ git branch -a 
* develop  # どのリモート追跡ブランチに対応するかがわからない
  main
  remotes/origin/dev　 # <<<devに対応するローカルブランチdevがない！！
  remotes/origin/main
```

# 背景
プルリクを送って承諾された後に、ブランチを消した際に、`git pull`して`git fetch -p`して`git branch -d プルリクしたブランチ`で消すのだるくない？一つでまとめられるっしょ!と思い作成しました。

# 開発環境
PC: MacPC M2
Shell: zsh(ファイルはshで実行)

# 導入手順
alias経由でgitコマンドを上書きしています。
1. PATHの通っているディレクトリを開く
1. その中に、下のコード(もしくは[こちら](https://github.com/bluecat101/ShellScript/blob/main/git_self/git_self.sh "Github"))を貼ってファイルを保存する
1. `chmod 744 ファイル名`で実行可能ファイルにする
1. `echo "alias git='ファイル名'" >> ~/.zshrc`でaliasの追加
1. `source ~/.zshrc`で更新(お使いの環境に合わせてsourceしてください)

### 補足(PATHの通っているディレクトリとはなんぞや)
`echo $PATH`を打った際に表示されるディレクトリです(繋がっているので見づらいです。)
僕のイメージですが、terminalが特に重要なディレクトリの住所を知っているディレクトリです。
＄PATHに登録することでこのディレクトリは重要やで！ということを知らせることができるので、その中のコマンドをどこからでも実行することができます。
$PATHに登録されていることを"通っている"と表現します。

↓とりあえず、新しくPATHの通っているディレクトリを追加する人用
```sh:terminal
cd ~/ # カレンとディレクトリに移動
mkdir SelfCommand # ディレクトリの作成
echo 'export PATH="$PATH:$HOME/SelfCommand"' >> ~/.zshrc # PATHに登録
source ~/.zshrc # PATHを更新
# SelfCommandはPATHが通ったディレクトリ！！
# この中に書いた実行ファイルはどこからでも呼び出し可能！
```

# コード
[こちら](https://github.com/bluecat101/ShellScript/blob/main/git_self/git_self.sh "Github")にも上がっています。(随時新しいオプションを追加しているので、下のコードと異なる場合がありまs)
```sh:git_self.sh
#!/bin/sh

## README ####################################################################
# 説明
# git pull -d を追加するコマンドです。
# git pull -dとはgit pullをしつつ、任意のブランチがリモートブランチに存在せず、リモート追跡ブランチが存在するときに、リモート追跡ブランチとローカルブランチを削除するオプションです。
# つまり、git fetch -pに対してローカルブランチまで消すオプションです。
# ---注意点---
# リモートリポジトリの名称は一般的あるoriginである必要があります。(複数名称がある場合に処理が大変だったため拡張性を消しています。)
# リモートブランチの名前とローカルブランチの名前は同じである必要があります。(ローカルブランチがどのリモートブランチと紐づいているのかを取得できる方法はありますが、大変であるため今回は同じ名前に限定しています。)
##############################################################################

### 作成する関数 ###
pull_delete(){
  # 初期設定
  remote_branch_name="remotes/origin/"

  post_branch=$(git branch -a)
  git fetch -p >/dev/null
  current_branch=$(git branch -a)
  for post_b in ${post_branch[@]}; do
    # remotes/origin/HEADに対応するHEADブランチは基本的に存在しないため
    if [[ "$post_b" == "remotes/origin/HEAD" ]]; then
      continue
    # remotes/branchが消されたのかを確認するので、ブランチの名前に"remotes/origin/"がつかないものは探索しなくて良いので抜ける
    elif [[ ! "$post_b" == *${remote_branch_name}* ]]; then
      continue
    fi

    has_branch=false
    # "remotes/origin/"の付くブランチが消されたかを確認する
    for currnet_b in ${current_branch[@]}; do
      if [[ $post_b == $currnet_b ]]; then
        has_branch=true
      fi
    done
    # ローカルブランチの削除
    if ! $has_branch ; then
      git branch -D ${post_b##$remote_branch_name}
    fi
  done
}



### 実行のメイン部分 ###
# 何のgitのコマンドが使われたかを取得
if [[ $# > 1 ]]; then
  GIT_COMMAND=$1
fi

# 再度gitコマンドを呼び出す用(追加したオプションを一緒に呼び出すと「そんなオプションはない」とエラーになるので、追加したオプションのみを消してコマンドを再構成)
RE_COMMAND="git"
# getoptsでoptionを取得できるが、どこに追加したオプション用の引数があるか位置特定できないので、引数をfor文で全部見ていく
for arg in "$@"
do
  # case文の中に他の追加したオプションも追加していくことで使用可能
  case $GIT_COMMAND in
    pull )
      # 追加したいオプションが複数ある時は|で繋げる
      # 追加オプションごとに分けると１つの引数に複数の追加オプションがあったときにループ内で戻ってこれないため、一括で管理している。
      target_option="d" 
      # 最初の正規表現で先頭に-が1つしかないオプションのみに対応している
      if [[ $arg =~ ^-[^-]+ && $arg =~ "$target_option" ]]; then
        ## 複数ある場合にはここに追加
        if [[ $arg == *"d"* ]]; then
          pull_delete
        fi
        # 使用した追加オプションを消す。//を使ったパラメータ展開では動作しなかったためsedを使用。
        # 追加オプションを消した後に他のオプションが残っていれば、コマンドの再実行の時に使用するので、argにセットする。
        deleted_opt=$(echo "$arg" | sed -E "s/$target_option//g")
        if [ $deleted_opt != "-" ]; then
          arg=$deleted_opt
        else
          continue
        fi
      fi
      ;;
  esac
  # 再度コマンドを実行する用にコマンドを再構築
  RE_COMMAND=$RE_COMMAND" "$arg
done 

# 追加オプションを消したコマンドを再実行 
$RE_COMMAND

```
# コードの説明
## 説明
### オプションにより実行される関数部分
```sh
  post_branch=$(git branch -a)
  git fetch -p >/dev/null
  current_branch=$(git branch -a)
```
ここでは、`git branch -a`,`git fetch -p`,`git branch -a`を順番に行うことで、現状のブランチを取得と`fetch -p`後のブランチを取得しています。これによって、fetch後のブランチにはリモートブランチに存在しないブランチがない状態です(リモート追跡ブランチがないだけであってローカルブランチはあります。)。
この差分を用いてローカルブランチを削除します。
```sh
    has_branch=false
    # "remotes/origin/"の付くブランチが消されたかを確認する
    for currnet_b in ${current_branch[@]}; do
      if [[ $post_b == $currnet_b ]]; then
        has_branch=true
      fi
    done
```
同じ名前のローカルブランチがあるかを確認しています。

### main部分
```sh
for arg in "$@"
```
この部分によって全ての引数を探索しています。
これにより、どんな形の引数でオプションが来ても対応できます。
例) 
- git pull -r -b
- git pull -rb

```sh
# 最初の正規表現で先頭に-が1つしかないオプションのみに対応している
if [[ $arg =~ ^-[^-]+ && $arg =~ "$target_option" ]]; then
```
もし、ハイフンが２つ(--)のオプションを追加したい場合には、この条件式の前半の部分が変わってきます。

## 特に重要、勉強になった部分
### [[]]
    [](一重四角括弧)だと、論理演算子等が使えないが、[[]]があると使える。
    ⚠️注意点としては、[ 値　]値の前後にスペースが必要。これを忘れるとエラーが出ないのにうまくいっていない状況になって原因がわかりにくい。
    
### ${変数##パターン}
    変数からパターンの部分を除外して返してくれる。
    文字列削除と同じこと
    
### $@
    引数を全て受け取る変数。
    今回は、getoptsを使用していないので助かりました。

### 関数の呼び出し
    関数の呼び出しの際には()をつけない。
    つけるとエラーになる。

### getopts(初めは使っていましたが結局使いませんでした。)
    初めは便利と思っていたので、使いたかったです。
    getoptsは何のオプションが付与されたかを全部探索してくれます。
    欠点:getoptsとcase文を使って動作を記述するのですが、今回は、追加したオプションのみ判別であったため使い勝手が悪かった。また、そのオプションがどこの引数の中に含まれているのかを取得することができない。

### ${変数/検索する文字/置換する文字}(初めは使っていましたが結局使いませんでした。)
    sedコマンド的な感じ
    検索する文字の部分に正規表現のパターンが入ったときにうまく動かなくなったので、使用しませんでした。
    sedコマンドで対応。    

## 同様に自作のオプションを追加したい人への注意点
今回、追加したオプションが他のオプションの名前に含まれていないことを確認して追加しています。競合が起きたときにどうなるかは未検証です。(理論上は、追加オプションが優先されます。)


# まとめ
全然手がつけられないんだろうなと思っていたが、shellが有能すぎた。。。
いろんなコマンドあるやん！
今後も自分の環境が便利になるようにオプションを追加していきたい！！