-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathworktree.zsh
More file actions
240 lines (197 loc) · 6.01 KB
/
worktree.zsh
File metadata and controls
240 lines (197 loc) · 6.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# Worktree management: wt create|list|rm|cd
declare -a WORKTREE_COLORS=(
"#6d28d9" # Purple
"#dc2626" # Red
"#059669" # Green
"#2563eb" # Blue
"#ea580c" # Orange
)
function _wt_ensure_repo() {
if ! git rev-parse --git-dir > /dev/null 2>&1; then
echo "Error: not a git repository"
return 1
fi
}
function _wt_main_root() {
dirname "$(git rev-parse --path-format=absolute --git-common-dir)"
}
function _wt_branches_dir() {
local main_root=$(_wt_main_root)
echo "$(dirname "$main_root")/$(basename "$main_root")-branches"
}
function _wt_color() {
local hash=$(echo "$1" | md5 -q | cut -c1-8)
local index=$(( (0x$hash % ${#WORKTREE_COLORS[@]}) + 1 ))
echo "${WORKTREE_COLORS[$index]}"
}
function wt() {
case "$1" in
create) shift; _wt_create "$@" ;;
list|ls) shift; _wt_list "$@" ;;
rm) shift; _wt_rm "$@" ;;
cd) shift; _wt_cd "$@" ;;
prune) shift; rm-branches "$@" ;;
*)
echo "Usage: wt <command>"
echo ""
echo "Commands:"
echo " create <branch> Create a worktree (creates branch if needed)"
echo " list List all worktrees"
echo " rm [branch] Remove a worktree (default: current)"
echo " cd [branch] Navigate to a worktree (default: main root)"
echo " prune [days] Remove old branches/worktrees (default: 7 days)"
;;
esac
}
function _wt_create() {
_wt_ensure_repo || return 1
local branch="$1"
if [[ -z "$branch" ]]; then
branch=$(git branch -a --format='%(refname:short)' 2>/dev/null | sed 's|^origin/||' | sort -u | fzf --height=~40% --prompt="branch> ") || return 0
fi
local main_root=$(_wt_main_root)
local worktree_dir="$(_wt_branches_dir)/$branch"
if [[ -d "$worktree_dir" ]]; then
echo "Worktree already exists, navigating to: $worktree_dir"
cd "$worktree_dir"
return 0
fi
# Start from latest origin/main
git fetch origin main --quiet 2>/dev/null
# Existing branch → check out; otherwise create new branch from origin/main
if git show-ref --verify --quiet "refs/heads/$branch"; then
git worktree add "$worktree_dir" "$branch" || return 1
else
git worktree add -b "$branch" "$worktree_dir" origin/main || return 1
fi
# Project-specific setup hook
if [[ -f "$main_root/scripts/worktree-add.sh" ]]; then
"$main_root/scripts/worktree-add.sh" "$worktree_dir"
fi
# VS Code title bar color
local color=$(_wt_color "$branch")
mkdir -p "$worktree_dir/.vscode"
cat > "$worktree_dir/.vscode/settings.json" << EOF
{
"workbench.colorCustomizations": {
"titleBar.activeBackground": "$color",
"titleBar.activeForeground": "#ffffff"
}
}
EOF
cd "$worktree_dir"
if [[ -f "package.json" ]]; then
echo "Installing dependencies..."
pnpm i
fi
echo "Created worktree: $worktree_dir"
}
function _wt_list() {
_wt_ensure_repo || return 1
git worktree list
}
function _wt_rm() {
_wt_ensure_repo || return 1
local branch="$1"
# Default to current worktree's branch, or fzf picker
if [[ -z "$branch" ]]; then
local branches_dir=$(_wt_branches_dir)
local current=$(pwd)
if [[ "$current" == "$branches_dir"/* ]]; then
branch="${current#$branches_dir/}"
else
branch=$(_wt_list_branches | fzf --height=~40% --prompt="remove worktree> ") || return 0
fi
fi
local worktree_dir="$(_wt_branches_dir)/$branch"
if [[ ! -d "$worktree_dir" ]]; then
echo "No worktree found for branch: $branch"
return 1
fi
# Move out if we're inside the worktree being removed
if [[ "$(pwd)" == "$worktree_dir"* ]]; then
cd "$(_wt_main_root)"
fi
git worktree remove "$worktree_dir" --force
echo "Removed worktree: $worktree_dir"
}
function _wt_cd() {
_wt_ensure_repo || return 1
local branch="$1"
# "main" → go to root and check out main
if [[ "$branch" == "main" ]]; then
cd "$(_wt_main_root)"
git checkout main
return $?
fi
# No arg → fzf picker: existing worktrees + open PRs
if [[ -z "$branch" ]]; then
local -a seen=()
local selection
selection=$({
# Existing worktrees (including main)
local b
for b in ${(f)"$(_wt_list_branches)"}; do
seen+=("$b")
echo "$b"
done
# Open PRs not already in worktrees
local pr_branch pr_line
while IFS=$'\t' read -r pr_branch pr_line; do
if (( ${seen[(Ie)$pr_branch]} == 0 )); then
echo "$pr_branch $pr_line"
fi
done < <(gh pr list --limit 30 --json headRefName,title,number --jq '.[] | "\(.headRefName)\t#\(.number) \(.title)"' 2>/dev/null)
} | fzf --height=~40% --prompt="worktree> " --delimiter='\t' --with-nth=1.. --tabstop=30) || return 0
branch="${selection%% *}"
fi
local worktree_dir="$(_wt_branches_dir)/$branch"
if [[ -d "$worktree_dir" ]]; then
cd "$worktree_dir"
return 0
fi
# Fall back to searching all worktrees (handles main, etc.)
local match=$(git worktree list | grep "\[$branch\]" | awk '{print $1}')
if [[ -n "$match" ]]; then
cd "$match"
return 0
fi
# Branch not checked out locally — create worktree
_wt_create "$branch"
}
# --- Tab completion ---
function _wt_list_branches() {
local branches=()
# Fast: list subdirectories from branches dir
local bdir=$(_wt_branches_dir 2>/dev/null)
if [[ -n "$bdir" && -d "$bdir" ]]; then
for d in "$bdir"/*(N/); do
branches+=("${d:t}")
done
fi
# Supplement: parse main worktree branch from git
local line branch
git worktree list --porcelain 2>/dev/null | while IFS= read -r line; do
if [[ "$line" == branch\ * ]]; then
branch="${line#branch refs/heads/}"
branches+=("$branch")
fi
done
# Deduplicate
printf '%s\n' "${branches[@]}" | sort -u
}
function _wt_completion() {
if (( CURRENT == 2 )); then
compadd create list ls rm cd
return
fi
case "${words[2]}" in
cd|rm)
compadd ${(f)"$(_wt_list_branches)"}
;;
create)
compadd ${(f)"$(git branch -a --format='%(refname:short)' 2>/dev/null | sed 's|^origin/||' | sort -u)"}
;;
esac
}
compdef _wt_completion wt