diff --git a/.gitmodules b/.gitmodules index 5fe7a61a..4b1ead7c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "ThirdParty/ghostty"] path = ThirdParty/ghostty url = https://github.com/ghostty-org/ghostty +[submodule "Resources/git-wt"] + path = Resources/git-wt + url = https://github.com/khoi/git-wt.git diff --git a/AGENTS.md b/AGENTS.md index 5662b04f..822526f8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -116,4 +116,4 @@ Reducer ← .terminalEvent(Event) ← AsyncStream ## References - `git@github.com:ghostty-org/ghostty.git` - Dive into this codebase when implementing Ghostty features -- `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 +- `git@github.com:khoi/git-wt.git` - Git worktree wrapper submodule at `Resources/git-wt` diff --git a/Makefile b/Makefile index 57ad2549..b0f376e2 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ VERSION ?= BUILD ?= XCODEBUILD_FLAGS ?= .DEFAULT_GOAL := help -.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 +.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 help: # Display this help. @-+echo "Run make with one of the following targets:" @@ -85,13 +85,6 @@ check: format lint # Format and lint log-stream: # Stream logs from the app via log stream log stream --predicate 'subsystem == "app.supabit.supacode"' --style compact --color always -update-wt: # Download git-wt binary to Resources - @mkdir -p "$(CURRENT_MAKEFILE_DIR)/Resources/git-wt" - @curl -fsSL "https://raw.githubusercontent.com/khoi/git-wt/refs/heads/main/wt" -o "$(CURRENT_MAKEFILE_DIR)/Resources/git-wt/wt" - @chmod +x "$(CURRENT_MAKEFILE_DIR)/Resources/git-wt/wt" - @git add "$(CURRENT_MAKEFILE_DIR)/Resources/git-wt/wt" - @git commit -m "update git-wt" - bump-version: # Bump app version (usage: make bump-version [VERSION=x.x.x] [BUILD=123]) @if [ -z "$(VERSION)" ]; then \ current="$$(/usr/bin/awk -F' = ' '/MARKETING_VERSION = [0-9.]+;/{gsub(/;/,"",$$2);print $$2; exit}' "$(CURRENT_MAKEFILE_DIR)/supacode.xcodeproj/project.pbxproj")"; \ diff --git a/Resources/git-wt b/Resources/git-wt new file mode 160000 index 00000000..45d15a33 --- /dev/null +++ b/Resources/git-wt @@ -0,0 +1 @@ +Subproject commit 45d15a33a53f2ea7d37f32bac3738747d2fa6877 diff --git a/Resources/git-wt/wt b/Resources/git-wt/wt deleted file mode 100755 index 8f23e493..00000000 --- a/Resources/git-wt/wt +++ /dev/null @@ -1,1773 +0,0 @@ -#!/bin/bash -set -euo pipefail - -usage() { - cat <<'USAGE' -wt - A simple git worktree sugar - -Usage: - wt [--base-dir ] - -Global Flags: - --base-dir base dir override - -h, --help show help - -Commands: - switch create or open a workspace - sw alias for switch - exec open a workspace and run a command - sync [from to] copy files between worktrees - ls list workspaces - rm remove a workspace - merge squash, rebase, and merge to target - here print current workspace branch - base print base dir - root print main worktree path - completion print shell completion - help [command] show help -USAGE -} - -switch_usage() { - cat <<'USAGE' -wt switch|sw - -Flags: - --from base ref for new branch - --path explicit worktree path - --fetch fetch remotes before resolving --from - --copy-all copy all files to new worktree - --copy-ignored copy gitignored files to new worktree - --copy-untracked copy untracked files to new worktree - --copy-modified copy modified files to new worktree - -f, --force overwrite existing destination files - -v, --verbose print copy progress - -h, --help show help -USAGE -} - -exec_usage() { - cat <<'USAGE' -wt exec -- - -Flags: - --from base ref for new branch - --path explicit worktree path - --fetch fetch remotes before resolving --from - -h, --help show help -USAGE -} - -sync_usage() { - cat <<'USAGE' -wt sync [ ] - -Defaults to primary worktree -> current worktree. - -Flags: - --copy-all copy all files - --copy-ignored copy gitignored files - --copy-untracked copy untracked files - --copy-modified copy modified files - -f, --force overwrite existing destination files - -v, --verbose print copy progress - -n, --dry-run show files that would be copied - -h, --help show help -USAGE -} - -ls_usage() { - cat <<'USAGE' -wt ls - -Flags: - --plain tab-delimited output - --json JSON output - -h, --help show help -USAGE -} - -rm_usage() { - cat <<'USAGE' -wt rm - -Flags: - -f, --force remove even if dirty - -h, --help show help -USAGE -} - -merge_usage() { - cat <<'USAGE' -wt merge - -Squash, rebase, and fast-forward merge into target branch. - -Flags: - --no-squash preserve individual commits - --no-rebase skip rebase (fail if not rebased) - --no-commit skip committing uncommitted changes - -h, --help show help -USAGE -} - -here_usage() { - cat <<'USAGE' -wt here -USAGE -} - -base_usage() { - cat <<'USAGE' -wt base -USAGE -} - -root_usage() { - cat <<'USAGE' -wt root -USAGE -} - -completion_usage() { - cat <<'USAGE' -wt completion - -Shells: - bash - zsh - fish -USAGE -} - -die() { - printf 'error: %s\n' "$1" >&2 - exit 1 -} - -BASE_DIR_OVERRIDE="" - -require_repo() { - local err - if ! err=$(git rev-parse --git-dir 2>&1); then - printf '%s\n' "$err" >&2 - die "not a git repository" - fi -} - -common_git_dir_abs() { - local common - common=$(git rev-parse --git-common-dir) - local target - case "$common" in - /*) target="$common" ;; - *) target="$(abs_path "$common")" ;; - esac - if [ -d "$target" ]; then - (cd "$target" && pwd -P) - return - fi - normalize_dir "$target" -} - -repo_is_bare() { - local is_bare - if [ -n "${1:-}" ]; then - is_bare=$(git --git-dir "$1" rev-parse --is-bare-repository) - else - is_bare=$(git rev-parse --is-bare-repository) - fi - [ "$is_bare" = "true" ] -} - -canonical_worktree_path() { - local path="$1" - local resolved - if resolved=$(git -C "$path" rev-parse --show-toplevel 2>/dev/null); then - normalize_dir "$resolved" - return - fi - normalize_dir "$path" -} - -main_worktree_path() { - local common - common=$(common_git_dir_abs) - if repo_is_bare; then - printf '%s\n' "$common" - return - fi - local path="" is_bare=0 - while IFS= read -r line || [ -n "$line" ]; do - if [ -z "$line" ]; then - if [ -n "$path" ] && [ "$is_bare" -eq 0 ]; then - canonical_worktree_path "$path" - return - fi - path="" - is_bare=0 - continue - fi - case "$line" in - worktree\ *) path="${line#worktree }" ;; - bare) is_bare=1 ;; - esac - done < <(git worktree list --porcelain) - if [ -n "$path" ] && [ "$is_bare" -eq 0 ]; then - canonical_worktree_path "$path" - return - fi - git rev-parse --show-toplevel -} - -primary_worktree_path() { - local root="$1" - local entry - while IFS= read -r entry; do - [ -n "$entry" ] || continue - local path head branch is_bare - parse_worktree_entry "$entry" - if [ "$is_bare" = "0" ] && [ -d "$path" ]; then - printf '%s\n' "$path" - return 0 - fi - done < <(worktree_entries "$root") - return 1 -} - -copy_source_path() { - local root="$1" - if repo_is_bare; then - primary_worktree_path "$root" || die "no linked worktree found for copy operations" - return - fi - printf '%s\n' "$root" -} - -abs_path() { - local path="$1" - case "$path" in - /*) printf '%s\n' "$path" ;; - *) printf '%s/%s\n' "$(pwd -P)" "$path" ;; - esac -} - -normalize_dir() { - local path="$1" - if [ "$path" != "/" ]; then - path="${path%/}" - fi - printf '%s\n' "$path" -} - -base_dir() { - local root="$1" - local override="$2" - local chosen - chosen="${GIT_WT_BASE:-}" - if [ -n "$override" ]; then - chosen="$override" - fi - if [ -z "$chosen" ]; then - normalize_dir "$(abs_path "$root/.worktrees")" - return - fi - case "$chosen" in - /*) normalize_dir "$(abs_path "$chosen")" ;; - *) normalize_dir "$(abs_path "$root/$chosen")" ;; - esac -} - -resolve_path() { - local root="$1" - local path="$2" - case "$path" in - /*) abs_path "$path" ;; - *) abs_path "$root/$path" ;; - esac -} - -worktree_entries() { - local root="$1" - local path="" head="" branch="" is_bare=0 - while IFS= read -r line || [ -n "$line" ]; do - if [ -z "$line" ]; then - if [ -n "$path" ]; then - local output_path="$path" - if [ "$is_bare" -eq 0 ]; then - output_path=$(canonical_worktree_path "$path") - fi - printf '%s\t%s\t%s\t%s\n' "$output_path" "$head" "$branch" "$is_bare" - fi - path="" - head="" - branch="" - is_bare=0 - continue - fi - local key value - key=${line%% *} - value=${line#* } - case "$key" in - worktree) path="$value" ;; - HEAD) head="$value" ;; - branch) branch="${value#refs/heads/}" ;; - detached) branch="" ;; - bare) is_bare=1 ;; - esac - done < <(git -C "$root" worktree list --porcelain) - if [ -n "$path" ]; then - local output_path="$path" - if [ "$is_bare" -eq 0 ]; then - output_path=$(canonical_worktree_path "$path") - fi - printf '%s\t%s\t%s\t%s\n' "$output_path" "$head" "$branch" "$is_bare" - fi -} - -parse_worktree_entry() { - local entry="$1" - local rest - path=${entry%%$'\t'*} - rest=${entry#*$'\t'} - head=${rest%%$'\t'*} - rest=${rest#*$'\t'} - branch=${rest%%$'\t'*} - is_bare=${rest#*$'\t'} -} - -find_worktree_by_branch() { - local root="$1" - local target_branch="$2" - local entry - while IFS= read -r entry; do - local path head branch is_bare - parse_worktree_entry "$entry" - if [ "$is_bare" = "1" ]; then - continue - fi - if [ "$branch" = "$target_branch" ]; then - printf '%s\n' "$path" - return 0 - fi - done < <(worktree_entries "$root") - return 1 -} - -find_worktree_by_path() { - local root="$1" - local target="$2" - local entry - while IFS= read -r entry; do - local path head branch is_bare - parse_worktree_entry "$entry" - if [ "$path" = "$target" ]; then - printf '%s\n' "$path" - return 0 - fi - if [ -d "$path" ] && [ -d "$target" ] && [ "$path" -ef "$target" ]; then - printf '%s\n' "$path" - return 0 - fi - done < <(worktree_entries "$root") - return 1 -} - -json_escape() { - printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -} - -detect_jobs() { - sysctl -n hw.logicalcpu 2>/dev/null || nproc 2>/dev/null || echo 1 -} - -list_ignored_files() { - local root="$1" - git -C "$root" ls-files --others --ignored --exclude-standard --directory | sed '/^\.git\//d' -} - -list_untracked_files() { - local root="$1" - git -C "$root" ls-files --others --exclude-standard | sed '/^\.git\//d' -} - -list_modified_files() { - local root="$1" - git -C "$root" ls-files --modified | sed '/^\.git\//d' -} - -list_tracked_files() { - local root="$1" - git -C "$root" ls-files | sed '/^\.git\//d' -} - -list_nested_worktree_prefixes() { - local src="$1" - local src_real - src_real=$(cd "$src" && pwd -P) - local entry - while IFS= read -r entry; do - [ -n "$entry" ] || continue - local path head branch is_bare - parse_worktree_entry "$entry" - [ "$is_bare" = "0" ] || continue - [ -d "$path" ] || continue - local path_real - path_real=$(cd "$path" && pwd -P) - if [ "$path_real" = "$src_real" ]; then - continue - fi - case "$path_real" in - "$src_real"/*) printf '%s\n' "${path_real#"$src_real"/}" ;; - esac - done < <(worktree_entries "$src") -} - -filter_nested_worktree_prefixes() { - local files="$1" - local prefixes="$2" - if [ -z "$prefixes" ]; then - printf '%s' "$files" - return - fi - local out="" file prefix skip file_trim prefix_trim - while IFS= read -r file; do - [ -n "$file" ] || continue - file_trim="${file%/}" - skip=0 - while IFS= read -r prefix; do - [ -n "$prefix" ] || continue - prefix_trim="${prefix%/}" - case "$file_trim" in - "$prefix_trim" | "$prefix_trim"/*) - skip=1 - break - ;; - esac - case "$prefix_trim" in - "$file_trim" | "$file_trim"/*) - skip=1 - break - ;; - esac - done <<< "$prefixes" - [ "$skip" -eq 1 ] && continue - out+="$file"$'\n' - done <<< "$files" - printf '%s' "$out" -} - -prune_nested_copy_paths() { - local files="$1" - local out="" dirs="" file file_trim dir skip - while IFS= read -r file; do - [ -n "$file" ] || continue - file_trim="${file%/}" - skip=0 - while IFS= read -r dir; do - [ -n "$dir" ] || continue - case "$file_trim" in - "$dir"/*) - skip=1 - break - ;; - esac - done <<< "$dirs" - [ "$skip" -eq 1 ] && continue - out+="$file"$'\n' - if [ "$file" != "$file_trim" ]; then - dirs+="$file_trim"$'\n' - fi - done <<< "$files" - printf '%s' "$out" -} - -collect_copy_files() { - local src="$1" - local copyignored="$2" - local copyuntracked="$3" - local copymodified="$4" - local copytracked="$5" - local sources=0 - [ "$copyignored" -eq 1 ] && sources=$((sources + 1)) - [ "$copyuntracked" -eq 1 ] && sources=$((sources + 1)) - [ "$copymodified" -eq 1 ] && sources=$((sources + 1)) - [ "$copytracked" -eq 1 ] && sources=$((sources + 1)) - local prefixes - prefixes=$(list_nested_worktree_prefixes "$src") - if [ "$sources" -eq 1 ]; then - if [ "$copyignored" -eq 1 ]; then - local ignored - ignored=$(filter_nested_worktree_prefixes "$(list_ignored_files "$src")" "$prefixes") - ignored=$(printf '%s' "$ignored" | sed '/^$/d' | LC_ALL=C sort -u) - prune_nested_copy_paths "$ignored" - return - fi - if [ "$copyuntracked" -eq 1 ]; then - filter_nested_worktree_prefixes "$(list_untracked_files "$src")" "$prefixes" - return - fi - if [ "$copymodified" -eq 1 ]; then - filter_nested_worktree_prefixes "$(list_modified_files "$src")" "$prefixes" - return - fi - filter_nested_worktree_prefixes "$(list_tracked_files "$src")" "$prefixes" - return - fi - local files="" - if [ "$copyignored" -eq 1 ]; then - files+=$(list_ignored_files "$src")$'\n' - fi - if [ "$copyuntracked" -eq 1 ]; then - files+=$(list_untracked_files "$src")$'\n' - fi - if [ "$copymodified" -eq 1 ]; then - files+=$(list_modified_files "$src")$'\n' - fi - if [ "$copytracked" -eq 1 ]; then - files+=$(list_tracked_files "$src")$'\n' - fi - local deduped - deduped=$(printf '%s' "$files" | sed '/^$/d' | LC_ALL=C sort -u) - deduped=$(filter_nested_worktree_prefixes "$deduped" "$prefixes") - prune_nested_copy_paths "$deduped" -} - -reflink_copy_file() { - local src="$1" - local dst="$2" - case "$(uname)" in - Darwin) - cp -c "$src" "$dst" 2>/dev/null || cp "$src" "$dst" 2>/dev/null || true - ;; - *) - cp --reflink=auto "$src" "$dst" 2>/dev/null || cp "$src" "$dst" 2>/dev/null || true - ;; - esac -} - -reflink_copy_tree() { - local src="$1" - local dst="$2" - mkdir -p "$dst" - case "$(uname)" in - Darwin) - cp -cRP "$src/." "$dst" 2>/dev/null || cp -RP "$src/." "$dst" 2>/dev/null || true - ;; - *) - cp -a --reflink=auto "$src/." "$dst" 2>/dev/null || cp -a "$src/." "$dst" 2>/dev/null || true - ;; - esac -} - -reflink_copy_tree_no_overwrite() { - local src="$1" - local dst="$2" - mkdir -p "$dst" - case "$(uname)" in - Darwin) - cp -cnRP "$src/." "$dst" 2>/dev/null || cp -nRP "$src/." "$dst" 2>/dev/null || true - ;; - *) - cp -a -n --reflink=auto "$src/." "$dst" 2>/dev/null || cp -a -n "$src/." "$dst" 2>/dev/null || true - ;; - esac -} - -reflink_copy_dir_new() { - local src="$1" - local dst="$2" - local src_dir="${src%/}" - local parent - parent=$(dirname "$dst") - mkdir -p "$parent" - case "$(uname)" in - Darwin) - cp -cRP "$src_dir" "$parent" 2>/dev/null || cp -RP "$src_dir" "$parent" 2>/dev/null || true - ;; - *) - cp -a --reflink=auto "$src_dir" "$parent" 2>/dev/null || cp -a "$src_dir" "$parent" 2>/dev/null || true - ;; - esac -} - -remove_path() { - local path="$1" - if [ -L "$path" ] || [ -f "$path" ]; then - rm -f "$path" 2>/dev/null || true - return - fi - if [ -d "$path" ]; then - rm -rf "$path" 2>/dev/null || true - fi -} - -copy_symlink() { - local src="$1" - local dst="$2" - local force="${3:-0}" - local target - target=$(readlink "$src" 2>/dev/null) || return 0 - if [ -e "$dst" ] || [ -L "$dst" ]; then - if [ "$force" -eq 1 ]; then - remove_path "$dst" - else - return 0 - fi - fi - mkdir -p "$(dirname "$dst")" - ln -s "$target" "$dst" 2>/dev/null || true -} - -copy_file() { - local src="$1" - local dst="$2" - local force="${3:-0}" - if [ -e "$dst" ] || [ -L "$dst" ]; then - if [ "$force" -eq 1 ]; then - remove_path "$dst" - else - return 0 - fi - fi - mkdir -p "$(dirname "$dst")" - reflink_copy_file "$src" "$dst" -} - -copy_dir_recursive() { - local src_dir="$1" - local dst_dir="$2" - local force="${3:-0}" - local dst_exists=0 - if [ -e "$dst_dir" ] || [ -L "$dst_dir" ]; then - dst_exists=1 - if [ -d "$dst_dir" ] && [ ! -L "$dst_dir" ]; then - : - elif [ "$force" -eq 1 ]; then - remove_path "$dst_dir" - dst_exists=0 - else - return 0 - fi - fi - if [ "$dst_exists" -eq 0 ]; then - reflink_copy_dir_new "$src_dir" "$dst_dir" - elif [ "$force" -eq 1 ]; then - reflink_copy_tree "$src_dir" "$dst_dir" - else - reflink_copy_tree_no_overwrite "$src_dir" "$dst_dir" - fi -} - -copy_tree() { - local src_dir="$1" - local dst_dir="$2" - local force="${3:-0}" - copy_dir_recursive "$src_dir" "$dst_dir" "$force" -} - -copy_path() { - local src="$1" - local dst="$2" - local force="${3:-0}" - if [ -L "$src" ]; then - copy_symlink "$src" "$dst" "$force" - elif [ -d "$src" ]; then - copy_tree "$src" "$dst" "$force" - elif [ -f "$src" ]; then - copy_file "$src" "$dst" "$force" - fi -} - -copy_entry() { - local entry="$1" src="$2" dst="$3" force="${4:-0}" verbose="${5:-0}" - [ -n "$entry" ] || return 0 - local entry_trim="${entry%/}" - local srcfile="$src/$entry_trim" - local dstfile="$dst/$entry_trim" - [ -e "$srcfile" ] || [ -L "$srcfile" ] || return 0 - [ "$verbose" -eq 1 ] && printf '%s\n' "$entry_trim" >&2 - copy_path "$srcfile" "$dstfile" "$force" -} - -copy_entries_parallel() { - local src="$1" dst="$2" files="$3" force="${4:-0}" verbose="${5:-0}" - local jobs - jobs=$(detect_jobs) - local tmpfile - tmpfile=$(mktemp) - printf '%s' "$files" | sed '/^$/d' > "$tmpfile" - export -f copy_entry copy_path copy_symlink copy_tree copy_dir_recursive \ - copy_file reflink_copy_file reflink_copy_tree reflink_copy_tree_no_overwrite \ - reflink_copy_dir_new remove_path - xargs -P "$jobs" -I{} bash -c 'copy_entry "$@"' _ {} "$src" "$dst" "$force" "$verbose" < "$tmpfile" - rm -f "$tmpfile" -} - -copy_files_to_worktree() { - local src="$1" - local dst="$2" - local copyignored="$3" - local copyuntracked="$4" - local copymodified="$5" - local copytracked="$6" - local force="${7:-0}" - local verbose="${8:-0}" - local files - files=$(collect_copy_files "$src" "$copyignored" "$copyuntracked" "$copymodified" "$copytracked") - copy_entries_parallel "$src" "$dst" "$files" "$force" "$verbose" -} - -copy_files_between() { - local src="$1" - local dst="$2" - local copyignored="$3" - local copyuntracked="$4" - local copymodified="$5" - local copytracked="$6" - local dryrun="${7:-0}" - local verbose="${8:-0}" - local force="${9:-0}" - if [ "$src" = "$dst" ] || { [ -d "$src" ] && [ -d "$dst" ] && [ "$src" -ef "$dst" ]; }; then - return 0 - fi - if [ "$dryrun" -eq 1 ] && [ "$verbose" -eq 0 ]; then - collect_copy_files "$src" "$copyignored" "$copyuntracked" "$copymodified" "$copytracked" - return 0 - fi - local files - files=$(collect_copy_files "$src" "$copyignored" "$copyuntracked" "$copymodified" "$copytracked") - if [ "$dryrun" -eq 1 ]; then - local file - while IFS= read -r file; do - [ -z "$file" ] && continue - local srcfile="$src/$file" - if [ ! -e "$srcfile" ] && [ ! -L "$srcfile" ]; then - continue - fi - [ "$verbose" -eq 1 ] && printf '%s\n' "$file" >&2 - printf '%s\n' "$file" - done <<< "$files" - return 0 - fi - copy_entries_parallel "$src" "$dst" "$files" "$force" "$verbose" -} - -open_path() { - local branch="$1" - local from="$2" - local base_override="$3" - local path_override="$4" - local fetch="$5" - local copyignored="${6:-0}" - local copyuntracked="${7:-0}" - local copymodified="${8:-0}" - local copytracked="${9:-0}" - local force="${10:-0}" - local verbose="${11:-0}" - require_repo - local root base path copy_src - root=$(main_worktree_path) - copy_src="$root" - if [ "$copyignored" -eq 1 ] || [ "$copyuntracked" -eq 1 ] || [ "$copymodified" -eq 1 ] || [ "$copytracked" -eq 1 ]; then - copy_src=$(copy_source_path "$root") - fi - base=$(base_dir "$root" "$base_override") - if [ -n "$path_override" ]; then - path=$(resolve_path "$root" "$path_override") - else - path=$(resolve_path "$root" "$base/$branch") - fi - local existing - if existing=$(find_worktree_by_branch "$root" "$branch"); then - printf '%s\n' "$existing" - return 0 - fi - if git -C "$root" show-ref --verify --quiet "refs/heads/$branch"; then - if [ -z "$path_override" ]; then - die "branch exists without worktree: '$branch'" - fi - if find_worktree_by_path "$root" "$path" >/dev/null; then - die "worktree path already in use: '$path'" - fi - mkdir -p "$(dirname "$path")" - git -C "$root" worktree add "$path" "$branch" >/dev/null - copy_files_to_worktree "$copy_src" "$path" "$copyignored" "$copyuntracked" "$copymodified" "$copytracked" "$force" "$verbose" - printf '%s\n' "$path" - return 0 - fi - if [ "$fetch" -eq 1 ]; then - git -C "$root" fetch - fi - if [ -z "$from" ]; then - from=$(git -C "$root" rev-parse HEAD) - fi - if ! git -C "$root" rev-parse --verify "${from}^{commit}" >/dev/null; then - die "invalid --from ref '$from'" - fi - mkdir -p "$(dirname "$path")" - git -C "$root" worktree add -b "$branch" "$path" "$from" >/dev/null - copy_files_to_worktree "$copy_src" "$path" "$copyignored" "$copyuntracked" "$copymodified" "$copytracked" "$force" "$verbose" - printf '%s\n' "$path" -} - -cmd_switch() { - local from="" path_override="" fetch=0 branch="" - local copyignored=0 copyuntracked=0 copymodified=0 copytracked=0 force=0 verbose=0 - while [ "$#" -gt 0 ]; do - case "$1" in - -h | --help) - switch_usage - exit 0 - ;; - --from) - [ -n "${2:-}" ] || die "missing value for --from" - from="$2" - shift - ;; - --from=*) from="${1#--from=}" ;; - --path) - [ -n "${2:-}" ] || die "missing value for --path" - path_override="$2" - shift - ;; - --path=*) path_override="${1#--path=}" ;; - --fetch) fetch=1 ;; - --copy-all) copyignored=1; copyuntracked=1; copymodified=1; copytracked=1 ;; - --copy-ignored) copyignored=1 ;; - --copy-untracked) copyuntracked=1 ;; - --copy-modified) copymodified=1 ;; - -f | --force) force=1 ;; - -v | --verbose) verbose=1 ;; - --*) die "unknown flag '$1'" ;; - *) if [ -z "$branch" ]; then branch="$1"; else die "unexpected argument '$1'"; fi ;; - esac - shift - done - [ -n "$branch" ] || die "missing branch" - open_path "$branch" "$from" "$BASE_DIR_OVERRIDE" "$path_override" "$fetch" "$copyignored" "$copyuntracked" "$copymodified" "$copytracked" "$force" "$verbose" -} - -cmd_exec() { - local from="" path_override="" fetch=0 branch="" - while [ "$#" -gt 0 ]; do - case "$1" in - -h | --help) - exec_usage - exit 0 - ;; - --from) - [ -n "${2:-}" ] || die "missing value for --from" - from="$2" - shift - ;; - --from=*) from="${1#--from=}" ;; - --path) - [ -n "${2:-}" ] || die "missing value for --path" - path_override="$2" - shift - ;; - --path=*) path_override="${1#--path=}" ;; - --fetch) fetch=1 ;; - --) shift; break ;; - --*) die "unknown flag '$1'" ;; - *) - if [ -z "$branch" ]; then - branch="$1" - else - die "missing -- before command" - fi - ;; - esac - shift - done - [ -n "$branch" ] || die "missing branch" - [ "$#" -gt 0 ] || die "missing command" - local path - path=$(open_path "$branch" "$from" "$BASE_DIR_OVERRIDE" "$path_override" "$fetch") - cd "$path" - exec "$@" -} - -cmd_sync() { - local copyignored=0 copyuntracked=0 copymodified=0 copytracked=0 dryrun=0 verbose=0 force=0 - local from_branch="" to_branch="" - while [ "$#" -gt 0 ]; do - case "$1" in - -h | --help) - sync_usage - exit 0 - ;; - --copy-all) copyignored=1; copyuntracked=1; copymodified=1; copytracked=1 ;; - --copy-ignored) copyignored=1 ;; - --copy-untracked) copyuntracked=1 ;; - --copy-modified) copymodified=1 ;; - -f | --force) force=1 ;; - -v | --verbose) verbose=1 ;; - -n | --dry-run) dryrun=1 ;; - --) - shift - break - ;; - --*) die "unknown flag '$1'" ;; - *) - if [ -z "$from_branch" ]; then - from_branch="$1" - elif [ -z "$to_branch" ]; then - to_branch="$1" - else - die "unexpected argument '$1'" - fi - ;; - esac - shift - done - for arg in "$@"; do - if [ -z "$from_branch" ]; then - from_branch="$arg" - elif [ -z "$to_branch" ]; then - to_branch="$arg" - else - die "unexpected argument '$arg'" - fi - done - if [ -n "$from_branch" ] && [ -z "$to_branch" ]; then - die "missing destination branch" - fi - if [ -z "$from_branch" ] && [ -n "$to_branch" ]; then - die "missing source branch" - fi - require_repo - if [ "$copyignored" -eq 0 ] && [ "$copyuntracked" -eq 0 ] && [ "$copymodified" -eq 0 ] && [ "$copytracked" -eq 0 ]; then - die "missing copy flag" - fi - local root src dst - root=$(main_worktree_path) - if [ -z "$from_branch" ]; then - src=$(copy_source_path "$root") - if dst=$(git rev-parse --show-toplevel 2>/dev/null); then - find_worktree_by_path "$root" "$dst" >/dev/null || die "current directory is not a worktree" - else - dst="$src" - fi - else - if ! src=$(find_worktree_by_branch "$root" "$from_branch"); then - die "workspace not found: '$from_branch'" - fi - if ! dst=$(find_worktree_by_branch "$root" "$to_branch"); then - die "workspace not found: '$to_branch'" - fi - fi - copy_files_between "$src" "$dst" "$copyignored" "$copyuntracked" "$copymodified" "$copytracked" "$dryrun" "$verbose" "$force" -} - -cmd_ls() { - local plain=0 json=0 - while [ "$#" -gt 0 ]; do - case "$1" in - -h | --help) - ls_usage - exit 0 - ;; - --plain) plain=1 ;; - --json) json=1 ;; - --*) die "unknown flag '$1'" ;; - *) die "unexpected argument '$1'" ;; - esac - shift - done - require_repo - local root base - root=$(main_worktree_path) - if [ "$plain" -eq 1 ] && [ "$json" -eq 1 ]; then - die "cannot use --plain and --json together" - fi - local mode="table" - if [ "$plain" -eq 1 ]; then - mode="plain" - elif [ "$json" -eq 1 ]; then - mode="json" - elif [ ! -t 1 ]; then - mode="plain" - fi - local entries - entries=$(worktree_entries "$root") - local filtered="" - if [ -n "$BASE_DIR_OVERRIDE" ]; then - base=$(base_dir "$root" "$BASE_DIR_OVERRIDE") - while IFS= read -r entry; do - [ -n "$entry" ] || continue - local path head branch is_bare - parse_worktree_entry "$entry" - if [ "$is_bare" = "1" ]; then - filtered+="(bare)\t$path\t$head\t1\n" - continue - fi - case "$path" in - "$base"/*) - filtered+="$branch\t$path\t$head\t0\n" - ;; - esac - done <<<"$entries" - else - while IFS= read -r entry; do - [ -n "$entry" ] || continue - local path head branch is_bare - parse_worktree_entry "$entry" - if [ "$is_bare" = "1" ]; then - branch="(bare)" - fi - filtered+="$branch\t$path\t$head\t$is_bare\n" - done <<<"$entries" - fi - case "$mode" in - json) - printf '[' - local first=1 - while IFS= read -r line || [ -n "$line" ]; do - [ -n "$line" ] || continue - local branch path head rest is_bare is_bare_json - branch=${line%%$'\t'*} - rest=${line#*$'\t'} - path=${rest%%$'\t'*} - rest=${rest#*$'\t'} - head=${rest%%$'\t'*} - is_bare=${rest#*$'\t'} - if [ "$is_bare" = "1" ]; then - is_bare_json="true" - else - is_bare_json="false" - fi - if [ "$first" -eq 0 ]; then - printf ',' - fi - first=0 - printf '{"branch":"%s","path":"%s","head":"%s","is_bare":%s}' "$(json_escape "$branch")" "$(json_escape "$path")" "$(json_escape "$head")" "$is_bare_json" - done <<<"$(printf '%b' "$filtered")" - printf ']\n' - ;; - plain) - printf '%b' "$filtered" | awk -F'\t' 'NF>=2 {print $1"\t"$2}' - ;; - table) - printf '%b' "$filtered" | awk -F'\t' 'NF>=3 {printf "%-30s %-60s %s\n", $1, $2, $3}' - ;; - esac -} - -cmd_rm() { - local force=0 branch="" - while [ "$#" -gt 0 ]; do - case "$1" in - -h | --help) - rm_usage - exit 0 - ;; - -f | --force) force=1 ;; - --*) die "unknown flag '$1'" ;; - *) if [ -z "$branch" ]; then branch="$1"; else die "unexpected argument '$1'"; fi ;; - esac - shift - done - [ -n "$branch" ] || die "missing branch" - require_repo - local root path - root=$(main_worktree_path) - if ! path=$(find_worktree_by_branch "$root" "$branch"); then - if [ "$force" -eq 1 ]; then - exit 0 - fi - die "workspace not found: '$branch'" - fi - if [ "$path" = "$root" ]; then - die "cannot remove main worktree" - fi - if [ "$force" -eq 0 ]; then - local dirty - dirty=$(git -C "$path" status --porcelain) - if [ -n "$dirty" ]; then - if [ -t 0 ]; then - printf "workspace '%s' has uncommitted changes. remove anyway? [y/N] " "$branch" >&2 - read -r answer - case "$answer" in - y | Y | yes | YES) force=1 ;; - *) exit 1 ;; - esac - else - die "workspace dirty: '$branch'" - fi - fi - fi - if [ "$force" -eq 1 ]; then - git -C "$root" worktree remove --force "$path" 2>/dev/null || true - command rm -rf "$path" 2>/dev/null || true - else - git -C "$root" worktree remove "$path" - fi - if [ "$force" -eq 1 ]; then - git -C "$root" branch -D "$branch" 2>/dev/null || true - else - git -C "$root" branch -d "$branch" - fi - printf '%s\n' "$path" -} - -cmd_merge() { - local target="" squash=1 rebase=1 commit=1 - while [ "$#" -gt 0 ]; do - case "$1" in - -h|--help) merge_usage; exit 0 ;; - --no-squash) squash=0 ;; - --no-rebase) rebase=0 ;; - --no-commit) commit=0 ;; - --*) die "unknown flag '$1'" ;; - *) if [ -z "$target" ]; then target="$1"; else die "unexpected argument '$1'"; fi ;; - esac - shift - done - - if [ -z "$target" ]; then - merge_usage - exit 1 - fi - - require_repo - local root branch - root=$(main_worktree_path) - branch=$(git symbolic-ref --short HEAD 2>/dev/null) || die "not on a branch (detached HEAD)" - - git show-ref --verify --quiet "refs/heads/$target" || die "target branch '$target' not found" - - [ "$branch" != "$target" ] || die "cannot merge branch into itself" - - if [ "$commit" -eq 0 ]; then - if [ -n "$(git status --porcelain)" ]; then - die "uncommitted changes present (use without --no-commit)" - fi - fi - - if [ "$commit" -eq 1 ] && [ -n "$(git status --porcelain)" ]; then - git add -A - git commit -m "WIP: uncommitted changes" || die "failed to commit changes" - printf 'committed uncommitted changes\n' >&2 - fi - - local merge_base - merge_base=$(git merge-base HEAD "$target") || die "no common ancestor with '$target'" - - if [ "$squash" -eq 1 ]; then - local commit_count - commit_count=$(git rev-list --count "$merge_base..HEAD") - if [ "$commit_count" -gt 1 ]; then - local subjects - subjects=$(git log --format='- %s' "$merge_base..HEAD") - git reset --soft "$merge_base" || die "failed to reset to merge base" - git commit -m "$(printf 'Squash %d commits\n\n%s' "$commit_count" "$subjects")" || die "failed to create squash commit" - printf 'squashed %d commits\n' "$commit_count" >&2 - elif [ "$commit_count" -eq 1 ]; then - printf 'single commit, nothing to squash\n' >&2 - else - printf 'no commits ahead of %s\n' "$target" >&2 - fi - fi - - if [ "$rebase" -eq 1 ]; then - if ! git merge-base --is-ancestor "$target" HEAD; then - git rebase "$target" || die "rebase failed (resolve conflicts and run: git rebase --continue)" - printf 'rebased onto %s\n' "$target" >&2 - else - printf 'already rebased onto %s\n' "$target" >&2 - fi - else - if ! git merge-base --is-ancestor "$target" HEAD; then - die "not rebased onto '$target' (run without --no-rebase)" - fi - fi - - if ! git merge-base --is-ancestor "$target" HEAD; then - die "cannot fast-forward: '$target' has commits not in HEAD" - fi - - local head_sha - head_sha=$(git rev-parse HEAD) - git update-ref "refs/heads/$target" "$head_sha" || die "failed to fast-forward '$target'" - printf 'merged to %s @ %s\n' "$target" "$(git rev-parse --short HEAD)" >&2 -} - -cmd_here() { - require_repo - local root base current - root=$(main_worktree_path) - base=$(base_dir "$root" "$BASE_DIR_OVERRIDE") - current=$(git rev-parse --show-toplevel) - case "$current" in - "$base"/*) ;; - *) exit 1 ;; - esac - local entry - while IFS= read -r entry; do - local path head branch is_bare - parse_worktree_entry "$entry" - if [ "$path" = "$current" ]; then - if [ -n "$branch" ]; then - printf '%s\n' "$branch" - exit 0 - fi - fi - done < <(worktree_entries "$root") - exit 1 -} - -cmd_base() { - require_repo - local root base - root=$(main_worktree_path) - base=$(base_dir "$root" "$BASE_DIR_OVERRIDE") - printf '%s\n' "$base" -} - -cmd_root() { - require_repo - local common - common=$(common_git_dir_abs) - if repo_is_bare "$common"; then - printf '%s\n' "$common" - return - fi - main_worktree_path -} - -completion_bash() { - local commands - commands="switch sw exec sync ls rm merge here base root help completion" - cat </dev/null 2>&1 || return 0 - -wt() { - case "\$1" in - switch|sw) - local i - for i in "\$@"; do - case "\$i" in - -h|--help) - command wt "\$@" - return \$? - ;; - esac - done - local out path - out=\$(command wt "\$@") - local rc=\$? - path=\$(printf '%s\n' "\$out" | tail -n 1) - if [[ \$rc -eq 0 && -n "\$path" && -d "\$path" ]]; then - if [[ "\$out" != "\$path" ]]; then - printf '%s\n' "\$out" | sed '\$d' - fi - cd "\$path" - else - [[ -n "\$out" ]] && printf '%s\n' "\$out" - return \$rc - fi - ;; - *) - command wt "\$@" - ;; - esac -} - -__wt_branches() { - git rev-parse --git-dir >/dev/null 2>&1 || return 0 - git for-each-ref --format='%(refname:short)' refs/heads 2>/dev/null -} - -_wt_complete() { - local cur prev cmd - cur="\${COMP_WORDS[COMP_CWORD]}" - prev="\${COMP_WORDS[COMP_CWORD-1]}" - cmd="" - compopt +o default +o bashdefault 2>/dev/null - local commands="$commands" - local flags="" - local i - local global_flags="--base-dir -h --help" - for i in "\${COMP_WORDS[@]}"; do - if [ "\$i" = "--" ]; then - return 0 - fi - done - if [ "\$prev" = "--base-dir" ]; then - return 0 - fi - i=1 - while [ "\$i" -lt "\$COMP_CWORD" ]; do - local word="\${COMP_WORDS[\$i]}" - case "\$word" in - --base-dir) - i=\$((i + 2)) - ;; - --base-dir=*) - i=\$((i + 1)) - ;; - -h|--help) - i=\$((i + 1)) - ;; - --*) - i=\$((i + 1)) - ;; - *) - cmd="\$word" - break - ;; - esac - done - if [ -z "\$cmd" ]; then - COMPREPLY=( \$(compgen -W "\$global_flags \$commands" -- "\$cur") ) - return 0 - fi - case "\$cmd" in - switch|sw) - flags="--from --path --fetch --copy-all --copy-ignored --copy-untracked --copy-modified -f --force -v --verbose -h --help" - case "\$prev" in - --from|--path) return 0 ;; - esac - COMPREPLY=( \$(compgen -W "\$flags \$(__wt_branches)" -- "\$cur") ) - ;; - exec) - flags="--from --path --fetch -h --help --" - case "\$prev" in - --from|--path|--) return 0 ;; - esac - if [ "\$COMP_CWORD" -eq 2 ]; then - COMPREPLY=( \$(compgen -W "\$(__wt_branches)" -- "\$cur") ) - else - COMPREPLY=( \$(compgen -W "\$flags" -- "\$cur") ) - fi - ;; - sync) - flags="--copy-all --copy-ignored --copy-untracked --copy-modified -f --force -v --verbose -n --dry-run -h --help" - COMPREPLY=( \$(compgen -W "\$flags \$(__wt_branches)" -- "\$cur") ) - ;; - rm) - flags="-f --force -h --help" - COMPREPLY=( \$(compgen -W "\$flags \$(__wt_branches)" -- "\$cur") ) - ;; - merge) - flags="--no-squash --no-rebase --no-commit -h --help" - COMPREPLY=( \$(compgen -W "\$flags \$(__wt_branches)" -- "\$cur") ) - ;; - ls) - flags="--plain --json -h --help" - COMPREPLY=( \$(compgen -W "\$flags" -- "\$cur") ) - ;; - here|base|root) - flags="-h --help" - COMPREPLY=( \$(compgen -W "\$flags" -- "\$cur") ) - ;; - help) - flags="switch sw exec sync ls rm merge here base root help completion" - COMPREPLY=( \$(compgen -W "\$flags" -- "\$cur") ) - ;; - completion) - flags="bash zsh fish" - COMPREPLY=( \$(compgen -W "\$flags" -- "\$cur") ) - ;; - esac -} - -complete -F _wt_complete wt -EOF -} - -completion_zsh() { - cat </dev/null 2>&1 || return 0 - -wt() { - case "\$1" in - switch|sw) - local i - for i in "\$@"; do - case "\$i" in - -h|--help) - command wt "\$@" - return \$? - ;; - esac - done - local out path - out=\$(command wt "\$@") - local rc=\$? - path=\$(printf '%s\n' "\$out" | tail -n 1) - if [[ \$rc -eq 0 && -n "\$path" && -d "\$path" ]]; then - if [[ "\$out" != "\$path" ]]; then - printf '%s\n' "\$out" | sed '\$d' - fi - cd "\$path" - else - [[ -n "\$out" ]] && printf '%s\n' "\$out" - return \$rc - fi - ;; - *) - command wt "\$@" - ;; - esac -} - -__wt_branches() { - git rev-parse --git-dir >/dev/null 2>&1 || return 0 - git for-each-ref --format='%(refname:short)' refs/heads 2>/dev/null -} - -_wt() { - 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 - subcmds=(switch sw exec sync ls rm merge here base root help completion) - subdescs=( - "create or open a workspace" - "alias for switch" - "open a workspace and run a command" - "copy files between worktrees" - "list workspaces" - "remove a workspace" - "squash, rebase, and merge to target" - "print current workspace branch" - "print base dir" - "print main worktree path" - "show help" - "print shell completion" - ) - global_flags=(--base-dir -h --help) - global_descs=("base dir override" "show help" "show help") - switch_flags=(--from --path --fetch --copy-all --copy-ignored --copy-untracked --copy-modified -f --force -v --verbose -h --help) - 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") - exec_flags=(--from --path --fetch -h --help --) - exec_descs=("base ref for new branch" "explicit worktree path" "fetch remotes before resolving --from" "show help" "show help" "end of flags") - sync_flags=(--copy-all --copy-ignored --copy-untracked --copy-modified -f --force -v --verbose -n --dry-run -h --help) - 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") - ls_flags=(--plain --json -h --help) - ls_descs=("tab-delimited output" "JSON output" "show help" "show help") - rm_flags=(-f --force -h --help) - rm_descs=("remove even if dirty" "remove even if dirty" "show help" "show help") - merge_flags=(--no-squash --no-rebase --no-commit -h --help) - merge_descs=("preserve individual commits" "skip rebase (fail if not rebased)" "skip committing uncommitted changes" "show help" "show help") - hb_flags=(-h --help) - hb_descs=("show help" "show help") - if (( CURRENT == 2 )); then - compadd -d global_descs -- \$global_flags - compadd -d subdescs -- \$subcmds - return 0 - fi - local cmd="" - local idx=2 - while (( idx <= CURRENT )); do - local word=\${words[idx]} - case "\$word" in - --base-dir) - idx=\$((idx + 2)) - ;; - --base-dir=*) - idx=\$((idx + 1)) - ;; - -h|--help) - idx=\$((idx + 1)) - ;; - --*) - idx=\$((idx + 1)) - ;; - *) - cmd=\$word - break - ;; - esac - done - if [[ "\${words[CURRENT-1]}" == --base-dir ]]; then - return 0 - fi - if [[ -z "\$cmd" ]]; then - compadd -d global_descs -- \$global_flags - compadd -d subdescs -- \$subcmds - return 0 - fi - case "\$cmd" in - switch|sw) - if [[ "\${words[CURRENT-1]}" == --from || "\${words[CURRENT-1]}" == --path ]]; then - return 0 - fi - compadd -d switch_descs -- \$switch_flags - compadd -- \${(f)"\$(__wt_branches)"} - return 0 - ;; - exec) - if [[ "\${words[CURRENT-1]}" == --from || "\${words[CURRENT-1]}" == --path || "\${words[CURRENT-1]}" == -- ]]; then - return 0 - fi - if (( CURRENT == 3 )); then - compadd -- \${(f)"\$(__wt_branches)"} - else - compadd -d exec_descs -- \$exec_flags - fi - return 0 - ;; - sync) - compadd -d sync_descs -- \$sync_flags - compadd -- \${(f)"\$(__wt_branches)"} - return 0 - ;; - rm) - compadd -d rm_descs -- \$rm_flags - compadd -- \${(f)"\$(__wt_branches)"} - return 0 - ;; - merge) - compadd -d merge_descs -- \$merge_flags - compadd -- \${(f)"\$(__wt_branches)"} - return 0 - ;; - ls) - compadd -d ls_descs -- \$ls_flags - return 0 - ;; - here|base|root) - compadd -d hb_descs -- \$hb_flags - return 0 - ;; - help) - compadd -d subdescs -- \$subcmds - return 0 - ;; - completion) - compadd -- bash zsh fish - return 0 - ;; - esac - return 0 -} - -compdef _wt wt -EOF -} - -completion_fish() { - local wt_bin="$0" - case "$wt_bin" in - */*) wt_bin="$(cd "$(dirname "$wt_bin")" && pwd -P)/$(basename "$wt_bin")" ;; - *) wt_bin="$(command -v "$wt_bin" 2>/dev/null || printf '%s' "$wt_bin")" ;; - esac - local wt_bin_escaped="${wt_bin//\'/\'\\\'\'}" - cat <<'EOF' -type -q wt; or return - -EOF - printf "set -g __wt_bin '%s'\n\n" "$wt_bin_escaped" - cat <<'EOF' -function wt - if test "$argv[1]" = "switch" -o "$argv[1]" = "sw" - if contains -- -h $argv; or contains -- --help $argv - command "$__wt_bin" $argv - return $status - end - set -g __wt_rc 0 - set -l out (begin - command "$__wt_bin" $argv - set -g __wt_rc $status - end | string collect | string trim --right) - set -l rc $__wt_rc - set -e __wt_rc - set -l out_lines (string split -- "\n" $out) - set -l path $out_lines[-1] - if test $rc -eq 0 -a -n "$path" -a -d "$path" - if test (count $out_lines) -gt 1 - printf '%s\n' $out_lines[1..-2] - end - cd "$path" - else - test -n "$out"; and printf '%s\n' "$out" - return $rc - end - else - command "$__wt_bin" $argv - end -end - -function __wt_branches - command git rev-parse --git-dir >/dev/null 2>&1 - or return - command git for-each-ref --format='%(refname:short)' refs/heads 2>/dev/null -end - -complete -c wt -f -complete -c wt -l base-dir -r -d 'base dir override' -complete -c wt -s h -l help -d 'show help' -complete -c wt -n '__fish_use_subcommand' -a switch -d 'create or open a workspace' -complete -c wt -n '__fish_use_subcommand' -a sw -d 'alias for switch' -complete -c wt -n '__fish_use_subcommand' -a exec -d 'open a workspace and run a command' -complete -c wt -n '__fish_use_subcommand' -a sync -d 'copy files between worktrees' -complete -c wt -n '__fish_use_subcommand' -a ls -d 'list workspaces' -complete -c wt -n '__fish_use_subcommand' -a rm -d 'remove a workspace' -complete -c wt -n '__fish_use_subcommand' -a merge -d 'squash, rebase, and merge to target' -complete -c wt -n '__fish_use_subcommand' -a here -d 'print current workspace branch' -complete -c wt -n '__fish_use_subcommand' -a base -d 'print base dir' -complete -c wt -n '__fish_use_subcommand' -a root -d 'print main worktree path' -complete -c wt -n '__fish_use_subcommand' -a help -d 'show help' -complete -c wt -n '__fish_use_subcommand' -a completion -d 'print shell completion' -complete -c wt -n '__fish_seen_subcommand_from switch sw exec sync rm merge' -a '(__wt_branches)' - -complete -c wt -n '__fish_seen_subcommand_from switch sw exec' -l from -r -d 'base ref for new branch' -complete -c wt -n '__fish_seen_subcommand_from switch sw exec' -l path -r -d 'explicit worktree path' -complete -c wt -n '__fish_seen_subcommand_from switch sw exec' -l fetch -d 'fetch remotes before resolving --from' -complete -c wt -n '__fish_seen_subcommand_from switch sw exec' -s h -l help -d 'show help' -complete -c wt -n '__fish_seen_subcommand_from switch sw' -l copy-all -d 'copy all files to new worktree' -complete -c wt -n '__fish_seen_subcommand_from switch sw' -l copy-ignored -d 'copy gitignored files to new worktree' -complete -c wt -n '__fish_seen_subcommand_from switch sw' -l copy-untracked -d 'copy untracked files to new worktree' -complete -c wt -n '__fish_seen_subcommand_from switch sw' -l copy-modified -d 'copy modified files to new worktree' -complete -c wt -n '__fish_seen_subcommand_from switch sw' -s f -l force -d 'overwrite existing destination files' -complete -c wt -n '__fish_seen_subcommand_from switch sw' -s v -l verbose -d 'print copy progress' -complete -c wt -n '__fish_seen_subcommand_from sync' -l copy-all -d 'copy all files' -complete -c wt -n '__fish_seen_subcommand_from sync' -l copy-ignored -d 'copy gitignored files' -complete -c wt -n '__fish_seen_subcommand_from sync' -l copy-untracked -d 'copy untracked files' -complete -c wt -n '__fish_seen_subcommand_from sync' -l copy-modified -d 'copy modified files' -complete -c wt -n '__fish_seen_subcommand_from sync' -s f -l force -d 'overwrite existing destination files' -complete -c wt -n '__fish_seen_subcommand_from sync' -s v -l verbose -d 'print copy progress' -complete -c wt -n '__fish_seen_subcommand_from sync' -s n -l dry-run -d 'show files that would be copied' -complete -c wt -n '__fish_seen_subcommand_from sync' -s h -l help -d 'show help' - -complete -c wt -n '__fish_seen_subcommand_from ls' -l plain -d 'tab-delimited output' -complete -c wt -n '__fish_seen_subcommand_from ls' -l json -d 'JSON output' -complete -c wt -n '__fish_seen_subcommand_from ls' -s h -l help -d 'show help' - -complete -c wt -n '__fish_seen_subcommand_from rm' -s f -l force -d 'remove even if dirty' -complete -c wt -n '__fish_seen_subcommand_from rm' -s h -l help -d 'show help' - -complete -c wt -n '__fish_seen_subcommand_from merge' -l no-squash -d 'preserve individual commits' -complete -c wt -n '__fish_seen_subcommand_from merge' -l no-rebase -d 'skip rebase (fail if not rebased)' -complete -c wt -n '__fish_seen_subcommand_from merge' -l no-commit -d 'skip committing uncommitted changes' -complete -c wt -n '__fish_seen_subcommand_from merge' -s h -l help -d 'show help' - -complete -c wt -n '__fish_seen_subcommand_from help' -a switch -d 'create or open a workspace' -complete -c wt -n '__fish_seen_subcommand_from help' -a sw -d 'alias for switch' -complete -c wt -n '__fish_seen_subcommand_from help' -a exec -d 'open a workspace and run a command' -complete -c wt -n '__fish_seen_subcommand_from help' -a sync -d 'copy files between worktrees' -complete -c wt -n '__fish_seen_subcommand_from help' -a ls -d 'list workspaces' -complete -c wt -n '__fish_seen_subcommand_from help' -a rm -d 'remove a workspace' -complete -c wt -n '__fish_seen_subcommand_from help' -a merge -d 'squash, rebase, and merge to target' -complete -c wt -n '__fish_seen_subcommand_from help' -a here -d 'print current workspace branch' -complete -c wt -n '__fish_seen_subcommand_from help' -a base -d 'print base dir' -complete -c wt -n '__fish_seen_subcommand_from help' -a root -d 'print main worktree path' -complete -c wt -n '__fish_seen_subcommand_from help' -a help -d 'show help' -complete -c wt -n '__fish_seen_subcommand_from help' -a completion -d 'print shell completion' -complete -c wt -n '__fish_seen_subcommand_from completion' -a 'bash zsh fish' -EOF -} - -cmd_completion() { - if [ "$#" -eq 0 ]; then - completion_usage - exit 1 - fi - case "$1" in - bash) completion_bash ;; - zsh) completion_zsh ;; - fish) completion_fish ;; - *) die "unknown shell '$1'" ;; - esac -} - -cmd_help() { - if [ "$#" -eq 0 ]; then - usage - exit 0 - fi - case "$1" in - switch | sw) switch_usage ;; - exec) exec_usage ;; - sync) sync_usage ;; - ls) ls_usage ;; - rm) rm_usage ;; - merge) merge_usage ;; - here) here_usage ;; - base) base_usage ;; - root) root_usage ;; - completion) completion_usage ;; - help) usage ;; - *) die "unknown command '$1'" ;; - esac -} - -while [ "$#" -gt 0 ]; do - case "$1" in - --base-dir) - [ -n "${2:-}" ] || die "missing value for --base-dir" - BASE_DIR_OVERRIDE="$2" - shift 2 - ;; - --base-dir=*) - BASE_DIR_OVERRIDE="${1#--base-dir=}" - shift - ;; - -h | --help) - break - ;; - --*) die "unknown flag '$1'" ;; - *) break ;; - esac -done - -case "${1:-}" in -"") - usage - exit 1 - ;; --h | --help) - usage - exit 0 - ;; -help) - shift - cmd_help "$@" - ;; -switch) - shift - cmd_switch "$@" - ;; -sw) - shift - cmd_switch "$@" - ;; -exec) - shift - cmd_exec "$@" - ;; -sync) - shift - cmd_sync "$@" - ;; -ls) - shift - cmd_ls "$@" - ;; -rm) - shift - cmd_rm "$@" - ;; -merge) - shift - cmd_merge "$@" - ;; -here) - shift - cmd_here "$@" - ;; -base) - shift - cmd_base "$@" - ;; -root) - shift - cmd_root "$@" - ;; -completion) - shift - cmd_completion "$@" - ;; -*) die "unknown command '$1'" ;; -esac