TurtleGit for Mac

View Markdown source ↗

Push dialog parity

Reference: PushDlg.cpp, IDD_PUSH, PushCommand and CAppUtils::DoPush/Push at the pinned commit in upstream.json.

The literal destination and source-option family records Git-owned branch/tag resolution, deletion and saved-default behavior, including corrections to the earlier automatic namespace expansion.

The separate native window follows the upstream Ref, Destination and Options order: all branches, editable local/remote references with browse buttons, named remote or arbitrary URL, Manage, force with lease/force, tags, upstream tracking, per-branch push defaults, submodule recursion and server push option. macOS uses configured Git credential helpers and SSH agents rather than PuTTY key loading. The original Push icon is used in the Log revision context menu.

Implemented behavior

Branch defaults come from Git configuration; source changes reload those defaults. Full tag identity is retained. Create Tag's Push checkbox opens this window with only the new tag selected. Log Push opens it with the selected revision. Reference browsers currently show searchable cached refs. Manage provides basic native remote creation, removal, fetch URL and optional push URL editing.

Force with lease excludes Force and Include Tags. Upstream tracking and saved push defaults have conditional enablement. Per-branch push settings are saved before transport, like upstream, and remain set if transport fails. All branches asks confirmation; an empty source asks confirmation for configured pushes or remote deletion. All branches with tags uses separate branch and tag pushes. All remotes reports completed destinations when a later destination fails. Preflight failures preserve the options dialog. Transport results use the separate owned progress sheet described below; success refreshes repository views before result acknowledgement.

Evidence

Six existing real Git integration tests cover named destinations/upstream configuration, selected and renamed tags, commit hashes to new branches, all branches plus tags, non-fast-forward rejection, stale and valid force-with-lease, partial all-remotes failure, arbitrary paths, saved defaults, remote deletion, invalid input and a literal server option containing spaces and punctuation. The current focused Push suite has ten passing tests, including read-only submission validation and a pre-cancelled request that preserves remote refs and local config. Mixed staged/unstaged contents are preserved by push.

Native QA used only a disposable documentation repository and local bare remote. The window pushed main to preview-main and set its upstream. Native Create Tag created an annotated native-tag-push, opened Push with that full tag ref, and sent only that tag. The remote contained only preview-main and native-tag-push; other fixture tags were absent. Index and working-tree patches matched byte-for-byte. Force-with-lease enablement was checked in the running window. Actual captures are site/assets/push.png and the updated site/assets/create-tag.png.

Remaining comparison work

Transport Cancel now remains available while Push runs; see the cancellation section below for exact scope and remaining acceptance. Passing local tests and native branch/tag checks do not establish full Push parity. The source and resource inventory therefore remain partial.

Transport cancellation

Push now uses the shared ConfirmKillProcess preference (default false) shown on the Dialogs settings page. With it enabled, Cancel asks the source question The process is still running. / Are you sure to abort? with Yes as the default. No leaves the operation running; Yes requests cancellation. The close gesture uses the same model path and keeps the window until transport finishes. Inputs remain disabled during transport; Cancel displays Cancelling… until the owned process group stops. The retained result now captures these inputs on failure/cancellation; its Push action opens a fresh dialog. The no-presenter headless compatibility path retains inputs for an in-place retry with a fresh token. Idle Cancel closes. Cancelled operations do not trigger success.

This maps CAppUtils::DoPush and CProgressDlg::OnCancel. The source queues separate commands for each remote and for branches/tags. Native cancellation stops the current command and prevents later commands from starting. Reports retain completed destinations/phases. Already-pushed refs and saved configuration are not rolled back; cancellation after server mutation can leave effects that require inspection. Pre-cancelled requests stop before settings are saved.

Cancellation QA records a headless native model with real owned wrapper/helper processes, an unrelated process, No/Yes confirmation, retained inputs, no success callback and exact local HEAD/index. It covers single-remote cancellation, cancellation after the first of two remotes finishes, and cancellation of tags after branches finish. Completed bare-remote refs remain; later refs are absent. Retrying each same model publishes the expected refs and closes on success. These are model/Git checks, not physical sheets, Cancel/Escape/window-close/default-button, layout/theme/accessibility or signed sandbox acceptance. Streaming output, full progress/post-operation UI, project hooks and network/authentication/signing parity remain incomplete.

Repository-scoped editable histories and clipboard

Push now presents native editable dropdowns for destination URL, remote branch and server push option. The source's PushURLS, RemoteBranch and PushOption histories are scoped to the repository path, unlike shared Pull/Fetch history. URL/option duplicate matching is case-sensitive and preserves exact UTF-16 spelling; destination duplicates match case-insensitively. Entries keep source order, line-folding/trimming and the 26-save/25-load boundary from HistoryCombo.

History load fills the dropdowns but leaves URL and server-option text empty, matching source LoadHistory's cleared selection. Destination load adds/selects a configured push branch, keeping an existing duplicate's spelling. Source changes reload its history/default; clearing the local source keeps the remote destination for deletion and updates tracking enablement. Browsing inserts the chosen destination into memory without saving. Selecting URL mode parses copied git pull then git fetch text, as PushDlg does, or selects the latest saved URL. The existing source parser retains its quote/offset/prefix quirks and native POSIX/file-URL additions; copied git push is not recognized by this source path. Selection does not execute Git or save history.

After confirmation and read-only option validation, ordinary submissions save the destination history and save URL history only in URL mode, before transport. Remote deletion (empty source with nonempty destination) and all-branch pushes exclude URL/branch saving. Server-option history saves for all accepted forms. Transport failures retain saved entries and input fields; invalid submissions do not add history. Transport revalidates before writing config/executing Git. Saving uses the submitted snapshot, so a queued field/default update cannot replace its values during validation. History normalization does not rewrite the actual server-option argument.

History QA records native-model URL and named pushes, failed transport, invalid submission, confirmation and deletion/all-branch exclusions, repository isolation, default/browse/case/ordering/limit behavior, clipboard prefilling, exact local HEAD/index and a hidden native combo selection. The cancellation matrix is also rerun against the new validation boundary. These are headless model/Git/hidden-control checks; physical dropdown editing/completion, Shift-Delete history removal, source locale trim/case equivalence, user pasteboard, keyboard/theme/VoiceOver and signed sandbox acceptance remain pending. Full Push and application parity remain incomplete.

Remembered options and source submission questions

Push now remembers All Remotes per repository and restores it only while more than one remote is available. An explicitly supplied source still overrides saved All Branches; it does not clear All Remotes. The submodule-recursion dropdown starts with push.recurseSubmodules unless a saved repository choice overrides it. The source stores indices 0/1/2 for None/Check/On-demand; native preferences use the same order. Absent or invalid native values fall back to Git config. Accepted submissions save these choices before transport, including failed transport; validation failure and an unanswered/No confirmation do not save them. URL mode saves All Remotes false. Full source remote-selection/config-default behavior and out-of-range registry equivalence are not established by this mapping.

Submission questions now use native Yes/No sheets and exact source text. All Branches asks Do you really want to push all local branches?, defaults to No and offers Don't show this message again. Empty source/destination asks the source both-empty question with Yes as default. Empty source with a destination asks the source remote-removal question, also defaulting to Yes, with warning style. The all-branches suppression choice is app-wide (PushAllBranches), matching the source remembered-answer scope. Source PushDlg explicitly remembers Yes when No plus suppression is selected: that invocation stops, while later all-branch submissions proceed without repeating the question. No suppression is offered for deletion/both-empty.

Preferences QA records configured/saved recursion precedence, repository isolation, multiple/single remote restoration, explicit-source override, invalid-submission non-persistence, exact question callbacks and No-with-suppression followed by a real successful all-branch push. Real native NSAlerts are constructed without display to inspect Yes/No ordering, return-key defaults, suppression text and deletion style, then closed. The four-Git history matrix also runs against the new preference-save gate. Physical modal interaction, Escape/default-button activation, sheet/window close, real submodule recursion, accessibility/theme and signed sandbox acceptance remain pending. Full Push and application parity remain incomplete.

Short source branch/tag uniqueness

Submission validation now follows the exact reference lookup in CGit::IsBranchTagNameUnique: it rejects a supplied short name when both refs/heads/<name> and refs/tags/<name> exist. Revision resolution alone can succeed with an ambiguity warning, so it is insufficient for this gate. The native implementation uses exact show-ref --verify --quiet lookups rather than relying on that warning or passing the ambiguous name to transport. Rejection occurs before history/config saving and is shared by direct core Push. Fully qualified references retain their branch/tag identity; the source check forms refs from the supplied text and also permits such uncollided expressions.

Source-validation QA records a real branch/tag collision, read-only rejection, unchanged config and absent remote refs, then qualified branch and tag pushes to separate expected ref namespaces. Native models reject the short collision without saving any history or creating its remote destination. The broader four-Git history workflow is also exercised. Native initial/dropdown/browser local-branch selections now normalize to short names; the source-presentation section below records their implementation and remaining acceptance. Alternate upstream CLI suffix-pattern lookup behavior, repository-access failures, physical controls and signed sandbox acceptance remain pending. This does not establish complete Push validation or application parity.

Push source presentation and branch defaults

Initial/current local branches, supplied refs/heads/ selections and browser choices now use short branch names. Initial remote refs use remotes/<remote>/…, matching PushDlg's initial stripping of refs/. Tags and commit/revision inputs retain their supplied identity. The local dropdown lists branch/remote choices and returns normalized names; selecting a branch that collides with a tag now reaches the short-name validation gate rather than bypassing it with a qualified ref. Manually entered qualified refs remain visible and retain their identity.

Normalization is opt-in for Push's use of the shared combo; Rebase continues to receive its existing qualified refs. Push branch defaults now prefer an exact local-branch lookup before revision resolution, so a same-named tag cannot mask that branch's configured push remote/destination. Tracked merge refs use source StripRefName behavior (refs/heads/ removed, other refs/ shortened), while an explicit pushbranch, including Gerrit refs/for/…, is preserved.

Presentation QA records configured defaults under a same-named tag, tracked-ref stripping/explicit destination preservation, initial/browser branch normalization and rejection, retained tag/ hash identity and actual hidden native combo callbacks with normalization on/off. The existing history/cancellation/preference matrices cover surrounding Git and model workflows. Physical typing/dropdowns/browser/Log or RefLog selection, full source default/remote-selection equivalence, resize/theme/accessibility and signed sandbox acceptance remain pending. Full Push and application parity incomplete.

Push's URL, remote-destination and server-option dropdowns now offer immediate history deletion with the shared native Shift+Delete receiver. See the deletion section in FETCH-PARITY.md for source selection/persistence rules and remaining physical popup/event-routing acceptance.

Source selection from Log and RefLog

The local-source ellipsis now opens the source's three choices in order: Browse References, Log, RefLog. Original repository-browser/Log artwork is reused in these menu rows. Browse References keeps the existing head/tag picker and local branch normalization. Log opens the existing native selection-mode Log scoped to the current source (empty source uses normal HEAD defaults), with working-tree rows hidden. RefLog opens the native HEAD reference log in selection mode, matching the source default; its reference chooser remains available.

Accepting either picker copies the selected immutable commit hash into the local source and reloads its push defaults/conditional controls. Cancel retains the source. Selection does not save histories or execute Push; explicit OK still validates and performs transport. The parent retains each picker and its existing repository-access lease while the sheet is open; closing the child releases it. Closing the parent also ends and closes its owned source picker windows. Only one source sheet opens at once. Busy/all-branches/pending-confirmation model gates prevent history-menu dispatch.

Source-picker QA records dispatch guards, source-scoped actual history loading without working-tree rows, real Log and HEAD RefLog selection callbacks, cancellation retention, hash control/default updates, unchanged HEAD/index/config and no remote refs before OK. Explicit Push then publishes the selected older commit while retaining local HEAD/index. These are headless model/Git checks. Physical menu/sheet accept/Cancel/focus/resize/theme, child/parent close lifecycle, full browser parity, accessibility and signed sandbox lease execution remain pending. Full Push/application parity incomplete.

Branch revision display

Dialogs settings now exposes upstream's Display branch revision number, default off (ShowBranchRevisionNumber). Push snapshots it at submission. For each successful destination in a single-source push, it appends the output of git rev-list --count --first-parent --end-of-options <source> --, including tags and revision expressions, as upstream DoPush does. All-branch pushes skip it. The number is a display aid and is not a unique commit identifier. A count failure after transport reports the destination as completed: published/deleted refs are not rolled back. Empty-source deletion with this option can therefore succeed remotely and then fail its count, matching the source command ordering.

Counter QA covers a merge whose total count differs from its first-parent count, two destinations, disabled/all-branch exclusion, count failure after deletion and native submission preference capture. Native Log shows Branch RevNo next to the hash only for a single revision in its first graph lane. It clears it on selection changes and captures the setting when opening, like upstream. Native lane layout is an adaptation; full equivalence for filtered/compressed/all-ref graphs remains pending. Log and Push physical setting, interaction, appearance/accessibility and signed runtime acceptance remain pending. Push's retained result/post-actions window, Request Pull and project hooks remain unfinished; this setting does not establish full Push or application parity.

Retained result and post-actions

Production Push now owns a separate native progress sheet. Validated fields and histories are captured before transport, and inputs remain locked until its result is acknowledged. Success retains raw command output and offers Create pull request, Push, Switch/Checkout, and Commit super project for an actual submodule checkout. Failures retain output; porcelain non-fast-forward [rejected] offers Pull, Fetch, Push, while other errors (including server [remote rejected]) offer Push. Completed destinations/command output survive a later remote failure or cancellation.

Follow-ups close/release the result before dispatch, once only. Request Pull receives the captured destination; Push opens a fresh dialog with the captured source. Rejected Pull enables its Show Push follow-up. Fetch restores the captured remote or arbitrary URL, or all-remotes mode. Superproject Commit opens its actual root; the App Store path requests folder authorization if the child lease cannot access it. Push dialogs are now distinct owned instances, preventing a follow-up from reusing an active result. No post-action performs a mutation until the new dialog is submitted.

The three AutoCloseGitProgress policies apply. All success results have post-actions, so no-options retains them and no-errors closes them. Failures stay open. If a cancellation prompt outlives successful transport, automatic close resumes after that prompt ends. Cancel uses ConfirmKillProcess in the child sheet; accepted cancellation retains a failed result and its completed phases until Close. Preflight validation errors still retain the options dialog. The headless no-presenter compatibility path retains previous programmatic completion behavior; production always presents the owned result. Missing production presentation cancels the owned request and releases its result after completion.

Progress QA records real transports and exact ordered actions, close policies, immutable presets/duplicate/dispatch guards, submodule detection, rejected-versus-hook failures, Fetch presets, and owned cancellation/retry. Hidden progress layout is not displayed acceptance. Complete progress controls/layout, project pre/post-Push hooks, physical sheets/defaults/menus/keyboard/close/themes/accessibility, actual nested follow-up windows and signed sandbox/network/Finder/App Store acceptance remain pending. Existing screenshots predate the progress sheet. Full Push/application parity remains incomplete.

Refresh after partial or failed transport

Push now emits a transport-result notification after its owned process stops, before result acknowledgement, for success, failure and cancellation. The success callback stays success-only. Preflight rejection emits no transport result. Root uses this completion signal to refresh Log, RefLog, Status, Commit and the current repository view, including failed results whose earlier destinations published refs, whose branch defaults were saved, or whose count failed after deleting a remote ref. Closing/choosing a result does not repeat the notification.

Refresh QA records actual partial publication and tracked refs appearing in reloaded native Log, failed saved-default reads, success/failed notification counts, preflight exclusion, and a real deletion whose failed count still refreshes Log. The retained-progress regression checks a cancelled transport notification without a success callback. Actual displayed cross-window refresh and signed/sandbox/network acceptance remain pending.

Live CLI output

Push now uses the byte-oriented GitCliOutputParser already ported for Clone. The baseline is CAppUtils::DoPush and CProgressDlg::UpdateCmdOutput / UpdateProgressFromLine: --progress, the shared command-list parser, CR replacement, phase/percentage, output limits and scrolling to the end. Each branch/tag transport for every remote, and the optional first-parent count, forwards typed stdout/stderr chunks through the owned process runner. Validation and saved-default commands remain outside the transport display. Wake-ups are coalesced; pending bytes are retained in the parser until consumed.

The result captures GitOutputLimitinKiB at construction (default 2048 KiB, bounded 16..102400). It shows live output, a green percentage bar when available, a phase label and end scrolling; parser drop mode stops display growth after its soft limit. The raw transport result and completed-destination diagnostics remain complete. Follow-up classification uses that raw result, so a rejection after visible truncation still offers Pull/Fetch/Push. Failure summaries include the failed destination and completed phases, without replaying already displayed Git output. Structured Git failure information also preserves a later count error. Headless compatibility without an owned result keeps its existing raw completion path. No new transport mutation or automatic post-action is introduced.

Live-output QA records focused Core checks and four-Git native receiver checks: Unicode split across writes, local CR replacement, phase/percentage while an owned helper still runs, a hidden progress host, complete raw callbacks, real published refs, No/Yes cancellation and child cleanup, captured 16 KiB limit after preference changes, and recovery after a real rejection whose output falls beyond that limit. Existing retained-result, partial-refresh and branch-counter receivers are regression gates. Helper-generated progress verifies delivery timing; it is not a real network throughput test.

Physical scrolling/focus/default buttons/keyboard/themes/accessibility, all progress controls, project hooks, authentication/libgit2 progress, real remote transfer timing and signed Finder/sandbox/App Store acceptance remain pending. Existing screenshots predate live Push output. Full Push/application parity is still incomplete.

Connected transport preparation and ownership — October 14, 2026

Push, Pull and Fetch now gather their initial remote/reference/default metadata before publishing the controls, retaining the previous accepted values on failure. Every Git read in this preparation carries owned cancellation, including Push's submodule default and superproject lookup. Source/remote changes replace and cancel old requests, guard the exact captured input and fence late publication. Independent operation accounting holds repository grants and blocks Quit until all superseded or retired workers settle.

Cancel during preparation leaves the window available, marks its metadata for Retry loading and prevents submission with stale defaults. Window close cancels and closes after settlement; forced closure retires callbacks and cancels owned reads. Retry repeats the relevant initial/source/remote preparation. Push keeps the source editable during source reads while locking dependent destination and option controls. Pull/Fetch show preparation activity and allow Cancel before transport progress exists. Their options, SSH settings and history keys retain source semantics.

Push now owns preflight, hooks, superproject lookup, transport and post-hook as one lifetime, including forced owner closure. A normal cancelled/failed Git Push still invokes PostPush using a fresh owned token, as the source does; an exiting owner suppresses that callback. Post-hook failure retains the actual Git result and includes its own diagnostic. An earlier kill-question reply cannot cancel a later token. Application termination stays blocked until the entire worker ends.

Debug and 15 focused Push Core tests passed. The initial native timeout exposed missing cancellation in Core Push validation; those reads and saved-setting writes were corrected, then the affected Core group and six grouped native cases passed with system/bundled Git. All final receivers were reaped. Retry also retains caller branch/remote presets. See the actual results and failure history. This does not certify physical source layout, keyboard/default focus, live transport servers, signed repository/script grants or App Store acceptance. Retained values are guarded publication, not an atomic snapshot against every external Git/configuration writer.

Connected transport reference selection — October 15, 2026

Push now uses the shared native reference tree/list for both source and destination, matching PushDlg.cpp's gPickRef_Head|gPickRef_Tag source scope and gPickRef_Remote destination scope. The previous reduced flat SwiftUI chooser is removed. Source choices keep tags canonical and use the existing local-branch normalization. Destination selection updates the configured remote and branch and clears arbitrary URL/all-remotes mode, as in the source handoff. The transport lookup recognizes slash-containing remote names with the longest configured prefix and preserves branch slashes and byte-distinct names. This does not change the general reference browser's configured-order remote lookup policy.

Browse References, Log and RefLog now share one owned Push selection request. The children inherit private preferences and hidden-owner visibility; Log starts at the current source with working-tree rows disabled. Root consumers configure its existing repository-bound revision commands through the shared selectable-Log helper. The source parent remains blocked from submission while a child owns its draft. A response is applied only after sheet closure, with original input focus restored when still available. Changed source/destination/remote/mode drafts, replaced requests and retired owners reject late results. Cancel preserves the source. Forced parent closure dismisses children and cancels their readers while independent process/grant/Quit ownership remains until settlement.

Pull/Fetch remote selection and Sync branch browsing now use the same post-sheet publication rule and captured-input checks. Pull/Fetch selectors inherit parent preferences, appearance and visibility, including owned SSH child prompts. A rejected presentation clears its browse state without disturbing another active request. Sync initially selects the fully qualified remote/branch; its selected remote uses the configured-prefix resolver instead of splitting at the first slash. Source RefLog and remote selectors retain their existing first-choice, editable branch and F5 semantics.

This ports the connected selection family. It does not submit a Push/Pull/Fetch merely by choosing a reference and does not certify transport progress, physical menus/typing/VoiceOver, authenticated servers or signed sandbox/App Store behavior.

The Debug build, 22 affected Core reference-browser checks and four grouped native cases passed on system and bundled Git. New native assertions exercise actual shared source/destination acceptance, private Log/RefLog children, stale draft rejection in all four consumers, a real paused reference-reader process and forced owner closure with process reaping. The selection-only scenarios retain HEAD, index and configuration bytes and submit no transport. Existing metadata retry, source replacement, normal/forced Close/Quit and post-cancel hook ownership also passed. All four receivers were reaped. See transport picker evidence.

Captured transport/progress presentation — October 15, 2026

The connected presentation family now consumes FetchVerbose and DownloadAnimation without extra dialog controls. It shares the source byte-limit interpretation across Clone, Push and common progress. The pinned ProgressDlgLinesLimit entry remains dormant, with no invented consumer. Full workflow, physical and signed acceptance remain partial.

Grouped evidence records 33 affected Core cases, the Debug build and four native groups on both Git engines, including the argument-recorder setup repair. Complete hosted/physical/signed parity is not established.

Source transport forms and exact native selections — October 17, 2026

The connected Push/Fetch/Pull options family keeps source group, row and option order, with native SSH key handling in the Windows PuTTY row. Explicit hosting bounds keep the complete forms and footers within their native windows. Existing original source icons remain on Push's Browse References/Log/RefLog menu.

Captured fields and radio/remote/history actions reject replaced bases, active pickers/managers, pending confirmations, busy or retired owners. Push still allows new source typing to replace an outstanding defaults read; dependent controls stay locked until the matching read settles. Source updates and option enablement now run in their model-owned mutation paths, avoiding delayed view observers. Fetch remote changes run once and preserve existing metadata cancellation/retry logic. Disabled Force/Lease/Tags, save/upstream, merge/FF, URL, branch, depth and configured Rebase inputs also reject retained callbacks, rather than depending only on their visual disabled state. History deletion captures both form and ordered list, so an old index cannot remove a newer entry. Accepted history scopes/save order remain.

Push confirmation requests have unique identity and capture all operation options, URL and SSH choice. Replaced or duplicate replies cannot execute a different draft, dismiss a newer question or save its suppression preference. Choosers likewise capture the complete operation draft before return publication. Native radios, tri-state Tags/Prune, source/reference/history combos and remote popups retire with their actual owner and refuse disabled/dismantled callbacks. Native reference deduplication, display matching and remote popup selection use exact UTF-8 names; canonically equivalent Git refs/remotes remain distinct. This does not change source-specific history case/trim rules or persistent Git settings.

The Debug build and all ten grouped native case/engine checks pass. The initial build needed explicit main-actor coordinator isolation. Initial source checks passed on both engines; a retained history fixture then submitted before source defaults settled. Its wait now includes preparation and settles the pick before submission. The final source/history/preparation/picker group passes on both engines. Raw review also repaired Rebase left alignment before the final group. Six final light/dark images were individually reviewed; all thirteen owned receivers from the attempts were reaped and independently absent. Core is unchanged and was not repeated. See batch evidence for exact attempts, raw capture provenance and scope. Complete physical dropdown/key/focus/VoiceOver/IME, source sorting at every locale, signed nested sheets and network credentials, Finder activation, sandbox/App Store and full application acceptance remain open.

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.

Commit continuation

The IDD_PUSH family also receives the captured successful Commit continuation. Source mapping and lifetime distinguish the ordinary footer direct transfer from amend/missing-map/result-menu forms, with private transport, warning and cancellation evidence retained in the shared checkpoint.