Switch/Checkout dialog parity
Reference: src/TortoiseProc/GitSwitchDlg.cpp, Commands/SwitchCommand.cpp,
IDD_GITSWITCH in src/Resources/TortoiseProcENG.rc, and the shared
CChooseVersion implementation, pinned to upstream.json.
Implemented comparison
The separate native window preserves the two groups and control order: Switch To with Branch, Tag and Commit radio rows and aligned selectors, then Option with Create New Branch and its name, force and Merge, three-state Track, and Override branch if exists. OK, Cancel and Help occupy the bottom row. Native controls replace Windows widgets. Branch/tag selectors retain full reference identity internally.
Local branches default to switching the existing branch; remote selections default to creating a branch with the remote branch's name and automatic tracking. Tag and commit selections default to creating a branch and remember that checkbox choice. New branch names are suggested from the reference or short revision. Editing the remote-derived name disables automatic tracking. Track has distinct unchecked, checked and mixed states; tracking controls require a remote and new branch.
The backend verifies the revision as a commit and validates new branch names before mutation. Existing branches require Override; a tag sharing the new branch's name produces Continue/Abort. Local branch checkout attaches HEAD. Tag, commit and remote checkout without a new branch detach HEAD. Force and Merge are passed to Git. Options errors keep the controls and entered name available. Production checkout hands captured options to shared native progress. Every attempt refreshes open status/log views; a successful result closes the options after acknowledgement.
App and Finder Switch commands, the status branch link, and the log revision checkout action share this native dialog. A log checkout preselects its exact commit, while retaining the repository access lease even if the workspace changes. Branch browse now owns the native all-reference namespace browser, including tags, remotes, notes/custom refs, metadata columns and filters. Commit browse owns the full Log in single-selection mode at the typed revision, with graph/details and no Working Tree row. Both follow CChooseVersion and preserve modal ownership. The reusable browser and Log themselves remain partial ports. Switch, Branch/Tag and New Worktree now share the full picker owner; Reset uses the same full dialogs through its dedicated owner. Complete commands, history combos and other consumers remain pending.
Verification
Seven real Git integration tests cover attaching a local branch while retaining mixed index/worktree edits, annotated tags, detached commits, branch creation, automatic/explicit/no remote tracking, hierarchical remote names, existing branch and tag conflicts, invalid names/revisions, dirty checkout rejection, explicit force, and merge checkout leaving conflict stages and markers. The historical suite at that checkpoint had 53 passing tests; this is not a current full-suite claim.
Native QA created native-switch-qa on a disposable repository, chose main in
the reference browse sheet, and switched back. Index and worktree patches matched
byte-for-byte before and after both operations. The branch name and new-branch /
override enablement were inspected. site/assets/switch-checkout.png is a capture
of the actual native dialog; no mockup or synthetic image was used.
Remaining differences
- Complete Browse References mutation/range/tree context commands, sort policies and physical tree/filter/menu acceptance; the native namespace/table/filter subset is now used by Switch. See reference browser parity.
- Full Log commands/revision controls, history combos and physical selection-mode graph/pagination/keyboard acceptance. The full native Log picker route is wired.
- Native UI verification of remote three-state tracking, tag warning/abort, force, merge conflict recovery and the log/Finder entry points; backend tests cover their checkout effects, but do not establish native UI parity.
- Complete keyboard navigation, resizing at all font/locale sizes and accessibility QA. Source layout and light/dark capture evidence is recorded below.
- Physical shared progress/cancellation, submodule checkout options, hooks and interactive prompts.
- Broader preference/state persistence and detached/unborn repository scenarios.
The dialog and its source files remain partial in the inventory. This is not full upstream parity or App Store readiness.
Shared options and recovery result
Regular Switch/Checkout now owns the same native result as express RefLog Switch and Branch creation. Read-only validation catches dialog errors before presenting; Core repeats validation when executing. Captured target/revision, Create New Branch/name, force, merge, tracking, override and accepted cross-name intent survive later edits and Retry. The name-conflict Continue uses the submitted draft; Abort clears it. A fresh options controller prevents another entry point replacing a pending draft. A presenter that cannot own its sheet cancels before Git mutation. AppKit sheet-dismissal completion releases the options owner and resumes a Branch creation handoff after detachment. Legacy no-presenter model callers retain their inline behavior.
Success actions follow PerformSwitch order: Submodule Update when the working
.gitmodules exists (even without a gitlink), Merge previous branch when attached,
Pull when the new HEAD is attached, then Commit. A gitlink without .gitmodules
does not add Submodule Update. Failure offers Resolve after merge conflicts,
Stash Save when not merging, Retry, and Switch with merge when not merging.
Retry retains original options with the effective merge setting; it uses a fresh
cancellation token. Existing original icons and split action button remain.
A follow-up dispatch closes the result first and cannot repeat.
The result captures AutoCloseGitProgress at submission. Manual/no-options retain success actions; no-errors closes successful results. Errors/cancellation remain reviewable. ConfirmKillProcess asks the source Yes/No question, with Yes default; No leaves the owned process running and Yes cancels its process group. Completion while a question is pending defers automatic close; a late answer cannot cancel completed work or repeat its close. Native close/Quit guards include the result, chooser and warning states; closing invalidates late callbacks.
Progress QA records focused Core validation, four-Git actual full-option execution and retry/cancellation effects, and regression receivers for express Switch and Branch/Tag. Hidden hosting/controller checks do not prove physical nested sheets, default buttons, Root factory routing, signing, Finder activation or signed sandbox acceptance. The existing Switch screenshot predates shared result ownership. Full Switch/application parity remains partial.
Owned full chooser checkpoint
The reference browser returns canonical names and refreshes the catalog before selecting the Branch/Tag/Commit row. Cancel refreshes the original branch selection. Branch/tag choices preserve the unused commit draft. A late-created tag is refreshed through the native window's F5 dispatch and selected through a fresh parent return catalog in the receiver. Switch defaults are reapplied for the selected namespace: existing local branches, remote new-branch/tracking suggestions and remembered tag/commit new-branch behavior; force and Merge remain unchanged. Native revision controls receive a once-only focus request after a successful handoff. Commit Log selection returns a full hash; cancellation preserves the typed draft. Duplicate/cross-target requests, Checkout/reload/parent close/Quit are gated while the picker or reference refresh is pending. Rejected presentation and forced parent close release owned children, cancel reference refresh and reject late results.
Switch picker QA records private hidden native receivers and the real Switch progress regression on both Git engines. Presenters release parent keyboard focus without ordering windows or showing real sheets. Physical keyboard/default buttons, sheet focus restoration, error/close-during-load recovery, visual/theme/accessibility comparison and signed security-scope/Finder acceptance remain unverified. Existing Switch screenshots predate these routes.
Initial metadata lifetime and browser-owned transaction
Initial reference and current-branch reads share one cancellation request and publish their values together after both complete. Closing Switch cancels that request; closed dialogs cannot publish metadata or errors. App Store reads enforce the retained repository security scope. This covers the shared chooser used by Switch, Branch/Tag and New Worktree; hidden lifecycle checks here exercise the browser-owned Switch route.
The owned progress controller retains the existing native sheet completion path. Rejected presentation releases an unstarted result without launching checkout; completion validates the controller identity before releasing ownership. The expanded reference-switch receiver checks a full private checkout transaction with captured options, all three close policies, acknowledgement and a post-action, then accepts live Current Branch from the unchanged browser catalog. See transaction QA. Signed scope, physical sheets remain unverified; the forced-checkout lifecycle checks below cover hidden owner/process cleanup.
Forced checkout lifecycle
Closing an owner forcibly now cancels both pre-progress validation and its owned checkout/progress, releases the progress controller and prevents late UI or operation callbacks. Validation threads cancellation through every Git read; normal cancellation/retry and successful post-actions retain their existing behavior. Progress outcome fields publish together only while the model is live. A direct forced progress close leaves Switch available for a fresh attempt, and an old completion cannot release that new controller.
The hidden browser route pauses each validation probe, previous-branch read, switch command and post-checkout status read. It verifies live process termination, owner/controller release, no late callbacks, pending-answer rejection and a fresh retry. Pausing after switch verifies completed HEAD changes are retained: abort is process cancellation, not rollback. See cleanup QA. Physical and signed acceptance, failure variants, external writers and full application parity remain incomplete.
The commit-revision row now uses the shared editable session combo, including programmatic custom choices, exact native Git token identity and disabled/retired/focus guards. Physical popup and complete dialog acceptance remain open.
The connected local command progress family adds live Reset/Switch/Stash output, shared native log styling/copy controls, bounded presentation, independent ownership and once-only kill questions.
Source form and accepted choices — October 17, 2026
The native form keeps all Switch To radio/selector/Browse rows, followed by source Create New Branch/name, Force/Merge, Track and Override rows and OK/Cancel/Help. Automatic host sizing is disabled and the native window has explicit content and minimum bounds. Initial focus requests Create New Branch after the first metadata read; picker completion retains its separate reference-focus route.
Revision/radio selection applies defaults synchronously through captured bindings,
rather than queued view change handlers. Source local/tag suggestions retain
Branch_; typed commit suggestions now use the pinned source's eight characters.
A remote name edit disables Track only if its exact byte spelling does not match
the remote's path suffix. An explicit later Track click is retained. Turning off
branch creation clears Track/Override, and disabled dependent bindings refuse
writes. Option/base/picker snapshots reject busy, replaced, warning and retired
owners; the picker captures every checkout option, not just its revision draft.
Native radios retire with their window just like shared Track controls.
Tag/commit Create New Branch preferences publish only after read-only validation accepts the captured choice and any cross-name warning. Rejected names, revisions, reference probes and unaccepted warnings leave the remembered choice untouched. Execution repeats validation; this is a preflight check rather than atomic protection against external Git writers. A resolved commit is parsed only from stdout and must be a complete object ID. Existence checks propagate Git failures other than the ordinary missing-ref result, preserving semantic error arguments.
The Debug build and all 14 focused Core checks pass. Source native checks pass
on both system and bundled Git; all four retained picker/progress case/engine checks
also pass, for six distinct native case/engine passes across the attempts. The first retained picker attempt lacked an explicit
owned key window for F5. Its repaired fixture rejects a foreign key window and
accepts its owned one without activating the app. The progress fixture's old
fixed-position command parser also needed to skip global -c arguments before
its held Switch; its cancellation assertions are retained. Exact results and raw
light/dark captures are recorded in batch evidence.
The two actual window captures were individually reviewed, and all eight owned
receivers from the attempts were reaped and independently absent. Native hidden picker
split-view warnings remain. Public remotes,
interactive credentials/hooks, physical keyboard/VoiceOver and sheet focus,
all filesystem/locale boundaries, signed Finder/sandbox and App Store acceptance
remain open. Full source/application parity is not certified.
Switch/Reset command progress and recovery — October 19, 2026
This continuation compares AppUtils.cpp::PerformSwitch, its post-exec conflict
check and post-command actions, with ProgressDlg.cpp::RunCmdList, completion
presentation and result closure at the pinned revision. Native Switch now shares
the connected command receipt, timed completion and owned result workflow with
Reset. The actual checkout invocation is captured immediately before its Git
launch, after validation; its quoted executable/arguments become a log preamble.
This is display text only. Git continues to receive its original argument array.
The styled native output retains Copy/Copy All, independent URL styling and the
captured output limit. The completed command has a distinct status/footer range,
terminal 100 percent and result current-work label. Elapsed time is captured at
the command boundary before later metadata/handoffs; the timing preference can
hide its suffix without removing the result. Cancellation with a Git result uses
the actual exit code; no-result cancellation/failure retains explicit native
fallback labels. An actual successful checkout with merge conflicts is treated
as failure by the source post-exec rule, with Has merge conflict retained.
Queued start/retry work acquires ownership before its Task runs. Actual command
and inspection work keeps per-instance pending ownership through forced closure;
post-actions, Close and automatic close wait for drain and owned question release.
Kill confirmation is one-shot and its answer waits for physical sheet detachment.
Retry/Switch with merge retain the accepted checkout options and fresh attempt.
Submodule Update, Merge previous branch, Pull, Commit and Stash retain repository
access, preferences and a captured appearance/opacity snapshot after result close.
The source Resolve-labelled conflict action intentionally opens Commit; it is not
silently changed to the separate Resolve dialog (AppUtils.cpp's failed-switch
callback). Forced owner closure never dispatches a queued child.
Grouped evidence records the actual checkpoint after validation; no new pass is claimed before those results. Physical progress/questions/default buttons, all checkout/recovery combinations, complete source rendering/geometry, signed grants and whole-port acceptance remain open. The CLI command presentation does not certify the alternate Windows libgit2 progress implementation or native taskbar/Dock rendering equivalence.
Standalone Switch's parent completion now follows the actual progress-controller
onClosed release after worker/child drain, rather than its earlier beginSheet
reply. A sheet ending alone cannot release the initiating workflow. Captured
Submodule Update and Clean presentations also reach their subsequently owned
progress windows. Child window caches keep idle Clean/Bisect windows from separate
preference domains independent, and identity-checked close callbacks cannot erase
a newer cached owner. An already active Bisect operation in the same repository
continues to exclude a competing operation across those domains; the cache change
does not permit concurrent bisect mutations.
Native source form control continuation
The connected form family records reference keyboard/focus and bounded captions, Push F5 refresh and Synchronization native split controls, with owned state and remaining acceptance kept explicit.