$shibayu36->blog;

クラスター株式会社のソフトウェアエンジニアです。エンジニアリングや読書などについて書いています。

fzfを使ってgit stashを便利に扱えるように

git stashをもっと便利に扱いたいと思い、fzfを使って使いやすくしてみた。以下のURLに載っているものを参考にして自分にとって使いやすいように改変した。

できたこと

  • 今の変更ファイルをfzfを使って選択して、選択したものだけをstash (git-stash-select)
  • stash一覧の中から中身をpreviewしながら選び、apply or deleteする (git-stashes)

現在の変更ファイルから一部を選んでgit stashするコマンド

fzfでGUI選択したファイルをgit stashするシェルスクリプト を参考に、git-stash-selectというコマンドを作った。

#!/usr/bin/env bash

# Get the root directory of the Git repository
git_repo_root=$(git rev-parse --show-toplevel)

# Add instructions to the list of modified files
instructions="Select multiple files by TAB, and then press Enter to stash them."

# Use fzf to select multiple files
selected_files=$(git status --porcelain |
                 fzf --multi \
                     --header="$instructions" \
                     --preview-window='down:70%' \
                     --preview="
                        if [[ {} =~ '^\?\?' ]]; then
                          cat $git_repo_root/{2};
                        else
                          git -C $git_repo_root diff --color=always {2};
                        fi
                     " |
                 awk '{ print $2 }'
                )

# Check if any files were selected
if [ -z "$selected_files" ]; then
    echo "No files selected. Exiting."
    exit 1
fi

# Store the selected files in an array
IFS=$'\n' read -rd '' -a files_array <<<"$selected_files"

# Filter out the instructions from the array
filtered_files_array=()
for file in "${files_array[@]}"; do
    if [[ $file != *"==="* ]]; then
        filtered_files_array+=("$file")
    fi
done

# Prompt for a stash message
echo "Enter a stash message:"
read stash_message

# Stash the selected files
git -C $git_repo_root stash push -u -m "$stash_message" -- "${filtered_files_array[@]}"

工夫ポイントはpreviewの部分だ。git diffなどはUntrackedなファイルの内容表示に弱く、そのままだと表示できない。そのためUntrackedな場合はcatを使うなどの場合分けをしている。

stash一覧の中から選んでapply or deleteするコマンド

git-stash-explore を参考に、git-stashesというコマンドを作った。

#!/usr/bin/env zsh

while out=$(git stash list "$@" |
            fzf --ansi --no-sort --reverse --print-query --query="$query" \
                --expect=enter,bspace \
                --bind="ctrl-space:preview-page-up" \
                --bind="space:preview-page-down" \
                --bind="k:preview-up" \
                --bind="j:preview-down" \
                --preview="echo {} | cut -d':' -f1 | xargs -I {STASH} sh -c 'git stash show --color=always -p {STASH}; git show --color=always --format="" -p {STASH}^3'" \
                --preview-window='down:85%');
do
    # Tokenize selection by newline
    selection=("${(f)out}")

    # Keep the query accross fzf calls
    query="$selection[1]"
    # Represents the key pressed
    key="$selection[2]"
    # Represents the stash, e.g. stash{1}
    reflog_selector=$(echo "$selection[3]" | cut -d ':' -f 1)

    case "$key" in
        # enter applies the stash to the current tree
        enter)
            git stash apply "$reflog_selector"
            break
            ;;
        # backspace will drop the stash
        bspace)
            git stash drop "$reflog_selector"
            ;;
    esac
done

こちらもUntrackedなファイル対策をしている。In git, is there a way to show untracked stashed files without applying the stash? - Stack Overflow によると、あるstashの中のUntrackedなファイルを表示するには git show 'stash@{0}^3'のように^3をつければ良いらしい。そこでgit stash show stash@{0}git show stash@{0}^3の両方を実行することで、stashの中のModifiedとUntrackedの両方を出力するようにした。

applyとdeleteについては、enterとbackspaceが直感的と感じたので、その2つにキーを割り当てている。

またstashの中身を見るときにpreviewのスクロールをしたい。このコマンドは文字列検索をほぼすることがないなと思ったので、tigなどに合わせてjでscroll down、kでscroll upとなるようにした。

まとめ

今回はgit stashをfzfでもっと便利に扱えるように、変更ファイルを選択してgit stashするコマンドと、stashの中から選択してapplyやdeleteをするコマンドを紹介した。便利なので良かったら使ってほしい。