TurtleGit for Mac

View Markdown source ↗

Fetch dialog parity

The literal refspec and Rebase preset family supersedes the earlier branch-only input restriction and CLI named-remote FETCH_HEAD adaptation below. It records captured request origin, source preset resolution and editable unresolved handoff.

See the October 18 completion and owned Rebase handoff follow-up for native sheet drain, failed handoff recovery and deferred Quit ownership.

Reference: PullFetchDlg.cpp, IDD_PULLFETCH, FetchCommand and CAppUtils::Fetch/DoFetch at the pinned commit in upstream.json. Native Pull shares this implementation; see PULL-PARITY.md for its merge options and remaining workflows.

Implemented

The native Fetch window retains Remote and Options groups, named remote/all remotes or arbitrary URL, remote branch and browse, Tags, Prune, Manage Remotes and bottom OK/Cancel/Help. The upstream merge controls remain visible and disabled in Fetch. The branch field and browse button are disabled for the default named-remote configured-refspec fetch and enabled for URLs. A NamedRemoteFetchAll preference is read, defaulting to true; its settings UI remains pending.

Tags and Prune use native three-state checkboxes: mixed omits the flag, checked explicitly enables it, unchecked explicitly disables it. CLI mixed values honor Git configuration. Selected source-mode libgit2 Fetch uses source AUTO tags when no explicit tag flag is supplied; Prune retains its configured default. Labels report configured named-remote defaults. An untracked branch with multiple remotes defaults to All unless a remembered remote is available. Depth is available only for a shallow repository, initially checked with depth 1, and requires a positive integer. Git receives literal argument arrays, preserving branch names and URL punctuation. Browse retrieves actual remote heads through ls-remote and offers a searchable native selection sheet. Manage reuses the basic remote settings sheet from Push. Configured Git credential helpers and SSH agents replace PuTTY key loading. Ordinary Fetch retains success or failure in an owned progress sheet, with source post-actions described below. Fetch → Rebase shares this owned progress and its post-execution decisions.

Evidence

Nine real Git integration tests cover configured tracking-ref updates without changing HEAD/index/worktree, three-state tags/prune overrides versus Git defaults, remote branch browsing with Unicode names, URL fetch to FETCH_HEAD, shallow depth 1 then 2, all-remotes updates and invalid destination/depth/refspec requests. Two additional Fetch/Rebase tests verify a pinned fetched branch, dirty-worktree preservation, replay ancestry and active-session rejection. The current focused Pull/Fetch tests passed all 18 checks within the 21-test run that also covers registered-parent metadata. Three added tests exercise submodule branch defaults, read-only lookup and fallback, as detailed below.

Native QA on the disposable documentation repository browsed preview-main from its local bare remote and fetched it via URL. Tags cycled mixed → checked → unchecked. A separate named-remote fetch populated the expected remote-tracking branch. After both operations, HEAD and index/worktree patches matched their original values byte-for-byte. Manage opened, selected the configured remote, displayed its correct URL, and closed back to Fetch. A deliberately missing fixture URL showed Git's error, retained its URL/branch, and allowed Cancel. site/assets/fetch.png is an actual capture.

Remaining parity

This shared source/resource remains partial; Fetch compilation and narrow verified workflows do not establish full Pull/Fetch or App Store parity.

Native Fetch → Rebase plan handoff was verified without changing HEAD; its target is the immutable selected fetched commit. See REBASE-PARITY.md for evidence and remaining upstream differences. The existing Fetch screenshot predates this enabled control.

Shared editable URL and branch history

Pull and Fetch now use native editable AppKit dropdowns for arbitrary URLs and remote branches, backed by shared History.PullURLS and History.PullRemoteBranch preferences across dialogs/repositories. URL identity compares exact UTF-16 units and preserves case; branch duplicate matching now also preserves exact identity, following the source control’s case-sensitive setting (corrected in the remote-dialog batch). Histories retain their source order, without sorting. Branch defaults are added/selected during load; browsing adds the selected branch to the front without saving until OK. Selecting URL mode uses a recognized clipboard link/command, otherwise picks the latest saved URL. URL mode also disables Launch Rebase After Fetch and its execution gate, matching the upstream radio transition.

OK saves the URL only in URL mode, before later transport; branch history is saved before transport for both modes. Failures retain the entries. Source history insertion folds each CR/LF into a space and trims surrounding ASCII whitespace. An existing first entry retains its spelling; later duplicates move to the front. The source limit is retained: load reads 25 entries, while a new insertion can save 26 because truncation occurs before insertion. The next load reads the first 25. Invocation uses the trimmed URL/branch rather than the history's line-folded text.

History QA records shared history across Pull, Fetch and repositories, real URL Pull/named Fetch, failed-transport state preservation, ordering/case/UTF-16/limit checks and a hidden native combo's ordered items and selection callback. These are model/Git/hidden-control checks; physical popup, text editing/completion, Shift-Delete deletion, Windows locale/trim equivalence, light/dark and signed sandbox acceptance remain pending. Full Pull/Fetch and application parity remain incomplete.

Registered submodule branch default

When the child has no configured tracking branch, Pull/Fetch now read the branch of its registered entry in the nearest parent worktree's .gitmodules. A child tracking branch still wins; an absent/empty parent branch falls back to the child's current branch. Detached HEAD can still use a configured parent branch. This is read-only and does not initialize a submodule, rewrite config, or change either repository's index/HEAD. Missing/inaccessible parent metadata leaves the optional default unavailable. Symlinked .gitmodules is skipped by the existing native registration guard.

The source calls git_submodule_branch, whose pinned implementation reads the .gitmodules snapshot. This display default intentionally does not use the parent's submodule.<name>.branch local override or expand the literal . value as git submodule update --remote would (see the Git branch-property documentation). See the pinned libgit2 implementation and PullFetchDlg::Refresh in the pinned upstream checkout. These distinctions avoid silently replacing the requested upstream dialog behavior with a different Git workflow.

Submodule-default QA records tracking priority, detached/attached fallback, literal-dot behavior, unrelated/unsafe metadata and byte-identical read-only lookup. Native model checks load the default into both dialogs/history, Fetch the selected branch and perform a real ff-only Pull while retaining the child branch and unrelated dirty file and preserving parent metadata. Module names differ from paths; paths include Unicode and a newline. Duplicate path/name collisions, includes and full libgit2 cache lookup, renamed/worktree variants, displayed dropdowns, parent access under the signed sandbox and other native acceptance remain pending. The full port is incomplete.

Clipboard URL and branch prefilling

Selecting Arbitrary URL now reads Unicode text from the macOS pasteboard (string, then file-URL representation). Pull first recognizes its git pull prefix and then git fetch; Fetch reverses that order. The shared source rules recognize nonempty lowercase HTTP/HTTPS/Git/SSH schemes, git@ and Windows drive paths. POSIX absolute paths and file:// URLs are explicit macOS additions. Unrecognized or empty text restores the latest URL history entry and retains the current branch. A recognized URL without a parsed branch also retains that branch.

The parser follows the source's UTF-16 offsets, first-line/NUL truncation, outer double-quote handling, case-sensitive prefix matching, command trimming and literal-space truncation to at most two command fields. The dialog's split keeps its source conditions: the first space must be after UTF-16 offset one and leave more than one branch unit; surrounding matching quotes are removed only from split fields longer than two units. Repeated spaces, one-unit branches, command prefixes without a token boundary and quoted unsplit arguments therefore retain the upstream behavior. This is field prefilling, with no shell interpretation or clipboard-triggered Git operation. Selection does not save history or mutate Git; OK still controls saving and transport.

Clipboard QA records parser vectors and native models using injected text, including preferred/alternate commands, quote/extra argument rules, Unicode offsets, history fallback and no mutation until OK. Real literal-path/file-URL Fetch, ff-only Pull and failed destination retention were checked. The user pasteboard was neither read nor modified by these tests. Physical pasteboard/radio/keyboard acceptance, complex shell quoting and paths with spaces, platform whitespace equivalence, clipboard support in other dialogs, physical history deletion and signed sandbox checks remain pending. Full port incomplete.

Transport cancellation

Cancel and the window close gesture request cancellation during Fetch, merge Pull and Fetch-before-Rebase. Controls remain locked until the operation finishes; Cancel shows Cancelling… while the owned process group stops. Ordinary Fetch and merge-based Pull close their owned progress/options after accepted cancellation finishes. Fetch-before-Rebase now shares the same owned progress policy. Headless models can retain results for inspection. Idle Cancel closes it. A cancelled fetch does not trigger success or open Rebase. A fresh cancellation token is created for every invocation. Merge-based Pull now retains the result in its own progress sheet; see PULL-PARITY.md for its actions and owning-window lifecycle. Cancellation does not roll back changes Git has already made.

The Dialogs settings page now exposes the source Confirm to kill running git process preference (ConfirmKillProcess, default false). When enabled, Cancel asks The process is still running. / Are you sure to abort?, with Yes as the default, matching the source MB_YESNO prompt. No leaves the transport running; Yes stops it. This adapts CProgressDlg::OnCancel and SetDialogs2; owned POSIX process-group signals replace the Windows console/process-tree APIs. The setting is currently consumed by Pull/Fetch and Push, rather than every operation in the application.

Cancellation QA records pre-cancelled core requests and headless native Fetch, Pull and Fetch-before-Rebase models. A real wrapper process and its child are stopped while an unrelated process survives; No/Yes confirmation, retained inputs, absent success/Rebase callbacks, unchanged HEAD/index when stopped before transport, and idle close are checked. A hidden settings layout is constructed and closed. These checks do not establish physical Cancel/Escape/window-close/sheet interaction, light/dark/accessibility, retry acceptance, cancellation after Git mutation, separate progress-window layout, streaming output or signed sandbox execution. Full parity remains incomplete.

Immediate history deletion

The shared editable histories now map HistoryCombo's open-dropdown Shift+Delete behavior. The macOS control accepts Shift+Forward Delete and Shift+Delete (backspace key on Mac keyboards). Only an enabled open popup owns this shortcut; closed controls keep normal text editing. It removes the highlighted entry, selects the next item at that index or the previous item at the end, and clears the field when the final entry is removed. Entries save immediately without reordering, so cancelling the dialog does not restore a removed history item. Invalid selections and busy models do not mutate history.

Pull/Fetch URL and branch histories remain shared. Push URL/destination/server- option histories remain repository-scoped; all five fields use the same native receiver and source selection/order rules. A window-scoped local key monitor handles the shortcut while the delegate reports its popup open; there is no global keyboard monitor.

Deletion QA records immediate persistence, next/previous/empty selection, invalid/busy gates, shared/scoped histories and cancel/reopen retention with byte-identical Git HEAD/index/config. Actual hidden native controls verify selected indices, popup notifications and direct native key-handler calls for open/closed/disabled/permission gates. Events are not sent to the app. Existing four-Git Pull/Fetch and Push history matrices also run. Physical dropdown event routing and highlight behavior, field-editor focus, actual Shift+Delete/Forward Delete/Escape, resize/theme/accessibility and signed sandbox acceptance remain pending. Source return-key/wheel handling and complete HistoryCombo parity are not established by these checks. Full port incomplete.

Ordinary Fetch progress and post-actions

Ordinary Fetch captures its options and keeps the owning dialog locked until its progress result closes. Success offers Show log, Reset…, Fetch…, Rebase… and Switch/Checkout… in upstream order. Bare repositories omit Rebase, as upstream does. Failure offers Retry, plus Show log for an all-remotes fetch. Retry reuses the captured arguments with a fresh cancellation token; macOS retains the same progress sheet rather than opening another progress window. All-remotes failure can follow a successful update of another remote; Retry does not roll that back.

Reset resolves the current tracked branch when selected, then opens native Reset with Hard selected. The Fetch result itself does not reset HEAD/index/worktree. The other actions route to native Log, a fresh Fetch dialog, Rebase After Fetch or Switch. Dispatch and close guards prevent duplicate actions. The original refresh.ico now supplies the Retry icon here and in Abort Merge; upstream IDI_REFRESH uses that resource, distinct from its ribbon Refresh bitmap.

Fetch progress QA records real Git checks for captured options, retained results, named/all-remotes retry, partial transport failure, fresh tracked Reset defaults, bare action conditions, duplicate dispatch and cancellation cleanup. It also records updated shared-history, clipboard and submodule regressions. No main GUI is launched by these receivers.

Fetch → Rebase now uses the owned progress and source decision prompts described below. Physical progress-sheet controls, destination windows, light/dark, keyboard/accessibility, streaming output, network credentials and signed sandbox acceptance remain pending. The existing screenshot shows options and predates this progress sheet. Full application parity is incomplete.

Fetch → Rebase decisions

Manual Launch Rebase After Fetch now follows the command-line DoFetch conditions and order. If the local branch is equal to or newer than the fetched target, it asks whether to open Rebase anyway (default No). If the pre-fetch conventional remote-tracking hash, fetched target and HEAD are equal and nonempty, it next asks whether to open Rebase for an unchanged branch (default No). These are separate questions; Yes on the first can lead to the second. If HEAD is an ancestor of the target, including equality, it offers Merge, Rebase and Abort (default Rebase). An unchecked answer affects this invocation only. Don't show this message again stores the selected answer under the original three OpenRebaseRemoteBranch keys, including Yes/No and all three fast-forward answers, as upstream ShowCheck does.

A No or Abort retains the successful Fetch result and its Log/Reset/Fetch/Switch actions. Rebase closes the owned result/options and opens the native plan with the immutable fetched hash. A Git-configured rebase Pull uses automatic mode, skips these questions, and forwards auto-start and preserve-merges. Diverged branches proceed to the plan without a fast-forward question.

Merge executes a real --ff-only merge of that immutable target. Its successful result closes automatically; failure stays in progress. A failure with working tree conflicts offers Resolve, routed to native Commit as the upstream callback does; a failure without conflicts offers no post-action. macOS uses the existing owned progress sheet for this merge phase rather than another modal window. Inputs, duplicate fetch/close and cancellation stay gated during an owned decision sheet; subsequent transport uses the existing cancellation policy.

Decision QA records real equal, ahead, diverged and fast-forward histories, held decision gates, retained Abort/No, saved answers, automatic handoff, actual fast-forward mutation, dirty/conflicted merge failures and captured retry. Ordinary Fetch/cancellation regressions also run. The native presenter is wired but physical button/default/Escape/suppression interaction and factory/editor acceptance remain unverified.

The earlier safety adaptation remains: fetchForRebase fetches the selected branch explicitly and pins FETCH_HEAD immediately. Source DoFetch uses its conventional remote-tracking ref. A custom refspec therefore does not make the native plan use a stale conventional ref; the unchanged test uses the pre-fetch conventional hash as upstream does. Native full remote chooser, empty-branch fallback/all-remotes Rebase, libgit2 mode, failure after mutation, hook/credential/streaming and signed sandbox acceptance remain pending. Full port and distribution remain incomplete.

Live Fetch and Fetch/Rebase output

The native result now streams the CLI Fetch phase through GitCliOutputParser, as CAppUtils::DoFetch uses CProgressDlg in the pinned source. Ordinary Fetch and the selected-branch Fetch/Rebase path forward typed stdout/stderr chunks; validation, revision resolution and ancestor queries are kept out of the visible transport log. Coalesced UI wake-ups retain parser-held bytes until consumption. The result captures GitOutputLimitinKiB (default 2048 KiB, bounded 16..102400), shows CR replacement, phase and green percentage, and scrolls to the end. After its soft display limit the parser drops presentation bytes; complete raw output and diagnostics remain available to the result model. Concise failures avoid replaying transport output already shown. A successful Fetch whose Rebase-target preparation fails preserves both transport and later command diagnostics.

Retry creates a fresh token/parser and clears previous bytes, truncation and phase while retaining captured options and display-limit policy. Existing Rebase questions, immutable target and automatic handoff are preserved. When manual Fetch/Rebase chooses its ff-only Merge, the Fetch percentage is cleared before that phase. The later Merge streaming section supersedes the original completion-time output behavior.

Fetch/Pull live-output QA records Core observer/result checks and four-Git hidden native checks for ordinary Fetch, Fetch/Rebase and merge Pull: split Unicode, remote CR replacement and 50% visible before an owned helper exits, actual tracking refs and immutable Rebase target, mixed changes preserved, No/Yes cancellation and owned child cleanup, captured 16 KiB limit with complete failure diagnostics and fresh Retry. Existing Fetch result and Rebase decision receivers are regression gates. Synthetic helper progress verifies delivery timing, not real network transfer cadence.

Physical scrolling, focus, keyboard/defaults, themes/accessibility, all progress controls, full libgit2/source-hook variants, real network authentication/timing, full modal ownership and signed Finder/sandbox/App Store acceptance remain pending. Existing screenshots predate this progress behavior.

Streaming the ff-only Merge phase

Manual Fetch/Rebase's Merge choice now streams its actual ff-only command. The pinned DoFetch creates a separate Merge CProgressDlg with no-error auto-close. TurtleGit retains its existing owned Fetch result window, preserves the bounded Fetch log as a prefix, and starts a fresh captured-limit parser/presentation for Merge. Each phase has its own soft display limit; the combined view can contain two bounded logs. Full raw Fetch and Merge diagnostics remain together. This is an existing Mac window-ownership adaptation, not complete upstream modal parity.

Successful ff-only Merge still closes explicitly; failure uses repository conflict state for Resolve. Confirmation pending at natural completion now defers that close. Ordinary Fetch auto-close and Rebase handoff also wait for the answer; late Yes and duplicate answers do not cancel completed transport or repeat the handoff. Root's Fetch completion notification receives complete raw diagnostics, independently of the bounded visible log.

Merge live-output QA records live Merge phase/percentage before helper release, real ff-only HEAD advancement, retained Fetch log/raw diagnostics, No/Yes cancellation leaving HEAD unchanged, and success while cancellation confirmation is held. Existing four-Git decision and live Fetch/Pull receivers cover the other routes. Physical/nested-modal factory, scrolling/theme/focus/default/accessibility, real network and signed acceptance remain pending. Older completion-time-output notes are superseded for this phase.

Owned reference-browser Fetch and lifetime

Remote reference Fetch now uses the native Fetch dialog, with the source's configured-remote prefix/order resolver, selected remote preset, owned progress acknowledgement and catalog refresh. The standalone interaction factory is shared with this route and remote-only tracking pickers. See reference browser parity.

Metadata/defaults/settings/branch-browse work shares owned cancellation and closed publication fences; branch browsing/settings reads enforce repository scope in AppStore builds. Plain Fetch validation threads the transport token. Fetch progress invalidates/cancels its process, freezes closed results and owns separate recovery and Reset metadata reads. Late Rebase answers cannot persist preferences after closure. Standalone Fetch/progress and pending confirmations now block Quit. Normal acknowledgement/post-actions and Cancel remain covered by regressions.

Merge-based Pull forced cleanup now has its own lifetime checks and receiver; see Pull parity. All Fetch-for-Rebase preflight cancellation, physical prompt/sheet restoration, every metadata/error/post-action stage and signed sandbox acceptance remain incomplete. Full Fetch/Pull parity remains partial.

Synchronization full-options Rebase ownership

Shift+Pull/Fetch from Synchronization now installs an awaited owned-Rebase handoff. Fetch progress and options remain alive but are suspended while the owned Rebase dialog runs. Automatic configured Pull forwards auto-start and Preserve Merges; manual Fetch retains manual Start. Synchronization reads final HEAD only after Rebase dismisses, including ordinary cancellation. Opening failures close suspended windows, retain fetched ref changes and keep the error visible across outgoing refresh; manual Refresh clears that status.

The standalone Fetch callback remains deferred when no owned handoff is installed. Full production factory, Push/Mail completion chains, physical input, rendered UI and signed sandbox acceptance are still pending. See Synchronization parity for the source comparison and verification scope.

The connected remote-dialog batch passed 22 Core tests and six grouped native checks across system and bundled Git. See the scoped QA record.

The connected October 14 transport-preparation batch gathers metadata before publishing controls, owns cancellation/grants/Quit until worker settlement, refuses stale remote replies and supplies Retry loading after cancellation. Normal close cancels and then closes; forced closure fences late publication. See the shared preparation/ownership scope. The Debug build and six grouped native cases passed after the Core Push cancellation repair; see batch evidence. Physical/source/signed/App Store acceptance remains open.

The October 15 transport reference selection family adds owned request identity, captured remote/branch/mode validation and post-sheet publication to Pull/Fetch's Select Remote Ref window. Child preferences/appearance/visibility now follow the options owner; failed or retired presentations cannot retain browse state or apply a late branch. Existing source query/typing/F5 semantics are retained. Physical authenticated and signed acceptance remains open.

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.

Optional libgit2 transport continuation — October 19, 2026

Fetch and its pinned Rebase preparation now consume the source master switch and operation mask through the optional libgit2 backend. Named/arbitrary/all-remotes, tag/prune/depth options, actual TLS decisions and owned credential/cancellation progress use the selected engine. The default remains Git CLI. Consult the linked batch QA for actual validation and remaining provider, public-server and signed acceptance.

Source-selected Fetch refspec lifecycle — October 23, 2026

Standalone selected libgit2 Fetch now appends its single requested raw branch refspec after loading the remote, then fetches using that same loaded object. The current fetch retains the previously configured spec set; a later fetch sees the new entry. Repeated selected requests retain source duplicate append order. The default named-remote configured fetch and All Remotes do not request an addition. CLI Fetch keeps its per-operation selection and does not acquire this configuration mutation.

Fetch-for-Rebase retains the selected named remote branch as its completed upstream. It does not substitute an unrelated FETCH_HEAD line when configured specs fetched other branches. Configured-but-not-yet-active targets can therefore produce a follow-up error after a successful fetch; the real command output and accepted configuration addition remain available. Anonymous URL requests with a nonempty raw spec fail safely because source append has no named configuration target. No synthetic remote name is created.

See the shared source lifecycle contract and dated checkpoint for source/API boundaries and actual grouped validation. Existing Mac All Remotes iteration and pinned libgit2's swallowed config-write return remain documented limitations. This continuation does not certify the entire Fetch/Pull resource or public, signed, physical, minimum-version/Intel and hosted integration acceptance.