Skip to content

fix: use pure Go filesystem traversal for git repo detection (fixes macOS ARM64 / Rosetta 2 failure)#31050

Merged
pelikhan merged 9 commits intomainfrom
copilot/fix-gh-aw-init-error
May 8, 2026
Merged

fix: use pure Go filesystem traversal for git repo detection (fixes macOS ARM64 / Rosetta 2 failure)#31050
pelikhan merged 9 commits intomainfrom
copilot/fix-gh-aw-init-error

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 8, 2026

gh aw init (and any command calling isGitRepo() / FindGitRoot()) fails with "not in a git repository" on macOS ARM64 when the x86_64 binary runs under Rosetta 2 — the exec.Command("git", "rev-parse", ...) subprocess call silently fails in that environment even when the user is inside a valid git repo.

Changes

  • pkg/gitutil/gitutil.go — Replace FindGitRoot() implementation: swap exec.Command("git", "rev-parse", "--show-toplevel") for os.Stat + filepath.Dir upward traversal. FindGitRootFrom(startDir string) now normalizes startDir to an absolute path via filepath.Abs, surfaces non-ErrNotExist stat errors (e.g. permission denied), and validates .git as either a directory (normal repo) or a regular file beginning with gitdir: (worktree / submodule pointer) — surfacing read errors instead of silently skipping an unreadable .git file.

  • pkg/cli/git.goisGitRepo() now delegates to gitutil.FindGitRoot() instead of its own exec.Command("git", "rev-parse", "--git-dir") call. findGitRootForPath() uses gitutil.FindGitRootFrom(dir).

  • pkg/gitutil/gitutil_test.goTestFindGitRootFrom extended to cover: start from repo root, start from nested subdir, start outside any repo, .git is a valid worktree marker file (detected from root and subdirectory), .git file without gitdir: prefix is not accepted, and relative path input (".") resolves correctly.

All actual git operations (commit, push, status, add, log) still invoke the git binary — only repository detection is changed.

// Before — fails silently under Rosetta 2
func isGitRepo() bool {
    cmd := exec.Command("git", "rev-parse", "--git-dir")
    return cmd.Run() == nil
}

// After — pure Go, no subprocess
func isGitRepo() bool {
    _, err := gitutil.FindGitRoot()
    return err == nil
}

[!WARNING]

Copilot AI and others added 3 commits May 8, 2026 15:57
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/82628923-30ee-4061-af73-61b141aba2e4

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
…rsal

Fixes `gh aw init` failing with "not in a git repository" on macOS ARM64
(Apple Silicon) when the x86_64 binary runs under Rosetta 2.

Previously, `isGitRepo()` and `FindGitRoot()` used `exec.Command("git", ...)`
to detect git repositories. Under Rosetta 2 (x86_64 emulation on ARM64), this
subprocess call can silently fail due to PATH differences or OS-level subprocess
restrictions.

Replace with pure Go filesystem traversal: walk up from the current directory
(or a given starting directory) looking for a `.git` entry (directory or file,
handling both normal repos and worktrees). This eliminates the dependency on
the `git` executable for repository detection, making it work regardless of
architecture, Rosetta translation, or git installation state.

Changes:
- `pkg/gitutil/gitutil.go`: `FindGitRoot()` now uses `os.Stat`/`filepath.Dir`
  traversal; new exported `FindGitRootFrom(startDir)` helper for callers that
  start from a specific directory
- `pkg/cli/git.go`: `isGitRepo()` delegates to `gitutil.FindGitRoot()`;
  `findGitRootForPath()` uses `gitutil.FindGitRootFrom(dir)` instead of exec
- `pkg/gitutil/gitutil_test.go`: add `TestFindGitRootFrom` tests
- `pkg/gitutil/spec_test.go`: update doc comment to reflect new implementation

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/82628923-30ee-4061-af73-61b141aba2e4

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
…t robustness

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/82628923-30ee-4061-af73-61b141aba2e4

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix 'gh aw init' fails on macOS ARM64 fix: use pure Go filesystem traversal for git repo detection (fixes macOS ARM64 / Rosetta 2 failure) May 8, 2026
Copilot AI requested a review from gh-aw-bot May 8, 2026 16:11
@pelikhan pelikhan marked this pull request as ready for review May 8, 2026 17:10
Copilot AI review requested due to automatic review settings May 8, 2026 17:10
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates git repository detection to avoid invoking the git executable (addressing macOS ARM64 Rosetta 2 failures) by switching to filesystem-based traversal and centralizing the logic in pkg/gitutil.

Changes:

  • Replaced FindGitRoot() implementation to locate repo roots by walking up the directory tree looking for a .git entry.
  • Added FindGitRootFrom(startDir) and updated CLI git-repo detection helpers to use gitutil instead of subprocess calls.
  • Added tests for FindGitRootFrom and updated the public API spec text accordingly; lockfile workflows were also modified.
Show a summary per file
File Description
pkg/gitutil/gitutil.go Implements filesystem-based git root discovery and adds FindGitRootFrom.
pkg/cli/git.go Switches repo detection/root lookup to gitutil.FindGitRoot* helpers.
pkg/gitutil/gitutil_test.go Adds test coverage for FindGitRootFrom.
pkg/gitutil/spec_test.go Updates the spec wording for FindGitRoot to match new behavior.
.github/workflows/spec-librarian.lock.yml Updates pinned action version annotations/manifest metadata (now inconsistent in-file).
.github/workflows/copilot-pr-nlp-analysis.lock.yml Updates pinned action version annotations/manifest metadata (now inconsistent/duplicated in-file).

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comments suppressed due to low confidence (3)

.github/workflows/spec-librarian.lock.yml:470

  • This step is annotated as # v9 while other actions/github-script steps in the same lockfile are annotated # v9.0.0 (e.g., line 452). Please keep the version annotations consistent (ideally by regenerating the lockfile) to avoid confusion and drift.
      - name: Determine automatic lockdown mode for GitHub MCP Server
        id: determine-automatic-lockdown
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9

.github/workflows/copilot-pr-nlp-analysis.lock.yml:53

  • The "Custom actions used" section now lists the same actions/github-script SHA twice with different versions (v9 and v9.0.0). This looks like an inconsistent regeneration and should be deduplicated/standardized so the lockfile accurately describes what is pinned.
#   - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
#   - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
#   - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
#   - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0

.github/workflows/copilot-pr-nlp-analysis.lock.yml:522

  • This step is annotated as # v9 while other actions/github-script steps in the same lockfile are annotated # v9.0.0. Please keep version annotations consistent (ideally via lockfile regeneration) to avoid drift.
      - name: Determine automatic lockdown mode for GitHub MCP Server
        id: determine-automatic-lockdown
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
  • Files reviewed: 6/6 changed files
  • Comments generated: 4

Comment on lines +297 to +301
func TestFindGitRootFrom(t *testing.T) {
t.Run("returns git root from the repository root itself", func(t *testing.T) {
gitRoot, err := FindGitRoot()
require.NoError(t, err, "must be inside a git repository")

@@ -1,5 +1,5 @@
# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"b21c866096e7bf37ad8390647a4e60737201a07a362b4a013fe73ebdfe63b049","strict":true,"agent_id":"copilot"}
# gh-aw-manifest: {"version":1,"secrets":["GH_AW_AGENT_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41","digest":"sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41","digest":"sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.41","digest":"sha256:62171f2fa508667b8b0a9e096f826983f312e3da0ce894f80c0f83a875af60fe","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.41@sha256:62171f2fa508667b8b0a9e096f826983f312e3da0ce894f80c0f83a875af60fe"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41","digest":"sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"ghcr.io/github/serena-mcp-server:latest","digest":"sha256:bf343399e3725c45528f531a230f3a04521d4cdef29f9a5af6282ff0d3c393c5","pinned_image":"ghcr.io/github/serena-mcp-server:latest@sha256:bf343399e3725c45528f531a230f3a04521d4cdef29f9a5af6282ff0d3c393c5"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]}
# gh-aw-manifest: {"version":1,"secrets":["GH_AW_AGENT_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41","digest":"sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41","digest":"sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.41","digest":"sha256:62171f2fa508667b8b0a9e096f826983f312e3da0ce894f80c0f83a875af60fe","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.41@sha256:62171f2fa508667b8b0a9e096f826983f312e3da0ce894f80c0f83a875af60fe"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41","digest":"sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"ghcr.io/github/serena-mcp-server:latest","digest":"sha256:bf343399e3725c45528f531a230f3a04521d4cdef29f9a5af6282ff0d3c393c5","pinned_image":"ghcr.io/github/serena-mcp-server:latest@sha256:bf343399e3725c45528f531a230f3a04521d4cdef29f9a5af6282ff0d3c393c5"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]}
@@ -1,5 +1,5 @@
# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"e680d07d85ca99dc6dc3ee4be59cdb3c9d1c49e7bf28efa85f1641c408445d8c","strict":true,"agent_id":"copilot"}
# gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/setup-python","sha":"a309ff8b426b58ec0e2a45f0f869d46889d02405","version":"v6.2.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41","digest":"sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41","digest":"sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41","digest":"sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]}
# gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/setup-python","sha":"a309ff8b426b58ec0e2a45f0f869d46889d02405","version":"v6.2.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41","digest":"sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41","digest":"sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41","digest":"sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]}
Comment thread pkg/gitutil/gitutil.go
Comment on lines +107 to +113
func FindGitRootFrom(startDir string) (string, error) {
dir := startDir
for {
if _, err := os.Stat(filepath.Join(dir, ".git")); err == nil {
return dir, nil
}
parent := filepath.Dir(dir)
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

🧪 Test Quality Sentinel Report

Test Quality Score: 90/100

Excellent test quality

Metric Value
New/modified tests analyzed 3 top-level functions (7 subtests)
✅ Design tests (behavioral contracts) 3 (100%)
⚠️ Implementation tests (low value) 0 (0%)
Tests with error/edge cases 2 of 3 (67%)
Duplicate test clusters 0
Test inflation detected No (ratio 1.06×)
🚨 Coding-guideline violations None

Test Classification Details

The PR modifies pkg/gitutil/gitutil_test.go (+38 lines) and pkg/gitutil/spec_test.go (+3/-2 lines). The core fix replaces git rev-parse subprocess calls with pure Go .git directory traversal; the new tests validate that new behaviour.

View per-test classification table
Test File Classification Issues Detected
TestFindGitRoot pkg/gitutil/gitutil_test.go ✅ Design Happy-path only; error path delegated to TestFindGitRootFrom
TestFindGitRootFrom (from root) pkg/gitutil/gitutil_test.go ✅ Design Verifies idempotency: starting at root returns root
TestFindGitRootFrom (from subdir) pkg/gitutil/gitutil_test.go ✅ Design Verifies upward traversal from a temp subdir
TestFindGitRootFrom (error – non-repo dir) pkg/gitutil/gitutil_test.go ✅ Design Edge case: explicit error path + error message assertion
TestReadFileFromHEADWithRoot (reads file) pkg/gitutil/gitutil_test.go ✅ Design Verifies correct content returned from HEAD
TestReadFileFromHEADWithRoot (path outside root) pkg/gitutil/gitutil_test.go ✅ Design Edge case: path-traversal rejection
TestReadFileFromHEADWithRoot (empty gitRoot) pkg/gitutil/gitutil_test.go ✅ Design Edge case: empty-string input guard

Flagged Tests — Requires Review

i️ TestFindGitRoot — happy-path only

Classification: Design test (high value), but missing an explicit error path.

Issue: FindGitRoot() is a thin wrapper around FindGitRootFrom(os.Getwd()). Its own error path ("not in a git repo") is tested indirectly via TestFindGitRootFrom. This is acceptable given the function's simplicity, but a direct error test (e.g., changing the working directory to t.TempDir() before calling) would make the contract self-contained.

Impact if deleted: Low — TestFindGitRootFrom already covers the error branch. Deleting TestFindGitRoot would leave the wrapper itself untested.

Suggested improvement (non-blocking): Add a subtest that changes os.Chdir to a temp directory with no .git ancestor and asserts the returned error, or explicitly document that the error path is covered by TestFindGitRootFrom.


Score Breakdown

Component Points Notes
Behavioral Coverage (40 pts) 40 3/3 tests are design tests (100%)
Error/Edge Case Coverage (30 pts) 20 2/3 top-level functions explicitly test error paths
Low Duplication (20 pts) 20 No copy-paste clusters detected
Proportional Growth (10 pts) 10 Test:prod ratio = 1.06 (well below 2:1 threshold)

Language Support

Tests analyzed:

  • 🐹 Go (*_test.go): 7 subtests across 3 functions — unit (//go:build !integration) ✅
  • 🟨 JavaScript (*.test.cjs, *.test.js): 0 tests changed in this PR

Verdict

Check passed. 0% of new tests are implementation tests (threshold: 30%). Build tags are present on all new/modified test files. No mock libraries detected. All tests exercise observable behavioral contracts of the new pure-Go git root detection.


📖 Understanding Test Classifications

Design Tests (High Value) verify what the system does:

  • Assert on observable outputs, return values, or state changes
  • Cover error paths and boundary conditions
  • Would catch a behavioral regression if deleted
  • Remain valid even after internal refactoring

Implementation Tests (Low Value) verify how the system does it:

  • Assert on internal function calls (mocking internals)
  • Only test the happy path with typical inputs
  • Break during legitimate refactoring even when behavior is correct
  • Give false assurance: they pass even when the system is wrong

Goal: Shift toward tests that describe the system's behavioral contract — the promises it makes to its users and collaborators.

References: §25568904322

🧪 Test quality analysis by Test Quality Sentinel · ● 7.3M ·

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: 90/100. Test quality is excellent — 0% of new tests are implementation tests (threshold: 30%). All 7 new subtests exercise behavioral contracts of the new pure-Go git root detection. Build tags present, no mock libraries used.

@pelikhan
Copy link
Copy Markdown
Collaborator

pelikhan commented May 8, 2026

@copilot merge main and recompile

Copilot AI and others added 2 commits May 8, 2026 19:17
…t-error

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 8, 2026

@copilot merge main and recompile

Done — merged latest main (8 commits) and ran make build && make recompile. All 217 workflow lock files were regenerated and tests pass.

Copilot AI requested a review from pelikhan May 8, 2026 19:20
@pelikhan
Copy link
Copy Markdown
Collaborator

pelikhan commented May 8, 2026

@copilot add integration tests for git repos and worktrees

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 8, 2026

@copilot add integration tests for git repos and worktrees

Done in the latest commit. Added four new test cases to TestFindGitRootFrom:

  • Worktree marker file.git is a regular file starting with gitdir:, detected from both the root and a subdirectory
  • Invalid .git file — a .git file without the gitdir: prefix is not treated as a valid repo root
  • Relative path inputFindGitRootFrom(".") resolves correctly via filepath.Abs

Also fixed FindGitRootFrom itself: it now normalizes startDir to an absolute path up front, surfaces non-ErrNotExist stat errors, and propagates os.ReadFile errors instead of silently continuing past an unreadable .git file.

@pelikhan pelikhan merged commit b77112c into main May 8, 2026
18 checks passed
@pelikhan pelikhan deleted the copilot/fix-gh-aw-init-error branch May 8, 2026 19:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

gh aw init fails with "not in a git repository" on macOS ARM64 (Apple Silicon)

4 participants