diff --git a/.abcd/work/issues/open/iss-72-core-isgitrepo-misses-worktrees.md b/.abcd/work/issues/resolved/iss-72-core-isgitrepo-misses-worktrees.md similarity index 66% rename from .abcd/work/issues/open/iss-72-core-isgitrepo-misses-worktrees.md rename to .abcd/work/issues/resolved/iss-72-core-isgitrepo-misses-worktrees.md index c9b64a86..64e68339 100644 --- a/.abcd/work/issues/open/iss-72-core-isgitrepo-misses-worktrees.md +++ b/.abcd/work/issues/resolved/iss-72-core-isgitrepo-misses-worktrees.md @@ -7,6 +7,7 @@ category: "bug" source: "agent-finding" found_during: "clean-slate-sweep" found_at: "internal/core/core.go" +resolution: "core.Status now tests .git existence (file or dir) not dir-ness, so a linked worktree/submodule (where .git is a gitfile) reports IsGitRepo=true. os.Stat is correct (rejects dangling symlinks). ruthless SHIP; scoped to IsGitRepo only. PR opened." --- core.Status.IsGitRepo=false in a linked git worktree or submodule: isDir(.git) requires a directory but .git is a regular gitfile (gitlink) in worktrees/submodules, so a genuine checkout reports not-a-git-repo (core.go:47, C1). Detector: an exists-not-isDir check for .git with a worktree fixture. Corpus: C1. \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 4985c7dd..206cf1b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -133,6 +133,11 @@ called out in a **Breaking** section. - The `created` and `updated` frontmatter fields on issues. Git is the canonical source of an issue's timeline; the ledger no longer duplicates it. +### Fixed + +- `abcd` status now reports `IsGitRepo` correctly in a linked git worktree or a + submodule, where `.git` is a regular gitfile rather than a directory (iss-72). + ### Security - **Release receipt-gate hardening** (iss-70). The `receipt_gate` record-lint diff --git a/internal/core/core.go b/internal/core/core.go index e2023256..6137f28e 100644 --- a/internal/core/core.go +++ b/internal/core/core.go @@ -43,8 +43,11 @@ func Status(dir string) (StatusInfo, error) { return StatusInfo{}, err } s := StatusInfo{ - Dir: abs, - IsGitRepo: isDir(filepath.Join(abs, ".git")), + Dir: abs, + // .git is a directory in a normal clone but a regular gitfile in a linked + // worktree or submodule — both are genuine checkouts, so test existence, not + // dir-ness. HasRecord/WorkTiers stay dir-only (those must be directories). + IsGitRepo: exists(filepath.Join(abs, ".git")), HasRecord: isDir(filepath.Join(abs, ".abcd", "development")), } for _, tier := range []struct{ path, name string }{ @@ -63,3 +66,11 @@ func isDir(p string) bool { fi, err := os.Stat(p) return err == nil && fi.IsDir() } + +// exists reports whether p exists (a file or a directory). A .git gitfile in a +// worktree/submodule is a regular file, so a plain existence check is the correct +// "is this a git checkout" test. +func exists(p string) bool { + _, err := os.Stat(p) + return err == nil +} diff --git a/internal/core/core_test.go b/internal/core/core_test.go index a6e94317..52c600e7 100644 --- a/internal/core/core_test.go +++ b/internal/core/core_test.go @@ -30,6 +30,23 @@ func TestStatusBareDir(t *testing.T) { } } +// TestStatusGitfileWorktree (iss-72) proves a linked worktree or submodule — +// where .git is a regular gitfile, not a directory — is still reported as a git +// repo. isDir would report false for a genuine checkout. +func TestStatusGitfileWorktree(t *testing.T) { + dir := t.TempDir() + if err := os.WriteFile(filepath.Join(dir, ".git"), []byte("gitdir: /somewhere/.git/worktrees/wt\n"), 0o644); err != nil { + t.Fatal(err) + } + s, err := Status(dir) + if err != nil { + t.Fatal(err) + } + if !s.IsGitRepo { + t.Fatal("a worktree/submodule .git gitfile must report IsGitRepo=true") + } +} + func TestStatusWithRecordAndGit(t *testing.T) { dir := t.TempDir() mustMkdir(t, filepath.Join(dir, ".git"))