#!/usr/bin/env zsh # Create a temporary worktree. # # Useful for doing quick tasks on other branches without modifying the current # working tree. So no broken/outdated symlinks to dotfiles or unnecessary # recompiling after switching back for example. # # Checks out the first argument in a worktree at a temporary directory. Then # spawns an interactive shell inside of it. # # When the shell closes the worktree is tried to be removed. Until that works # without problems (e.g. dirty), a new shell is spawned to resolve all conflicts # (e.g. stashing). # # Instead of dropping in an interactive shell, the commands to execute can be # passed via stdin. If any conflicts arise, all further shells are interactive. # TODO: Override with flag that just `stash -u` # # Examples for scripted usage: # Merge branches without leaving the current one: # % git-checkout-worktree main <<<"git merge dev" # # Same for rebase (as `git rebase dev feature` switches to `feature`): # % git-checkout-worktree feature <<<"git rebase dev" # # # TODO: The git alias version seems to break with the same arguments that the # zsh native one works with. # TODO: Supplying a flag like --no-checkout breaks the naming of the worktree emulate -L zsh -o err_return -o no_unset local git_dir cwd_offset REPO_NAME WORKTREE_PATH # Use the folder name of the main working tree to make calls from another # temporary working tree possible git_dir="${$(git rev-parse --git-dir):A}" [[ $git_dir == */.git/modules/* ]] || git_dir="${git_dir%%/.git*}" REPO_NAME="${git_dir:t}" WORKTREE_PATH="$(mktemp -d -p "" "wtree.$REPO_NAME.${1//\//_}.XXX")" local errc ret=0 git worktree add "$WORKTREE_PATH" "$@" || ret=$? if (( ret )); then rmdir "$WORKTREE_PATH" return $ret fi trap ' errc=$? <&2 printf "Exiting abnormally. Check and possibly remove '$WORKTREE_PATH' manually.\n" return $errc ' INT QUIT TERM EXIT cwd_offset="${${PWD#$(git rev-parse --show-toplevel)}#/}" pushd -q "$WORKTREE_PATH" [[ ! -d $cwd_offset ]] || cd "$cwd_offset" "$SHELL" && errc=$? || errc=$? (( !errc )) || echo "shell exited with $errc" # Restart the shell (forcefully interactive) until the worktree is removed until [[ ! -e "$WORKTREE_PATH" ]] || git worktree remove "$WORKTREE_PATH"; do [[ -t 0 ]] || >&2 printf "Dropping into interactive shell to resolve conflicts\n" "$SHELL" -i && errc=$? || errc=$? (( !errc )) || echo "shell exited with $errc" done # Reset traps and PWD trap '-' INT QUIT TERM EXIT popd -q || true git worktree prune return $errc