2019-12-09 [長年日記]

ぼくのかんがえたさいきょうのfzf-git-checkout

先週適当に書いたスクリプトからいろいろ調べて、git branchが案外融通きかないな?というところからgit for-each-refを使えばいいんじゃないだろうかということで、サクッと作ってみた。

方針としては以下のような感じ。

  • 選択肢の表示(色)はなるべくgit branchに合わせる
    • HEADに*を表示し、リモートブランチは赤で表示
  • ローカルブランチはupstreamと状態を表示したい
  • リモートブランチを選択したときに、エラーになったりデタッチになってほしくない
function fzf-git-checkout {
  check-git-repository || return $?

  local branches=$(unbuffer git for-each-ref \
    --format='%(HEAD) %(if)%(HEAD)%(then)%(color:green)%(refname:short)%(color:reset)%(else)%(refname:short)%(end)%(if) %(upstream:short) %(then) -> %(color:red)%(upstream:short)%(color:reset) %(color:cyan)%(upstream:track)%(color:reset) %(end)' \
    refs/heads/)
  branches+="\n"
  branches+=$(unbuffer git for-each-ref \
    --format='  %(color:red)%(refname:short)%(color:reset)' \
    refs/remotes/)
  local branch=$(echo $branches | fzf --exit-0 --reverse --ansi \
    --preview-window up:`expr $LINES / 2` \
    --preview="echo {} | awk '{print substr(\$0, 3)}' | awk '{print \$1}' | xargs git log --color --graph --decorate=full -20 --date=short --format=\"%C(yellow)%h%C(reset) %C(magenta)[%ad] %C(cyan)%an%C(reset)%C(reset)%C(auto)%d%C(reset)%n       %s\"")
  [[ $branch == "" ]] && return
  branch=$(echo $branch | awk '{print substr($0,3)}' | awk '{print $1}')
  local refs=$(git show-ref $branch | awk '{print $2}')
  if [[ $refs == "refs/remotes/"* ]];then
    # remote branch
    b=$(echo $branch | sed "s#[^/]*/##")
    git checkout -b $b $branch 2> /dev/null || echo "$b is already exist"; git checkout $b
  else
    git checkout $branch
  fi
}

選択されたブランチに対して、git show-refによりrefspecを参照することによりリモートブランチかどうかを判定し、リモートのブランチの場合は-bでブランチを作成してdetachを防止する。これだけだとすでに同名のローカルブランチが存在した場合エラーになるので、エラーメッセージを表示しつつ、既存のローカルブランチをチェックアウトする。

この場合、リモートブランチとローカルブランチが別のものを指している場合等では意図するものとは別のものをチェックアウトしてしまう可能性があるけど、レアケースなのでメッセージを出すことにより許容範囲内ということにした。

branch_a -> origin/branch_a
origin/branch_a
upstream/branch_a
# のときに、upstream/branch_aをチェックアウトしようとすると、
# 意図せずorigin/branch_aをトラッキングしているbranch_aをチェックアウトしてしまう。
Tags: zsh fzf