Repository navigation
[Worktree] Prune worktrees, and lock or unlock them (part 3) - #723
Conversation
5af0218 to
ccf86bf
Compare
7fc36e9 to
7cb3df3
Compare
7cb3df3 to
e6d8e84
Compare
e6d8e84 to
9d0bc62
Compare
hannesa2
left a comment
There was a problem hiding this comment.
Nice, I like the older git handling
There was a problem hiding this comment.
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
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.
9d0bc62 to
bca8a15
Compare
|
@hannesa2 , I've force-pushed to fix Copilot's Prune finding; the Compare link on the force-push shows the change. |
| #define PBGitWorktreePruneVersion "2.5" | ||
| #define PBGitWorktreeLockVersion "2.10" | ||
| #define PBGitWorktreeStateVersion "2.31" |
There was a problem hiding this comment.
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
bca8a15 to
3fd4a4a
Compare



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:
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
Worktree is Locked
Older git
GitX accepts any git from 1.6.0 (
MIN_GIT_VERSION), but the worktree commands arrived later. Checked againstbuiltin/worktree.cat each git tag:worktree add,pruneworktree list --porcelainlock/unlock, with--reasonremove,movelocked/prunablelines in the porcelainlist -zAs agreed on the issue (comment):
.git/worktreesby hand, the porcelain stays plain rather than-z, andMIN_GIT_VERSIONis 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
PATHor 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.git worktree listafter 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.