native macOS codings agent orchestrator
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: switch git-wt to submodule

khoi 145b992b 60651841

+5 -1782
+3
.gitmodules
··· 1 1 [submodule "ThirdParty/ghostty"] 2 2 path = ThirdParty/ghostty 3 3 url = https://github.com/ghostty-org/ghostty 4 + [submodule "Resources/git-wt"] 5 + path = Resources/git-wt 6 + url = https://github.com/khoi/git-wt.git
+1 -1
AGENTS.md
··· 116 116 ## References 117 117 118 118 - `git@github.com:ghostty-org/ghostty.git` - Dive into this codebase when implementing Ghostty features 119 - - `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 119 + - `git@github.com:khoi/git-wt.git` - Git worktree wrapper submodule at `Resources/git-wt`
+1 -8
Makefile
··· 18 18 BUILD ?= 19 19 XCODEBUILD_FLAGS ?= 20 20 .DEFAULT_GOAL := help 21 - .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 21 + .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 22 22 23 23 help: # Display this help. 24 24 @-+echo "Run make with one of the following targets:" ··· 84 84 85 85 log-stream: # Stream logs from the app via log stream 86 86 log stream --predicate 'subsystem == "app.supabit.supacode"' --style compact --color always 87 - 88 - update-wt: # Download git-wt binary to Resources 89 - @mkdir -p "$(CURRENT_MAKEFILE_DIR)/Resources/git-wt" 90 - @curl -fsSL "https://raw.githubusercontent.com/khoi/git-wt/refs/heads/main/wt" -o "$(CURRENT_MAKEFILE_DIR)/Resources/git-wt/wt" 91 - @chmod +x "$(CURRENT_MAKEFILE_DIR)/Resources/git-wt/wt" 92 - @git add "$(CURRENT_MAKEFILE_DIR)/Resources/git-wt/wt" 93 - @git commit -m "update git-wt" 94 87 95 88 bump-version: # Bump app version (usage: make bump-version [VERSION=x.x.x] [BUILD=123]) 96 89 @if [ -z "$(VERSION)" ]; then \
-1773
Resources/git-wt/wt
··· 1 - #!/bin/bash 2 - set -euo pipefail 3 - 4 - usage() { 5 - cat <<'USAGE' 6 - wt - A simple git worktree sugar 7 - 8 - Usage: 9 - wt [--base-dir <dir>] <command> 10 - 11 - Global Flags: 12 - --base-dir <dir> base dir override 13 - -h, --help show help 14 - 15 - Commands: 16 - switch <branch> create or open a workspace 17 - sw <branch> alias for switch 18 - exec <branch> open a workspace and run a command 19 - sync [from to] copy files between worktrees 20 - ls list workspaces 21 - rm <branch> remove a workspace 22 - merge <target> squash, rebase, and merge to target 23 - here print current workspace branch 24 - base print base dir 25 - root print main worktree path 26 - completion print shell completion 27 - help [command] show help 28 - USAGE 29 - } 30 - 31 - switch_usage() { 32 - cat <<'USAGE' 33 - wt switch|sw <branch> 34 - 35 - Flags: 36 - --from <ref> base ref for new branch 37 - --path <dir> explicit worktree path 38 - --fetch fetch remotes before resolving --from 39 - --copy-all copy all files to new worktree 40 - --copy-ignored copy gitignored files to new worktree 41 - --copy-untracked copy untracked files to new worktree 42 - --copy-modified copy modified files to new worktree 43 - -f, --force overwrite existing destination files 44 - -v, --verbose print copy progress 45 - -h, --help show help 46 - USAGE 47 - } 48 - 49 - exec_usage() { 50 - cat <<'USAGE' 51 - wt exec <branch> -- <cmd...> 52 - 53 - Flags: 54 - --from <ref> base ref for new branch 55 - --path <dir> explicit worktree path 56 - --fetch fetch remotes before resolving --from 57 - -h, --help show help 58 - USAGE 59 - } 60 - 61 - sync_usage() { 62 - cat <<'USAGE' 63 - wt sync [<from-branch> <to-branch>] 64 - 65 - Defaults to primary worktree -> current worktree. 66 - 67 - Flags: 68 - --copy-all copy all files 69 - --copy-ignored copy gitignored files 70 - --copy-untracked copy untracked files 71 - --copy-modified copy modified files 72 - -f, --force overwrite existing destination files 73 - -v, --verbose print copy progress 74 - -n, --dry-run show files that would be copied 75 - -h, --help show help 76 - USAGE 77 - } 78 - 79 - ls_usage() { 80 - cat <<'USAGE' 81 - wt ls 82 - 83 - Flags: 84 - --plain tab-delimited output 85 - --json JSON output 86 - -h, --help show help 87 - USAGE 88 - } 89 - 90 - rm_usage() { 91 - cat <<'USAGE' 92 - wt rm <branch> 93 - 94 - Flags: 95 - -f, --force remove even if dirty 96 - -h, --help show help 97 - USAGE 98 - } 99 - 100 - merge_usage() { 101 - cat <<'USAGE' 102 - wt merge <target> 103 - 104 - Squash, rebase, and fast-forward merge into target branch. 105 - 106 - Flags: 107 - --no-squash preserve individual commits 108 - --no-rebase skip rebase (fail if not rebased) 109 - --no-commit skip committing uncommitted changes 110 - -h, --help show help 111 - USAGE 112 - } 113 - 114 - here_usage() { 115 - cat <<'USAGE' 116 - wt here 117 - USAGE 118 - } 119 - 120 - base_usage() { 121 - cat <<'USAGE' 122 - wt base 123 - USAGE 124 - } 125 - 126 - root_usage() { 127 - cat <<'USAGE' 128 - wt root 129 - USAGE 130 - } 131 - 132 - completion_usage() { 133 - cat <<'USAGE' 134 - wt completion <shell> 135 - 136 - Shells: 137 - bash 138 - zsh 139 - fish 140 - USAGE 141 - } 142 - 143 - die() { 144 - printf 'error: %s\n' "$1" >&2 145 - exit 1 146 - } 147 - 148 - BASE_DIR_OVERRIDE="" 149 - 150 - require_repo() { 151 - local err 152 - if ! err=$(git rev-parse --git-dir 2>&1); then 153 - printf '%s\n' "$err" >&2 154 - die "not a git repository" 155 - fi 156 - } 157 - 158 - common_git_dir_abs() { 159 - local common 160 - common=$(git rev-parse --git-common-dir) 161 - local target 162 - case "$common" in 163 - /*) target="$common" ;; 164 - *) target="$(abs_path "$common")" ;; 165 - esac 166 - if [ -d "$target" ]; then 167 - (cd "$target" && pwd -P) 168 - return 169 - fi 170 - normalize_dir "$target" 171 - } 172 - 173 - repo_is_bare() { 174 - local is_bare 175 - if [ -n "${1:-}" ]; then 176 - is_bare=$(git --git-dir "$1" rev-parse --is-bare-repository) 177 - else 178 - is_bare=$(git rev-parse --is-bare-repository) 179 - fi 180 - [ "$is_bare" = "true" ] 181 - } 182 - 183 - canonical_worktree_path() { 184 - local path="$1" 185 - local resolved 186 - if resolved=$(git -C "$path" rev-parse --show-toplevel 2>/dev/null); then 187 - normalize_dir "$resolved" 188 - return 189 - fi 190 - normalize_dir "$path" 191 - } 192 - 193 - main_worktree_path() { 194 - local common 195 - common=$(common_git_dir_abs) 196 - if repo_is_bare; then 197 - printf '%s\n' "$common" 198 - return 199 - fi 200 - local path="" is_bare=0 201 - while IFS= read -r line || [ -n "$line" ]; do 202 - if [ -z "$line" ]; then 203 - if [ -n "$path" ] && [ "$is_bare" -eq 0 ]; then 204 - canonical_worktree_path "$path" 205 - return 206 - fi 207 - path="" 208 - is_bare=0 209 - continue 210 - fi 211 - case "$line" in 212 - worktree\ *) path="${line#worktree }" ;; 213 - bare) is_bare=1 ;; 214 - esac 215 - done < <(git worktree list --porcelain) 216 - if [ -n "$path" ] && [ "$is_bare" -eq 0 ]; then 217 - canonical_worktree_path "$path" 218 - return 219 - fi 220 - git rev-parse --show-toplevel 221 - } 222 - 223 - primary_worktree_path() { 224 - local root="$1" 225 - local entry 226 - while IFS= read -r entry; do 227 - [ -n "$entry" ] || continue 228 - local path head branch is_bare 229 - parse_worktree_entry "$entry" 230 - if [ "$is_bare" = "0" ] && [ -d "$path" ]; then 231 - printf '%s\n' "$path" 232 - return 0 233 - fi 234 - done < <(worktree_entries "$root") 235 - return 1 236 - } 237 - 238 - copy_source_path() { 239 - local root="$1" 240 - if repo_is_bare; then 241 - primary_worktree_path "$root" || die "no linked worktree found for copy operations" 242 - return 243 - fi 244 - printf '%s\n' "$root" 245 - } 246 - 247 - abs_path() { 248 - local path="$1" 249 - case "$path" in 250 - /*) printf '%s\n' "$path" ;; 251 - *) printf '%s/%s\n' "$(pwd -P)" "$path" ;; 252 - esac 253 - } 254 - 255 - normalize_dir() { 256 - local path="$1" 257 - if [ "$path" != "/" ]; then 258 - path="${path%/}" 259 - fi 260 - printf '%s\n' "$path" 261 - } 262 - 263 - base_dir() { 264 - local root="$1" 265 - local override="$2" 266 - local chosen 267 - chosen="${GIT_WT_BASE:-}" 268 - if [ -n "$override" ]; then 269 - chosen="$override" 270 - fi 271 - if [ -z "$chosen" ]; then 272 - normalize_dir "$(abs_path "$root/.worktrees")" 273 - return 274 - fi 275 - case "$chosen" in 276 - /*) normalize_dir "$(abs_path "$chosen")" ;; 277 - *) normalize_dir "$(abs_path "$root/$chosen")" ;; 278 - esac 279 - } 280 - 281 - resolve_path() { 282 - local root="$1" 283 - local path="$2" 284 - case "$path" in 285 - /*) abs_path "$path" ;; 286 - *) abs_path "$root/$path" ;; 287 - esac 288 - } 289 - 290 - worktree_entries() { 291 - local root="$1" 292 - local path="" head="" branch="" is_bare=0 293 - while IFS= read -r line || [ -n "$line" ]; do 294 - if [ -z "$line" ]; then 295 - if [ -n "$path" ]; then 296 - local output_path="$path" 297 - if [ "$is_bare" -eq 0 ]; then 298 - output_path=$(canonical_worktree_path "$path") 299 - fi 300 - printf '%s\t%s\t%s\t%s\n' "$output_path" "$head" "$branch" "$is_bare" 301 - fi 302 - path="" 303 - head="" 304 - branch="" 305 - is_bare=0 306 - continue 307 - fi 308 - local key value 309 - key=${line%% *} 310 - value=${line#* } 311 - case "$key" in 312 - worktree) path="$value" ;; 313 - HEAD) head="$value" ;; 314 - branch) branch="${value#refs/heads/}" ;; 315 - detached) branch="" ;; 316 - bare) is_bare=1 ;; 317 - esac 318 - done < <(git -C "$root" worktree list --porcelain) 319 - if [ -n "$path" ]; then 320 - local output_path="$path" 321 - if [ "$is_bare" -eq 0 ]; then 322 - output_path=$(canonical_worktree_path "$path") 323 - fi 324 - printf '%s\t%s\t%s\t%s\n' "$output_path" "$head" "$branch" "$is_bare" 325 - fi 326 - } 327 - 328 - parse_worktree_entry() { 329 - local entry="$1" 330 - local rest 331 - path=${entry%%$'\t'*} 332 - rest=${entry#*$'\t'} 333 - head=${rest%%$'\t'*} 334 - rest=${rest#*$'\t'} 335 - branch=${rest%%$'\t'*} 336 - is_bare=${rest#*$'\t'} 337 - } 338 - 339 - find_worktree_by_branch() { 340 - local root="$1" 341 - local target_branch="$2" 342 - local entry 343 - while IFS= read -r entry; do 344 - local path head branch is_bare 345 - parse_worktree_entry "$entry" 346 - if [ "$is_bare" = "1" ]; then 347 - continue 348 - fi 349 - if [ "$branch" = "$target_branch" ]; then 350 - printf '%s\n' "$path" 351 - return 0 352 - fi 353 - done < <(worktree_entries "$root") 354 - return 1 355 - } 356 - 357 - find_worktree_by_path() { 358 - local root="$1" 359 - local target="$2" 360 - local entry 361 - while IFS= read -r entry; do 362 - local path head branch is_bare 363 - parse_worktree_entry "$entry" 364 - if [ "$path" = "$target" ]; then 365 - printf '%s\n' "$path" 366 - return 0 367 - fi 368 - if [ -d "$path" ] && [ -d "$target" ] && [ "$path" -ef "$target" ]; then 369 - printf '%s\n' "$path" 370 - return 0 371 - fi 372 - done < <(worktree_entries "$root") 373 - return 1 374 - } 375 - 376 - json_escape() { 377 - printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' 378 - } 379 - 380 - detect_jobs() { 381 - sysctl -n hw.logicalcpu 2>/dev/null || nproc 2>/dev/null || echo 1 382 - } 383 - 384 - list_ignored_files() { 385 - local root="$1" 386 - git -C "$root" ls-files --others --ignored --exclude-standard --directory | sed '/^\.git\//d' 387 - } 388 - 389 - list_untracked_files() { 390 - local root="$1" 391 - git -C "$root" ls-files --others --exclude-standard | sed '/^\.git\//d' 392 - } 393 - 394 - list_modified_files() { 395 - local root="$1" 396 - git -C "$root" ls-files --modified | sed '/^\.git\//d' 397 - } 398 - 399 - list_tracked_files() { 400 - local root="$1" 401 - git -C "$root" ls-files | sed '/^\.git\//d' 402 - } 403 - 404 - list_nested_worktree_prefixes() { 405 - local src="$1" 406 - local src_real 407 - src_real=$(cd "$src" && pwd -P) 408 - local entry 409 - while IFS= read -r entry; do 410 - [ -n "$entry" ] || continue 411 - local path head branch is_bare 412 - parse_worktree_entry "$entry" 413 - [ "$is_bare" = "0" ] || continue 414 - [ -d "$path" ] || continue 415 - local path_real 416 - path_real=$(cd "$path" && pwd -P) 417 - if [ "$path_real" = "$src_real" ]; then 418 - continue 419 - fi 420 - case "$path_real" in 421 - "$src_real"/*) printf '%s\n' "${path_real#"$src_real"/}" ;; 422 - esac 423 - done < <(worktree_entries "$src") 424 - } 425 - 426 - filter_nested_worktree_prefixes() { 427 - local files="$1" 428 - local prefixes="$2" 429 - if [ -z "$prefixes" ]; then 430 - printf '%s' "$files" 431 - return 432 - fi 433 - local out="" file prefix skip file_trim prefix_trim 434 - while IFS= read -r file; do 435 - [ -n "$file" ] || continue 436 - file_trim="${file%/}" 437 - skip=0 438 - while IFS= read -r prefix; do 439 - [ -n "$prefix" ] || continue 440 - prefix_trim="${prefix%/}" 441 - case "$file_trim" in 442 - "$prefix_trim" | "$prefix_trim"/*) 443 - skip=1 444 - break 445 - ;; 446 - esac 447 - case "$prefix_trim" in 448 - "$file_trim" | "$file_trim"/*) 449 - skip=1 450 - break 451 - ;; 452 - esac 453 - done <<< "$prefixes" 454 - [ "$skip" -eq 1 ] && continue 455 - out+="$file"$'\n' 456 - done <<< "$files" 457 - printf '%s' "$out" 458 - } 459 - 460 - prune_nested_copy_paths() { 461 - local files="$1" 462 - local out="" dirs="" file file_trim dir skip 463 - while IFS= read -r file; do 464 - [ -n "$file" ] || continue 465 - file_trim="${file%/}" 466 - skip=0 467 - while IFS= read -r dir; do 468 - [ -n "$dir" ] || continue 469 - case "$file_trim" in 470 - "$dir"/*) 471 - skip=1 472 - break 473 - ;; 474 - esac 475 - done <<< "$dirs" 476 - [ "$skip" -eq 1 ] && continue 477 - out+="$file"$'\n' 478 - if [ "$file" != "$file_trim" ]; then 479 - dirs+="$file_trim"$'\n' 480 - fi 481 - done <<< "$files" 482 - printf '%s' "$out" 483 - } 484 - 485 - collect_copy_files() { 486 - local src="$1" 487 - local copyignored="$2" 488 - local copyuntracked="$3" 489 - local copymodified="$4" 490 - local copytracked="$5" 491 - local sources=0 492 - [ "$copyignored" -eq 1 ] && sources=$((sources + 1)) 493 - [ "$copyuntracked" -eq 1 ] && sources=$((sources + 1)) 494 - [ "$copymodified" -eq 1 ] && sources=$((sources + 1)) 495 - [ "$copytracked" -eq 1 ] && sources=$((sources + 1)) 496 - local prefixes 497 - prefixes=$(list_nested_worktree_prefixes "$src") 498 - if [ "$sources" -eq 1 ]; then 499 - if [ "$copyignored" -eq 1 ]; then 500 - local ignored 501 - ignored=$(filter_nested_worktree_prefixes "$(list_ignored_files "$src")" "$prefixes") 502 - ignored=$(printf '%s' "$ignored" | sed '/^$/d' | LC_ALL=C sort -u) 503 - prune_nested_copy_paths "$ignored" 504 - return 505 - fi 506 - if [ "$copyuntracked" -eq 1 ]; then 507 - filter_nested_worktree_prefixes "$(list_untracked_files "$src")" "$prefixes" 508 - return 509 - fi 510 - if [ "$copymodified" -eq 1 ]; then 511 - filter_nested_worktree_prefixes "$(list_modified_files "$src")" "$prefixes" 512 - return 513 - fi 514 - filter_nested_worktree_prefixes "$(list_tracked_files "$src")" "$prefixes" 515 - return 516 - fi 517 - local files="" 518 - if [ "$copyignored" -eq 1 ]; then 519 - files+=$(list_ignored_files "$src")$'\n' 520 - fi 521 - if [ "$copyuntracked" -eq 1 ]; then 522 - files+=$(list_untracked_files "$src")$'\n' 523 - fi 524 - if [ "$copymodified" -eq 1 ]; then 525 - files+=$(list_modified_files "$src")$'\n' 526 - fi 527 - if [ "$copytracked" -eq 1 ]; then 528 - files+=$(list_tracked_files "$src")$'\n' 529 - fi 530 - local deduped 531 - deduped=$(printf '%s' "$files" | sed '/^$/d' | LC_ALL=C sort -u) 532 - deduped=$(filter_nested_worktree_prefixes "$deduped" "$prefixes") 533 - prune_nested_copy_paths "$deduped" 534 - } 535 - 536 - reflink_copy_file() { 537 - local src="$1" 538 - local dst="$2" 539 - case "$(uname)" in 540 - Darwin) 541 - cp -c "$src" "$dst" 2>/dev/null || cp "$src" "$dst" 2>/dev/null || true 542 - ;; 543 - *) 544 - cp --reflink=auto "$src" "$dst" 2>/dev/null || cp "$src" "$dst" 2>/dev/null || true 545 - ;; 546 - esac 547 - } 548 - 549 - reflink_copy_tree() { 550 - local src="$1" 551 - local dst="$2" 552 - mkdir -p "$dst" 553 - case "$(uname)" in 554 - Darwin) 555 - cp -cRP "$src/." "$dst" 2>/dev/null || cp -RP "$src/." "$dst" 2>/dev/null || true 556 - ;; 557 - *) 558 - cp -a --reflink=auto "$src/." "$dst" 2>/dev/null || cp -a "$src/." "$dst" 2>/dev/null || true 559 - ;; 560 - esac 561 - } 562 - 563 - reflink_copy_tree_no_overwrite() { 564 - local src="$1" 565 - local dst="$2" 566 - mkdir -p "$dst" 567 - case "$(uname)" in 568 - Darwin) 569 - cp -cnRP "$src/." "$dst" 2>/dev/null || cp -nRP "$src/." "$dst" 2>/dev/null || true 570 - ;; 571 - *) 572 - cp -a -n --reflink=auto "$src/." "$dst" 2>/dev/null || cp -a -n "$src/." "$dst" 2>/dev/null || true 573 - ;; 574 - esac 575 - } 576 - 577 - reflink_copy_dir_new() { 578 - local src="$1" 579 - local dst="$2" 580 - local src_dir="${src%/}" 581 - local parent 582 - parent=$(dirname "$dst") 583 - mkdir -p "$parent" 584 - case "$(uname)" in 585 - Darwin) 586 - cp -cRP "$src_dir" "$parent" 2>/dev/null || cp -RP "$src_dir" "$parent" 2>/dev/null || true 587 - ;; 588 - *) 589 - cp -a --reflink=auto "$src_dir" "$parent" 2>/dev/null || cp -a "$src_dir" "$parent" 2>/dev/null || true 590 - ;; 591 - esac 592 - } 593 - 594 - remove_path() { 595 - local path="$1" 596 - if [ -L "$path" ] || [ -f "$path" ]; then 597 - rm -f "$path" 2>/dev/null || true 598 - return 599 - fi 600 - if [ -d "$path" ]; then 601 - rm -rf "$path" 2>/dev/null || true 602 - fi 603 - } 604 - 605 - copy_symlink() { 606 - local src="$1" 607 - local dst="$2" 608 - local force="${3:-0}" 609 - local target 610 - target=$(readlink "$src" 2>/dev/null) || return 0 611 - if [ -e "$dst" ] || [ -L "$dst" ]; then 612 - if [ "$force" -eq 1 ]; then 613 - remove_path "$dst" 614 - else 615 - return 0 616 - fi 617 - fi 618 - mkdir -p "$(dirname "$dst")" 619 - ln -s "$target" "$dst" 2>/dev/null || true 620 - } 621 - 622 - copy_file() { 623 - local src="$1" 624 - local dst="$2" 625 - local force="${3:-0}" 626 - if [ -e "$dst" ] || [ -L "$dst" ]; then 627 - if [ "$force" -eq 1 ]; then 628 - remove_path "$dst" 629 - else 630 - return 0 631 - fi 632 - fi 633 - mkdir -p "$(dirname "$dst")" 634 - reflink_copy_file "$src" "$dst" 635 - } 636 - 637 - copy_dir_recursive() { 638 - local src_dir="$1" 639 - local dst_dir="$2" 640 - local force="${3:-0}" 641 - local dst_exists=0 642 - if [ -e "$dst_dir" ] || [ -L "$dst_dir" ]; then 643 - dst_exists=1 644 - if [ -d "$dst_dir" ] && [ ! -L "$dst_dir" ]; then 645 - : 646 - elif [ "$force" -eq 1 ]; then 647 - remove_path "$dst_dir" 648 - dst_exists=0 649 - else 650 - return 0 651 - fi 652 - fi 653 - if [ "$dst_exists" -eq 0 ]; then 654 - reflink_copy_dir_new "$src_dir" "$dst_dir" 655 - elif [ "$force" -eq 1 ]; then 656 - reflink_copy_tree "$src_dir" "$dst_dir" 657 - else 658 - reflink_copy_tree_no_overwrite "$src_dir" "$dst_dir" 659 - fi 660 - } 661 - 662 - copy_tree() { 663 - local src_dir="$1" 664 - local dst_dir="$2" 665 - local force="${3:-0}" 666 - copy_dir_recursive "$src_dir" "$dst_dir" "$force" 667 - } 668 - 669 - copy_path() { 670 - local src="$1" 671 - local dst="$2" 672 - local force="${3:-0}" 673 - if [ -L "$src" ]; then 674 - copy_symlink "$src" "$dst" "$force" 675 - elif [ -d "$src" ]; then 676 - copy_tree "$src" "$dst" "$force" 677 - elif [ -f "$src" ]; then 678 - copy_file "$src" "$dst" "$force" 679 - fi 680 - } 681 - 682 - copy_entry() { 683 - local entry="$1" src="$2" dst="$3" force="${4:-0}" verbose="${5:-0}" 684 - [ -n "$entry" ] || return 0 685 - local entry_trim="${entry%/}" 686 - local srcfile="$src/$entry_trim" 687 - local dstfile="$dst/$entry_trim" 688 - [ -e "$srcfile" ] || [ -L "$srcfile" ] || return 0 689 - [ "$verbose" -eq 1 ] && printf '%s\n' "$entry_trim" >&2 690 - copy_path "$srcfile" "$dstfile" "$force" 691 - } 692 - 693 - copy_entries_parallel() { 694 - local src="$1" dst="$2" files="$3" force="${4:-0}" verbose="${5:-0}" 695 - local jobs 696 - jobs=$(detect_jobs) 697 - local tmpfile 698 - tmpfile=$(mktemp) 699 - printf '%s' "$files" | sed '/^$/d' > "$tmpfile" 700 - export -f copy_entry copy_path copy_symlink copy_tree copy_dir_recursive \ 701 - copy_file reflink_copy_file reflink_copy_tree reflink_copy_tree_no_overwrite \ 702 - reflink_copy_dir_new remove_path 703 - xargs -P "$jobs" -I{} bash -c 'copy_entry "$@"' _ {} "$src" "$dst" "$force" "$verbose" < "$tmpfile" 704 - rm -f "$tmpfile" 705 - } 706 - 707 - copy_files_to_worktree() { 708 - local src="$1" 709 - local dst="$2" 710 - local copyignored="$3" 711 - local copyuntracked="$4" 712 - local copymodified="$5" 713 - local copytracked="$6" 714 - local force="${7:-0}" 715 - local verbose="${8:-0}" 716 - local files 717 - files=$(collect_copy_files "$src" "$copyignored" "$copyuntracked" "$copymodified" "$copytracked") 718 - copy_entries_parallel "$src" "$dst" "$files" "$force" "$verbose" 719 - } 720 - 721 - copy_files_between() { 722 - local src="$1" 723 - local dst="$2" 724 - local copyignored="$3" 725 - local copyuntracked="$4" 726 - local copymodified="$5" 727 - local copytracked="$6" 728 - local dryrun="${7:-0}" 729 - local verbose="${8:-0}" 730 - local force="${9:-0}" 731 - if [ "$src" = "$dst" ] || { [ -d "$src" ] && [ -d "$dst" ] && [ "$src" -ef "$dst" ]; }; then 732 - return 0 733 - fi 734 - if [ "$dryrun" -eq 1 ] && [ "$verbose" -eq 0 ]; then 735 - collect_copy_files "$src" "$copyignored" "$copyuntracked" "$copymodified" "$copytracked" 736 - return 0 737 - fi 738 - local files 739 - files=$(collect_copy_files "$src" "$copyignored" "$copyuntracked" "$copymodified" "$copytracked") 740 - if [ "$dryrun" -eq 1 ]; then 741 - local file 742 - while IFS= read -r file; do 743 - [ -z "$file" ] && continue 744 - local srcfile="$src/$file" 745 - if [ ! -e "$srcfile" ] && [ ! -L "$srcfile" ]; then 746 - continue 747 - fi 748 - [ "$verbose" -eq 1 ] && printf '%s\n' "$file" >&2 749 - printf '%s\n' "$file" 750 - done <<< "$files" 751 - return 0 752 - fi 753 - copy_entries_parallel "$src" "$dst" "$files" "$force" "$verbose" 754 - } 755 - 756 - open_path() { 757 - local branch="$1" 758 - local from="$2" 759 - local base_override="$3" 760 - local path_override="$4" 761 - local fetch="$5" 762 - local copyignored="${6:-0}" 763 - local copyuntracked="${7:-0}" 764 - local copymodified="${8:-0}" 765 - local copytracked="${9:-0}" 766 - local force="${10:-0}" 767 - local verbose="${11:-0}" 768 - require_repo 769 - local root base path copy_src 770 - root=$(main_worktree_path) 771 - copy_src="$root" 772 - if [ "$copyignored" -eq 1 ] || [ "$copyuntracked" -eq 1 ] || [ "$copymodified" -eq 1 ] || [ "$copytracked" -eq 1 ]; then 773 - copy_src=$(copy_source_path "$root") 774 - fi 775 - base=$(base_dir "$root" "$base_override") 776 - if [ -n "$path_override" ]; then 777 - path=$(resolve_path "$root" "$path_override") 778 - else 779 - path=$(resolve_path "$root" "$base/$branch") 780 - fi 781 - local existing 782 - if existing=$(find_worktree_by_branch "$root" "$branch"); then 783 - printf '%s\n' "$existing" 784 - return 0 785 - fi 786 - if git -C "$root" show-ref --verify --quiet "refs/heads/$branch"; then 787 - if [ -z "$path_override" ]; then 788 - die "branch exists without worktree: '$branch'" 789 - fi 790 - if find_worktree_by_path "$root" "$path" >/dev/null; then 791 - die "worktree path already in use: '$path'" 792 - fi 793 - mkdir -p "$(dirname "$path")" 794 - git -C "$root" worktree add "$path" "$branch" >/dev/null 795 - copy_files_to_worktree "$copy_src" "$path" "$copyignored" "$copyuntracked" "$copymodified" "$copytracked" "$force" "$verbose" 796 - printf '%s\n' "$path" 797 - return 0 798 - fi 799 - if [ "$fetch" -eq 1 ]; then 800 - git -C "$root" fetch 801 - fi 802 - if [ -z "$from" ]; then 803 - from=$(git -C "$root" rev-parse HEAD) 804 - fi 805 - if ! git -C "$root" rev-parse --verify "${from}^{commit}" >/dev/null; then 806 - die "invalid --from ref '$from'" 807 - fi 808 - mkdir -p "$(dirname "$path")" 809 - git -C "$root" worktree add -b "$branch" "$path" "$from" >/dev/null 810 - copy_files_to_worktree "$copy_src" "$path" "$copyignored" "$copyuntracked" "$copymodified" "$copytracked" "$force" "$verbose" 811 - printf '%s\n' "$path" 812 - } 813 - 814 - cmd_switch() { 815 - local from="" path_override="" fetch=0 branch="" 816 - local copyignored=0 copyuntracked=0 copymodified=0 copytracked=0 force=0 verbose=0 817 - while [ "$#" -gt 0 ]; do 818 - case "$1" in 819 - -h | --help) 820 - switch_usage 821 - exit 0 822 - ;; 823 - --from) 824 - [ -n "${2:-}" ] || die "missing value for --from" 825 - from="$2" 826 - shift 827 - ;; 828 - --from=*) from="${1#--from=}" ;; 829 - --path) 830 - [ -n "${2:-}" ] || die "missing value for --path" 831 - path_override="$2" 832 - shift 833 - ;; 834 - --path=*) path_override="${1#--path=}" ;; 835 - --fetch) fetch=1 ;; 836 - --copy-all) copyignored=1; copyuntracked=1; copymodified=1; copytracked=1 ;; 837 - --copy-ignored) copyignored=1 ;; 838 - --copy-untracked) copyuntracked=1 ;; 839 - --copy-modified) copymodified=1 ;; 840 - -f | --force) force=1 ;; 841 - -v | --verbose) verbose=1 ;; 842 - --*) die "unknown flag '$1'" ;; 843 - *) if [ -z "$branch" ]; then branch="$1"; else die "unexpected argument '$1'"; fi ;; 844 - esac 845 - shift 846 - done 847 - [ -n "$branch" ] || die "missing branch" 848 - open_path "$branch" "$from" "$BASE_DIR_OVERRIDE" "$path_override" "$fetch" "$copyignored" "$copyuntracked" "$copymodified" "$copytracked" "$force" "$verbose" 849 - } 850 - 851 - cmd_exec() { 852 - local from="" path_override="" fetch=0 branch="" 853 - while [ "$#" -gt 0 ]; do 854 - case "$1" in 855 - -h | --help) 856 - exec_usage 857 - exit 0 858 - ;; 859 - --from) 860 - [ -n "${2:-}" ] || die "missing value for --from" 861 - from="$2" 862 - shift 863 - ;; 864 - --from=*) from="${1#--from=}" ;; 865 - --path) 866 - [ -n "${2:-}" ] || die "missing value for --path" 867 - path_override="$2" 868 - shift 869 - ;; 870 - --path=*) path_override="${1#--path=}" ;; 871 - --fetch) fetch=1 ;; 872 - --) shift; break ;; 873 - --*) die "unknown flag '$1'" ;; 874 - *) 875 - if [ -z "$branch" ]; then 876 - branch="$1" 877 - else 878 - die "missing -- before command" 879 - fi 880 - ;; 881 - esac 882 - shift 883 - done 884 - [ -n "$branch" ] || die "missing branch" 885 - [ "$#" -gt 0 ] || die "missing command" 886 - local path 887 - path=$(open_path "$branch" "$from" "$BASE_DIR_OVERRIDE" "$path_override" "$fetch") 888 - cd "$path" 889 - exec "$@" 890 - } 891 - 892 - cmd_sync() { 893 - local copyignored=0 copyuntracked=0 copymodified=0 copytracked=0 dryrun=0 verbose=0 force=0 894 - local from_branch="" to_branch="" 895 - while [ "$#" -gt 0 ]; do 896 - case "$1" in 897 - -h | --help) 898 - sync_usage 899 - exit 0 900 - ;; 901 - --copy-all) copyignored=1; copyuntracked=1; copymodified=1; copytracked=1 ;; 902 - --copy-ignored) copyignored=1 ;; 903 - --copy-untracked) copyuntracked=1 ;; 904 - --copy-modified) copymodified=1 ;; 905 - -f | --force) force=1 ;; 906 - -v | --verbose) verbose=1 ;; 907 - -n | --dry-run) dryrun=1 ;; 908 - --) 909 - shift 910 - break 911 - ;; 912 - --*) die "unknown flag '$1'" ;; 913 - *) 914 - if [ -z "$from_branch" ]; then 915 - from_branch="$1" 916 - elif [ -z "$to_branch" ]; then 917 - to_branch="$1" 918 - else 919 - die "unexpected argument '$1'" 920 - fi 921 - ;; 922 - esac 923 - shift 924 - done 925 - for arg in "$@"; do 926 - if [ -z "$from_branch" ]; then 927 - from_branch="$arg" 928 - elif [ -z "$to_branch" ]; then 929 - to_branch="$arg" 930 - else 931 - die "unexpected argument '$arg'" 932 - fi 933 - done 934 - if [ -n "$from_branch" ] && [ -z "$to_branch" ]; then 935 - die "missing destination branch" 936 - fi 937 - if [ -z "$from_branch" ] && [ -n "$to_branch" ]; then 938 - die "missing source branch" 939 - fi 940 - require_repo 941 - if [ "$copyignored" -eq 0 ] && [ "$copyuntracked" -eq 0 ] && [ "$copymodified" -eq 0 ] && [ "$copytracked" -eq 0 ]; then 942 - die "missing copy flag" 943 - fi 944 - local root src dst 945 - root=$(main_worktree_path) 946 - if [ -z "$from_branch" ]; then 947 - src=$(copy_source_path "$root") 948 - if dst=$(git rev-parse --show-toplevel 2>/dev/null); then 949 - find_worktree_by_path "$root" "$dst" >/dev/null || die "current directory is not a worktree" 950 - else 951 - dst="$src" 952 - fi 953 - else 954 - if ! src=$(find_worktree_by_branch "$root" "$from_branch"); then 955 - die "workspace not found: '$from_branch'" 956 - fi 957 - if ! dst=$(find_worktree_by_branch "$root" "$to_branch"); then 958 - die "workspace not found: '$to_branch'" 959 - fi 960 - fi 961 - copy_files_between "$src" "$dst" "$copyignored" "$copyuntracked" "$copymodified" "$copytracked" "$dryrun" "$verbose" "$force" 962 - } 963 - 964 - cmd_ls() { 965 - local plain=0 json=0 966 - while [ "$#" -gt 0 ]; do 967 - case "$1" in 968 - -h | --help) 969 - ls_usage 970 - exit 0 971 - ;; 972 - --plain) plain=1 ;; 973 - --json) json=1 ;; 974 - --*) die "unknown flag '$1'" ;; 975 - *) die "unexpected argument '$1'" ;; 976 - esac 977 - shift 978 - done 979 - require_repo 980 - local root base 981 - root=$(main_worktree_path) 982 - if [ "$plain" -eq 1 ] && [ "$json" -eq 1 ]; then 983 - die "cannot use --plain and --json together" 984 - fi 985 - local mode="table" 986 - if [ "$plain" -eq 1 ]; then 987 - mode="plain" 988 - elif [ "$json" -eq 1 ]; then 989 - mode="json" 990 - elif [ ! -t 1 ]; then 991 - mode="plain" 992 - fi 993 - local entries 994 - entries=$(worktree_entries "$root") 995 - local filtered="" 996 - if [ -n "$BASE_DIR_OVERRIDE" ]; then 997 - base=$(base_dir "$root" "$BASE_DIR_OVERRIDE") 998 - while IFS= read -r entry; do 999 - [ -n "$entry" ] || continue 1000 - local path head branch is_bare 1001 - parse_worktree_entry "$entry" 1002 - if [ "$is_bare" = "1" ]; then 1003 - filtered+="(bare)\t$path\t$head\t1\n" 1004 - continue 1005 - fi 1006 - case "$path" in 1007 - "$base"/*) 1008 - filtered+="$branch\t$path\t$head\t0\n" 1009 - ;; 1010 - esac 1011 - done <<<"$entries" 1012 - else 1013 - while IFS= read -r entry; do 1014 - [ -n "$entry" ] || continue 1015 - local path head branch is_bare 1016 - parse_worktree_entry "$entry" 1017 - if [ "$is_bare" = "1" ]; then 1018 - branch="(bare)" 1019 - fi 1020 - filtered+="$branch\t$path\t$head\t$is_bare\n" 1021 - done <<<"$entries" 1022 - fi 1023 - case "$mode" in 1024 - json) 1025 - printf '[' 1026 - local first=1 1027 - while IFS= read -r line || [ -n "$line" ]; do 1028 - [ -n "$line" ] || continue 1029 - local branch path head rest is_bare is_bare_json 1030 - branch=${line%%$'\t'*} 1031 - rest=${line#*$'\t'} 1032 - path=${rest%%$'\t'*} 1033 - rest=${rest#*$'\t'} 1034 - head=${rest%%$'\t'*} 1035 - is_bare=${rest#*$'\t'} 1036 - if [ "$is_bare" = "1" ]; then 1037 - is_bare_json="true" 1038 - else 1039 - is_bare_json="false" 1040 - fi 1041 - if [ "$first" -eq 0 ]; then 1042 - printf ',' 1043 - fi 1044 - first=0 1045 - printf '{"branch":"%s","path":"%s","head":"%s","is_bare":%s}' "$(json_escape "$branch")" "$(json_escape "$path")" "$(json_escape "$head")" "$is_bare_json" 1046 - done <<<"$(printf '%b' "$filtered")" 1047 - printf ']\n' 1048 - ;; 1049 - plain) 1050 - printf '%b' "$filtered" | awk -F'\t' 'NF>=2 {print $1"\t"$2}' 1051 - ;; 1052 - table) 1053 - printf '%b' "$filtered" | awk -F'\t' 'NF>=3 {printf "%-30s %-60s %s\n", $1, $2, $3}' 1054 - ;; 1055 - esac 1056 - } 1057 - 1058 - cmd_rm() { 1059 - local force=0 branch="" 1060 - while [ "$#" -gt 0 ]; do 1061 - case "$1" in 1062 - -h | --help) 1063 - rm_usage 1064 - exit 0 1065 - ;; 1066 - -f | --force) force=1 ;; 1067 - --*) die "unknown flag '$1'" ;; 1068 - *) if [ -z "$branch" ]; then branch="$1"; else die "unexpected argument '$1'"; fi ;; 1069 - esac 1070 - shift 1071 - done 1072 - [ -n "$branch" ] || die "missing branch" 1073 - require_repo 1074 - local root path 1075 - root=$(main_worktree_path) 1076 - if ! path=$(find_worktree_by_branch "$root" "$branch"); then 1077 - if [ "$force" -eq 1 ]; then 1078 - exit 0 1079 - fi 1080 - die "workspace not found: '$branch'" 1081 - fi 1082 - if [ "$path" = "$root" ]; then 1083 - die "cannot remove main worktree" 1084 - fi 1085 - if [ "$force" -eq 0 ]; then 1086 - local dirty 1087 - dirty=$(git -C "$path" status --porcelain) 1088 - if [ -n "$dirty" ]; then 1089 - if [ -t 0 ]; then 1090 - printf "workspace '%s' has uncommitted changes. remove anyway? [y/N] " "$branch" >&2 1091 - read -r answer 1092 - case "$answer" in 1093 - y | Y | yes | YES) force=1 ;; 1094 - *) exit 1 ;; 1095 - esac 1096 - else 1097 - die "workspace dirty: '$branch'" 1098 - fi 1099 - fi 1100 - fi 1101 - if [ "$force" -eq 1 ]; then 1102 - git -C "$root" worktree remove --force "$path" 2>/dev/null || true 1103 - command rm -rf "$path" 2>/dev/null || true 1104 - else 1105 - git -C "$root" worktree remove "$path" 1106 - fi 1107 - if [ "$force" -eq 1 ]; then 1108 - git -C "$root" branch -D "$branch" 2>/dev/null || true 1109 - else 1110 - git -C "$root" branch -d "$branch" 1111 - fi 1112 - printf '%s\n' "$path" 1113 - } 1114 - 1115 - cmd_merge() { 1116 - local target="" squash=1 rebase=1 commit=1 1117 - while [ "$#" -gt 0 ]; do 1118 - case "$1" in 1119 - -h|--help) merge_usage; exit 0 ;; 1120 - --no-squash) squash=0 ;; 1121 - --no-rebase) rebase=0 ;; 1122 - --no-commit) commit=0 ;; 1123 - --*) die "unknown flag '$1'" ;; 1124 - *) if [ -z "$target" ]; then target="$1"; else die "unexpected argument '$1'"; fi ;; 1125 - esac 1126 - shift 1127 - done 1128 - 1129 - if [ -z "$target" ]; then 1130 - merge_usage 1131 - exit 1 1132 - fi 1133 - 1134 - require_repo 1135 - local root branch 1136 - root=$(main_worktree_path) 1137 - branch=$(git symbolic-ref --short HEAD 2>/dev/null) || die "not on a branch (detached HEAD)" 1138 - 1139 - git show-ref --verify --quiet "refs/heads/$target" || die "target branch '$target' not found" 1140 - 1141 - [ "$branch" != "$target" ] || die "cannot merge branch into itself" 1142 - 1143 - if [ "$commit" -eq 0 ]; then 1144 - if [ -n "$(git status --porcelain)" ]; then 1145 - die "uncommitted changes present (use without --no-commit)" 1146 - fi 1147 - fi 1148 - 1149 - if [ "$commit" -eq 1 ] && [ -n "$(git status --porcelain)" ]; then 1150 - git add -A 1151 - git commit -m "WIP: uncommitted changes" || die "failed to commit changes" 1152 - printf 'committed uncommitted changes\n' >&2 1153 - fi 1154 - 1155 - local merge_base 1156 - merge_base=$(git merge-base HEAD "$target") || die "no common ancestor with '$target'" 1157 - 1158 - if [ "$squash" -eq 1 ]; then 1159 - local commit_count 1160 - commit_count=$(git rev-list --count "$merge_base..HEAD") 1161 - if [ "$commit_count" -gt 1 ]; then 1162 - local subjects 1163 - subjects=$(git log --format='- %s' "$merge_base..HEAD") 1164 - git reset --soft "$merge_base" || die "failed to reset to merge base" 1165 - git commit -m "$(printf 'Squash %d commits\n\n%s' "$commit_count" "$subjects")" || die "failed to create squash commit" 1166 - printf 'squashed %d commits\n' "$commit_count" >&2 1167 - elif [ "$commit_count" -eq 1 ]; then 1168 - printf 'single commit, nothing to squash\n' >&2 1169 - else 1170 - printf 'no commits ahead of %s\n' "$target" >&2 1171 - fi 1172 - fi 1173 - 1174 - if [ "$rebase" -eq 1 ]; then 1175 - if ! git merge-base --is-ancestor "$target" HEAD; then 1176 - git rebase "$target" || die "rebase failed (resolve conflicts and run: git rebase --continue)" 1177 - printf 'rebased onto %s\n' "$target" >&2 1178 - else 1179 - printf 'already rebased onto %s\n' "$target" >&2 1180 - fi 1181 - else 1182 - if ! git merge-base --is-ancestor "$target" HEAD; then 1183 - die "not rebased onto '$target' (run without --no-rebase)" 1184 - fi 1185 - fi 1186 - 1187 - if ! git merge-base --is-ancestor "$target" HEAD; then 1188 - die "cannot fast-forward: '$target' has commits not in HEAD" 1189 - fi 1190 - 1191 - local head_sha 1192 - head_sha=$(git rev-parse HEAD) 1193 - git update-ref "refs/heads/$target" "$head_sha" || die "failed to fast-forward '$target'" 1194 - printf 'merged to %s @ %s\n' "$target" "$(git rev-parse --short HEAD)" >&2 1195 - } 1196 - 1197 - cmd_here() { 1198 - require_repo 1199 - local root base current 1200 - root=$(main_worktree_path) 1201 - base=$(base_dir "$root" "$BASE_DIR_OVERRIDE") 1202 - current=$(git rev-parse --show-toplevel) 1203 - case "$current" in 1204 - "$base"/*) ;; 1205 - *) exit 1 ;; 1206 - esac 1207 - local entry 1208 - while IFS= read -r entry; do 1209 - local path head branch is_bare 1210 - parse_worktree_entry "$entry" 1211 - if [ "$path" = "$current" ]; then 1212 - if [ -n "$branch" ]; then 1213 - printf '%s\n' "$branch" 1214 - exit 0 1215 - fi 1216 - fi 1217 - done < <(worktree_entries "$root") 1218 - exit 1 1219 - } 1220 - 1221 - cmd_base() { 1222 - require_repo 1223 - local root base 1224 - root=$(main_worktree_path) 1225 - base=$(base_dir "$root" "$BASE_DIR_OVERRIDE") 1226 - printf '%s\n' "$base" 1227 - } 1228 - 1229 - cmd_root() { 1230 - require_repo 1231 - local common 1232 - common=$(common_git_dir_abs) 1233 - if repo_is_bare "$common"; then 1234 - printf '%s\n' "$common" 1235 - return 1236 - fi 1237 - main_worktree_path 1238 - } 1239 - 1240 - completion_bash() { 1241 - local commands 1242 - commands="switch sw exec sync ls rm merge here base root help completion" 1243 - cat <<EOF 1244 - command -v wt >/dev/null 2>&1 || return 0 1245 - 1246 - wt() { 1247 - case "\$1" in 1248 - switch|sw) 1249 - local i 1250 - for i in "\$@"; do 1251 - case "\$i" in 1252 - -h|--help) 1253 - command wt "\$@" 1254 - return \$? 1255 - ;; 1256 - esac 1257 - done 1258 - local out path 1259 - out=\$(command wt "\$@") 1260 - local rc=\$? 1261 - path=\$(printf '%s\n' "\$out" | tail -n 1) 1262 - if [[ \$rc -eq 0 && -n "\$path" && -d "\$path" ]]; then 1263 - if [[ "\$out" != "\$path" ]]; then 1264 - printf '%s\n' "\$out" | sed '\$d' 1265 - fi 1266 - cd "\$path" 1267 - else 1268 - [[ -n "\$out" ]] && printf '%s\n' "\$out" 1269 - return \$rc 1270 - fi 1271 - ;; 1272 - *) 1273 - command wt "\$@" 1274 - ;; 1275 - esac 1276 - } 1277 - 1278 - __wt_branches() { 1279 - git rev-parse --git-dir >/dev/null 2>&1 || return 0 1280 - git for-each-ref --format='%(refname:short)' refs/heads 2>/dev/null 1281 - } 1282 - 1283 - _wt_complete() { 1284 - local cur prev cmd 1285 - cur="\${COMP_WORDS[COMP_CWORD]}" 1286 - prev="\${COMP_WORDS[COMP_CWORD-1]}" 1287 - cmd="" 1288 - compopt +o default +o bashdefault 2>/dev/null 1289 - local commands="$commands" 1290 - local flags="" 1291 - local i 1292 - local global_flags="--base-dir -h --help" 1293 - for i in "\${COMP_WORDS[@]}"; do 1294 - if [ "\$i" = "--" ]; then 1295 - return 0 1296 - fi 1297 - done 1298 - if [ "\$prev" = "--base-dir" ]; then 1299 - return 0 1300 - fi 1301 - i=1 1302 - while [ "\$i" -lt "\$COMP_CWORD" ]; do 1303 - local word="\${COMP_WORDS[\$i]}" 1304 - case "\$word" in 1305 - --base-dir) 1306 - i=\$((i + 2)) 1307 - ;; 1308 - --base-dir=*) 1309 - i=\$((i + 1)) 1310 - ;; 1311 - -h|--help) 1312 - i=\$((i + 1)) 1313 - ;; 1314 - --*) 1315 - i=\$((i + 1)) 1316 - ;; 1317 - *) 1318 - cmd="\$word" 1319 - break 1320 - ;; 1321 - esac 1322 - done 1323 - if [ -z "\$cmd" ]; then 1324 - COMPREPLY=( \$(compgen -W "\$global_flags \$commands" -- "\$cur") ) 1325 - return 0 1326 - fi 1327 - case "\$cmd" in 1328 - switch|sw) 1329 - flags="--from --path --fetch --copy-all --copy-ignored --copy-untracked --copy-modified -f --force -v --verbose -h --help" 1330 - case "\$prev" in 1331 - --from|--path) return 0 ;; 1332 - esac 1333 - COMPREPLY=( \$(compgen -W "\$flags \$(__wt_branches)" -- "\$cur") ) 1334 - ;; 1335 - exec) 1336 - flags="--from --path --fetch -h --help --" 1337 - case "\$prev" in 1338 - --from|--path|--) return 0 ;; 1339 - esac 1340 - if [ "\$COMP_CWORD" -eq 2 ]; then 1341 - COMPREPLY=( \$(compgen -W "\$(__wt_branches)" -- "\$cur") ) 1342 - else 1343 - COMPREPLY=( \$(compgen -W "\$flags" -- "\$cur") ) 1344 - fi 1345 - ;; 1346 - sync) 1347 - flags="--copy-all --copy-ignored --copy-untracked --copy-modified -f --force -v --verbose -n --dry-run -h --help" 1348 - COMPREPLY=( \$(compgen -W "\$flags \$(__wt_branches)" -- "\$cur") ) 1349 - ;; 1350 - rm) 1351 - flags="-f --force -h --help" 1352 - COMPREPLY=( \$(compgen -W "\$flags \$(__wt_branches)" -- "\$cur") ) 1353 - ;; 1354 - merge) 1355 - flags="--no-squash --no-rebase --no-commit -h --help" 1356 - COMPREPLY=( \$(compgen -W "\$flags \$(__wt_branches)" -- "\$cur") ) 1357 - ;; 1358 - ls) 1359 - flags="--plain --json -h --help" 1360 - COMPREPLY=( \$(compgen -W "\$flags" -- "\$cur") ) 1361 - ;; 1362 - here|base|root) 1363 - flags="-h --help" 1364 - COMPREPLY=( \$(compgen -W "\$flags" -- "\$cur") ) 1365 - ;; 1366 - help) 1367 - flags="switch sw exec sync ls rm merge here base root help completion" 1368 - COMPREPLY=( \$(compgen -W "\$flags" -- "\$cur") ) 1369 - ;; 1370 - completion) 1371 - flags="bash zsh fish" 1372 - COMPREPLY=( \$(compgen -W "\$flags" -- "\$cur") ) 1373 - ;; 1374 - esac 1375 - } 1376 - 1377 - complete -F _wt_complete wt 1378 - EOF 1379 - } 1380 - 1381 - completion_zsh() { 1382 - cat <<EOF 1383 - command -v wt >/dev/null 2>&1 || return 0 1384 - 1385 - wt() { 1386 - case "\$1" in 1387 - switch|sw) 1388 - local i 1389 - for i in "\$@"; do 1390 - case "\$i" in 1391 - -h|--help) 1392 - command wt "\$@" 1393 - return \$? 1394 - ;; 1395 - esac 1396 - done 1397 - local out path 1398 - out=\$(command wt "\$@") 1399 - local rc=\$? 1400 - path=\$(printf '%s\n' "\$out" | tail -n 1) 1401 - if [[ \$rc -eq 0 && -n "\$path" && -d "\$path" ]]; then 1402 - if [[ "\$out" != "\$path" ]]; then 1403 - printf '%s\n' "\$out" | sed '\$d' 1404 - fi 1405 - cd "\$path" 1406 - else 1407 - [[ -n "\$out" ]] && printf '%s\n' "\$out" 1408 - return \$rc 1409 - fi 1410 - ;; 1411 - *) 1412 - command wt "\$@" 1413 - ;; 1414 - esac 1415 - } 1416 - 1417 - __wt_branches() { 1418 - git rev-parse --git-dir >/dev/null 2>&1 || return 0 1419 - git for-each-ref --format='%(refname:short)' refs/heads 2>/dev/null 1420 - } 1421 - 1422 - _wt() { 1423 - 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 1424 - subcmds=(switch sw exec sync ls rm merge here base root help completion) 1425 - subdescs=( 1426 - "create or open a workspace" 1427 - "alias for switch" 1428 - "open a workspace and run a command" 1429 - "copy files between worktrees" 1430 - "list workspaces" 1431 - "remove a workspace" 1432 - "squash, rebase, and merge to target" 1433 - "print current workspace branch" 1434 - "print base dir" 1435 - "print main worktree path" 1436 - "show help" 1437 - "print shell completion" 1438 - ) 1439 - global_flags=(--base-dir -h --help) 1440 - global_descs=("base dir override" "show help" "show help") 1441 - switch_flags=(--from --path --fetch --copy-all --copy-ignored --copy-untracked --copy-modified -f --force -v --verbose -h --help) 1442 - 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") 1443 - exec_flags=(--from --path --fetch -h --help --) 1444 - exec_descs=("base ref for new branch" "explicit worktree path" "fetch remotes before resolving --from" "show help" "show help" "end of flags") 1445 - sync_flags=(--copy-all --copy-ignored --copy-untracked --copy-modified -f --force -v --verbose -n --dry-run -h --help) 1446 - 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") 1447 - ls_flags=(--plain --json -h --help) 1448 - ls_descs=("tab-delimited output" "JSON output" "show help" "show help") 1449 - rm_flags=(-f --force -h --help) 1450 - rm_descs=("remove even if dirty" "remove even if dirty" "show help" "show help") 1451 - merge_flags=(--no-squash --no-rebase --no-commit -h --help) 1452 - merge_descs=("preserve individual commits" "skip rebase (fail if not rebased)" "skip committing uncommitted changes" "show help" "show help") 1453 - hb_flags=(-h --help) 1454 - hb_descs=("show help" "show help") 1455 - if (( CURRENT == 2 )); then 1456 - compadd -d global_descs -- \$global_flags 1457 - compadd -d subdescs -- \$subcmds 1458 - return 0 1459 - fi 1460 - local cmd="" 1461 - local idx=2 1462 - while (( idx <= CURRENT )); do 1463 - local word=\${words[idx]} 1464 - case "\$word" in 1465 - --base-dir) 1466 - idx=\$((idx + 2)) 1467 - ;; 1468 - --base-dir=*) 1469 - idx=\$((idx + 1)) 1470 - ;; 1471 - -h|--help) 1472 - idx=\$((idx + 1)) 1473 - ;; 1474 - --*) 1475 - idx=\$((idx + 1)) 1476 - ;; 1477 - *) 1478 - cmd=\$word 1479 - break 1480 - ;; 1481 - esac 1482 - done 1483 - if [[ "\${words[CURRENT-1]}" == --base-dir ]]; then 1484 - return 0 1485 - fi 1486 - if [[ -z "\$cmd" ]]; then 1487 - compadd -d global_descs -- \$global_flags 1488 - compadd -d subdescs -- \$subcmds 1489 - return 0 1490 - fi 1491 - case "\$cmd" in 1492 - switch|sw) 1493 - if [[ "\${words[CURRENT-1]}" == --from || "\${words[CURRENT-1]}" == --path ]]; then 1494 - return 0 1495 - fi 1496 - compadd -d switch_descs -- \$switch_flags 1497 - compadd -- \${(f)"\$(__wt_branches)"} 1498 - return 0 1499 - ;; 1500 - exec) 1501 - if [[ "\${words[CURRENT-1]}" == --from || "\${words[CURRENT-1]}" == --path || "\${words[CURRENT-1]}" == -- ]]; then 1502 - return 0 1503 - fi 1504 - if (( CURRENT == 3 )); then 1505 - compadd -- \${(f)"\$(__wt_branches)"} 1506 - else 1507 - compadd -d exec_descs -- \$exec_flags 1508 - fi 1509 - return 0 1510 - ;; 1511 - sync) 1512 - compadd -d sync_descs -- \$sync_flags 1513 - compadd -- \${(f)"\$(__wt_branches)"} 1514 - return 0 1515 - ;; 1516 - rm) 1517 - compadd -d rm_descs -- \$rm_flags 1518 - compadd -- \${(f)"\$(__wt_branches)"} 1519 - return 0 1520 - ;; 1521 - merge) 1522 - compadd -d merge_descs -- \$merge_flags 1523 - compadd -- \${(f)"\$(__wt_branches)"} 1524 - return 0 1525 - ;; 1526 - ls) 1527 - compadd -d ls_descs -- \$ls_flags 1528 - return 0 1529 - ;; 1530 - here|base|root) 1531 - compadd -d hb_descs -- \$hb_flags 1532 - return 0 1533 - ;; 1534 - help) 1535 - compadd -d subdescs -- \$subcmds 1536 - return 0 1537 - ;; 1538 - completion) 1539 - compadd -- bash zsh fish 1540 - return 0 1541 - ;; 1542 - esac 1543 - return 0 1544 - } 1545 - 1546 - compdef _wt wt 1547 - EOF 1548 - } 1549 - 1550 - completion_fish() { 1551 - local wt_bin="$0" 1552 - case "$wt_bin" in 1553 - */*) wt_bin="$(cd "$(dirname "$wt_bin")" && pwd -P)/$(basename "$wt_bin")" ;; 1554 - *) wt_bin="$(command -v "$wt_bin" 2>/dev/null || printf '%s' "$wt_bin")" ;; 1555 - esac 1556 - local wt_bin_escaped="${wt_bin//\'/\'\\\'\'}" 1557 - cat <<'EOF' 1558 - type -q wt; or return 1559 - 1560 - EOF 1561 - printf "set -g __wt_bin '%s'\n\n" "$wt_bin_escaped" 1562 - cat <<'EOF' 1563 - function wt 1564 - if test "$argv[1]" = "switch" -o "$argv[1]" = "sw" 1565 - if contains -- -h $argv; or contains -- --help $argv 1566 - command "$__wt_bin" $argv 1567 - return $status 1568 - end 1569 - set -g __wt_rc 0 1570 - set -l out (begin 1571 - command "$__wt_bin" $argv 1572 - set -g __wt_rc $status 1573 - end | string collect | string trim --right) 1574 - set -l rc $__wt_rc 1575 - set -e __wt_rc 1576 - set -l out_lines (string split -- "\n" $out) 1577 - set -l path $out_lines[-1] 1578 - if test $rc -eq 0 -a -n "$path" -a -d "$path" 1579 - if test (count $out_lines) -gt 1 1580 - printf '%s\n' $out_lines[1..-2] 1581 - end 1582 - cd "$path" 1583 - else 1584 - test -n "$out"; and printf '%s\n' "$out" 1585 - return $rc 1586 - end 1587 - else 1588 - command "$__wt_bin" $argv 1589 - end 1590 - end 1591 - 1592 - function __wt_branches 1593 - command git rev-parse --git-dir >/dev/null 2>&1 1594 - or return 1595 - command git for-each-ref --format='%(refname:short)' refs/heads 2>/dev/null 1596 - end 1597 - 1598 - complete -c wt -f 1599 - complete -c wt -l base-dir -r -d 'base dir override' 1600 - complete -c wt -s h -l help -d 'show help' 1601 - complete -c wt -n '__fish_use_subcommand' -a switch -d 'create or open a workspace' 1602 - complete -c wt -n '__fish_use_subcommand' -a sw -d 'alias for switch' 1603 - complete -c wt -n '__fish_use_subcommand' -a exec -d 'open a workspace and run a command' 1604 - complete -c wt -n '__fish_use_subcommand' -a sync -d 'copy files between worktrees' 1605 - complete -c wt -n '__fish_use_subcommand' -a ls -d 'list workspaces' 1606 - complete -c wt -n '__fish_use_subcommand' -a rm -d 'remove a workspace' 1607 - complete -c wt -n '__fish_use_subcommand' -a merge -d 'squash, rebase, and merge to target' 1608 - complete -c wt -n '__fish_use_subcommand' -a here -d 'print current workspace branch' 1609 - complete -c wt -n '__fish_use_subcommand' -a base -d 'print base dir' 1610 - complete -c wt -n '__fish_use_subcommand' -a root -d 'print main worktree path' 1611 - complete -c wt -n '__fish_use_subcommand' -a help -d 'show help' 1612 - complete -c wt -n '__fish_use_subcommand' -a completion -d 'print shell completion' 1613 - complete -c wt -n '__fish_seen_subcommand_from switch sw exec sync rm merge' -a '(__wt_branches)' 1614 - 1615 - complete -c wt -n '__fish_seen_subcommand_from switch sw exec' -l from -r -d 'base ref for new branch' 1616 - complete -c wt -n '__fish_seen_subcommand_from switch sw exec' -l path -r -d 'explicit worktree path' 1617 - complete -c wt -n '__fish_seen_subcommand_from switch sw exec' -l fetch -d 'fetch remotes before resolving --from' 1618 - complete -c wt -n '__fish_seen_subcommand_from switch sw exec' -s h -l help -d 'show help' 1619 - complete -c wt -n '__fish_seen_subcommand_from switch sw' -l copy-all -d 'copy all files to new worktree' 1620 - complete -c wt -n '__fish_seen_subcommand_from switch sw' -l copy-ignored -d 'copy gitignored files to new worktree' 1621 - complete -c wt -n '__fish_seen_subcommand_from switch sw' -l copy-untracked -d 'copy untracked files to new worktree' 1622 - complete -c wt -n '__fish_seen_subcommand_from switch sw' -l copy-modified -d 'copy modified files to new worktree' 1623 - complete -c wt -n '__fish_seen_subcommand_from switch sw' -s f -l force -d 'overwrite existing destination files' 1624 - complete -c wt -n '__fish_seen_subcommand_from switch sw' -s v -l verbose -d 'print copy progress' 1625 - complete -c wt -n '__fish_seen_subcommand_from sync' -l copy-all -d 'copy all files' 1626 - complete -c wt -n '__fish_seen_subcommand_from sync' -l copy-ignored -d 'copy gitignored files' 1627 - complete -c wt -n '__fish_seen_subcommand_from sync' -l copy-untracked -d 'copy untracked files' 1628 - complete -c wt -n '__fish_seen_subcommand_from sync' -l copy-modified -d 'copy modified files' 1629 - complete -c wt -n '__fish_seen_subcommand_from sync' -s f -l force -d 'overwrite existing destination files' 1630 - complete -c wt -n '__fish_seen_subcommand_from sync' -s v -l verbose -d 'print copy progress' 1631 - complete -c wt -n '__fish_seen_subcommand_from sync' -s n -l dry-run -d 'show files that would be copied' 1632 - complete -c wt -n '__fish_seen_subcommand_from sync' -s h -l help -d 'show help' 1633 - 1634 - complete -c wt -n '__fish_seen_subcommand_from ls' -l plain -d 'tab-delimited output' 1635 - complete -c wt -n '__fish_seen_subcommand_from ls' -l json -d 'JSON output' 1636 - complete -c wt -n '__fish_seen_subcommand_from ls' -s h -l help -d 'show help' 1637 - 1638 - complete -c wt -n '__fish_seen_subcommand_from rm' -s f -l force -d 'remove even if dirty' 1639 - complete -c wt -n '__fish_seen_subcommand_from rm' -s h -l help -d 'show help' 1640 - 1641 - complete -c wt -n '__fish_seen_subcommand_from merge' -l no-squash -d 'preserve individual commits' 1642 - complete -c wt -n '__fish_seen_subcommand_from merge' -l no-rebase -d 'skip rebase (fail if not rebased)' 1643 - complete -c wt -n '__fish_seen_subcommand_from merge' -l no-commit -d 'skip committing uncommitted changes' 1644 - complete -c wt -n '__fish_seen_subcommand_from merge' -s h -l help -d 'show help' 1645 - 1646 - complete -c wt -n '__fish_seen_subcommand_from help' -a switch -d 'create or open a workspace' 1647 - complete -c wt -n '__fish_seen_subcommand_from help' -a sw -d 'alias for switch' 1648 - complete -c wt -n '__fish_seen_subcommand_from help' -a exec -d 'open a workspace and run a command' 1649 - complete -c wt -n '__fish_seen_subcommand_from help' -a sync -d 'copy files between worktrees' 1650 - complete -c wt -n '__fish_seen_subcommand_from help' -a ls -d 'list workspaces' 1651 - complete -c wt -n '__fish_seen_subcommand_from help' -a rm -d 'remove a workspace' 1652 - complete -c wt -n '__fish_seen_subcommand_from help' -a merge -d 'squash, rebase, and merge to target' 1653 - complete -c wt -n '__fish_seen_subcommand_from help' -a here -d 'print current workspace branch' 1654 - complete -c wt -n '__fish_seen_subcommand_from help' -a base -d 'print base dir' 1655 - complete -c wt -n '__fish_seen_subcommand_from help' -a root -d 'print main worktree path' 1656 - complete -c wt -n '__fish_seen_subcommand_from help' -a help -d 'show help' 1657 - complete -c wt -n '__fish_seen_subcommand_from help' -a completion -d 'print shell completion' 1658 - complete -c wt -n '__fish_seen_subcommand_from completion' -a 'bash zsh fish' 1659 - EOF 1660 - } 1661 - 1662 - cmd_completion() { 1663 - if [ "$#" -eq 0 ]; then 1664 - completion_usage 1665 - exit 1 1666 - fi 1667 - case "$1" in 1668 - bash) completion_bash ;; 1669 - zsh) completion_zsh ;; 1670 - fish) completion_fish ;; 1671 - *) die "unknown shell '$1'" ;; 1672 - esac 1673 - } 1674 - 1675 - cmd_help() { 1676 - if [ "$#" -eq 0 ]; then 1677 - usage 1678 - exit 0 1679 - fi 1680 - case "$1" in 1681 - switch | sw) switch_usage ;; 1682 - exec) exec_usage ;; 1683 - sync) sync_usage ;; 1684 - ls) ls_usage ;; 1685 - rm) rm_usage ;; 1686 - merge) merge_usage ;; 1687 - here) here_usage ;; 1688 - base) base_usage ;; 1689 - root) root_usage ;; 1690 - completion) completion_usage ;; 1691 - help) usage ;; 1692 - *) die "unknown command '$1'" ;; 1693 - esac 1694 - } 1695 - 1696 - while [ "$#" -gt 0 ]; do 1697 - case "$1" in 1698 - --base-dir) 1699 - [ -n "${2:-}" ] || die "missing value for --base-dir" 1700 - BASE_DIR_OVERRIDE="$2" 1701 - shift 2 1702 - ;; 1703 - --base-dir=*) 1704 - BASE_DIR_OVERRIDE="${1#--base-dir=}" 1705 - shift 1706 - ;; 1707 - -h | --help) 1708 - break 1709 - ;; 1710 - --*) die "unknown flag '$1'" ;; 1711 - *) break ;; 1712 - esac 1713 - done 1714 - 1715 - case "${1:-}" in 1716 - "") 1717 - usage 1718 - exit 1 1719 - ;; 1720 - -h | --help) 1721 - usage 1722 - exit 0 1723 - ;; 1724 - help) 1725 - shift 1726 - cmd_help "$@" 1727 - ;; 1728 - switch) 1729 - shift 1730 - cmd_switch "$@" 1731 - ;; 1732 - sw) 1733 - shift 1734 - cmd_switch "$@" 1735 - ;; 1736 - exec) 1737 - shift 1738 - cmd_exec "$@" 1739 - ;; 1740 - sync) 1741 - shift 1742 - cmd_sync "$@" 1743 - ;; 1744 - ls) 1745 - shift 1746 - cmd_ls "$@" 1747 - ;; 1748 - rm) 1749 - shift 1750 - cmd_rm "$@" 1751 - ;; 1752 - merge) 1753 - shift 1754 - cmd_merge "$@" 1755 - ;; 1756 - here) 1757 - shift 1758 - cmd_here "$@" 1759 - ;; 1760 - base) 1761 - shift 1762 - cmd_base "$@" 1763 - ;; 1764 - root) 1765 - shift 1766 - cmd_root "$@" 1767 - ;; 1768 - completion) 1769 - shift 1770 - cmd_completion "$@" 1771 - ;; 1772 - *) die "unknown command '$1'" ;; 1773 - esac