From a4a21b095ea7c0daa953351107640347f16dea04 Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Mon, 19 Oct 2026 23:37:57 +0530 Subject: [PATCH 1/2] refactor(git): centralize linked-worktree validity checks --- base_api_manifest.yaml | 2 +- docs/api-reference.md | 1 + docs/v2-api-contract.md | 2 +- docs/v2-symbol-map.md | 2 +- lib/bash/git/README.md | 9 ++++++++ lib/bash/git/lib_git.sh | 12 +++++++++++ lib/bash/git/tests/lib_git.bats | 37 +++++++++++++++++++++++++++++++++ scripts/api-manifest | 25 ++++++++++++++++++---- 8 files changed, 83 insertions(+), 7 deletions(-) diff --git a/base_api_manifest.yaml b/base_api_manifest.yaml index 423c9d9..18fb7b9 100644 --- a/base_api_manifest.yaml +++ b/base_api_manifest.yaml @@ -108,7 +108,7 @@ modules: dependencies: std,str optional_commands: git,awk,grep,mktemp,rm,sleep artifacts: lib/bash/git/lib_git.sh,lib/bash/git/README.md,lib/bash/git/tests/lib_git.bats - public_symbols: base_git_branch_merged_to_ref,base_git_branch_upstream,base_git_check_script_up_to_date,base_git_detect_default_branch,base_git_get_current_branch,base_git_list_remote_branches,base_git_list_worktree_branches,base_git_update_repo,base_git_worktree_path_for_branch + public_symbols: base_git_branch_merged_to_ref,base_git_branch_upstream,base_git_check_script_up_to_date,base_git_detect_default_branch,base_git_get_current_branch,base_git_list_remote_branches,base_git_list_worktree_branches,base_git_update_repo,base_git_worktree_path_for_branch,base_git_worktree_path_is_valid signature_source: lib/bash/git/README.md inputs: documented per symbol in the module README and API charter outputs: documented per symbol; named outputs are caller-owned diff --git a/docs/api-reference.md b/docs/api-reference.md index 717ac37..8844576 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -165,6 +165,7 @@ statuses, and side effects are normative in the linked module README and - `base_git_list_worktree_branches` — signature: see [`lib/bash/git/README.md`](../lib/bash/git/README.md). - `base_git_update_repo` — signature: see [`lib/bash/git/README.md`](../lib/bash/git/README.md). - `base_git_worktree_path_for_branch` — signature: see [`lib/bash/git/README.md`](../lib/bash/git/README.md). +- `base_git_worktree_path_is_valid` — signature: see [`lib/bash/git/README.md`](../lib/bash/git/README.md). ### `gh` diff --git a/docs/v2-api-contract.md b/docs/v2-api-contract.md index b551b9f..59846ac 100644 --- a/docs/v2-api-contract.md +++ b/docs/v2-api-contract.md @@ -215,7 +215,7 @@ signature/effects reference; this table makes coverage auditable. | std validation/reflection | `base_std_assert_variable_name`, `base_std_assert_indexed_array`, `base_std_assert_associative_array`, `base_std_command_path`, `base_std_function_exists`, `base_std_assert_function_exists`, `base_std_assert_not_null`, `base_std_assert_integer`, `base_std_assert_integer_range`, `base_std_assert_arg_count`, `base_std_assert_command_exists`, `base_std_assert_file_exists`, `base_std_assert_executable`, `base_std_assert_dir_exists` | Predicates return status; explicit `assert_*` APIs are intentional fail-fast precondition checks; named outputs are validated before writes. | | std miscellaneous | `base_std_safe_cd`, `base_std_safe_unalias`, `base_std_get_my_source_dir`, `base_std_ask_yes_no`, `base_std_wait_for_enter` | `safe_cd` changes `PWD`; source-dir writes one validated output; interactive functions return recoverable EOF/non-TTY statuses. | | file | `base_file_section_exists`, `base_file_section_needs_update`, `base_file_update_file_section` | Read-only predicates do not mutate; update is idempotent, symlink-preserving, atomic, metadata-preserving, and conflict-aware. | -| git | `base_git_detect_default_branch`, `base_git_worktree_path_for_branch`, `base_git_list_worktree_branches`, `base_git_branch_upstream`, `base_git_branch_merged_to_ref`, `base_git_list_remote_branches`, `base_git_update_repo`, `base_git_get_current_branch`, `base_git_check_script_up_to_date` | Usage and contract errors return `2`; recoverable Git failures and false predicates return `1` unless a function documents a specific status. Read-only inspections use named outputs/stdout as documented; freshness outcomes are `3` dirty, `4` behind, and `5` diverged. | +| git | `base_git_detect_default_branch`, `base_git_worktree_path_for_branch`, `base_git_list_worktree_branches`, `base_git_branch_upstream`, `base_git_branch_merged_to_ref`, `base_git_list_remote_branches`, `base_git_update_repo`, `base_git_get_current_branch`, `base_git_check_script_up_to_date`, `base_git_worktree_path_is_valid` | Usage and contract errors return `2`; recoverable Git failures and false predicates return `1` unless a function documents a specific status. Read-only inspections use named outputs/stdout as documented; freshness outcomes are `3` dirty, `4` behind, and `5` diverged. `base_git_worktree_path_is_valid` returns success only for an existing linked worktree whose Git metadata is valid; ordinary repositories, missing paths, and stale linked-worktree metadata return `1` without diagnostics. | | gh (stable since `2.0.0`; private process implementation dependency shipped in `2.1.0`) | `base_gh_require_cli`, `base_gh_auth_status_diagnostics`, `base_gh_report_command_failure`, `base_gh_run`, `base_gh_repo_from_remote_url`, `base_gh_infer_repo_from_origin`, `base_gh_repo_default_branch`, `base_gh_api_with_retry` | Usage and contract errors return `2`; recoverable GitHub failures return `1` unless the helper preserves the underlying `gh` status. Diagnostics go stderr; repository/API values use named outputs; retries are bounded and mutation-aware. The public API remains compatible through v2.x; the private process dependency is included in the v2.1.0 artifact. | | str | `base_str_lower`, `base_str_upper`, `base_str_ltrim`, `base_str_rtrim`, `base_str_trim`, `base_str_contains`, `base_str_starts_with`, `base_str_ends_with`, `base_str_split`, `base_str_join` | String transforms/predicates preserve caller values until validation succeeds; split/join use validated named outputs. | | arg | `base_arg_parse` | Parses into caller-owned validated arrays/maps and leaves them unchanged on failure. | diff --git a/docs/v2-symbol-map.md b/docs/v2-symbol-map.md index 4286b46..0bcb534 100644 --- a/docs/v2-symbol-map.md +++ b/docs/v2-symbol-map.md @@ -49,7 +49,7 @@ is part of the caller's intended policy. | Module | Legacy names | v2 names | | --- | --- | --- | | file | `file_section_exists`, `file_section_needs_update`, `update_file_section` | `base_file_section_exists`, `base_file_section_needs_update`, `base_file_update_file_section` | -| git | `git_detect_default_branch`, `git_worktree_path_for_branch`, `git_list_worktree_branches`, `git_branch_upstream`, `git_branch_merged_to_ref`, `git_list_remote_branches`, `git_update_repo`, `git_get_current_branch`, `check_script_up_to_date` | `base_git_detect_default_branch`, `base_git_worktree_path_for_branch`, `base_git_list_worktree_branches`, `base_git_branch_upstream`, `base_git_branch_merged_to_ref`, `base_git_list_remote_branches`, `base_git_update_repo`, `base_git_get_current_branch`, `base_git_check_script_up_to_date` | +| git | `git_detect_default_branch`, `git_worktree_path_for_branch`, `git_list_worktree_branches`, `git_branch_upstream`, `git_branch_merged_to_ref`, `git_list_remote_branches`, `git_update_repo`, `git_get_current_branch`, `check_script_up_to_date` | `base_git_detect_default_branch`, `base_git_worktree_path_for_branch`, `base_git_list_worktree_branches`, `base_git_branch_upstream`, `base_git_branch_merged_to_ref`, `base_git_list_remote_branches`, `base_git_update_repo`, `base_git_get_current_branch`, `base_git_check_script_up_to_date`, `base_git_worktree_path_is_valid` | | gh | `gh_require_cli`, `gh_auth_status_diagnostics`, `gh_report_command_failure`, `gh_run`, `gh_repo_from_remote_url`, `gh_infer_repo_from_origin`, `gh_repo_default_branch`, `gh_api_with_retry` | `base_gh_require_cli`, `base_gh_auth_status_diagnostics`, `base_gh_report_command_failure`, `base_gh_run`, `base_gh_repo_from_remote_url`, `base_gh_infer_repo_from_origin`, `base_gh_repo_default_branch`, `base_gh_api_with_retry` | | str | `str_lower`, `str_upper`, `str_ltrim`, `str_rtrim`, `str_trim`, `str_contains`, `str_starts_with`, `str_ends_with`, `str_split`, `str_join` | `base_str_lower`, `base_str_upper`, `base_str_ltrim`, `base_str_rtrim`, `base_str_trim`, `base_str_contains`, `base_str_starts_with`, `base_str_ends_with`, `base_str_split`, `base_str_join` | | arg | `arg_parse` | `base_arg_parse` | diff --git a/lib/bash/git/README.md b/lib/bash/git/README.md index 845520d..0ea22a7 100644 --- a/lib/bash/git/README.md +++ b/lib/bash/git/README.md @@ -18,6 +18,10 @@ Source `lib/bash/std/lib_std.sh` before this library so logging and shared error - `base_git_worktree_path_for_branch [--result VAR] [repo]` Print the worktree path attached to a local branch, or store its exact value in `VAR` when it can contain trailing newlines. +- `base_git_worktree_path_is_valid ` + Return success only when `path` is a valid linked Git worktree. Detached + linked worktrees are valid; ordinary repositories, missing paths, and stale + linked-worktree metadata return `1` without printing Git diagnostics. - `base_git_list_worktree_branches [repo]` Print tab-separated worktree path and branch rows. Backslash, tab, newline, and carriage-return bytes in paths are escaped as `\\`, `\t`, `\n`, and @@ -70,6 +74,11 @@ base_git_worktree_path_for_branch --result worktree_path feature/topic "$PWD" - Worktree helpers parse Git's NUL-delimited porcelain format. Use the lookup helper's `--result` form whenever a path may end in one or more newlines; command substitution necessarily strips those bytes from stdout. +- `base_git_worktree_path_is_valid` is intentionally limited to linked + worktrees: it requires a `.git` file and a successful `git -C + rev-parse --is-inside-work-tree` probe. A `.git` directory is an ordinary + repository checkout, not a linked worktree, and a `.git` file whose Git + administrative record is stale is invalid. - `base_git_update_repo` only attempts updates when the checked-out branch is the detected default branch, or an explicit expected branch passed by the caller. - `base_git_update_repo` requires `git_repo` to be the repository root (including a symlink that resolves to that root); it rejects ordinary descendants rather diff --git a/lib/bash/git/lib_git.sh b/lib/bash/git/lib_git.sh index 8243da4..457780f 100644 --- a/lib/bash/git/lib_git.sh +++ b/lib/bash/git/lib_git.sh @@ -161,6 +161,18 @@ base_git_worktree_path_for_branch() { fi } +base_git_worktree_path_is_valid() { + if (($# != 1)); then + base_std_log_error -l base_bash_libs.git "Usage: base_git_worktree_path_is_valid " + return 2 + fi + + local path="$1" + + [[ -d "$path" && -f "$path/.git" ]] || return 1 + git -C "$path" rev-parse --is-inside-work-tree > /dev/null 2>&1 || return 1 +} + base_git_list_worktree_branches() { if (($# > 1)); then base_std_log_error -l base_bash_libs.git "Usage: base_git_list_worktree_branches [repo_dir]" diff --git a/lib/bash/git/tests/lib_git.bats b/lib/bash/git/tests/lib_git.bats index 15ab550..b994f60 100644 --- a/lib/bash/git/tests/lib_git.bats +++ b/lib/bash/git/tests/lib_git.bats @@ -44,6 +44,7 @@ setup() { base_git_branch_merged_to_ref \ base_git_update_repo \ base_git_get_current_branch \ + base_git_worktree_path_is_valid \ base_git_check_script_up_to_date; do bats_run "$BASH" -c ' mode="$1" @@ -205,6 +206,42 @@ setup() { unset -f git } +@test "base_git_worktree_path_is_valid accepts linked worktrees and rejects other paths" { + local repo="$TEST_TMPDIR/repo" + local linked="$TEST_TMPDIR/linked worktree" + local detached="$TEST_TMPDIR/detached worktree" + local clone="$TEST_TMPDIR/clone" + local stale="$TEST_TMPDIR/stale worktree" + + init_git_repo "$repo" + printf 'base\n' > "$repo/data.txt" + commit_all "$repo" "Initial commit" + git -C "$repo" worktree add -b feature "$linked" >/dev/null 2>&1 + git -C "$repo" worktree add --detach "$detached" HEAD >/dev/null 2>&1 + git clone --quiet "$repo" "$clone" + mkdir -p "$stale" + printf 'gitdir: %s\n' "$TEST_TMPDIR/missing-worktree-admin" > "$stale/.git" + + base_git_worktree_path_is_valid "$linked" + base_git_worktree_path_is_valid "$detached" + ! base_git_worktree_path_is_valid "$repo" + ! base_git_worktree_path_is_valid "$clone" + ! base_git_worktree_path_is_valid "$stale" + ! base_git_worktree_path_is_valid "$TEST_TMPDIR/missing" +} + +@test "base_git_worktree_path_is_valid suppresses stale metadata diagnostics" { + local stale="$TEST_TMPDIR/stale-worktree" + + mkdir -p "$stale" + printf 'gitdir: %s\n' "$TEST_TMPDIR/missing-worktree-admin" > "$stale/.git" + + capture_command base_git_worktree_path_is_valid "$stale" + + [ "$status" -eq 1 ] + [ -z "$output" ] +} + @test "git worktree helpers parse canonical porcelain output" { git() { if [[ "${1:-}" == "worktree" || "${3:-}" == "worktree" ]]; then diff --git a/scripts/api-manifest b/scripts/api-manifest index 8feb7e7..a800be3 100755 --- a/scripts/api-manifest +++ b/scripts/api-manifest @@ -556,7 +556,7 @@ release_module_check() { check_release_tree() { local manifest="$1" local release_ref="$2" - local symbol + local symbol release_manifest="$manifest" historical_manifest="" local -a fields=() local -A release_module_sources=() local -A release_module_docs=() @@ -569,6 +569,19 @@ check_release_tree() { error "release reference is not a local commit: $release_ref" return 1 } + if [[ "$manifest" == "$default_manifest" ]]; then + historical_manifest="$(mktemp "${TMPDIR:-/tmp}/base-api-release-manifest.XXXXXX")" || return 1 + if ! git -C "$repo_root" show "$release_ref:base_api_manifest.yaml" > "$historical_manifest" 2> /dev/null; then + rm -f "$historical_manifest" + error "release reference '$release_ref' does not contain base_api_manifest.yaml" + return 1 + fi + # The compatibility check above verifies that every historical stable + # symbol remains available. The release tree check must inspect the + # historical manifest so additive stable APIs are allowed in a minor + # v2 release without pretending they were present in v2.0.0. + release_manifest="$historical_manifest" + fi while IFS=$'\t' read -r -a fields; do [[ -n "${fields[0]-}" ]] || continue release_module_sources["${fields[0]}"]="${fields[2]}" @@ -576,11 +589,15 @@ check_release_tree() { release_module_tests["${fields[0]}"]="${fields[4]}" release_module_symbols["${fields[0]}"]="${fields[8]}" release_module_dependencies["${fields[0]}"]="${fields[5]}" - done < <(manifest_rows "$manifest") + done < <(manifest_rows "$release_manifest") while IFS=$'\t' read -r -a fields; do [[ -n "${fields[0]-}" && "${fields[14]}" == stable ]] || continue - release_module_check "${fields[0]}" "${fields[0]}" || return 1 - done < <(manifest_rows "$manifest") + release_module_check "${fields[0]}" "${fields[0]}" || { + rm -f "$historical_manifest" + return 1 + } + done < <(manifest_rows "$release_manifest") + rm -f "$historical_manifest" printf 'Stable API release reference is valid: %s\n' "$release_ref" } From 857df0d374bb6b567b0e19e09171d33757d19c60 Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Wed, 4 Nov 2026 00:13:36 +0530 Subject: [PATCH 2/2] fix(git): reject submodules as linked worktrees --- CHANGELOG.md | 4 ++++ docs/v2-api-contract.md | 2 +- lib/bash/git/README.md | 8 +++++--- lib/bash/git/lib_git.sh | 13 +++++++++++++ lib/bash/git/tests/lib_git.bats | 16 ++++++++++++++++ 5 files changed, 39 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f888308..68cd037 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ and versions are tracked in the repo-root `VERSION` file. - Moved hosted macOS validation to the pinned macOS 15 runner while preserving the logical macOS 14 compatibility and release-platform contract. +### Fixed + +- Reject Git submodule checkouts when validating linked worktree paths. + ## [2.2.2] - 2026-10-08 ### Documentation diff --git a/docs/v2-api-contract.md b/docs/v2-api-contract.md index 59846ac..d8a5e00 100644 --- a/docs/v2-api-contract.md +++ b/docs/v2-api-contract.md @@ -215,7 +215,7 @@ signature/effects reference; this table makes coverage auditable. | std validation/reflection | `base_std_assert_variable_name`, `base_std_assert_indexed_array`, `base_std_assert_associative_array`, `base_std_command_path`, `base_std_function_exists`, `base_std_assert_function_exists`, `base_std_assert_not_null`, `base_std_assert_integer`, `base_std_assert_integer_range`, `base_std_assert_arg_count`, `base_std_assert_command_exists`, `base_std_assert_file_exists`, `base_std_assert_executable`, `base_std_assert_dir_exists` | Predicates return status; explicit `assert_*` APIs are intentional fail-fast precondition checks; named outputs are validated before writes. | | std miscellaneous | `base_std_safe_cd`, `base_std_safe_unalias`, `base_std_get_my_source_dir`, `base_std_ask_yes_no`, `base_std_wait_for_enter` | `safe_cd` changes `PWD`; source-dir writes one validated output; interactive functions return recoverable EOF/non-TTY statuses. | | file | `base_file_section_exists`, `base_file_section_needs_update`, `base_file_update_file_section` | Read-only predicates do not mutate; update is idempotent, symlink-preserving, atomic, metadata-preserving, and conflict-aware. | -| git | `base_git_detect_default_branch`, `base_git_worktree_path_for_branch`, `base_git_list_worktree_branches`, `base_git_branch_upstream`, `base_git_branch_merged_to_ref`, `base_git_list_remote_branches`, `base_git_update_repo`, `base_git_get_current_branch`, `base_git_check_script_up_to_date`, `base_git_worktree_path_is_valid` | Usage and contract errors return `2`; recoverable Git failures and false predicates return `1` unless a function documents a specific status. Read-only inspections use named outputs/stdout as documented; freshness outcomes are `3` dirty, `4` behind, and `5` diverged. `base_git_worktree_path_is_valid` returns success only for an existing linked worktree whose Git metadata is valid; ordinary repositories, missing paths, and stale linked-worktree metadata return `1` without diagnostics. | +| git | `base_git_detect_default_branch`, `base_git_worktree_path_for_branch`, `base_git_list_worktree_branches`, `base_git_branch_upstream`, `base_git_branch_merged_to_ref`, `base_git_list_remote_branches`, `base_git_update_repo`, `base_git_get_current_branch`, `base_git_check_script_up_to_date`, `base_git_worktree_path_is_valid` | Usage and contract errors return `2`; recoverable Git failures and false predicates return `1` unless a function documents a specific status. Read-only inspections use named outputs/stdout as documented; freshness outcomes are `3` dirty, `4` behind, and `5` diverged. `base_git_worktree_path_is_valid` returns success only for an existing linked worktree whose resolved Git directory is below the repository's `worktrees/` administrative directory; ordinary repositories, submodules, missing paths, and stale linked-worktree metadata return `1` without diagnostics. | | gh (stable since `2.0.0`; private process implementation dependency shipped in `2.1.0`) | `base_gh_require_cli`, `base_gh_auth_status_diagnostics`, `base_gh_report_command_failure`, `base_gh_run`, `base_gh_repo_from_remote_url`, `base_gh_infer_repo_from_origin`, `base_gh_repo_default_branch`, `base_gh_api_with_retry` | Usage and contract errors return `2`; recoverable GitHub failures return `1` unless the helper preserves the underlying `gh` status. Diagnostics go stderr; repository/API values use named outputs; retries are bounded and mutation-aware. The public API remains compatible through v2.x; the private process dependency is included in the v2.1.0 artifact. | | str | `base_str_lower`, `base_str_upper`, `base_str_ltrim`, `base_str_rtrim`, `base_str_trim`, `base_str_contains`, `base_str_starts_with`, `base_str_ends_with`, `base_str_split`, `base_str_join` | String transforms/predicates preserve caller values until validation succeeds; split/join use validated named outputs. | | arg | `base_arg_parse` | Parses into caller-owned validated arrays/maps and leaves them unchanged on failure. | diff --git a/lib/bash/git/README.md b/lib/bash/git/README.md index 0ea22a7..f8d461e 100644 --- a/lib/bash/git/README.md +++ b/lib/bash/git/README.md @@ -76,9 +76,11 @@ base_git_worktree_path_for_branch --result worktree_path feature/topic "$PWD" command substitution necessarily strips those bytes from stdout. - `base_git_worktree_path_is_valid` is intentionally limited to linked worktrees: it requires a `.git` file and a successful `git -C - rev-parse --is-inside-work-tree` probe. A `.git` directory is an ordinary - repository checkout, not a linked worktree, and a `.git` file whose Git - administrative record is stale is invalid. + rev-parse --is-inside-work-tree` probe whose resolved Git directory is below + the repository's `worktrees/` administrative directory. A `.git` directory + is an ordinary repository checkout, a submodule `.git` file points below + `modules/`, and a `.git` file whose Git administrative record is stale; all + three cases are invalid linked worktrees. - `base_git_update_repo` only attempts updates when the checked-out branch is the detected default branch, or an explicit expected branch passed by the caller. - `base_git_update_repo` requires `git_repo` to be the repository root (including a symlink that resolves to that root); it rejects ordinary descendants rather diff --git a/lib/bash/git/lib_git.sh b/lib/bash/git/lib_git.sh index 457780f..b5639ea 100644 --- a/lib/bash/git/lib_git.sh +++ b/lib/bash/git/lib_git.sh @@ -168,9 +168,22 @@ base_git_worktree_path_is_valid() { fi local path="$1" + local git_dir common_dir git_dir_abs common_dir_abs worktrees_dir [[ -d "$path" && -f "$path/.git" ]] || return 1 git -C "$path" rev-parse --is-inside-work-tree > /dev/null 2>&1 || return 1 + + git_dir="$(git -C "$path" rev-parse --git-dir 2> /dev/null)" || return 1 + common_dir="$(git -C "$path" rev-parse --git-common-dir 2> /dev/null)" || return 1 + git_dir_abs="$(cd -- "$git_dir" 2> /dev/null && pwd -P)" || return 1 + common_dir_abs="$(cd -- "$common_dir" 2> /dev/null && pwd -P)" || return 1 + worktrees_dir="$common_dir_abs/worktrees" + + [[ -d "$worktrees_dir" ]] || return 1 + case "$git_dir_abs" in + "$worktrees_dir"/*) return 0 ;; + *) return 1 ;; + esac } base_git_list_worktree_branches() { diff --git a/lib/bash/git/tests/lib_git.bats b/lib/bash/git/tests/lib_git.bats index b994f60..2c23f40 100644 --- a/lib/bash/git/tests/lib_git.bats +++ b/lib/bash/git/tests/lib_git.bats @@ -230,6 +230,22 @@ setup() { ! base_git_worktree_path_is_valid "$TEST_TMPDIR/missing" } +@test "base_git_worktree_path_is_valid rejects submodule checkouts" { + local source_repo="$TEST_TMPDIR/submodule-source" + local repo="$TEST_TMPDIR/repo" + + init_git_repo "$source_repo" + printf 'submodule\n' > "$source_repo/data.txt" + commit_all "$source_repo" "Initial submodule commit" + + init_git_repo "$repo" + printf 'superproject\n' > "$repo/data.txt" + commit_all "$repo" "Initial superproject commit" + git -C "$repo" -c protocol.file.allow=always submodule add "$source_repo" submodule >/dev/null 2>&1 + + ! base_git_worktree_path_is_valid "$repo/submodule" +} + @test "base_git_worktree_path_is_valid suppresses stale metadata diagnostics" { local stale="$TEST_TMPDIR/stale-worktree"