···116116## References
117117118118- `git@github.com:ghostty-org/ghostty.git` - Dive into this codebase when implementing Ghostty features
119119-- `git@github.com:khoi/git-wt.git` - Bundled git worktree wrapper (in Resources/git-wt/wt), modified in the repo directly, do not modify the bundled script we have
119119+- `git@github.com:khoi/git-wt.git` - Git worktree wrapper submodule at `Resources/git-wt`
+1-8
Makefile
···1818BUILD ?=
1919XCODEBUILD_FLAGS ?=
2020.DEFAULT_GOAL := help
2121-.PHONY: build-ghostty-xcframework build-app run-app install-dev-build archive export-archive format lint check test update-wt bump-version bump-and-release log-stream
2121+.PHONY: build-ghostty-xcframework build-app run-app install-dev-build archive export-archive format lint check test bump-version bump-and-release log-stream
22222323help: # Display this help.
2424 @-+echo "Run make with one of the following targets:"
···84848585log-stream: # Stream logs from the app via log stream
8686 log stream --predicate 'subsystem == "app.supabit.supacode"' --style compact --color always
8787-8888-update-wt: # Download git-wt binary to Resources
8989- @mkdir -p "$(CURRENT_MAKEFILE_DIR)/Resources/git-wt"
9090- @curl -fsSL "https://raw.githubusercontent.com/khoi/git-wt/refs/heads/main/wt" -o "$(CURRENT_MAKEFILE_DIR)/Resources/git-wt/wt"
9191- @chmod +x "$(CURRENT_MAKEFILE_DIR)/Resources/git-wt/wt"
9292- @git add "$(CURRENT_MAKEFILE_DIR)/Resources/git-wt/wt"
9393- @git commit -m "update git-wt"
94879588bump-version: # Bump app version (usage: make bump-version [VERSION=x.x.x] [BUILD=123])
9689 @if [ -z "$(VERSION)" ]; then \
-1773
Resources/git-wt/wt
···11-#!/bin/bash
22-set -euo pipefail
33-44-usage() {
55- cat <<'USAGE'
66-wt - A simple git worktree sugar
77-88-Usage:
99- wt [--base-dir <dir>] <command>
1010-1111-Global Flags:
1212- --base-dir <dir> base dir override
1313- -h, --help show help
1414-1515-Commands:
1616- switch <branch> create or open a workspace
1717- sw <branch> alias for switch
1818- exec <branch> open a workspace and run a command
1919- sync [from to] copy files between worktrees
2020- ls list workspaces
2121- rm <branch> remove a workspace
2222- merge <target> squash, rebase, and merge to target
2323- here print current workspace branch
2424- base print base dir
2525- root print main worktree path
2626- completion print shell completion
2727- help [command] show help
2828-USAGE
2929-}
3030-3131-switch_usage() {
3232- cat <<'USAGE'
3333-wt switch|sw <branch>
3434-3535-Flags:
3636- --from <ref> base ref for new branch
3737- --path <dir> explicit worktree path
3838- --fetch fetch remotes before resolving --from
3939- --copy-all copy all files to new worktree
4040- --copy-ignored copy gitignored files to new worktree
4141- --copy-untracked copy untracked files to new worktree
4242- --copy-modified copy modified files to new worktree
4343- -f, --force overwrite existing destination files
4444- -v, --verbose print copy progress
4545- -h, --help show help
4646-USAGE
4747-}
4848-4949-exec_usage() {
5050- cat <<'USAGE'
5151-wt exec <branch> -- <cmd...>
5252-5353-Flags:
5454- --from <ref> base ref for new branch
5555- --path <dir> explicit worktree path
5656- --fetch fetch remotes before resolving --from
5757- -h, --help show help
5858-USAGE
5959-}
6060-6161-sync_usage() {
6262- cat <<'USAGE'
6363-wt sync [<from-branch> <to-branch>]
6464-6565-Defaults to primary worktree -> current worktree.
6666-6767-Flags:
6868- --copy-all copy all files
6969- --copy-ignored copy gitignored files
7070- --copy-untracked copy untracked files
7171- --copy-modified copy modified files
7272- -f, --force overwrite existing destination files
7373- -v, --verbose print copy progress
7474- -n, --dry-run show files that would be copied
7575- -h, --help show help
7676-USAGE
7777-}
7878-7979-ls_usage() {
8080- cat <<'USAGE'
8181-wt ls
8282-8383-Flags:
8484- --plain tab-delimited output
8585- --json JSON output
8686- -h, --help show help
8787-USAGE
8888-}
8989-9090-rm_usage() {
9191- cat <<'USAGE'
9292-wt rm <branch>
9393-9494-Flags:
9595- -f, --force remove even if dirty
9696- -h, --help show help
9797-USAGE
9898-}
9999-100100-merge_usage() {
101101- cat <<'USAGE'
102102-wt merge <target>
103103-104104-Squash, rebase, and fast-forward merge into target branch.
105105-106106-Flags:
107107- --no-squash preserve individual commits
108108- --no-rebase skip rebase (fail if not rebased)
109109- --no-commit skip committing uncommitted changes
110110- -h, --help show help
111111-USAGE
112112-}
113113-114114-here_usage() {
115115- cat <<'USAGE'
116116-wt here
117117-USAGE
118118-}
119119-120120-base_usage() {
121121- cat <<'USAGE'
122122-wt base
123123-USAGE
124124-}
125125-126126-root_usage() {
127127- cat <<'USAGE'
128128-wt root
129129-USAGE
130130-}
131131-132132-completion_usage() {
133133- cat <<'USAGE'
134134-wt completion <shell>
135135-136136-Shells:
137137- bash
138138- zsh
139139- fish
140140-USAGE
141141-}
142142-143143-die() {
144144- printf 'error: %s\n' "$1" >&2
145145- exit 1
146146-}
147147-148148-BASE_DIR_OVERRIDE=""
149149-150150-require_repo() {
151151- local err
152152- if ! err=$(git rev-parse --git-dir 2>&1); then
153153- printf '%s\n' "$err" >&2
154154- die "not a git repository"
155155- fi
156156-}
157157-158158-common_git_dir_abs() {
159159- local common
160160- common=$(git rev-parse --git-common-dir)
161161- local target
162162- case "$common" in
163163- /*) target="$common" ;;
164164- *) target="$(abs_path "$common")" ;;
165165- esac
166166- if [ -d "$target" ]; then
167167- (cd "$target" && pwd -P)
168168- return
169169- fi
170170- normalize_dir "$target"
171171-}
172172-173173-repo_is_bare() {
174174- local is_bare
175175- if [ -n "${1:-}" ]; then
176176- is_bare=$(git --git-dir "$1" rev-parse --is-bare-repository)
177177- else
178178- is_bare=$(git rev-parse --is-bare-repository)
179179- fi
180180- [ "$is_bare" = "true" ]
181181-}
182182-183183-canonical_worktree_path() {
184184- local path="$1"
185185- local resolved
186186- if resolved=$(git -C "$path" rev-parse --show-toplevel 2>/dev/null); then
187187- normalize_dir "$resolved"
188188- return
189189- fi
190190- normalize_dir "$path"
191191-}
192192-193193-main_worktree_path() {
194194- local common
195195- common=$(common_git_dir_abs)
196196- if repo_is_bare; then
197197- printf '%s\n' "$common"
198198- return
199199- fi
200200- local path="" is_bare=0
201201- while IFS= read -r line || [ -n "$line" ]; do
202202- if [ -z "$line" ]; then
203203- if [ -n "$path" ] && [ "$is_bare" -eq 0 ]; then
204204- canonical_worktree_path "$path"
205205- return
206206- fi
207207- path=""
208208- is_bare=0
209209- continue
210210- fi
211211- case "$line" in
212212- worktree\ *) path="${line#worktree }" ;;
213213- bare) is_bare=1 ;;
214214- esac
215215- done < <(git worktree list --porcelain)
216216- if [ -n "$path" ] && [ "$is_bare" -eq 0 ]; then
217217- canonical_worktree_path "$path"
218218- return
219219- fi
220220- git rev-parse --show-toplevel
221221-}
222222-223223-primary_worktree_path() {
224224- local root="$1"
225225- local entry
226226- while IFS= read -r entry; do
227227- [ -n "$entry" ] || continue
228228- local path head branch is_bare
229229- parse_worktree_entry "$entry"
230230- if [ "$is_bare" = "0" ] && [ -d "$path" ]; then
231231- printf '%s\n' "$path"
232232- return 0
233233- fi
234234- done < <(worktree_entries "$root")
235235- return 1
236236-}
237237-238238-copy_source_path() {
239239- local root="$1"
240240- if repo_is_bare; then
241241- primary_worktree_path "$root" || die "no linked worktree found for copy operations"
242242- return
243243- fi
244244- printf '%s\n' "$root"
245245-}
246246-247247-abs_path() {
248248- local path="$1"
249249- case "$path" in
250250- /*) printf '%s\n' "$path" ;;
251251- *) printf '%s/%s\n' "$(pwd -P)" "$path" ;;
252252- esac
253253-}
254254-255255-normalize_dir() {
256256- local path="$1"
257257- if [ "$path" != "/" ]; then
258258- path="${path%/}"
259259- fi
260260- printf '%s\n' "$path"
261261-}
262262-263263-base_dir() {
264264- local root="$1"
265265- local override="$2"
266266- local chosen
267267- chosen="${GIT_WT_BASE:-}"
268268- if [ -n "$override" ]; then
269269- chosen="$override"
270270- fi
271271- if [ -z "$chosen" ]; then
272272- normalize_dir "$(abs_path "$root/.worktrees")"
273273- return
274274- fi
275275- case "$chosen" in
276276- /*) normalize_dir "$(abs_path "$chosen")" ;;
277277- *) normalize_dir "$(abs_path "$root/$chosen")" ;;
278278- esac
279279-}
280280-281281-resolve_path() {
282282- local root="$1"
283283- local path="$2"
284284- case "$path" in
285285- /*) abs_path "$path" ;;
286286- *) abs_path "$root/$path" ;;
287287- esac
288288-}
289289-290290-worktree_entries() {
291291- local root="$1"
292292- local path="" head="" branch="" is_bare=0
293293- while IFS= read -r line || [ -n "$line" ]; do
294294- if [ -z "$line" ]; then
295295- if [ -n "$path" ]; then
296296- local output_path="$path"
297297- if [ "$is_bare" -eq 0 ]; then
298298- output_path=$(canonical_worktree_path "$path")
299299- fi
300300- printf '%s\t%s\t%s\t%s\n' "$output_path" "$head" "$branch" "$is_bare"
301301- fi
302302- path=""
303303- head=""
304304- branch=""
305305- is_bare=0
306306- continue
307307- fi
308308- local key value
309309- key=${line%% *}
310310- value=${line#* }
311311- case "$key" in
312312- worktree) path="$value" ;;
313313- HEAD) head="$value" ;;
314314- branch) branch="${value#refs/heads/}" ;;
315315- detached) branch="" ;;
316316- bare) is_bare=1 ;;
317317- esac
318318- done < <(git -C "$root" worktree list --porcelain)
319319- if [ -n "$path" ]; then
320320- local output_path="$path"
321321- if [ "$is_bare" -eq 0 ]; then
322322- output_path=$(canonical_worktree_path "$path")
323323- fi
324324- printf '%s\t%s\t%s\t%s\n' "$output_path" "$head" "$branch" "$is_bare"
325325- fi
326326-}
327327-328328-parse_worktree_entry() {
329329- local entry="$1"
330330- local rest
331331- path=${entry%%$'\t'*}
332332- rest=${entry#*$'\t'}
333333- head=${rest%%$'\t'*}
334334- rest=${rest#*$'\t'}
335335- branch=${rest%%$'\t'*}
336336- is_bare=${rest#*$'\t'}
337337-}
338338-339339-find_worktree_by_branch() {
340340- local root="$1"
341341- local target_branch="$2"
342342- local entry
343343- while IFS= read -r entry; do
344344- local path head branch is_bare
345345- parse_worktree_entry "$entry"
346346- if [ "$is_bare" = "1" ]; then
347347- continue
348348- fi
349349- if [ "$branch" = "$target_branch" ]; then
350350- printf '%s\n' "$path"
351351- return 0
352352- fi
353353- done < <(worktree_entries "$root")
354354- return 1
355355-}
356356-357357-find_worktree_by_path() {
358358- local root="$1"
359359- local target="$2"
360360- local entry
361361- while IFS= read -r entry; do
362362- local path head branch is_bare
363363- parse_worktree_entry "$entry"
364364- if [ "$path" = "$target" ]; then
365365- printf '%s\n' "$path"
366366- return 0
367367- fi
368368- if [ -d "$path" ] && [ -d "$target" ] && [ "$path" -ef "$target" ]; then
369369- printf '%s\n' "$path"
370370- return 0
371371- fi
372372- done < <(worktree_entries "$root")
373373- return 1
374374-}
375375-376376-json_escape() {
377377- printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'
378378-}
379379-380380-detect_jobs() {
381381- sysctl -n hw.logicalcpu 2>/dev/null || nproc 2>/dev/null || echo 1
382382-}
383383-384384-list_ignored_files() {
385385- local root="$1"
386386- git -C "$root" ls-files --others --ignored --exclude-standard --directory | sed '/^\.git\//d'
387387-}
388388-389389-list_untracked_files() {
390390- local root="$1"
391391- git -C "$root" ls-files --others --exclude-standard | sed '/^\.git\//d'
392392-}
393393-394394-list_modified_files() {
395395- local root="$1"
396396- git -C "$root" ls-files --modified | sed '/^\.git\//d'
397397-}
398398-399399-list_tracked_files() {
400400- local root="$1"
401401- git -C "$root" ls-files | sed '/^\.git\//d'
402402-}
403403-404404-list_nested_worktree_prefixes() {
405405- local src="$1"
406406- local src_real
407407- src_real=$(cd "$src" && pwd -P)
408408- local entry
409409- while IFS= read -r entry; do
410410- [ -n "$entry" ] || continue
411411- local path head branch is_bare
412412- parse_worktree_entry "$entry"
413413- [ "$is_bare" = "0" ] || continue
414414- [ -d "$path" ] || continue
415415- local path_real
416416- path_real=$(cd "$path" && pwd -P)
417417- if [ "$path_real" = "$src_real" ]; then
418418- continue
419419- fi
420420- case "$path_real" in
421421- "$src_real"/*) printf '%s\n' "${path_real#"$src_real"/}" ;;
422422- esac
423423- done < <(worktree_entries "$src")
424424-}
425425-426426-filter_nested_worktree_prefixes() {
427427- local files="$1"
428428- local prefixes="$2"
429429- if [ -z "$prefixes" ]; then
430430- printf '%s' "$files"
431431- return
432432- fi
433433- local out="" file prefix skip file_trim prefix_trim
434434- while IFS= read -r file; do
435435- [ -n "$file" ] || continue
436436- file_trim="${file%/}"
437437- skip=0
438438- while IFS= read -r prefix; do
439439- [ -n "$prefix" ] || continue
440440- prefix_trim="${prefix%/}"
441441- case "$file_trim" in
442442- "$prefix_trim" | "$prefix_trim"/*)
443443- skip=1
444444- break
445445- ;;
446446- esac
447447- case "$prefix_trim" in
448448- "$file_trim" | "$file_trim"/*)
449449- skip=1
450450- break
451451- ;;
452452- esac
453453- done <<< "$prefixes"
454454- [ "$skip" -eq 1 ] && continue
455455- out+="$file"$'\n'
456456- done <<< "$files"
457457- printf '%s' "$out"
458458-}
459459-460460-prune_nested_copy_paths() {
461461- local files="$1"
462462- local out="" dirs="" file file_trim dir skip
463463- while IFS= read -r file; do
464464- [ -n "$file" ] || continue
465465- file_trim="${file%/}"
466466- skip=0
467467- while IFS= read -r dir; do
468468- [ -n "$dir" ] || continue
469469- case "$file_trim" in
470470- "$dir"/*)
471471- skip=1
472472- break
473473- ;;
474474- esac
475475- done <<< "$dirs"
476476- [ "$skip" -eq 1 ] && continue
477477- out+="$file"$'\n'
478478- if [ "$file" != "$file_trim" ]; then
479479- dirs+="$file_trim"$'\n'
480480- fi
481481- done <<< "$files"
482482- printf '%s' "$out"
483483-}
484484-485485-collect_copy_files() {
486486- local src="$1"
487487- local copyignored="$2"
488488- local copyuntracked="$3"
489489- local copymodified="$4"
490490- local copytracked="$5"
491491- local sources=0
492492- [ "$copyignored" -eq 1 ] && sources=$((sources + 1))
493493- [ "$copyuntracked" -eq 1 ] && sources=$((sources + 1))
494494- [ "$copymodified" -eq 1 ] && sources=$((sources + 1))
495495- [ "$copytracked" -eq 1 ] && sources=$((sources + 1))
496496- local prefixes
497497- prefixes=$(list_nested_worktree_prefixes "$src")
498498- if [ "$sources" -eq 1 ]; then
499499- if [ "$copyignored" -eq 1 ]; then
500500- local ignored
501501- ignored=$(filter_nested_worktree_prefixes "$(list_ignored_files "$src")" "$prefixes")
502502- ignored=$(printf '%s' "$ignored" | sed '/^$/d' | LC_ALL=C sort -u)
503503- prune_nested_copy_paths "$ignored"
504504- return
505505- fi
506506- if [ "$copyuntracked" -eq 1 ]; then
507507- filter_nested_worktree_prefixes "$(list_untracked_files "$src")" "$prefixes"
508508- return
509509- fi
510510- if [ "$copymodified" -eq 1 ]; then
511511- filter_nested_worktree_prefixes "$(list_modified_files "$src")" "$prefixes"
512512- return
513513- fi
514514- filter_nested_worktree_prefixes "$(list_tracked_files "$src")" "$prefixes"
515515- return
516516- fi
517517- local files=""
518518- if [ "$copyignored" -eq 1 ]; then
519519- files+=$(list_ignored_files "$src")$'\n'
520520- fi
521521- if [ "$copyuntracked" -eq 1 ]; then
522522- files+=$(list_untracked_files "$src")$'\n'
523523- fi
524524- if [ "$copymodified" -eq 1 ]; then
525525- files+=$(list_modified_files "$src")$'\n'
526526- fi
527527- if [ "$copytracked" -eq 1 ]; then
528528- files+=$(list_tracked_files "$src")$'\n'
529529- fi
530530- local deduped
531531- deduped=$(printf '%s' "$files" | sed '/^$/d' | LC_ALL=C sort -u)
532532- deduped=$(filter_nested_worktree_prefixes "$deduped" "$prefixes")
533533- prune_nested_copy_paths "$deduped"
534534-}
535535-536536-reflink_copy_file() {
537537- local src="$1"
538538- local dst="$2"
539539- case "$(uname)" in
540540- Darwin)
541541- cp -c "$src" "$dst" 2>/dev/null || cp "$src" "$dst" 2>/dev/null || true
542542- ;;
543543- *)
544544- cp --reflink=auto "$src" "$dst" 2>/dev/null || cp "$src" "$dst" 2>/dev/null || true
545545- ;;
546546- esac
547547-}
548548-549549-reflink_copy_tree() {
550550- local src="$1"
551551- local dst="$2"
552552- mkdir -p "$dst"
553553- case "$(uname)" in
554554- Darwin)
555555- cp -cRP "$src/." "$dst" 2>/dev/null || cp -RP "$src/." "$dst" 2>/dev/null || true
556556- ;;
557557- *)
558558- cp -a --reflink=auto "$src/." "$dst" 2>/dev/null || cp -a "$src/." "$dst" 2>/dev/null || true
559559- ;;
560560- esac
561561-}
562562-563563-reflink_copy_tree_no_overwrite() {
564564- local src="$1"
565565- local dst="$2"
566566- mkdir -p "$dst"
567567- case "$(uname)" in
568568- Darwin)
569569- cp -cnRP "$src/." "$dst" 2>/dev/null || cp -nRP "$src/." "$dst" 2>/dev/null || true
570570- ;;
571571- *)
572572- cp -a -n --reflink=auto "$src/." "$dst" 2>/dev/null || cp -a -n "$src/." "$dst" 2>/dev/null || true
573573- ;;
574574- esac
575575-}
576576-577577-reflink_copy_dir_new() {
578578- local src="$1"
579579- local dst="$2"
580580- local src_dir="${src%/}"
581581- local parent
582582- parent=$(dirname "$dst")
583583- mkdir -p "$parent"
584584- case "$(uname)" in
585585- Darwin)
586586- cp -cRP "$src_dir" "$parent" 2>/dev/null || cp -RP "$src_dir" "$parent" 2>/dev/null || true
587587- ;;
588588- *)
589589- cp -a --reflink=auto "$src_dir" "$parent" 2>/dev/null || cp -a "$src_dir" "$parent" 2>/dev/null || true
590590- ;;
591591- esac
592592-}
593593-594594-remove_path() {
595595- local path="$1"
596596- if [ -L "$path" ] || [ -f "$path" ]; then
597597- rm -f "$path" 2>/dev/null || true
598598- return
599599- fi
600600- if [ -d "$path" ]; then
601601- rm -rf "$path" 2>/dev/null || true
602602- fi
603603-}
604604-605605-copy_symlink() {
606606- local src="$1"
607607- local dst="$2"
608608- local force="${3:-0}"
609609- local target
610610- target=$(readlink "$src" 2>/dev/null) || return 0
611611- if [ -e "$dst" ] || [ -L "$dst" ]; then
612612- if [ "$force" -eq 1 ]; then
613613- remove_path "$dst"
614614- else
615615- return 0
616616- fi
617617- fi
618618- mkdir -p "$(dirname "$dst")"
619619- ln -s "$target" "$dst" 2>/dev/null || true
620620-}
621621-622622-copy_file() {
623623- local src="$1"
624624- local dst="$2"
625625- local force="${3:-0}"
626626- if [ -e "$dst" ] || [ -L "$dst" ]; then
627627- if [ "$force" -eq 1 ]; then
628628- remove_path "$dst"
629629- else
630630- return 0
631631- fi
632632- fi
633633- mkdir -p "$(dirname "$dst")"
634634- reflink_copy_file "$src" "$dst"
635635-}
636636-637637-copy_dir_recursive() {
638638- local src_dir="$1"
639639- local dst_dir="$2"
640640- local force="${3:-0}"
641641- local dst_exists=0
642642- if [ -e "$dst_dir" ] || [ -L "$dst_dir" ]; then
643643- dst_exists=1
644644- if [ -d "$dst_dir" ] && [ ! -L "$dst_dir" ]; then
645645- :
646646- elif [ "$force" -eq 1 ]; then
647647- remove_path "$dst_dir"
648648- dst_exists=0
649649- else
650650- return 0
651651- fi
652652- fi
653653- if [ "$dst_exists" -eq 0 ]; then
654654- reflink_copy_dir_new "$src_dir" "$dst_dir"
655655- elif [ "$force" -eq 1 ]; then
656656- reflink_copy_tree "$src_dir" "$dst_dir"
657657- else
658658- reflink_copy_tree_no_overwrite "$src_dir" "$dst_dir"
659659- fi
660660-}
661661-662662-copy_tree() {
663663- local src_dir="$1"
664664- local dst_dir="$2"
665665- local force="${3:-0}"
666666- copy_dir_recursive "$src_dir" "$dst_dir" "$force"
667667-}
668668-669669-copy_path() {
670670- local src="$1"
671671- local dst="$2"
672672- local force="${3:-0}"
673673- if [ -L "$src" ]; then
674674- copy_symlink "$src" "$dst" "$force"
675675- elif [ -d "$src" ]; then
676676- copy_tree "$src" "$dst" "$force"
677677- elif [ -f "$src" ]; then
678678- copy_file "$src" "$dst" "$force"
679679- fi
680680-}
681681-682682-copy_entry() {
683683- local entry="$1" src="$2" dst="$3" force="${4:-0}" verbose="${5:-0}"
684684- [ -n "$entry" ] || return 0
685685- local entry_trim="${entry%/}"
686686- local srcfile="$src/$entry_trim"
687687- local dstfile="$dst/$entry_trim"
688688- [ -e "$srcfile" ] || [ -L "$srcfile" ] || return 0
689689- [ "$verbose" -eq 1 ] && printf '%s\n' "$entry_trim" >&2
690690- copy_path "$srcfile" "$dstfile" "$force"
691691-}
692692-693693-copy_entries_parallel() {
694694- local src="$1" dst="$2" files="$3" force="${4:-0}" verbose="${5:-0}"
695695- local jobs
696696- jobs=$(detect_jobs)
697697- local tmpfile
698698- tmpfile=$(mktemp)
699699- printf '%s' "$files" | sed '/^$/d' > "$tmpfile"
700700- export -f copy_entry copy_path copy_symlink copy_tree copy_dir_recursive \
701701- copy_file reflink_copy_file reflink_copy_tree reflink_copy_tree_no_overwrite \
702702- reflink_copy_dir_new remove_path
703703- xargs -P "$jobs" -I{} bash -c 'copy_entry "$@"' _ {} "$src" "$dst" "$force" "$verbose" < "$tmpfile"
704704- rm -f "$tmpfile"
705705-}
706706-707707-copy_files_to_worktree() {
708708- local src="$1"
709709- local dst="$2"
710710- local copyignored="$3"
711711- local copyuntracked="$4"
712712- local copymodified="$5"
713713- local copytracked="$6"
714714- local force="${7:-0}"
715715- local verbose="${8:-0}"
716716- local files
717717- files=$(collect_copy_files "$src" "$copyignored" "$copyuntracked" "$copymodified" "$copytracked")
718718- copy_entries_parallel "$src" "$dst" "$files" "$force" "$verbose"
719719-}
720720-721721-copy_files_between() {
722722- local src="$1"
723723- local dst="$2"
724724- local copyignored="$3"
725725- local copyuntracked="$4"
726726- local copymodified="$5"
727727- local copytracked="$6"
728728- local dryrun="${7:-0}"
729729- local verbose="${8:-0}"
730730- local force="${9:-0}"
731731- if [ "$src" = "$dst" ] || { [ -d "$src" ] && [ -d "$dst" ] && [ "$src" -ef "$dst" ]; }; then
732732- return 0
733733- fi
734734- if [ "$dryrun" -eq 1 ] && [ "$verbose" -eq 0 ]; then
735735- collect_copy_files "$src" "$copyignored" "$copyuntracked" "$copymodified" "$copytracked"
736736- return 0
737737- fi
738738- local files
739739- files=$(collect_copy_files "$src" "$copyignored" "$copyuntracked" "$copymodified" "$copytracked")
740740- if [ "$dryrun" -eq 1 ]; then
741741- local file
742742- while IFS= read -r file; do
743743- [ -z "$file" ] && continue
744744- local srcfile="$src/$file"
745745- if [ ! -e "$srcfile" ] && [ ! -L "$srcfile" ]; then
746746- continue
747747- fi
748748- [ "$verbose" -eq 1 ] && printf '%s\n' "$file" >&2
749749- printf '%s\n' "$file"
750750- done <<< "$files"
751751- return 0
752752- fi
753753- copy_entries_parallel "$src" "$dst" "$files" "$force" "$verbose"
754754-}
755755-756756-open_path() {
757757- local branch="$1"
758758- local from="$2"
759759- local base_override="$3"
760760- local path_override="$4"
761761- local fetch="$5"
762762- local copyignored="${6:-0}"
763763- local copyuntracked="${7:-0}"
764764- local copymodified="${8:-0}"
765765- local copytracked="${9:-0}"
766766- local force="${10:-0}"
767767- local verbose="${11:-0}"
768768- require_repo
769769- local root base path copy_src
770770- root=$(main_worktree_path)
771771- copy_src="$root"
772772- if [ "$copyignored" -eq 1 ] || [ "$copyuntracked" -eq 1 ] || [ "$copymodified" -eq 1 ] || [ "$copytracked" -eq 1 ]; then
773773- copy_src=$(copy_source_path "$root")
774774- fi
775775- base=$(base_dir "$root" "$base_override")
776776- if [ -n "$path_override" ]; then
777777- path=$(resolve_path "$root" "$path_override")
778778- else
779779- path=$(resolve_path "$root" "$base/$branch")
780780- fi
781781- local existing
782782- if existing=$(find_worktree_by_branch "$root" "$branch"); then
783783- printf '%s\n' "$existing"
784784- return 0
785785- fi
786786- if git -C "$root" show-ref --verify --quiet "refs/heads/$branch"; then
787787- if [ -z "$path_override" ]; then
788788- die "branch exists without worktree: '$branch'"
789789- fi
790790- if find_worktree_by_path "$root" "$path" >/dev/null; then
791791- die "worktree path already in use: '$path'"
792792- fi
793793- mkdir -p "$(dirname "$path")"
794794- git -C "$root" worktree add "$path" "$branch" >/dev/null
795795- copy_files_to_worktree "$copy_src" "$path" "$copyignored" "$copyuntracked" "$copymodified" "$copytracked" "$force" "$verbose"
796796- printf '%s\n' "$path"
797797- return 0
798798- fi
799799- if [ "$fetch" -eq 1 ]; then
800800- git -C "$root" fetch
801801- fi
802802- if [ -z "$from" ]; then
803803- from=$(git -C "$root" rev-parse HEAD)
804804- fi
805805- if ! git -C "$root" rev-parse --verify "${from}^{commit}" >/dev/null; then
806806- die "invalid --from ref '$from'"
807807- fi
808808- mkdir -p "$(dirname "$path")"
809809- git -C "$root" worktree add -b "$branch" "$path" "$from" >/dev/null
810810- copy_files_to_worktree "$copy_src" "$path" "$copyignored" "$copyuntracked" "$copymodified" "$copytracked" "$force" "$verbose"
811811- printf '%s\n' "$path"
812812-}
813813-814814-cmd_switch() {
815815- local from="" path_override="" fetch=0 branch=""
816816- local copyignored=0 copyuntracked=0 copymodified=0 copytracked=0 force=0 verbose=0
817817- while [ "$#" -gt 0 ]; do
818818- case "$1" in
819819- -h | --help)
820820- switch_usage
821821- exit 0
822822- ;;
823823- --from)
824824- [ -n "${2:-}" ] || die "missing value for --from"
825825- from="$2"
826826- shift
827827- ;;
828828- --from=*) from="${1#--from=}" ;;
829829- --path)
830830- [ -n "${2:-}" ] || die "missing value for --path"
831831- path_override="$2"
832832- shift
833833- ;;
834834- --path=*) path_override="${1#--path=}" ;;
835835- --fetch) fetch=1 ;;
836836- --copy-all) copyignored=1; copyuntracked=1; copymodified=1; copytracked=1 ;;
837837- --copy-ignored) copyignored=1 ;;
838838- --copy-untracked) copyuntracked=1 ;;
839839- --copy-modified) copymodified=1 ;;
840840- -f | --force) force=1 ;;
841841- -v | --verbose) verbose=1 ;;
842842- --*) die "unknown flag '$1'" ;;
843843- *) if [ -z "$branch" ]; then branch="$1"; else die "unexpected argument '$1'"; fi ;;
844844- esac
845845- shift
846846- done
847847- [ -n "$branch" ] || die "missing branch"
848848- open_path "$branch" "$from" "$BASE_DIR_OVERRIDE" "$path_override" "$fetch" "$copyignored" "$copyuntracked" "$copymodified" "$copytracked" "$force" "$verbose"
849849-}
850850-851851-cmd_exec() {
852852- local from="" path_override="" fetch=0 branch=""
853853- while [ "$#" -gt 0 ]; do
854854- case "$1" in
855855- -h | --help)
856856- exec_usage
857857- exit 0
858858- ;;
859859- --from)
860860- [ -n "${2:-}" ] || die "missing value for --from"
861861- from="$2"
862862- shift
863863- ;;
864864- --from=*) from="${1#--from=}" ;;
865865- --path)
866866- [ -n "${2:-}" ] || die "missing value for --path"
867867- path_override="$2"
868868- shift
869869- ;;
870870- --path=*) path_override="${1#--path=}" ;;
871871- --fetch) fetch=1 ;;
872872- --) shift; break ;;
873873- --*) die "unknown flag '$1'" ;;
874874- *)
875875- if [ -z "$branch" ]; then
876876- branch="$1"
877877- else
878878- die "missing -- before command"
879879- fi
880880- ;;
881881- esac
882882- shift
883883- done
884884- [ -n "$branch" ] || die "missing branch"
885885- [ "$#" -gt 0 ] || die "missing command"
886886- local path
887887- path=$(open_path "$branch" "$from" "$BASE_DIR_OVERRIDE" "$path_override" "$fetch")
888888- cd "$path"
889889- exec "$@"
890890-}
891891-892892-cmd_sync() {
893893- local copyignored=0 copyuntracked=0 copymodified=0 copytracked=0 dryrun=0 verbose=0 force=0
894894- local from_branch="" to_branch=""
895895- while [ "$#" -gt 0 ]; do
896896- case "$1" in
897897- -h | --help)
898898- sync_usage
899899- exit 0
900900- ;;
901901- --copy-all) copyignored=1; copyuntracked=1; copymodified=1; copytracked=1 ;;
902902- --copy-ignored) copyignored=1 ;;
903903- --copy-untracked) copyuntracked=1 ;;
904904- --copy-modified) copymodified=1 ;;
905905- -f | --force) force=1 ;;
906906- -v | --verbose) verbose=1 ;;
907907- -n | --dry-run) dryrun=1 ;;
908908- --)
909909- shift
910910- break
911911- ;;
912912- --*) die "unknown flag '$1'" ;;
913913- *)
914914- if [ -z "$from_branch" ]; then
915915- from_branch="$1"
916916- elif [ -z "$to_branch" ]; then
917917- to_branch="$1"
918918- else
919919- die "unexpected argument '$1'"
920920- fi
921921- ;;
922922- esac
923923- shift
924924- done
925925- for arg in "$@"; do
926926- if [ -z "$from_branch" ]; then
927927- from_branch="$arg"
928928- elif [ -z "$to_branch" ]; then
929929- to_branch="$arg"
930930- else
931931- die "unexpected argument '$arg'"
932932- fi
933933- done
934934- if [ -n "$from_branch" ] && [ -z "$to_branch" ]; then
935935- die "missing destination branch"
936936- fi
937937- if [ -z "$from_branch" ] && [ -n "$to_branch" ]; then
938938- die "missing source branch"
939939- fi
940940- require_repo
941941- if [ "$copyignored" -eq 0 ] && [ "$copyuntracked" -eq 0 ] && [ "$copymodified" -eq 0 ] && [ "$copytracked" -eq 0 ]; then
942942- die "missing copy flag"
943943- fi
944944- local root src dst
945945- root=$(main_worktree_path)
946946- if [ -z "$from_branch" ]; then
947947- src=$(copy_source_path "$root")
948948- if dst=$(git rev-parse --show-toplevel 2>/dev/null); then
949949- find_worktree_by_path "$root" "$dst" >/dev/null || die "current directory is not a worktree"
950950- else
951951- dst="$src"
952952- fi
953953- else
954954- if ! src=$(find_worktree_by_branch "$root" "$from_branch"); then
955955- die "workspace not found: '$from_branch'"
956956- fi
957957- if ! dst=$(find_worktree_by_branch "$root" "$to_branch"); then
958958- die "workspace not found: '$to_branch'"
959959- fi
960960- fi
961961- copy_files_between "$src" "$dst" "$copyignored" "$copyuntracked" "$copymodified" "$copytracked" "$dryrun" "$verbose" "$force"
962962-}
963963-964964-cmd_ls() {
965965- local plain=0 json=0
966966- while [ "$#" -gt 0 ]; do
967967- case "$1" in
968968- -h | --help)
969969- ls_usage
970970- exit 0
971971- ;;
972972- --plain) plain=1 ;;
973973- --json) json=1 ;;
974974- --*) die "unknown flag '$1'" ;;
975975- *) die "unexpected argument '$1'" ;;
976976- esac
977977- shift
978978- done
979979- require_repo
980980- local root base
981981- root=$(main_worktree_path)
982982- if [ "$plain" -eq 1 ] && [ "$json" -eq 1 ]; then
983983- die "cannot use --plain and --json together"
984984- fi
985985- local mode="table"
986986- if [ "$plain" -eq 1 ]; then
987987- mode="plain"
988988- elif [ "$json" -eq 1 ]; then
989989- mode="json"
990990- elif [ ! -t 1 ]; then
991991- mode="plain"
992992- fi
993993- local entries
994994- entries=$(worktree_entries "$root")
995995- local filtered=""
996996- if [ -n "$BASE_DIR_OVERRIDE" ]; then
997997- base=$(base_dir "$root" "$BASE_DIR_OVERRIDE")
998998- while IFS= read -r entry; do
999999- [ -n "$entry" ] || continue
10001000- local path head branch is_bare
10011001- parse_worktree_entry "$entry"
10021002- if [ "$is_bare" = "1" ]; then
10031003- filtered+="(bare)\t$path\t$head\t1\n"
10041004- continue
10051005- fi
10061006- case "$path" in
10071007- "$base"/*)
10081008- filtered+="$branch\t$path\t$head\t0\n"
10091009- ;;
10101010- esac
10111011- done <<<"$entries"
10121012- else
10131013- while IFS= read -r entry; do
10141014- [ -n "$entry" ] || continue
10151015- local path head branch is_bare
10161016- parse_worktree_entry "$entry"
10171017- if [ "$is_bare" = "1" ]; then
10181018- branch="(bare)"
10191019- fi
10201020- filtered+="$branch\t$path\t$head\t$is_bare\n"
10211021- done <<<"$entries"
10221022- fi
10231023- case "$mode" in
10241024- json)
10251025- printf '['
10261026- local first=1
10271027- while IFS= read -r line || [ -n "$line" ]; do
10281028- [ -n "$line" ] || continue
10291029- local branch path head rest is_bare is_bare_json
10301030- branch=${line%%$'\t'*}
10311031- rest=${line#*$'\t'}
10321032- path=${rest%%$'\t'*}
10331033- rest=${rest#*$'\t'}
10341034- head=${rest%%$'\t'*}
10351035- is_bare=${rest#*$'\t'}
10361036- if [ "$is_bare" = "1" ]; then
10371037- is_bare_json="true"
10381038- else
10391039- is_bare_json="false"
10401040- fi
10411041- if [ "$first" -eq 0 ]; then
10421042- printf ','
10431043- fi
10441044- first=0
10451045- printf '{"branch":"%s","path":"%s","head":"%s","is_bare":%s}' "$(json_escape "$branch")" "$(json_escape "$path")" "$(json_escape "$head")" "$is_bare_json"
10461046- done <<<"$(printf '%b' "$filtered")"
10471047- printf ']\n'
10481048- ;;
10491049- plain)
10501050- printf '%b' "$filtered" | awk -F'\t' 'NF>=2 {print $1"\t"$2}'
10511051- ;;
10521052- table)
10531053- printf '%b' "$filtered" | awk -F'\t' 'NF>=3 {printf "%-30s %-60s %s\n", $1, $2, $3}'
10541054- ;;
10551055- esac
10561056-}
10571057-10581058-cmd_rm() {
10591059- local force=0 branch=""
10601060- while [ "$#" -gt 0 ]; do
10611061- case "$1" in
10621062- -h | --help)
10631063- rm_usage
10641064- exit 0
10651065- ;;
10661066- -f | --force) force=1 ;;
10671067- --*) die "unknown flag '$1'" ;;
10681068- *) if [ -z "$branch" ]; then branch="$1"; else die "unexpected argument '$1'"; fi ;;
10691069- esac
10701070- shift
10711071- done
10721072- [ -n "$branch" ] || die "missing branch"
10731073- require_repo
10741074- local root path
10751075- root=$(main_worktree_path)
10761076- if ! path=$(find_worktree_by_branch "$root" "$branch"); then
10771077- if [ "$force" -eq 1 ]; then
10781078- exit 0
10791079- fi
10801080- die "workspace not found: '$branch'"
10811081- fi
10821082- if [ "$path" = "$root" ]; then
10831083- die "cannot remove main worktree"
10841084- fi
10851085- if [ "$force" -eq 0 ]; then
10861086- local dirty
10871087- dirty=$(git -C "$path" status --porcelain)
10881088- if [ -n "$dirty" ]; then
10891089- if [ -t 0 ]; then
10901090- printf "workspace '%s' has uncommitted changes. remove anyway? [y/N] " "$branch" >&2
10911091- read -r answer
10921092- case "$answer" in
10931093- y | Y | yes | YES) force=1 ;;
10941094- *) exit 1 ;;
10951095- esac
10961096- else
10971097- die "workspace dirty: '$branch'"
10981098- fi
10991099- fi
11001100- fi
11011101- if [ "$force" -eq 1 ]; then
11021102- git -C "$root" worktree remove --force "$path" 2>/dev/null || true
11031103- command rm -rf "$path" 2>/dev/null || true
11041104- else
11051105- git -C "$root" worktree remove "$path"
11061106- fi
11071107- if [ "$force" -eq 1 ]; then
11081108- git -C "$root" branch -D "$branch" 2>/dev/null || true
11091109- else
11101110- git -C "$root" branch -d "$branch"
11111111- fi
11121112- printf '%s\n' "$path"
11131113-}
11141114-11151115-cmd_merge() {
11161116- local target="" squash=1 rebase=1 commit=1
11171117- while [ "$#" -gt 0 ]; do
11181118- case "$1" in
11191119- -h|--help) merge_usage; exit 0 ;;
11201120- --no-squash) squash=0 ;;
11211121- --no-rebase) rebase=0 ;;
11221122- --no-commit) commit=0 ;;
11231123- --*) die "unknown flag '$1'" ;;
11241124- *) if [ -z "$target" ]; then target="$1"; else die "unexpected argument '$1'"; fi ;;
11251125- esac
11261126- shift
11271127- done
11281128-11291129- if [ -z "$target" ]; then
11301130- merge_usage
11311131- exit 1
11321132- fi
11331133-11341134- require_repo
11351135- local root branch
11361136- root=$(main_worktree_path)
11371137- branch=$(git symbolic-ref --short HEAD 2>/dev/null) || die "not on a branch (detached HEAD)"
11381138-11391139- git show-ref --verify --quiet "refs/heads/$target" || die "target branch '$target' not found"
11401140-11411141- [ "$branch" != "$target" ] || die "cannot merge branch into itself"
11421142-11431143- if [ "$commit" -eq 0 ]; then
11441144- if [ -n "$(git status --porcelain)" ]; then
11451145- die "uncommitted changes present (use without --no-commit)"
11461146- fi
11471147- fi
11481148-11491149- if [ "$commit" -eq 1 ] && [ -n "$(git status --porcelain)" ]; then
11501150- git add -A
11511151- git commit -m "WIP: uncommitted changes" || die "failed to commit changes"
11521152- printf 'committed uncommitted changes\n' >&2
11531153- fi
11541154-11551155- local merge_base
11561156- merge_base=$(git merge-base HEAD "$target") || die "no common ancestor with '$target'"
11571157-11581158- if [ "$squash" -eq 1 ]; then
11591159- local commit_count
11601160- commit_count=$(git rev-list --count "$merge_base..HEAD")
11611161- if [ "$commit_count" -gt 1 ]; then
11621162- local subjects
11631163- subjects=$(git log --format='- %s' "$merge_base..HEAD")
11641164- git reset --soft "$merge_base" || die "failed to reset to merge base"
11651165- git commit -m "$(printf 'Squash %d commits\n\n%s' "$commit_count" "$subjects")" || die "failed to create squash commit"
11661166- printf 'squashed %d commits\n' "$commit_count" >&2
11671167- elif [ "$commit_count" -eq 1 ]; then
11681168- printf 'single commit, nothing to squash\n' >&2
11691169- else
11701170- printf 'no commits ahead of %s\n' "$target" >&2
11711171- fi
11721172- fi
11731173-11741174- if [ "$rebase" -eq 1 ]; then
11751175- if ! git merge-base --is-ancestor "$target" HEAD; then
11761176- git rebase "$target" || die "rebase failed (resolve conflicts and run: git rebase --continue)"
11771177- printf 'rebased onto %s\n' "$target" >&2
11781178- else
11791179- printf 'already rebased onto %s\n' "$target" >&2
11801180- fi
11811181- else
11821182- if ! git merge-base --is-ancestor "$target" HEAD; then
11831183- die "not rebased onto '$target' (run without --no-rebase)"
11841184- fi
11851185- fi
11861186-11871187- if ! git merge-base --is-ancestor "$target" HEAD; then
11881188- die "cannot fast-forward: '$target' has commits not in HEAD"
11891189- fi
11901190-11911191- local head_sha
11921192- head_sha=$(git rev-parse HEAD)
11931193- git update-ref "refs/heads/$target" "$head_sha" || die "failed to fast-forward '$target'"
11941194- printf 'merged to %s @ %s\n' "$target" "$(git rev-parse --short HEAD)" >&2
11951195-}
11961196-11971197-cmd_here() {
11981198- require_repo
11991199- local root base current
12001200- root=$(main_worktree_path)
12011201- base=$(base_dir "$root" "$BASE_DIR_OVERRIDE")
12021202- current=$(git rev-parse --show-toplevel)
12031203- case "$current" in
12041204- "$base"/*) ;;
12051205- *) exit 1 ;;
12061206- esac
12071207- local entry
12081208- while IFS= read -r entry; do
12091209- local path head branch is_bare
12101210- parse_worktree_entry "$entry"
12111211- if [ "$path" = "$current" ]; then
12121212- if [ -n "$branch" ]; then
12131213- printf '%s\n' "$branch"
12141214- exit 0
12151215- fi
12161216- fi
12171217- done < <(worktree_entries "$root")
12181218- exit 1
12191219-}
12201220-12211221-cmd_base() {
12221222- require_repo
12231223- local root base
12241224- root=$(main_worktree_path)
12251225- base=$(base_dir "$root" "$BASE_DIR_OVERRIDE")
12261226- printf '%s\n' "$base"
12271227-}
12281228-12291229-cmd_root() {
12301230- require_repo
12311231- local common
12321232- common=$(common_git_dir_abs)
12331233- if repo_is_bare "$common"; then
12341234- printf '%s\n' "$common"
12351235- return
12361236- fi
12371237- main_worktree_path
12381238-}
12391239-12401240-completion_bash() {
12411241- local commands
12421242- commands="switch sw exec sync ls rm merge here base root help completion"
12431243- cat <<EOF
12441244-command -v wt >/dev/null 2>&1 || return 0
12451245-12461246-wt() {
12471247- case "\$1" in
12481248- switch|sw)
12491249- local i
12501250- for i in "\$@"; do
12511251- case "\$i" in
12521252- -h|--help)
12531253- command wt "\$@"
12541254- return \$?
12551255- ;;
12561256- esac
12571257- done
12581258- local out path
12591259- out=\$(command wt "\$@")
12601260- local rc=\$?
12611261- path=\$(printf '%s\n' "\$out" | tail -n 1)
12621262- if [[ \$rc -eq 0 && -n "\$path" && -d "\$path" ]]; then
12631263- if [[ "\$out" != "\$path" ]]; then
12641264- printf '%s\n' "\$out" | sed '\$d'
12651265- fi
12661266- cd "\$path"
12671267- else
12681268- [[ -n "\$out" ]] && printf '%s\n' "\$out"
12691269- return \$rc
12701270- fi
12711271- ;;
12721272- *)
12731273- command wt "\$@"
12741274- ;;
12751275- esac
12761276-}
12771277-12781278-__wt_branches() {
12791279- git rev-parse --git-dir >/dev/null 2>&1 || return 0
12801280- git for-each-ref --format='%(refname:short)' refs/heads 2>/dev/null
12811281-}
12821282-12831283-_wt_complete() {
12841284- local cur prev cmd
12851285- cur="\${COMP_WORDS[COMP_CWORD]}"
12861286- prev="\${COMP_WORDS[COMP_CWORD-1]}"
12871287- cmd=""
12881288- compopt +o default +o bashdefault 2>/dev/null
12891289- local commands="$commands"
12901290- local flags=""
12911291- local i
12921292- local global_flags="--base-dir -h --help"
12931293- for i in "\${COMP_WORDS[@]}"; do
12941294- if [ "\$i" = "--" ]; then
12951295- return 0
12961296- fi
12971297- done
12981298- if [ "\$prev" = "--base-dir" ]; then
12991299- return 0
13001300- fi
13011301- i=1
13021302- while [ "\$i" -lt "\$COMP_CWORD" ]; do
13031303- local word="\${COMP_WORDS[\$i]}"
13041304- case "\$word" in
13051305- --base-dir)
13061306- i=\$((i + 2))
13071307- ;;
13081308- --base-dir=*)
13091309- i=\$((i + 1))
13101310- ;;
13111311- -h|--help)
13121312- i=\$((i + 1))
13131313- ;;
13141314- --*)
13151315- i=\$((i + 1))
13161316- ;;
13171317- *)
13181318- cmd="\$word"
13191319- break
13201320- ;;
13211321- esac
13221322- done
13231323- if [ -z "\$cmd" ]; then
13241324- COMPREPLY=( \$(compgen -W "\$global_flags \$commands" -- "\$cur") )
13251325- return 0
13261326- fi
13271327- case "\$cmd" in
13281328- switch|sw)
13291329- flags="--from --path --fetch --copy-all --copy-ignored --copy-untracked --copy-modified -f --force -v --verbose -h --help"
13301330- case "\$prev" in
13311331- --from|--path) return 0 ;;
13321332- esac
13331333- COMPREPLY=( \$(compgen -W "\$flags \$(__wt_branches)" -- "\$cur") )
13341334- ;;
13351335- exec)
13361336- flags="--from --path --fetch -h --help --"
13371337- case "\$prev" in
13381338- --from|--path|--) return 0 ;;
13391339- esac
13401340- if [ "\$COMP_CWORD" -eq 2 ]; then
13411341- COMPREPLY=( \$(compgen -W "\$(__wt_branches)" -- "\$cur") )
13421342- else
13431343- COMPREPLY=( \$(compgen -W "\$flags" -- "\$cur") )
13441344- fi
13451345- ;;
13461346- sync)
13471347- flags="--copy-all --copy-ignored --copy-untracked --copy-modified -f --force -v --verbose -n --dry-run -h --help"
13481348- COMPREPLY=( \$(compgen -W "\$flags \$(__wt_branches)" -- "\$cur") )
13491349- ;;
13501350- rm)
13511351- flags="-f --force -h --help"
13521352- COMPREPLY=( \$(compgen -W "\$flags \$(__wt_branches)" -- "\$cur") )
13531353- ;;
13541354- merge)
13551355- flags="--no-squash --no-rebase --no-commit -h --help"
13561356- COMPREPLY=( \$(compgen -W "\$flags \$(__wt_branches)" -- "\$cur") )
13571357- ;;
13581358- ls)
13591359- flags="--plain --json -h --help"
13601360- COMPREPLY=( \$(compgen -W "\$flags" -- "\$cur") )
13611361- ;;
13621362- here|base|root)
13631363- flags="-h --help"
13641364- COMPREPLY=( \$(compgen -W "\$flags" -- "\$cur") )
13651365- ;;
13661366- help)
13671367- flags="switch sw exec sync ls rm merge here base root help completion"
13681368- COMPREPLY=( \$(compgen -W "\$flags" -- "\$cur") )
13691369- ;;
13701370- completion)
13711371- flags="bash zsh fish"
13721372- COMPREPLY=( \$(compgen -W "\$flags" -- "\$cur") )
13731373- ;;
13741374- esac
13751375-}
13761376-13771377-complete -F _wt_complete wt
13781378-EOF
13791379-}
13801380-13811381-completion_zsh() {
13821382- cat <<EOF
13831383-command -v wt >/dev/null 2>&1 || return 0
13841384-13851385-wt() {
13861386- case "\$1" in
13871387- switch|sw)
13881388- local i
13891389- for i in "\$@"; do
13901390- case "\$i" in
13911391- -h|--help)
13921392- command wt "\$@"
13931393- return \$?
13941394- ;;
13951395- esac
13961396- done
13971397- local out path
13981398- out=\$(command wt "\$@")
13991399- local rc=\$?
14001400- path=\$(printf '%s\n' "\$out" | tail -n 1)
14011401- if [[ \$rc -eq 0 && -n "\$path" && -d "\$path" ]]; then
14021402- if [[ "\$out" != "\$path" ]]; then
14031403- printf '%s\n' "\$out" | sed '\$d'
14041404- fi
14051405- cd "\$path"
14061406- else
14071407- [[ -n "\$out" ]] && printf '%s\n' "\$out"
14081408- return \$rc
14091409- fi
14101410- ;;
14111411- *)
14121412- command wt "\$@"
14131413- ;;
14141414- esac
14151415-}
14161416-14171417-__wt_branches() {
14181418- git rev-parse --git-dir >/dev/null 2>&1 || return 0
14191419- git for-each-ref --format='%(refname:short)' refs/heads 2>/dev/null
14201420-}
14211421-14221422-_wt() {
14231423- local -a subcmds subdescs switch_flags switch_descs exec_flags exec_descs sync_flags sync_descs ls_flags ls_descs rm_flags rm_descs merge_flags merge_descs hb_flags hb_descs global_flags global_descs
14241424- subcmds=(switch sw exec sync ls rm merge here base root help completion)
14251425- subdescs=(
14261426- "create or open a workspace"
14271427- "alias for switch"
14281428- "open a workspace and run a command"
14291429- "copy files between worktrees"
14301430- "list workspaces"
14311431- "remove a workspace"
14321432- "squash, rebase, and merge to target"
14331433- "print current workspace branch"
14341434- "print base dir"
14351435- "print main worktree path"
14361436- "show help"
14371437- "print shell completion"
14381438- )
14391439- global_flags=(--base-dir -h --help)
14401440- global_descs=("base dir override" "show help" "show help")
14411441- switch_flags=(--from --path --fetch --copy-all --copy-ignored --copy-untracked --copy-modified -f --force -v --verbose -h --help)
14421442- switch_descs=("base ref for new branch" "explicit worktree path" "fetch remotes before resolving --from" "copy all files to new worktree" "copy gitignored files to new worktree" "copy untracked files to new worktree" "copy modified files to new worktree" "overwrite existing destination files" "overwrite existing destination files" "print copy progress" "print copy progress" "show help" "show help")
14431443- exec_flags=(--from --path --fetch -h --help --)
14441444- exec_descs=("base ref for new branch" "explicit worktree path" "fetch remotes before resolving --from" "show help" "show help" "end of flags")
14451445- sync_flags=(--copy-all --copy-ignored --copy-untracked --copy-modified -f --force -v --verbose -n --dry-run -h --help)
14461446- sync_descs=("copy all files" "copy gitignored files" "copy untracked files" "copy modified files" "overwrite existing destination files" "overwrite existing destination files" "print copy progress" "print copy progress" "show files that would be copied" "show files that would be copied" "show help" "show help")
14471447- ls_flags=(--plain --json -h --help)
14481448- ls_descs=("tab-delimited output" "JSON output" "show help" "show help")
14491449- rm_flags=(-f --force -h --help)
14501450- rm_descs=("remove even if dirty" "remove even if dirty" "show help" "show help")
14511451- merge_flags=(--no-squash --no-rebase --no-commit -h --help)
14521452- merge_descs=("preserve individual commits" "skip rebase (fail if not rebased)" "skip committing uncommitted changes" "show help" "show help")
14531453- hb_flags=(-h --help)
14541454- hb_descs=("show help" "show help")
14551455- if (( CURRENT == 2 )); then
14561456- compadd -d global_descs -- \$global_flags
14571457- compadd -d subdescs -- \$subcmds
14581458- return 0
14591459- fi
14601460- local cmd=""
14611461- local idx=2
14621462- while (( idx <= CURRENT )); do
14631463- local word=\${words[idx]}
14641464- case "\$word" in
14651465- --base-dir)
14661466- idx=\$((idx + 2))
14671467- ;;
14681468- --base-dir=*)
14691469- idx=\$((idx + 1))
14701470- ;;
14711471- -h|--help)
14721472- idx=\$((idx + 1))
14731473- ;;
14741474- --*)
14751475- idx=\$((idx + 1))
14761476- ;;
14771477- *)
14781478- cmd=\$word
14791479- break
14801480- ;;
14811481- esac
14821482- done
14831483- if [[ "\${words[CURRENT-1]}" == --base-dir ]]; then
14841484- return 0
14851485- fi
14861486- if [[ -z "\$cmd" ]]; then
14871487- compadd -d global_descs -- \$global_flags
14881488- compadd -d subdescs -- \$subcmds
14891489- return 0
14901490- fi
14911491- case "\$cmd" in
14921492- switch|sw)
14931493- if [[ "\${words[CURRENT-1]}" == --from || "\${words[CURRENT-1]}" == --path ]]; then
14941494- return 0
14951495- fi
14961496- compadd -d switch_descs -- \$switch_flags
14971497- compadd -- \${(f)"\$(__wt_branches)"}
14981498- return 0
14991499- ;;
15001500- exec)
15011501- if [[ "\${words[CURRENT-1]}" == --from || "\${words[CURRENT-1]}" == --path || "\${words[CURRENT-1]}" == -- ]]; then
15021502- return 0
15031503- fi
15041504- if (( CURRENT == 3 )); then
15051505- compadd -- \${(f)"\$(__wt_branches)"}
15061506- else
15071507- compadd -d exec_descs -- \$exec_flags
15081508- fi
15091509- return 0
15101510- ;;
15111511- sync)
15121512- compadd -d sync_descs -- \$sync_flags
15131513- compadd -- \${(f)"\$(__wt_branches)"}
15141514- return 0
15151515- ;;
15161516- rm)
15171517- compadd -d rm_descs -- \$rm_flags
15181518- compadd -- \${(f)"\$(__wt_branches)"}
15191519- return 0
15201520- ;;
15211521- merge)
15221522- compadd -d merge_descs -- \$merge_flags
15231523- compadd -- \${(f)"\$(__wt_branches)"}
15241524- return 0
15251525- ;;
15261526- ls)
15271527- compadd -d ls_descs -- \$ls_flags
15281528- return 0
15291529- ;;
15301530- here|base|root)
15311531- compadd -d hb_descs -- \$hb_flags
15321532- return 0
15331533- ;;
15341534- help)
15351535- compadd -d subdescs -- \$subcmds
15361536- return 0
15371537- ;;
15381538- completion)
15391539- compadd -- bash zsh fish
15401540- return 0
15411541- ;;
15421542- esac
15431543- return 0
15441544-}
15451545-15461546-compdef _wt wt
15471547-EOF
15481548-}
15491549-15501550-completion_fish() {
15511551- local wt_bin="$0"
15521552- case "$wt_bin" in
15531553- */*) wt_bin="$(cd "$(dirname "$wt_bin")" && pwd -P)/$(basename "$wt_bin")" ;;
15541554- *) wt_bin="$(command -v "$wt_bin" 2>/dev/null || printf '%s' "$wt_bin")" ;;
15551555- esac
15561556- local wt_bin_escaped="${wt_bin//\'/\'\\\'\'}"
15571557- cat <<'EOF'
15581558-type -q wt; or return
15591559-15601560-EOF
15611561- printf "set -g __wt_bin '%s'\n\n" "$wt_bin_escaped"
15621562- cat <<'EOF'
15631563-function wt
15641564- if test "$argv[1]" = "switch" -o "$argv[1]" = "sw"
15651565- if contains -- -h $argv; or contains -- --help $argv
15661566- command "$__wt_bin" $argv
15671567- return $status
15681568- end
15691569- set -g __wt_rc 0
15701570- set -l out (begin
15711571- command "$__wt_bin" $argv
15721572- set -g __wt_rc $status
15731573- end | string collect | string trim --right)
15741574- set -l rc $__wt_rc
15751575- set -e __wt_rc
15761576- set -l out_lines (string split -- "\n" $out)
15771577- set -l path $out_lines[-1]
15781578- if test $rc -eq 0 -a -n "$path" -a -d "$path"
15791579- if test (count $out_lines) -gt 1
15801580- printf '%s\n' $out_lines[1..-2]
15811581- end
15821582- cd "$path"
15831583- else
15841584- test -n "$out"; and printf '%s\n' "$out"
15851585- return $rc
15861586- end
15871587- else
15881588- command "$__wt_bin" $argv
15891589- end
15901590-end
15911591-15921592-function __wt_branches
15931593- command git rev-parse --git-dir >/dev/null 2>&1
15941594- or return
15951595- command git for-each-ref --format='%(refname:short)' refs/heads 2>/dev/null
15961596-end
15971597-15981598-complete -c wt -f
15991599-complete -c wt -l base-dir -r -d 'base dir override'
16001600-complete -c wt -s h -l help -d 'show help'
16011601-complete -c wt -n '__fish_use_subcommand' -a switch -d 'create or open a workspace'
16021602-complete -c wt -n '__fish_use_subcommand' -a sw -d 'alias for switch'
16031603-complete -c wt -n '__fish_use_subcommand' -a exec -d 'open a workspace and run a command'
16041604-complete -c wt -n '__fish_use_subcommand' -a sync -d 'copy files between worktrees'
16051605-complete -c wt -n '__fish_use_subcommand' -a ls -d 'list workspaces'
16061606-complete -c wt -n '__fish_use_subcommand' -a rm -d 'remove a workspace'
16071607-complete -c wt -n '__fish_use_subcommand' -a merge -d 'squash, rebase, and merge to target'
16081608-complete -c wt -n '__fish_use_subcommand' -a here -d 'print current workspace branch'
16091609-complete -c wt -n '__fish_use_subcommand' -a base -d 'print base dir'
16101610-complete -c wt -n '__fish_use_subcommand' -a root -d 'print main worktree path'
16111611-complete -c wt -n '__fish_use_subcommand' -a help -d 'show help'
16121612-complete -c wt -n '__fish_use_subcommand' -a completion -d 'print shell completion'
16131613-complete -c wt -n '__fish_seen_subcommand_from switch sw exec sync rm merge' -a '(__wt_branches)'
16141614-16151615-complete -c wt -n '__fish_seen_subcommand_from switch sw exec' -l from -r -d 'base ref for new branch'
16161616-complete -c wt -n '__fish_seen_subcommand_from switch sw exec' -l path -r -d 'explicit worktree path'
16171617-complete -c wt -n '__fish_seen_subcommand_from switch sw exec' -l fetch -d 'fetch remotes before resolving --from'
16181618-complete -c wt -n '__fish_seen_subcommand_from switch sw exec' -s h -l help -d 'show help'
16191619-complete -c wt -n '__fish_seen_subcommand_from switch sw' -l copy-all -d 'copy all files to new worktree'
16201620-complete -c wt -n '__fish_seen_subcommand_from switch sw' -l copy-ignored -d 'copy gitignored files to new worktree'
16211621-complete -c wt -n '__fish_seen_subcommand_from switch sw' -l copy-untracked -d 'copy untracked files to new worktree'
16221622-complete -c wt -n '__fish_seen_subcommand_from switch sw' -l copy-modified -d 'copy modified files to new worktree'
16231623-complete -c wt -n '__fish_seen_subcommand_from switch sw' -s f -l force -d 'overwrite existing destination files'
16241624-complete -c wt -n '__fish_seen_subcommand_from switch sw' -s v -l verbose -d 'print copy progress'
16251625-complete -c wt -n '__fish_seen_subcommand_from sync' -l copy-all -d 'copy all files'
16261626-complete -c wt -n '__fish_seen_subcommand_from sync' -l copy-ignored -d 'copy gitignored files'
16271627-complete -c wt -n '__fish_seen_subcommand_from sync' -l copy-untracked -d 'copy untracked files'
16281628-complete -c wt -n '__fish_seen_subcommand_from sync' -l copy-modified -d 'copy modified files'
16291629-complete -c wt -n '__fish_seen_subcommand_from sync' -s f -l force -d 'overwrite existing destination files'
16301630-complete -c wt -n '__fish_seen_subcommand_from sync' -s v -l verbose -d 'print copy progress'
16311631-complete -c wt -n '__fish_seen_subcommand_from sync' -s n -l dry-run -d 'show files that would be copied'
16321632-complete -c wt -n '__fish_seen_subcommand_from sync' -s h -l help -d 'show help'
16331633-16341634-complete -c wt -n '__fish_seen_subcommand_from ls' -l plain -d 'tab-delimited output'
16351635-complete -c wt -n '__fish_seen_subcommand_from ls' -l json -d 'JSON output'
16361636-complete -c wt -n '__fish_seen_subcommand_from ls' -s h -l help -d 'show help'
16371637-16381638-complete -c wt -n '__fish_seen_subcommand_from rm' -s f -l force -d 'remove even if dirty'
16391639-complete -c wt -n '__fish_seen_subcommand_from rm' -s h -l help -d 'show help'
16401640-16411641-complete -c wt -n '__fish_seen_subcommand_from merge' -l no-squash -d 'preserve individual commits'
16421642-complete -c wt -n '__fish_seen_subcommand_from merge' -l no-rebase -d 'skip rebase (fail if not rebased)'
16431643-complete -c wt -n '__fish_seen_subcommand_from merge' -l no-commit -d 'skip committing uncommitted changes'
16441644-complete -c wt -n '__fish_seen_subcommand_from merge' -s h -l help -d 'show help'
16451645-16461646-complete -c wt -n '__fish_seen_subcommand_from help' -a switch -d 'create or open a workspace'
16471647-complete -c wt -n '__fish_seen_subcommand_from help' -a sw -d 'alias for switch'
16481648-complete -c wt -n '__fish_seen_subcommand_from help' -a exec -d 'open a workspace and run a command'
16491649-complete -c wt -n '__fish_seen_subcommand_from help' -a sync -d 'copy files between worktrees'
16501650-complete -c wt -n '__fish_seen_subcommand_from help' -a ls -d 'list workspaces'
16511651-complete -c wt -n '__fish_seen_subcommand_from help' -a rm -d 'remove a workspace'
16521652-complete -c wt -n '__fish_seen_subcommand_from help' -a merge -d 'squash, rebase, and merge to target'
16531653-complete -c wt -n '__fish_seen_subcommand_from help' -a here -d 'print current workspace branch'
16541654-complete -c wt -n '__fish_seen_subcommand_from help' -a base -d 'print base dir'
16551655-complete -c wt -n '__fish_seen_subcommand_from help' -a root -d 'print main worktree path'
16561656-complete -c wt -n '__fish_seen_subcommand_from help' -a help -d 'show help'
16571657-complete -c wt -n '__fish_seen_subcommand_from help' -a completion -d 'print shell completion'
16581658-complete -c wt -n '__fish_seen_subcommand_from completion' -a 'bash zsh fish'
16591659-EOF
16601660-}
16611661-16621662-cmd_completion() {
16631663- if [ "$#" -eq 0 ]; then
16641664- completion_usage
16651665- exit 1
16661666- fi
16671667- case "$1" in
16681668- bash) completion_bash ;;
16691669- zsh) completion_zsh ;;
16701670- fish) completion_fish ;;
16711671- *) die "unknown shell '$1'" ;;
16721672- esac
16731673-}
16741674-16751675-cmd_help() {
16761676- if [ "$#" -eq 0 ]; then
16771677- usage
16781678- exit 0
16791679- fi
16801680- case "$1" in
16811681- switch | sw) switch_usage ;;
16821682- exec) exec_usage ;;
16831683- sync) sync_usage ;;
16841684- ls) ls_usage ;;
16851685- rm) rm_usage ;;
16861686- merge) merge_usage ;;
16871687- here) here_usage ;;
16881688- base) base_usage ;;
16891689- root) root_usage ;;
16901690- completion) completion_usage ;;
16911691- help) usage ;;
16921692- *) die "unknown command '$1'" ;;
16931693- esac
16941694-}
16951695-16961696-while [ "$#" -gt 0 ]; do
16971697- case "$1" in
16981698- --base-dir)
16991699- [ -n "${2:-}" ] || die "missing value for --base-dir"
17001700- BASE_DIR_OVERRIDE="$2"
17011701- shift 2
17021702- ;;
17031703- --base-dir=*)
17041704- BASE_DIR_OVERRIDE="${1#--base-dir=}"
17051705- shift
17061706- ;;
17071707- -h | --help)
17081708- break
17091709- ;;
17101710- --*) die "unknown flag '$1'" ;;
17111711- *) break ;;
17121712- esac
17131713-done
17141714-17151715-case "${1:-}" in
17161716-"")
17171717- usage
17181718- exit 1
17191719- ;;
17201720--h | --help)
17211721- usage
17221722- exit 0
17231723- ;;
17241724-help)
17251725- shift
17261726- cmd_help "$@"
17271727- ;;
17281728-switch)
17291729- shift
17301730- cmd_switch "$@"
17311731- ;;
17321732-sw)
17331733- shift
17341734- cmd_switch "$@"
17351735- ;;
17361736-exec)
17371737- shift
17381738- cmd_exec "$@"
17391739- ;;
17401740-sync)
17411741- shift
17421742- cmd_sync "$@"
17431743- ;;
17441744-ls)
17451745- shift
17461746- cmd_ls "$@"
17471747- ;;
17481748-rm)
17491749- shift
17501750- cmd_rm "$@"
17511751- ;;
17521752-merge)
17531753- shift
17541754- cmd_merge "$@"
17551755- ;;
17561756-here)
17571757- shift
17581758- cmd_here "$@"
17591759- ;;
17601760-base)
17611761- shift
17621762- cmd_base "$@"
17631763- ;;
17641764-root)
17651765- shift
17661766- cmd_root "$@"
17671767- ;;
17681768-completion)
17691769- shift
17701770- cmd_completion "$@"
17711771- ;;
17721772-*) die "unknown command '$1'" ;;
17731773-esac