03 / 06
todev
移動 / 選択zsh/repo.zsh依存: fzf
ABOUT
目的のブランチへ最短距離で移動して git pull まで済ませる。
指定ブランチ(省略時は develop)の worktree があればそこへ cd、なければ git switch する。ローカルにブランチが無い場合は remote-tracking branch から作成し、それも無ければ現在の HEAD から新規作成する。最後に upstream が設定されていれば git pull で最新化する。-s を付けると worktree・ローカル・リモートの全ブランチから fzf で選択でき、引数は絞り込みクエリになる。
USAGE
todev [-s] [branch]
OPTIONS
- -s
- fzf でブランチを選択する。branch 引数は絞り込みクエリになる
- branch
- 移動先ブランチ。省略時は develop
EXAMPLES
$ todevdevelop へ移動して pull
$ todev mainmain へ移動して pull
$ todev -sfzf でブランチを選んでから移動
DEMO
DEMO GIF SLOT
src/assets/gifs/todev.gif を追加するとここに表示されます
SOURCE
# worktree / ローカル / リモートブランチを「ブランチ名<TAB>パス」形式で出す内部関数
# worktree があるブランチはパス付き、それ以外はパス空。同じ名前は worktree を優先する
_todev_branch_choices() {
local repo_path="${1:-.}"
{
_git_worktree_choices "$repo_path"
git -C "$repo_path" for-each-ref --format=$'%(refname:short)\t' refs/heads/
git -C "$repo_path" for-each-ref --format='%(refname:short)' refs/remotes/ |
grep -v '/HEAD$' |
awk -F/ '{
branch = $2
for (i = 3; i <= NF; i++) branch = branch "/" $i
if (branch != "") print branch "\t"
}'
} | awk -F '\t' 'NF && $1 != "" && !seen[$1]++'
}
# 指定ブランチの worktree へ移動し、最新化する。
# ブランチが別 worktree で使われている場合はそこへ移動し、
# ブランチが存在しない場合は remote-tracking branch または現在の HEAD から作成する。
# usage: todev [-s] [branch]
# - 引数なし: develop へ移動する
# - -s: fzf でブランチを選んでから実行する
# - branch: そのブランチへ移動する。-s 付きなら絞り込みクエリになる
# example: todev
# example: todev main
# example: todev -s
# example: todev -s main
todev() {
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "このディレクトリはGitリポジトリではありません" >&2
return 1
fi
local select_branch=0
local target_branch=""
while [[ $# -gt 0 ]]; do
case "$1" in
-s)
select_branch=1
shift
;;
-*)
echo "unknown option: $1" >&2
echo "usage: todev [-s] [branch]" >&2
return 1
;;
*)
if [[ -n "$target_branch" ]]; then
echo "usage: todev [-s] [branch]" >&2
return 1
fi
target_branch="$1"
shift
;;
esac
done
local repo_root
repo_root=$(git rev-parse --show-toplevel) || return 1
if (( select_branch )); then
local branch_choices
branch_choices=$(_todev_branch_choices "$repo_root") || return 1
if [[ -n "$target_branch" ]]; then
branch_choices=$(printf '%s\n' "$branch_choices" |
awk -v query="$target_branch" 'index(tolower($1), tolower(query))')
fi
local branch_count
branch_count=$(printf '%s\n' "$branch_choices" | sed '/^$/d' | wc -l | tr -d ' ')
local selected
if (( branch_count == 0 )); then
if [[ -n "$target_branch" ]]; then
echo "一致するブランチがありません: $target_branch" >&2
else
echo "ブランチが見つかりません" >&2
fi
return 1
elif (( branch_count == 1 )); then
selected="$branch_choices"
else
selected=$(printf '%s\n' "$branch_choices" |
fzf --header 'Select branch' --with-nth=1 --delimiter=$'\t')
fi
if [[ -z "$selected" ]]; then
return 1
fi
target_branch=$(printf '%s\n' "$selected" | cut -f1)
elif [[ -z "$target_branch" ]]; then
target_branch="develop"
fi
local worktree_branch worktree_path target_worktree=""
while IFS=$'\t' read -r worktree_branch worktree_path; do
if [[ "$worktree_branch" == "$target_branch" ]]; then
target_worktree="$worktree_path"
break
fi
done <<< "$(_git_worktree_choices "$repo_root")"
if [[ -n "$target_worktree" ]]; then
cd "$target_worktree" || return 1
elif git show-ref --verify --quiet "refs/heads/${target_branch}"; then
git switch "$target_branch" || return 1
else
local remote_branch=""
if git show-ref --verify --quiet "refs/remotes/origin/${target_branch}"; then
remote_branch="origin/${target_branch}"
else
local -a remote_branch_candidates
remote_branch_candidates=(${(f)"$(git for-each-ref \
--format='%(refname:short)' "refs/remotes/*/${target_branch}")"})
if (( ${#remote_branch_candidates[@]} == 1 )); then
remote_branch="${remote_branch_candidates[1]}"
elif (( ${#remote_branch_candidates[@]} > 1 )); then
echo "${target_branch} の remote-tracking branch が複数あります:" >&2
printf ' %s\n' "${remote_branch_candidates[@]}" >&2
return 1
fi
fi
if [[ -n "$remote_branch" ]]; then
git switch -c "$target_branch" --track "$remote_branch" || return 1
else
git switch -c "$target_branch" || return 1
fi
fi
if git rev-parse --abbrev-ref --symbolic-full-name '@{upstream}' >/dev/null 2>&1; then
git pull
else
echo "${target_branch} に upstream が未設定のため git pull はスキップします。"
fi
}