Skip to content

Run file explorer git status without optional locks - #7173

Merged
austinywang merged 11 commits into
mainfrom
issue-4779-git-status-no-optional-locks
Jul 2, 2026
Merged

austinywang merged 11 commits into
mainfrom
issue-4779-git-status-no-optional-locks

Conversation

@austinywang

@austinywang austinywang commented Jul 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Set GIT_OPTIONAL_LOCKS=0 for file explorer git status probes, including the remote SSH status command path.
  • Split the file-explorer git status helper out of the over-budget store file and make it an instance value with injectable process paths/environment.
  • Parse git status --porcelain=v1 -z output so quoted, escaped, and whitespace-heavy filenames keep the correct file explorer badges.
  • Use segment-aware subtree filtering so status badges do not leak into sibling paths that only share a string prefix.
  • Add regressions for index-byte preservation, quoted/backslash filenames, and sibling-prefix filtering.

Repro notes

  • In a scratch repo, touching a tracked file without changing contents made plain git status --porcelain rewrite .git/index.
  • Running the equivalent command with GIT_OPTIONAL_LOCKS=0 left .git/index byte-identical, which is the lock-free background-refresh behavior cmux needs.

Validation

  • git diff --check
  • python3 scripts/swift_file_length_budget.py --repo-root . --budget .github/swift-file-length-budget.tsv
  • ./scripts/lint-pbxproj-test-wiring.sh
  • ./scripts/check-pbxproj.sh
  • Not run: local tests/builds, per branch instruction; CI should validate the regression/fix.

Fixes #4779

Supersedes #4805 — thanks @SpencerJung for the original diagnosis and one-line local fix; this PR extends it to the SSH variant and adds regression coverage.

@vercel

vercel Bot commented Jul 2, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Canceled Canceled Jul 2, 2026 8:19am
cmux-staging Building Building Preview, Comment Jul 2, 2026 8:19am

@coderabbitai

coderabbitai Bot commented Jul 2, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@austinywang, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 2 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: aa634cad-7036-4de2-94d3-bdacf1527470

📥 Commits

Reviewing files that changed from the base of the PR and between fb3958d and b2e59f3.

📒 Files selected for processing (5)
  • Sources/FileExplorerStore.swift
  • Sources/GitFileStatus.swift
  • Sources/GitStatusProvider.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/FileExplorerGitStatusProviderTests.swift
📝 Walkthrough

Walkthrough

The GitFileStatus and GitStatusProvider implementation is moved from FileExplorerStore.swift into a new standalone Sources/GitStatusProvider.swift, with non-locking Git environment settings preserved. The Xcode project is updated to reference the new source file and a new test file, which verifies status queries do not modify the git index.

Changes

GitStatusProvider extraction and non-locking status queries

Layer / File(s) Summary
Remove inline Git status code
Sources/FileExplorerStore.swift
Deletes the previously inline GitFileStatus/GitStatusProvider implementation from FileExplorerStore.swift.
New GitStatusProvider: types and local/SSH fetching
Sources/GitStatusProvider.swift
Defines GitFileStatus and GitStatusProvider.fetchStatus/fetchStatusSSH, returning absolute-path status maps filtered to the explorer root, using a non-locking git command prefix.
Porcelain parsing and path resolution
Sources/GitStatusProvider.swift
Parses git status --porcelain output into status characters and absolute paths (including rename handling), filters to entries under the explorer root, and marks parent directories as .untracked/.modified.
Process execution helpers
Sources/GitStatusProvider.swift
Adds helpers to resolve repo root and run git/ssh processes with stderr suppressed, setting GIT_OPTIONAL_LOCKS=0 to avoid index locking.
Xcode project wiring
cmux.xcodeproj/project.pbxproj
Adds file references and build-phase entries for the new GitStatusProvider.swift and FileExplorerGitStatusProviderTests.swift files.
Non-locking index test
cmuxTests/FileExplorerGitStatusProviderTests.swift
Adds a test verifying .git/index bytes are unchanged after calling GitStatusProvider.fetchStatus, with temp-repo and git-execution helpers.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant FileExplorerStore
  participant GitStatusProvider
  participant GitProcess
  participant SSHProcess

  FileExplorerStore->>GitStatusProvider: fetchStatus(directory)
  GitStatusProvider->>GitProcess: git rev-parse --show-toplevel (non-locking env)
  GitProcess-->>GitStatusProvider: repoRoot
  GitStatusProvider->>GitProcess: git status --porcelain
  GitProcess-->>GitStatusProvider: porcelain output
  GitStatusProvider-->>FileExplorerStore: absolute-path status map

  FileExplorerStore->>GitStatusProvider: fetchStatusSSH(directory, destination)
  GitStatusProvider->>SSHProcess: remote repo root + git status --porcelain
  SSHProcess-->>GitStatusProvider: marker-split output
  GitStatusProvider-->>FileExplorerStore: absolute-path status map
Loading

Related issues: #4779 (Git status polling interrupts user's git actions) — addressed by moving status fetching to a dedicated provider that sets GIT_OPTIONAL_LOCKS=0 to avoid creating index locks during status queries.

Suggested labels: swift, tests, xcodeproj

Suggested reviewers: (none specified)

🐰 A rabbit dug the status code free,
Gave it a burrow of its own to be,
No more locks upon your rebase quest,
The index stays calm, untouched, at rest,
Hop, test, and merge — a tidy git decree!


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Cmux Swift File And Package Boundaries ❌ Error GitStatusProvider is 201 lines of independent git/protocol logic in app-target Sources/, with a matching CmuxGit package already present. Move GitStatusProvider/GitFileStatus into a small SwiftPM package target (e.g. CmuxGit) and keep FileExplorerStore as a thin consumer.
Description check ⚠️ Warning The PR description covers summary and validation, but it omits the required Testing, Demo Video, Review Trigger, and Checklist sections from the template. Add the missing template sections, including a Testing section, Demo Video URL or N/A, the review-trigger block, and the checklist items.
✅ Passed checks (23 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: running file explorer git status without optional locks.
Linked Issues check ✅ Passed The PR satisfies #4779 by making git status probes non-locking, including SSH, and adds a regression test for index stability.
Out of Scope Changes check ✅ Passed The file split, project wiring, and test addition are all in scope for the non-locking git status fix.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Cmux Swift Actor Isolation ✅ Passed The PR adds a value-type GitStatusProvider and keeps FileExplorerStore mutations on the main queue; it doesn’t introduce new actor-isolation debt.
Cmux Swift Blocking Runtime ✅ Passed No new production semaphores/sleeps/locks/polling; the process waits are the moved git helper, and the new waitUntilExit is test-only.
Cmux Browser Automation Off-Main ✅ Passed PR only changes file-explorer git status code and tests; no browser.* socket automation, WebKit/AppKit waits, or control-socket policy paths were modified.
Cmux Expensive Synchronous Load ✅ Passed PR adds off-main git-status polling and a regression test; it does not introduce any agent-history loader or other expensive sync load on main/interactive paths.
Cmux Cache Substitution Correctness ✅ Passed The change only refactors git-status polling and sets GIT_OPTIONAL_LOCKS=0 for a transient UI map, not a persisted cache/path swap.
Cmux No Hacky Sleeps ✅ Passed No touched non-Swift runtime/build file adds sleeps or timers; the only asyncAfter found is a pre-existing Swift debounce, which is out of scope.
Cmux Algorithmic Complexity ✅ Passed Git status parsing is linear over porcelain entries; no nested full-collection scans, repeated sorting, or batch rescans were introduced.
Cmux Swift Concurrency ✅ Passed The diff adds a synchronous Process-based GitStatusProvider and tests; no new DispatchQueue, Combine, completion-handler, or fire-and-forget Task patterns were introduced.
Cmux Swift @Concurrent ✅ Passed No new nonisolated async work lacks @concurrent; Git status work is synchronous and already hopped to a background queue before running from UI code.
Cmux Swiftpm Lockfiles ✅ Passed pbxproj changes only add source/test file wiring; no Package.resolved, Package.swift dependency, or .gitignore lockfile-policy changes were touched.
Cmux Swift Logging ✅ Passed PASS: The added/updated Swift files introduce no print/debugPrint/dump/NSLog/Logger calls, and the new provider/test code contains no ad hoc or sensitive logging.
Cmux User-Facing Error Privacy ✅ Passed The diff only moves internal git-status logic and adds tests; no new user-facing errors, alerts, or output text were introduced.
Cmux Full Internationalization ✅ Passed Touched production strings use String(localized:defaultValue:), and matching xcstrings entries already exist for the affected keys; no new unlocalized user-facing text was added.
Cmux Swiftui State Layout ✅ Passed PR only adds a git-status provider/test and moves code out of FileExplorerStore; no new SwiftUI view-boundary, GeometryReader, or lazy-row store pattern is introduced.
Cmux Architecture Rethink ✅ Passed PASS: The PR extracts GitStatusProvider and adds GIT_OPTIONAL_LOCKS=0 to existing status probes; it introduces no new timing, observer, or state-ownership pattern.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR only adds git-status provider/test code; touched files contain no NSWindow/NSPanel/WindowGroup code or cmuxAuxiliaryWindowIdentifiers changes.
Cmux Source Artifacts ✅ Passed Only submodule gitlink updates (ghostty, vendor/bonsplit) changed; no logs, caches, build output, or scratch artifacts were added.
Cmux No Test Or Debug Seam In Production Source ✅ Passed No new DEBUG/test-only seam was added under Sources; GitStatusProvider is a real implementation and the test-only code stays in cmuxTests.
Cmux No Ambient Global State ✅ Passed GitStatusProvider is constructable and injected via init; the diff adds no new top-level func/var, singleton, or static-only namespace.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-4779-git-status-no-optional-locks

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 2, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes the file-explorer git status probe from acquiring optional git locks (rewriting .git/index on a stat-only touch) by setting GIT_OPTIONAL_LOCKS=0 for both local and remote-SSH paths, and extends the fix with correct null-delimited --porcelain=v1 -z parsing to handle quoted, escaped, and whitespace-heavy filenames. The previous caseless-enum static-method namespace is replaced with an injectable struct GitStatusProvider: Sendable, and sibling-prefix filtering is corrected from a string-prefix check to a segment-aware containment test.

  • Lock-free probing: GIT_OPTIONAL_LOCKS=0 is set via process.environment for local git runs and via env … git in the remote shell command for SSH paths; the existing DispatchQueue.global dispatch pattern is unchanged.
  • Null-delimited parsing: Switching to -z removes all quoting/escaping from git's output; the parser correctly handles the two-entry protocol for renamed and copied files via statusUsesSecondPath.
  • Segment-aware filtering: path(_:isContainedIn:) appends / before hasPrefix so work/ no longer bleeds status badges into workspace-sibling/.

Confidence Score: 5/5

Safe to merge; the changes are a well-contained extraction and correctness fix with no regressions on the core git-status path.

The core logic is correct and well-covered: three real-filesystem tests (index byte-identity, quoted filenames, sibling filtering) plus two fake-executable tests for environment propagation and type-T/UU mapping. The pipe-before-waitUntilExit ordering in the new runGit/runSSH is the safe pattern. The only gap is that the fake-git case match in one test ignores the -z argument, so a future removal of -z would go undetected by that test — a test coverage nit, not a production defect.

cmuxTests/FileExplorerGitStatusProviderTests.swift — the statusQueryMapsTypeChangedAndUnmergedEntries fake-git script case match should be tightened to include -z.

Important Files Changed

Filename Overview
Sources/GitStatusProvider.swift New injectable struct with GIT_OPTIONAL_LOCKS=0, null-delimited porcelain=v1 parsing, and segment-aware path containment; all logic is correct.
Sources/GitFileStatus.swift Trivial enum split out from FileExplorerStore.swift; gains Equatable conformance needed by tests.
Sources/FileExplorerStore.swift Removes the static-namespace GitStatusProvider, replaces call sites with the injected instance; pre-existing DispatchQueue.global pattern unchanged.
cmuxTests/FileExplorerGitStatusProviderTests.swift Good regression coverage for index preservation, quoted filenames, and sibling filtering; the fake-git case match in statusQueryMapsTypeChangedAndUnmergedEntries does not assert the -z flag was forwarded.
cmux.xcodeproj/project.pbxproj Wires the two new source files and the new test file into the correct targets; no stray or missing references.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant FES as FileExplorerStore
    participant GSP as GitStatusProvider
    participant Git as /usr/bin/git<br/>(GIT_OPTIONAL_LOCKS=0)
    participant SSH as /usr/bin/ssh

    Note over FES: DispatchQueue.global(.utility)

    alt Local filesystem
        FES->>GSP: fetchStatus(directory:)
        GSP->>Git: rev-parse --show-toplevel
        Git-->>GSP: repoRoot
        GSP->>Git: "status --porcelain=v1 -z"
        Git-->>GSP: NUL-delimited entries
        GSP->>GSP: parseGitStatus() segment-aware path filter
        GSP-->>FES: [absolutePath: GitFileStatus]
    else Remote SSH
        FES->>GSP: fetchStatusSSH(directory:destination:...)
        GSP->>SSH: "cd && env GIT_OPTIONAL_LOCKS=0 git rev-parse && echo sentinel && env git status --porcelain=v1 -z"
        SSH-->>GSP: repoRoot + sentinel + NUL-delimited entries
        GSP->>GSP: split on sentinel then parseGitStatus()
        GSP-->>FES: [absolutePath: GitFileStatus]
    end

    FES->>FES: "DispatchQueue.main.async gitStatusByPath = status"
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant FES as FileExplorerStore
    participant GSP as GitStatusProvider
    participant Git as /usr/bin/git<br/>(GIT_OPTIONAL_LOCKS=0)
    participant SSH as /usr/bin/ssh

    Note over FES: DispatchQueue.global(.utility)

    alt Local filesystem
        FES->>GSP: fetchStatus(directory:)
        GSP->>Git: rev-parse --show-toplevel
        Git-->>GSP: repoRoot
        GSP->>Git: "status --porcelain=v1 -z"
        Git-->>GSP: NUL-delimited entries
        GSP->>GSP: parseGitStatus() segment-aware path filter
        GSP-->>FES: [absolutePath: GitFileStatus]
    else Remote SSH
        FES->>GSP: fetchStatusSSH(directory:destination:...)
        GSP->>SSH: "cd && env GIT_OPTIONAL_LOCKS=0 git rev-parse && echo sentinel && env git status --porcelain=v1 -z"
        SSH-->>GSP: repoRoot + sentinel + NUL-delimited entries
        GSP->>GSP: split on sentinel then parseGitStatus()
        GSP-->>FES: [absolutePath: GitFileStatus]
    end

    FES->>FES: "DispatchQueue.main.async gitStatusByPath = status"
Loading

Reviews (7): Last reviewed commit: "Address git status provider review feedb..." | Re-trigger Greptile

Comment thread cmuxTests/FileExplorerStoreTests.swift Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 2 files

Re-trigger cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

3 issues found across 5 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread Sources/GitStatusProvider.swift Outdated
Comment thread Sources/GitStatusProvider.swift Outdated
Comment thread Sources/GitStatusProvider.swift Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmuxTests/FileExplorerGitStatusProviderTests.swift`:
- Around line 46-66: Simplify runGit by replacing the `#expect` plus manual
GitSetupFailure throw with a single `#require` boolean check that includes the
same failure message; use the runGit helper as the target and remove the
now-unused GitSetupFailure type entirely.

In `@Sources/GitStatusProvider.swift`:
- Around line 62-68: The status filter in GitStatusProvider currently uses a
simple hasPrefix(explorerRoot) check, which can incorrectly include sibling
paths that only share the same name prefix. Update the absolutePath containment
check to use the same boundary-aware contract as
FileExplorerStore.path(_:isContainedIn:)—only accept paths that are exactly the
explorer root or start with the root plus a path separator—then keep the
existing statusMap assignment and markParentDirectories call for truly contained
paths.
- Line 12: The remote git invocation in GitStatusProvider currently relies on
shell-specific variable assignment, which breaks on csh/tcsh remotes. Update the
nonLockingRemoteGitCommand constant to invoke git through env so the
nonLockingGitEnvironmentKey is set in a shell-agnostic way, and keep the change
localized to the GitStatusProvider command-building logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4c9914f4-de8f-4886-b262-9aae55c58f2c

📥 Commits

Reviewing files that changed from the base of the PR and between 70de366 and fb3958d.

📒 Files selected for processing (4)
  • Sources/FileExplorerStore.swift
  • Sources/GitStatusProvider.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/FileExplorerGitStatusProviderTests.swift
💤 Files with no reviewable changes (1)
  • Sources/FileExplorerStore.swift

Comment thread cmuxTests/FileExplorerGitStatusProviderTests.swift
Comment thread Sources/GitStatusProvider.swift Outdated
Comment thread Sources/GitStatusProvider.swift Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

3 issues found across 5 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread Sources/GitFileStatus.swift
Comment thread Sources/GitStatusProvider.swift
Comment thread Sources/FileExplorerStore.swift Outdated
@austinywang
austinywang merged commit 553fe35 into main Jul 2, 2026
38 checks passed

This branch was successfully deployed

1 active deployment
Preview – cmux — b2e59f31 Deployed Jul 2, 2026 by vercel[bot]
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.

Git status polling interrupts user's git actions

1 participant