ちなみに

火曜日の空は僕を押しつぶした。

worktreeにcdするのをやめた

git worktreeの管理ツールが色々出てきている。git-wtはtmux統合やfzfベースのインタラクティブ選択が便利だし、他にもworktreeの管理をいい感じにするツールはいくつかある。

ただ、こういったツールの紹介を見ていると「worktreeへの移動をどう楽にするか」に注目が集まっている気がする。でも、worktreeにcdする必要ってそもそもあるんだろうか。

worktreeでやりたいことはコードを書くかAIに書かせるかのどちらかで、だったらworktreeの中でエディタやAIツールを直接起動すればいい。gtrはそれができる。

gtr editor my-feature    # エディタで開く
gtr ai my-feature         # AIツールを起動

ディレクトリの移動は発生しない。

僕のグローバル設定はこう。

git config --global gtr.ai.default claude
git config --global gtr.editor.default vscode
git config --global gtr.copy.includedirs .claude

copy.includedirsでworktree作成時に自動コピーするディレクトリを指定できる。.claudeを入れておくとClaude Codeのプロジェクト設定を引き継げるのでめちゃくちゃ便利。

fzfとの組み合わせ

折角なので、zshに定義しているfzf連携も紹介する。worktree一覧をfzfで表示して、キーバインドでエディタやAIを直接起動できるようにしている。

_git_worktree_format() {
  local root=$1
  local git_cmd=$2
  local line path branch mark is_root
  local base_branch=$("$git_cmd" symbolic-ref --quiet refs/remotes/origin/HEAD 2>/dev/null)
  base_branch=${base_branch#refs/remotes/origin/}
  base_branch=${base_branch:-main}

  while read -r line; do
    path=${line%% *}
    branch=${line##*\[}
    branch=${branch%\]*}
    if ! "$git_cmd" -C "$path" diff --quiet HEAD 2>/dev/null; then
      mark="*"
    elif [[ "$branch" != "$base_branch" ]] && [ -z "$("$git_cmd" -C "$path" log "origin/${base_branch}...HEAD" --oneline 2>/dev/null)" ]; then
      mark="x"
    else
      mark=""
    fi
    if [ "$path" = "$root" ]; then
      is_root="(root)"
    else
      is_root=""
    fi
    print "[${branch}]${mark}\t${path}\t${is_root}"
  done
}

_git_worktree_colorize() {
  sed -e $'s/\\[\\([^]]*\\)\\]\\*\\(.*\\)(root)/\e[1;36m[\\1]\e[1;31m*\e[0m\\2\e[90m(root)\e[0m/' \
      -e $'s/\\[\\([^]]*\\)\\]x\\(.*\\)(root)/\e[1;36m[\\1]\e[1;31mx\e[0m\\2\e[90m(root)\e[0m/' \
      -e $'s/\\[\\([^]]*\\)\\] \\(.*\\)(root)/\e[1;36m[\\1]\e[0m \\2\e[90m(root)\e[0m/' \
      -e $'s/\\[\\([^]]*\\)\\]\\*/\e[1;33m[\\1]\e[1;31m*\e[0m/' \
      -e $'s/\\[\\([^]]*\\)\\]x/\e[1;33m[\\1]\e[1;31mx\e[0m/' \
      -e $'s/\\[\\([^]]*\\)\\] /\e[1;33m[\\1]\e[0m /'
}

# alt-w で起動
# enter:cd | C-e:editor | C-a:AI -c | M-a:AI | C-d:delete
git_worktree_list() {
  local worktrees=$(git worktree list)
  local root=$(echo "$worktrees" | head -1 | awk '{print $1}')
  local git_cmd=$(command -v git)
  local result=$(
    echo "$worktrees" | _git_worktree_format "$root" "$git_cmd" | column -ts $'\t' | _git_worktree_colorize |
    fzf --ansi \
      --height 50% \
      --no-sort \
      --header 'enter:cd | C-e:editor | C-a:AI -c | M-a:AI | C-d:delete' \
      --expect ctrl-e,ctrl-a,alt-a \
      --bind 'ctrl-d:execute-silent(branch=$(echo {1} | tr -d "[]*"); git worktree remove {2}; git branch -D $branch)+abort' \
      --preview 'cd {2} && git log --oneline -10 --color=always'
  )

  local key="${result%%$'\n'*}"
  local selection="${result#*$'\n'}"
  local branch=$(echo "$selection" | awk '{print $1}' | sed 's/[][]//g; s/[*x]$//')
  local path=$(echo "$selection" | awk '{print $2}')

  case "$key" in
    ctrl-e)
      BUFFER="git gtr editor '$branch'"
      zle accept-line
      return
      ;;
    ctrl-a)
      BUFFER="git gtr ai '$branch' -- -c"
      zle accept-line
      return
      ;;
    alt-a)
      BUFFER="git gtr ai '$branch'"
      zle accept-line
      return
      ;;
    *)
      if [ -n "$path" ]; then
        BUFFER="cd ${path}"
        zle accept-line
        return
      fi
      ;;
  esac
  zle clear-screen
}
zle -N git_worktree_list
bindkey '^[w' git_worktree_list

同じキーバインドをPR一覧(alt-p)とブランチ一覧(alt-b)にも定義していて、どの入口から入っても「選ぶ→エディタ or AI」の流れになっている(cdはあくまでフォールバック)。

# alt-p: PR一覧 → enter:worktree | C-e:editor | C-a:AI
bindkey '^[p' fzf-pullreq

# alt-b: ブランチ一覧 → enter:switch | C-t:worktree | C-e:editor | C-a:AI
bindkey '^[b' fzf-branch

みなさんもworktree管理ツールを選ぶとき、「cdを楽にする」じゃなくて「cdしなくていい」ほうを試してみてください。

追記

いつの間にか git-wt にも editor を開くコマンドが追加されていた https://github.com/toritori0318/git-wt?tab=readme-ov-file#open-in-editor