diff --git a/lib/config.sh b/lib/config.sh index f1935f9..4c1a19e 100644 --- a/lib/config.sh +++ b/lib/config.sh @@ -10,24 +10,38 @@ # 5. Environment variables # 6. Fallback values +# Resolve the main repo root from the current git context. +# Works from the main repo root, a subdirectory, or a linked worktree. +# Returns: absolute path to main repo root or empty on failure +_resolve_main_repo_root() { + local git_common_dir repo_root + git_common_dir=$(git rev-parse --git-common-dir 2>/dev/null) || return 1 + + [ -n "$git_common_dir" ] || return 1 + + case "$git_common_dir" in + /*) + repo_root="${git_common_dir%/.git}" + ;; + *) + repo_root=$( + unset CDPATH + cd -P -- "$git_common_dir/.." 2>/dev/null && pwd -P + ) || return 1 + ;; + esac + + [ -n "$repo_root" ] || return 1 + printf "%s" "$repo_root" +} + # Get the path to .gtrconfig file in main repo root # Usage: _gtrconfig_path # Returns: path to .gtrconfig or empty if not in a repo -# Note: Uses --git-common-dir to find main repo even from worktrees +# Note: Uses _resolve_main_repo_root to find main repo even from worktrees/subdirectories _gtrconfig_path() { - local git_common_dir repo_root - git_common_dir=$(git rev-parse --git-common-dir 2>/dev/null) || return 0 - - # git-common-dir returns: - # - ".git" when in main repo (relative) - # - "/absolute/path/to/repo/.git" when in worktree (absolute) - if [ "$git_common_dir" = ".git" ]; then - # In main repo - use show-toplevel - repo_root=$(git rev-parse --show-toplevel 2>/dev/null) || return 0 - else - # In worktree - strip /.git suffix from absolute path - repo_root="${git_common_dir%/.git}" - fi + local repo_root + repo_root=$(_resolve_main_repo_root) || return 0 printf "%s/.gtrconfig" "$repo_root" } diff --git a/lib/core.sh b/lib/core.sh index 37a53e4..5bd4dba 100644 --- a/lib/core.sh +++ b/lib/core.sh @@ -15,24 +15,8 @@ declare _ctx_is_main _ctx_worktree_path _ctx_branch # Returns: absolute path to main repo root # Exit code: 0 on success, 1 if not in a git repo discover_repo_root() { - local root git_common_dir - git_common_dir=$(git rev-parse --git-common-dir 2>/dev/null) - - if [ -z "$git_common_dir" ]; then - log_error "Not in a git repository" - return 1 - fi - - # --git-common-dir returns: - # ".git" (relative) when in the main repo - # "/absolute/path/to/repo/.git" when in a worktree - if [ "$git_common_dir" = ".git" ]; then - root=$(git rev-parse --show-toplevel 2>/dev/null) - else - root="${git_common_dir%/.git}" - fi - - if [ -z "$root" ]; then + local root + if ! root=$(_resolve_main_repo_root); then log_error "Not in a git repository" return 1 fi diff --git a/tests/cmd_list.bats b/tests/cmd_list.bats index 2b37693..bc96502 100644 --- a/tests/cmd_list.bats +++ b/tests/cmd_list.bats @@ -76,3 +76,12 @@ teardown() { [[ "$output" == *"$TEST_REPO"* ]] [[ "$output" == *"wt-porcelain"* ]] } + +@test "cmd_list from a repo subdirectory shows the main repo root" { + mkdir -p "$TEST_REPO/subdir/nested" + cd "$TEST_REPO/subdir/nested" + run cmd_list + [ "$status" -eq 0 ] + [[ "$output" == *"$TEST_REPO"* ]] + [[ "$output" != *"subdir/..-worktrees"* ]] +} diff --git a/tests/config.bats b/tests/config.bats index e8abb5f..8d85f61 100644 --- a/tests/config.bats +++ b/tests/config.bats @@ -6,6 +6,13 @@ setup() { source "$PROJECT_ROOT/lib/config.sh" } +teardown() { + if [ -n "${TEST_REPO:-}" ]; then + teardown_integration_repo + unset TEST_REPO TEST_WORKTREES_DIR + fi +} + # ── Key mapping ────────────────────────────────────────────────────────────── @test "cfg_map_to_file_key maps gtr.copy.include to copy.include" { @@ -125,3 +132,27 @@ setup() { [[ "$result" == *"vscode"* ]] [[ "$result" == *"[local]"* ]] } + +# ── Repo context integration ───────────────────────────────────────────────── + +@test "_resolve_main_repo_root returns the repo root from a subdirectory" { + setup_integration_repo + mkdir -p "$TEST_REPO/subdir/nested" + cd "$TEST_REPO/subdir/nested" + local expected + expected=$(cd "$TEST_REPO" && pwd -P) + + result=$(_resolve_main_repo_root) + [ "$result" = "$expected" ] +} + +@test "_gtrconfig_path points at the repo root from a subdirectory" { + setup_integration_repo + mkdir -p "$TEST_REPO/subdir/nested" + cd "$TEST_REPO/subdir/nested" + local expected + expected="$(cd "$TEST_REPO" && pwd -P)/.gtrconfig" + + result=$(_gtrconfig_path) + [ "$result" = "$expected" ] +} diff --git a/tests/core_resolve_target.bats b/tests/core_resolve_target.bats index 7d8f9b3..1ab1a3f 100644 --- a/tests/core_resolve_target.bats +++ b/tests/core_resolve_target.bats @@ -112,3 +112,23 @@ teardown() { expected=$(pwd -P) [ "$root" = "$expected" ] } + +@test "discover_repo_root returns main repo root when called from a repo subdirectory" { + mkdir -p "$TEST_REPO/subdir/nested" + cd "$TEST_REPO/subdir/nested" + local root expected + root=$(discover_repo_root) + expected=$(cd "$TEST_REPO" && pwd -P) + [ "$root" = "$expected" ] +} + +@test "discover_repo_root returns 1 outside a git repository" { + local outside_repo + outside_repo=$(mktemp -d) + cd "$outside_repo" + + run discover_repo_root + [ "$status" -eq 1 ] + + rm -rf "$outside_repo" +}