Skip to content

[Worktree] Prune worktrees, and lock or unlock them (part 3) - #723

Merged
hannesa2 merged 4 commits into
masterfrom
prune-and-lock-worktrees
Sep 26, 2026
Merged

hannesa2 merged 4 commits into
masterfrom
prune-and-lock-worktrees

Conversation

@goneng

@goneng goneng commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

Slice 3 of #675: prune worktrees, and lock or unlock them, with a check of git's version that the remaining slice builds on.

Changes

  • The menu of a branch another worktree holds is the same in the sidebar and on its label in the history list, and gains the worktree actions as a group of their own:

    Open Worktree of Branch "X"
    Copy name "X"
    ----
    Reveal Worktree in Finder
    Lock Worktree... / Unlock Worktree
    ----
    (branch items, unchanged)
    

    A detached worktree has no branch, so its sidebar row offers Lock or Unlock and Reveal Worktree in Finder alone. Lock asks for an optional reason on a sheet; git keeps it and the row's tooltip shows it. The main worktree's Lock is disabled and says why, since git refuses to lock it.

  • The WORKTREES header gains Prune Worktrees..., which shows git's own dry run (git worktree prune --dry-run --verbose) before anything is removed. It is enabled when a worktree is prunable or its folder is gone; moving a folder away touches nothing in .git, so the menu checks the folders itself rather than wait for the list to be re-read.

  • Locked and unavailable worktrees are marked, following JetBrains' IDEs, which show nothing until a worktree is locked or prunable. A locked row gets a lock where the checkmark goes, without the checkmark's pill but in the pill's colour, so it follows the checkmark through every state: row selected or not, window active or not. A row git reports prunable, or whose folder is not there, is dimmed and says which in its tooltip. Double-clicking one, in the sidebar or on a branch label, explains the missing folder (connect the disk, or prune) rather than failing to open it.

  • Git version check: GitX keeps the version it read when it accepted the git binary, and each action checks it against what it needs.

  • Fix a crash when no git binary is found, in its own commit: the worktree lookup called a method Show every worktree in the sidebar as the branch it holds #709 left declared but never implemented. The commit also drops the other declaration Show every worktree in the sidebar as the branch it holds #709 left without an implementation.

Screenshots

Lock Worktree

image

Worktree is Locked

image

Older git

GitX accepts any git from 1.6.0 (MIN_GIT_VERSION), but the worktree commands arrived later. Checked against builtin/worktree.c at each git tag:

Feature First git version
worktree add, prune 2.5
worktree list --porcelain 2.7
lock / unlock, with --reason 2.10
remove, move 2.17
locked / prunable lines in the porcelain 2.31
list -z 2.36

As agreed on the issue (comment):

  • Before 2.10, Lock is shown disabled with a tooltip naming the version needed and the one found.
  • Before 2.31 git does not say whether a worktree is locked or prunable, so both Lock and Unlock are offered, and Prune is always enabled; its dry run decides.
  • Nothing falls back to editing .git/worktrees by hand, the porcelain stays plain rather than -z, and MIN_GIT_VERSION is unchanged.

Apple's git on macOS 13 and later is new enough for all of it; the checks matter for an older git picked up from PATH or set in the preferences.

Test plan

  • make unit-test: 263 tests, 43 of them new, among them the version boundaries (2.9.5/2.10, 2.30.9/2.31) as plain strings, lock, unlock and prune against a real repository with a linked worktree, and the lock taking the pill's colour in all four states and when the window goes inactive. Each of the four commits passes on its own.
  • In the Debug build, on a scratch repository with two linked worktrees: lock through the sheet with a reason, unlock, Reveal Worktree in Finder, and prune after moving a worktree's folder away, checking git worktree list after each step; the menu order read back from the live sidebar, with an item sent up the responder chain from it; and the lock, the dimmed row and the missing-folder sheet read back from the live app, after moving a worktree's folder away.
  • The missing-folder and no-git fixes each started from a failing test.

@goneng
goneng force-pushed the prune-and-lock-worktrees branch 2 times, most recently from 5af0218 to ccf86bf Compare September 24, 2026 00:07
@goneng goneng changed the title [Worktree] Prune and lock worktrees from the sidebar [Worktree] Prune worktrees, and lock or unlock them Sep 24, 2026
@goneng
goneng force-pushed the prune-and-lock-worktrees branch from 7fc36e9 to 7cb3df3 Compare September 25, 2026 15:06
@goneng
goneng force-pushed the prune-and-lock-worktrees branch from 7cb3df3 to e6d8e84 Compare September 25, 2026 18:49
@goneng
goneng force-pushed the prune-and-lock-worktrees branch from e6d8e84 to 9d0bc62 Compare September 25, 2026 21:54
@goneng
goneng marked this pull request as ready for review September 25, 2026 22:11
@goneng
goneng requested a review from hannesa2 September 25, 2026 22:12
@goneng goneng changed the title [Worktree] Prune worktrees, and lock or unlock them [Worktree] Prune worktrees, and lock or unlock them (part 3) Sep 26, 2026
hannesa2
hannesa2 previously approved these changes Sep 26, 2026

@hannesa2 hannesa2 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice, I like the older git handling

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Unresolved issues remain in menu reachability and gating, unavailable-worktree handling, UI dimming, synchronous Git operations, and nil-reference safety.

Review effort: Lite
Findings: 3 Medium severity

Open (3)
What changed in this PR

Adds Git worktree locking, unlocking, pruning, availability indicators, version gating, and related tests.

Changes:

  • Adds repository APIs and UI actions for worktree management.
  • Marks locked, missing, and prunable worktrees.
  • Adds Git-version handling and comprehensive test coverage.
File Reviewed change
GitXTests/​PBSourceViewBadgeColorTests.m Tests lock badge coloring.
GitXTests/​PBGitWorktreeTests.m Tests worktree state parsing.
GitXTests/​PBGitWorktreeManagementTests.m Tests management actions and menus.
GitXTests/​PBGitSidebarWorktreeGroupTests.m Tests sidebar rendering and interactions.
GitXTests/​PBGitRepositoryWorktreeTests.m Tests repository worktree behavior.
GitXTests/​PBGitRefMenuCopyTests.m Tests shared menu ordering.
GitXTests/​PBGitBinaryVersionTests.m Tests Git version handling.
GitX.xcodeproj/​project.pbxproj Registers new tests.
Classes/​Views/​PBSourceViewBadge.h Exposes badge color calculation.
Classes/​Views/​PBSidebarTableViewCell.m Renders lock and unavailable states.
Classes/​Views/​PBSidebarTableViewCell.h Adds sidebar state properties.
Classes/​git/​PBSourceViewGitWorktreeItem.m Detects unavailable worktrees.
Classes/​git/​PBSourceViewGitWorktreeItem.h Exposes availability state.
Classes/​git/​PBGitWorktree.m Tracks worktree state.
Classes/​git/​PBGitWorktree.h Exposes worktree state.
Classes/​git/​PBGitRepository.m Implements worktree management commands.
Classes/​git/​PBGitRepository.h Declares worktree management APIs.
Classes/​git/​PBGitBinary.m Caches and compares Git versions.
Classes/​git/​PBGitBinary.h Defines version requirements.
Classes/​Controllers/​PBGitWindowController.m Handles worktree operations and sheets.
Classes/​Controllers/​PBGitWindowController.h Declares worktree actions.
Classes/​Controllers/​PBGitSidebarController.m Integrates sidebar menus and states.
Classes/​Controllers/​PBGitHistoryController.m Builds worktree menus and version gates.
Classes/​Controllers/​PBGitHistoryController.h Declares menu APIs.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread Classes/git/PBGitRepository.m Resolved
@goneng

goneng commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator Author

@hannesa2 , I've force-pushed to fix Copilot's Prune finding; the Compare link on the force-push shows the change.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Four moderate unresolved findings remain in worktree availability handling, row rendering, version gating, and sidebar refresh behavior.

Review effort: Lite
Findings: 2 Medium severity

Open (2)
Resolved since last review (3)

Comment thread Classes/Views/PBSidebarTableViewCell.m Resolved
Comment thread Classes/git/PBGitBinary.h
Comment on lines +12 to +14
#define PBGitWorktreePruneVersion "2.5"
#define PBGitWorktreeLockVersion "2.10"
#define PBGitWorktreeStateVersion "2.31"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The listing and its missing version check are already on master (from #629), and this PR does not change them. With git 2.5 or 2.6 the list fails and the WORKTREES group stays empty, which is harmless. Prune only needs 2.5, and it works there because pruning does not use the list. Lock stays disabled until 2.10. A 2.7 gate would only skip one failing process per refresh, so it can go in a separate change if it is ever wanted.

GitX accepts any git from 1.6.0, but the worktree commands arrived
between 2.5 and 2.31, so each action has to know whether the git in use
can perform it. The version table and the plan are on #675.

- Keep the version read when a git binary is accepted, so a git path
  never comes without one and asking for it starts no second process
- Compare a version against a minimum part by part, as numbers
- Name the versions the worktree actions need: 2.5 to prune, 2.10 to
  lock, and 2.31 for git to report lock and prune state
- Word the tooltip for a git too old, naming the version needed and
  the one found
Slice 3 of #675: the actions that keep the worktree list tidy, offered
wherever a worktree's branch is offered, and only as far as the git in
use can carry them out.

- Give Reveal Worktree in Finder and Lock or Unlock a group of their
  own after Open Worktree and Copy name, in the menu the sidebar row and
  the history label share; git refuses to lock the main worktree, so
  there Lock is disabled and says why
- Ask for an optional lock reason on a sheet; git keeps it and the
  row's tooltip shows it
- Offer Prune on the WORKTREES header and confirm it with git's own dry
  run, enabling it when a worktree is prunable or its folder is gone,
  since moving a folder away touches nothing git watches
- Offer both Lock and Unlock, and always Prune, before git 2.31, which
  does not report whether a worktree is locked or prunable
With no git to run, the worktree lookup called -takeWorktreePaths:,
which the move to a worktree model in #709 left declared but never
implemented, so the repository died on an unrecognized selector.

- Store an empty list through -takeWorktrees:, as a lookup that found
  nothing would
- Drop the two declarations #709 left behind with no implementation,
  one of which is what let the call compile
A worktree row looked the same whatever its state, and only its tooltip
said it was locked or that its folder was gone. As in JetBrains' IDEs,
a row stays plain until something about it is unusual.

- Show a light cyan lock where the checkmark goes, without the
  checkmark's pill; a worktree row is never the branch checked out here
- Dim a row git reports prunable or whose folder is not there, and say
  which in its tooltip
- Explain a missing folder on a double-click, in the sidebar or on a
  branch label, rather than fail to open it: connect the disk, or prune
@goneng
goneng force-pushed the prune-and-lock-worktrees branch from bca8a15 to 3fd4a4a Compare September 26, 2026 10:33
@hannesa2

Copy link
Copy Markdown
Contributor

Screenshot differs: emulatorApi=arm64 with 2 screenshot(s)

setpoint (old) | diff | actual screenshot (new)

commit-context-menu.pngscreenshot

history-view.pngscreenshot

@goneng
goneng requested a review from hannesa2 September 26, 2026 11:50
@hannesa2
hannesa2 merged commit a942993 into master Sep 26, 2026
3 checks passed
@hannesa2
hannesa2 deleted the prune-and-lock-worktrees branch September 26, 2026 16:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants