From 77ce6db8b42af9526f4a01f101c2bf5292477d7a Mon Sep 17 00:00:00 2001 From: carlfriedrich Date: Mon, 7 Sep 2026 14:12:12 +0200 Subject: [PATCH] feat: resolve a relative FORGIT_WORKTREE_ADD_DIR per repository A relative FORGIT_WORKTREE_ADD_DIR is resolved against the root of the main worktree on every invocation, so a single setting such as `.worktrees` is valid in every repository. Absolute paths keep their meaning. The variable previously only took a full path, so users resolved the repository root in their shell startup files, which runs git outside a repository on every new shell and freezes the path for the whole session. --- README.md | 2 +- bin/git-forgit | 18 ++++++++++++++-- tests/worktree.test.sh | 49 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 tests/worktree.test.sh diff --git a/README.md b/README.md index 4819a64..7c580c5 100644 --- a/README.md +++ b/README.md @@ -381,7 +381,7 @@ export FORGIT_LOG_FZF_OPTS=' | `FORGIT_FULLSCREEN_CONTEXT` | lines of diff context in full-screen mode | 10 | | `FORGIT_DIR_VIEW` | command used to preview directories | `tree` if available, otherwise `find` | | `FORGIT_CLEAN_LIST_FILES_OPTS` | arguments passed to `git ls-files` together with `--others` to determine which files are shown when invoking `forgit clean` | | -| `FORGIT_WORKTREE_ADD_DIR` | directory where new worktrees are created | `/.wt` | +| `FORGIT_WORKTREE_ADD_DIR` | directory where new worktrees are created (a relative path is resolved against the main worktree root) | `.wt` | | `FORGIT_CHECKOUT_BRANCH_AUTO_CREATE_BRANCH` | prevents forgit from auto creating a new branch with `gcb ` when `` does not exist when set to `false` | | | `FORGIT_SWITCH_AUTO_CREATE_BRANCH` | prevents forgit from auto creating a new branch with `gsw ` when `` does not exist when set to `false` | | diff --git a/bin/git-forgit b/bin/git-forgit index 77e87cd..65918a5 100755 --- a/bin/git-forgit +++ b/bin/git-forgit @@ -1583,6 +1583,20 @@ _forgit_main_worktree_root() { git worktree list --porcelain | sed -n 's/^worktree //p;q' } +# Get the directory in which new worktrees are created +# A relative FORGIT_WORKTREE_ADD_DIR is resolved against the root of the main +# worktree, so that a single setting is valid in every repository. Anchoring it +# to the main worktree rather than the current one also keeps adding a worktree +# from a linked worktree from nesting the directory any deeper. +_forgit_worktree_add_dir() { + local dir="${FORGIT_WORKTREE_ADD_DIR:-.wt}" + if [[ $dir == /* ]]; then + echo "$dir" + else + echo "$(_forgit_main_worktree_root)/$dir" + fi +} + # Parse git worktree list --porcelain output and format it for display # Output format: [XY] /path/to/worktree (branch) 3 hours ago # X: '*' = current worktree, ' ' = other @@ -1740,8 +1754,8 @@ _forgit_worktree_delete() { _forgit_worktree_add() { _forgit_inside_git_repo || return 1 - # Default to main worktree root, not current worktree's root - local wt_dir="${FORGIT_WORKTREE_ADD_DIR:-$(_forgit_main_worktree_root)/.wt}" + local wt_dir + wt_dir=$(_forgit_worktree_add_dir) # Two or more arguments: pass through to git directly if [[ $# -ge 2 ]]; then diff --git a/tests/worktree.test.sh b/tests/worktree.test.sh new file mode 100644 index 0000000..654dbb1 --- /dev/null +++ b/tests/worktree.test.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +function set_up_before_script() { + source bin/git-forgit + + # Ignore global git config files + export GIT_CONFIG_SYSTEM=/dev/null + export GIT_CONFIG_GLOBAL=/dev/null + + unset FORGIT_WORKTREE_ADD_DIR + + # Create a temporary git repository for testing + MAIN_WORKTREE_ROOT="$(bashunit::temp_dir)/main" + git init -q "$MAIN_WORKTREE_ROOT" + cd "$MAIN_WORKTREE_ROOT" || return 1 + git config user.email "test@example.com" + git config user.name "Test User" + echo "initial" >README.md + git add README.md + git commit -q -m "Initial commit" +} + +function test_worktree_add_dir_defaults_to_dot_wt_in_main_worktree_root() { + assert_same "$MAIN_WORKTREE_ROOT/.wt" "$(_forgit_worktree_add_dir)" +} + +function test_worktree_add_dir_resolves_a_relative_path_against_main_worktree_root() { + assert_same "$MAIN_WORKTREE_ROOT/.worktrees" \ + "$(FORGIT_WORKTREE_ADD_DIR=.worktrees _forgit_worktree_add_dir)" +} + +function test_worktree_add_dir_resolves_a_nested_relative_path() { + assert_same "$MAIN_WORKTREE_ROOT/build/wt" \ + "$(FORGIT_WORKTREE_ADD_DIR=build/wt _forgit_worktree_add_dir)" +} + +function test_worktree_add_dir_uses_an_absolute_path_as_is() { + assert_same "/somewhere/else" \ + "$(FORGIT_WORKTREE_ADD_DIR=/somewhere/else _forgit_worktree_add_dir)" +} + +function test_worktree_add_dir_resolves_a_relative_path_from_a_linked_worktree() { + local linked_worktree="$MAIN_WORKTREE_ROOT/.worktrees/linked" + git worktree add -q -b linked "$linked_worktree" >/dev/null 2>&1 + cd "$linked_worktree" || return 1 + + assert_same "$MAIN_WORKTREE_ROOT/.worktrees" \ + "$(FORGIT_WORKTREE_ADD_DIR=.worktrees _forgit_worktree_add_dir)" +}