Skip to content

Fix remote-branch delete doing nothing, and expose prune-on-fetch - #570

Merged
hannesa2 merged 3 commits into
gitx:masterfrom
goneng:fix-remote-branch-delete-and-prune
Aug 12, 2026
Merged

hannesa2 merged 3 commits into
gitx:masterfrom
goneng:fix-remote-branch-delete-and-prune

Conversation

@goneng

@goneng goneng commented Aug 11, 2026 •

Copy link
Copy Markdown
Collaborator

Deleting a branch under a remote in the sidebar did nothing at all, and separately, branches deleted upstream never disappeared from the sidebar.

The delete bug

-deleteRef: filtered the sender's ref through refishForSender:refishTypes: with @[branch, remote, tag]. A ref under a remote reports kGitXRemoteBranchType, which was not in that list, so the method returned before ever reaching git: no confirmation dialog, no error, nothing logged. The remote node itself is kGitXRemoteType, which is why "Delete origin..." worked and made the failure look inconsistent rather than total.

Remote branches now run git push <remote> --delete <branch> through the same progress sheet as push and pull. The routing in PBGitRepository -deleteRef: checks for a remote branch before the remote check, because isRemote is true both for refs/remotes/origin and for refs/remotes/origin/branch; without that ordering a branch delete would have removed the entire remote.

Because this now deletes on the server, the confirmation passes a nil suppression identifier. -confirmDialog:suppressionIdentifier:forAction: skips the dialog outright once an identifier has been suppressed, so sharing "Delete Ref" with local deletes would have let anyone who ticked "do not show again" trigger silent server-side deletes. Both delete alerts also default to Cancel now, with Delete marked destructive.

The stale refs

GitX ran git fetch <remote> with no --prune, and nothing in the codebase pruned. Since GitX shells out to plain git with no -c overrides, fetch.prune already worked; it simply had no UI. Two layers now:

  • fetch.prune is a row in the Git Preferences dialog, repository and global, tri-state like the other booleans there.
  • A General preference chooses "Use git config (fetch.prune)" (the default), "Always prune", or "Never prune", passing --prune / --no-prune / nothing. The default keeps the git config setting authoritative.

Git Preferences dialog, while we were in there

  • The panes are 10% wider, so a long project name no longer pushes the toolbar items off the edge, and the project name in the pane label is capped at 25 characters.
  • Following git-gui, which titles the dialog "… Preferences" and names only the scope per section, the pane labels are now <repo> Prefs. and Global Prefs.. This window has no room for a title of its own, so the labels carry both.
  • Both panes share the plain text document icon, matching the full colour style of the App Settings toolbar.
  • Rebase on Pull and Prune on Fetch sit together, just above Summarize Merge Commits.

Test plan

  • Against a scratch remote, verified the exact commands: git push origin --delete <branch> removes the branch upstream and drops the local tracking ref on its own; a plain fetch leaves a stale ref, --prune removes it, fetch.prune=true prunes on a plain fetch, and --no-prune keeps the stale ref even with fetch.prune=true.
  • Both xibs compile with no new ibtool diagnostics, checked by diffing notices against the base.
  • Each commit builds independently.
  • Not yet verified on screen: the two new preference panes and the delete dialogs. Worth a look when running, particularly the General pane and the two Git Preferences panes, which grew by 28pt.

Keyboard focus in the preference windows

Found while reviewing the panes above, and shared by both preference windows since they are both built on DBPrefsWindowController.

  • App Settings ignored the keyboard until it was clicked. The panel does become key, but nothing inside it did: initialFirstResponder was set to the pane's container view, which never accepts focus, so Tab went to the window and stopped. Git Preferences escaped this by luck of geometry, its first control being a plain text field. Fixed by recalculating the key view loop once the pane is in the hierarchy and starting it at the first control that actually takes focus. This also means that after switching panes, Tab continues into the pane on display instead of restarting at the first toolbar item.
  • The wrong tab stayed drawn as selected. setSelectedItemIdentifier: was only called when the window opened. AppKit moves the selection itself when an item is clicked, but not when it is activated from the keyboard, so the previously selected tab kept its selected styling while a different pane was displayed. The selection is now set wherever a pane is displayed, which covers every route in.

The focus ring is still dropped on every pane swap, unchanged from before. Return is deliberately left alone; note that in Git Preferences it triggers Save and closes the dialog, since Save carries the Return key equivalent.

Measured by driving the app from the keyboard, before and after:

before after
App Settings, first Tab window, never moves first checkbox
Space on a focused tab pane switches pane switches, selection follows it
Tab after switching panes first toolbar item first control of the pane on display

@goneng
goneng force-pushed the fix-remote-branch-delete-and-prune branch 6 times, most recently from af77480 to 675635b Compare August 11, 2026 23:11
goneng added 3 commits August 12, 2026 03:04
-deleteRef: filtered the sender's ref through a type list that omitted
kGitXRemoteBranchType, so it returned before ever reaching git.

- Add -deleteRemoteBranch:error:, running "git push <remote> --delete
  <branch>" through the progress sheet already used by push and pull
- Route remote branches in -deleteRef: ahead of the remote check, since
  isRemote is true both for a remote and for a branch under it
- Confirm without a suppression identifier, and default both delete
  alerts to Cancel with Delete marked destructive
- Title the item Delete "branch" on "remote", and correct the menu's
  localization comments, which described the wrong cases
Branches deleted upstream lingered in the sidebar indefinitely: GitX
never pruned, and fetch.prune had no UI anywhere in the app.

- Add a fetch.prune row to the Git Preferences dialog, repository and
  global, tri-state like the other booleans there
- Add a General preference offering use git config (the default),
  always prune, or never prune
- Pass --prune or --no-prune to git fetch accordingly, leaving the
  default case for fetch.prune and remote.<name>.prune to decide
Both preference windows left the key view loop unreachable on open, and
kept drawing the previous tab as selected when a pane was activated from
the keyboard.

- Point the window's initialFirstResponder at the first control that
  accepts focus rather than at the pane container, which never does, so
  Tab has a way into a freshly shown pane and lands in the pane on
  display rather than restarting at the first toolbar item
- Recalculate the key view loop once the pane is in the hierarchy, so
  that lookup sees the controls it is about to order
- Set the toolbar's selected item wherever a pane is displayed rather
  than only when the window opens; AppKit syncs the selection for a
  mouse click but not for a keyboard activation

The focus ring is still dropped on every pane swap, as it always was.
@goneng
goneng force-pushed the fix-remote-branch-delete-and-prune branch from 675635b to a65558f Compare August 12, 2026 00:11
@hannesa2
hannesa2 merged commit 4f02a44 into gitx:master Aug 12, 2026
3 checks passed
@goneng
goneng deleted the fix-remote-branch-delete-and-prune branch August 12, 2026 12:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants