[Python] ローカルのgitのブランチをお掃除するスクリプトを書いた

## はじめに

みなさんローカルに溜まっていくgitのブランチをどのように整理していますか？

自分は

```shell
$ git branch
foo
bar
baz

$ git branch -D bar baz
$ git branch
foo
```

のように、 `git branch` で一覧を表示した後、不要なものをコピペして削除していました。

`prune` や `--merged` を使えば、マージされているブランチを
__いい感じに__ 削除できるようですが、
「一覧から好きなものを選んで削除したい！」と思ったので、作ってみました。

簡単にコマンドライン上でのチェックボックスを実装できるライブラリはないかなと探し、
[PyInquirer](https://github.com/CITGuru/PyInquirer) を見つけたので、これを使いました。


## 完成したもの(gif)
![sweep_branch.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192104/fa7d626c-ecfa-0c07-080a-9039eebcd14b.gif)


## 実装

### やりたいことの整理
やりたいことは、
1. ブランチ一覧の取得
2. 一覧からの複数選択
3. 選択したブランチの一括削除
です。

1, 3については `はじめに` でも書いたように

```shell
$ git branch
$ git branch -D foo bar baz
```
で実現できるます。
これらはPythonスクリプト内で `subprocess` を使って実行するようにして、

最終的なスクリプトは以下のようになるイメージで進めます。

```python
if __name__ == '__main__':
    output = subprocess.check_output(['git', '-P', 'branch'])

    # output から branch_list を抽出する処理
    # ...

    subprocess.call(['git', 'branch', '-D'] + delete_branch_list)
```

2について `PyInquirer` を使って実装し、
選択したブランチの一覧を `git branch -D` に渡すような処理を書いていきます。

### PyInquirerの挙動確認

実装する前にまず、`PyInquirer` の使い方について簡単に触れます。
[quickstart](https://github.com/CITGuru/PyInquirer#quickstart)には
>you define a list of questions and hand them to prompt
prompt returns a list of answers

と書かれています。

今回は複数選択のためにチェックボックスを使いたいので、
[Pyinquireが用意してくれているexample](https://github.com/CITGuru/PyInquirer/blob/master/examples/checkbox.py)
を少し見ます。

exampleを少し編集して、挙動を確認します。

```python
from pprint import pprint

from PyInquirer import prompt, Separator

questions = [
    {
        'type': 'checkbox',
        'qmark': '😃',
        'message': 'Select toppings',
        'name': 'toppings',
        'choices': [
            {'name': 'Ham'},
            {'name': 'Ground Meat'},
            {'name': 'Bacon'}
        ]
    },
    {
        'type': 'checkbox',
        'qmark': '🧀',
        'message': 'Select toppings2',
        'name': 'toppings2',
        'choices': [
            {'name': 'Mozzarella',},
            {'name': 'Cheddar'},
            {'name': 'Parmesan'},
        ]
    }
]
answers = prompt(questions)
pprint(answers)

```

実行すると
まずquestionsの一つ目のchoicesが表示されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192104/a3afa239-a6b1-f778-72cd-dddcd8dfe47d.png)

次に二つ目のchoicesが表示。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192104/c2c1b948-fcdd-0c2a-91ea-6fa3b898dc65.png)


最後にそれぞれで選択したchoicesの結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192104/73b6bf3d-f99b-06ba-0a23-144c10e400f1.png)


が得られました。

quickstartに書いてある通りですが
`prompt` は、引数に渡した `questions` を順番に実行し、
各questionの回答を辞書に詰めて返してくれることが分かりました。

(チェックボックスは List で返してくれる)

では `PyInquirer` の挙動が確認できたので実装します。

### お掃除スクリプト実装
上で試した結果、promptに渡す questionsをこのようにすれば良さそうです。

```python
branch_choices = [
    {'name': 'foo'},
    {'name': 'bar'},
    {'name': 'baz'}
]
questions = [
    {
        'type': 'checkbox',
        'name': 'delete_branch_list',
        'message': '削除するブランチを選択',
        'choices': branch_choices
    }
]
```

なので、まず `branch_choices` を作成する処理を書きます。

```python
# -P オプションをつけることで paginate なしでブランチの一覧を取得できる
output = subprocess.check_output(['git', '-P', 'branch'])
# split('\n')で最後に改行分の空文字が入ってくるので[:-1]とする
branch_choices = [
    {'name': branch_name.replace(' ', '').replace('*', '')}
    for branch_name in output.decode().split('\n')[:-1]
]
```

ではこれをchoicesとするquestionsをpromptに渡します。

```python
questions = [
    {
        'type': 'checkbox',
        'name': 'delete_branch_list',
        'message': '削除するブランチを選択',
        'choices': branch_choices
    }
]

answers = prompt(questions)
```

これでanswersに `delete_branch_list` という key で、
選択したブランチのlistを詰めて返してくれるようになりました。

一応、選択なしだった時のif文等を追加して、完成させます。

↓が完成物です。

```python
# -*- coding: utf-8 -*-

import sys
from PyInquirer import prompt, Separator
import subprocess



output = subprocess.check_output(['git', '-P', 'branch'])
# split('\n') で最後に空文字が入ってくるので[:-1]としている
branch_choices = [
    {'name': branch_name.replace(' ', '').replace('*', '')}
    for branch_name in output.decode().split('\n')[:-1]
]
questions = [
    {
        'type': 'checkbox',
        'name': 'delete_branch_list',
        'message': '削除するブランチを選択',
        'choices': branch_choices
    }
]

answers = prompt(questions)

if not answers:
    sys.exit()

delete_branch_list = answers.get('delete_branch_list')

if not delete_branch_list:
    print('選択されたブランチはありませんでした。')
    sys.exit()

subprocess.call(['git', 'branch', '-D'] + delete_branch_list)
```


## おわり
`PyInquirer` 非常に便利でした。

ありがとうございました。