TurtleGit for Mac

View Markdown source ↗

Format Patch port audit

Baseline: TortoiseGit 7338078f8ddd924b8cddee35f512f2286072136d.

The repository operation is implemented and tested. A native dialog, app/Finder action, progress sheet and mail composition handoff compile. Core and selected hidden native workflows are verified below. Physical visual/keyboard and signed sandbox acceptance remain pending; this is not a completed dialog port.

Source Blob Replacement
src/TortoiseProc/Commands/FormatPatchCommand.cpp 0e05626d7c64c484276c45d5a004d2d9f3d668d3 Sources/TurtleGitCore/FormatPatch.swift, command only
src/TortoiseProc/FormatPatchDlg.cpp b8ad0c02bb27397700a6aee773d87ce7656d62c8 Sources/TurtleGitMac/FormatPatchWindow.swift, partial native UI
src/Git/Git.cpp 43dee91dbf94e46564b4cc1a139717fcbd8f6803 Sole for-merge FETCH_HEAD record selection only
src/TortoiseProc/GitLogListAction.cpp 88c255c4c80578c099bbcd4604f6e088f2f9d40a Log command start/end presets
src/TortoiseProc/GitLogListBase.cpp 19518f37689e19b7e913c9ff8cc9d65ec223c187 Patch selection eligibility
src/TortoiseProc/ProgressDlg.cpp 557988a1303dc86b11c9c74d29be4797aa7e6090 Native interrupt/stop workflow, partial

The operation uses the source's git format-patch [--no-prefix] -o directory with one of --end-of-options since --, -count --, or --end-of-options from..to --. Count accepts 1 through INT_MAX. Since FETCH_HEAD selects the only record eligible for merge; it rejects zero or multiple records, rather than exporting from an arbitrary first record. Arguments never pass through a shell. Existing patches are replaced as in upstream; Git controls patch numbering, subject filenames, binary data, mail headers and configured formatting. Export works with a bare repository when the destination is outside its administrative directory. Git metadata destinations, including symlink aliases and Git admin roots without a .git name, are rejected as a macOS adaptation. Output directories can be created by Git.

Native dialog requirements audited from IDD_FORMAT_PATCH

Evidence and remaining work

The unified-diff button now honors the dedicated Unified Diff Viewer preference and source's Shift inversion, with exact read-only bytes for an external macOS application. The preference retains a disabled application for Shift use and is independent of Alternative Editor. The settings/launch/keyboard implementation compiles; native and signed acceptance remain pending. See UNIFIED-DIFF-VIEWER-PARITY.md.

The native dialog uses the original Output Directory and Version groups, editable AppKit history fields, AppKit radio buttons, commit-count field and stepper, From/To Log selection sheets, both options and the four footer actions. The Since chooser lists local and remote references without tags. Histories and options persist with UserDefaults. Its fixed height and horizontal resizing match the source intent; geometry uses the shared native geometry store. Repository and output-directory access leases stay retained through Git operations. Store builds require a covered directory grant, and typed destinations outside existing grants open the folder chooser.

Log's Format Patch context command now presets the same start/end values as the source. One selected commit opens Since at that hash, exporting commits after it; the inactive range fields are preset to hash~1 and hash. Two selections can be nonadjacent; three or more must form contiguous displayed rows. Multiple rows preset Range from the oldest selected hash's first parent (~1) to the newest selected hash. Git determines the resulting range, which can include commits between the selected endpoints; this is not a list of independent selected patches. A root commit lacks the required first parent and Git reports that error as in source. The helper also supports oldest-first order and rejects multi-row continuity when rows are hidden; the current native Log has newest-first Git query results without an additional client-side row filter. Original menupatch.ico is used for this Log command. The Since reference picker now has a case-insensitive branch filter. Applying a preset to a busy or open-progress dialog is blocked. Different preset ranges retain separate native dialogs so a new Log request does not replace the inputs or access leases of an export in progress.

Create Patch Serial appears in the app sidebar and automatic action menu, with upstream menudiff.ico (IDI_CREATEPATCH) in app and Finder menus. Finder enables it for one directory selection. The read-only unified-diff viewer uses source's HEAD-to-working-tree stat/patch command, excludes external diff tools and applies the no-prefix choice. The progress sheet displays errors or Git's output and blocks closing/Quit while Git is active. Optional mail uses the native compose-email service with exported patch attachments, retaining the controller and output grant until its callback. No message was sent during testing.

Format Patch now passes an explicit cancellation token to its Git commands, including metadata preflight. The optional Git runner spawns an owned POSIX process group with explicit standard streams and environment. Cancel interrupts that group, allows 100 ms, sends termination, allows another 100 ms, then forces the group to stop before reaping its leader. This replaces upstream's Ctrl-C, Windows descendant enumeration and potentially long waits. Other operations keep the existing Foundation runner unless they explicitly opt into this path; this is not a claim of cancellation parity for every dialog. Helpers that detach into separate sessions would need additional handling and are not covered by the group test.

The native sheet has Cancel while busy, Stopping Git after a request, Cancelled after it stops, and Close/Retry through the parent dialog. Escape is routed to cancel or finish; implicit sheet dismissal is blocked. Partial patches are kept and diagnostics are displayed, as in the source's interrupted command. Cancellation does not report success or open mail composition, including a cancellation accepted just before the awaited result returns to the UI. The output refresh callback now runs after success, failure or cancellation so partial files can be seen in status. The parent dialog also blocks editing/export/preset replacement while its mail composition service is active. These native interactions are implemented but remain unverified through actual UI control.

An earlier Debug preview launched once, but the computer-control connection failed with “Sky Computer Use native pipe closed before response” twice. No accessibility state or screenshot was obtained, so no layout or native interaction pass is claimed. Normal Quit could not be invoked through that failed connection; the exact owned PID/executable was rechecked and terminated. No other app was closed.

Seven FormatPatchTests pass. They exercise all three selections, empty ranges, original mail headers and numbered filenames, exact binary patch application with git am, unchanged HEAD/index/working bytes, no-prefix output, replacement of existing patches, invalid selections, option-shaped revision rejection, metadata aliases, bare export and FETCH_HEAD disambiguation. These integration tests do not prove native UI, Finder integration, progress cancellation or signed sandbox access.

The added Log tests cover one/two/contiguous/sparse/hidden/stale row selections, both orderings, and real patch generation from a three-commit repository using the presets. The one-row case exports only the later commit; two and three rows produce the source's inclusive oldest-to-newest series. Export leaves HEAD and index unchanged. Native Log menu activation and preset fields remain unverified.

Three GitProcessCancellationTests cover an already-cancelled export with no output directory, ordinary success/failure using the cancellable runner, and a slow owned helper that writes a partial patch plus stdout/stderr and starts a sleeping child. Both PIDs share the owned group and disappear after cancellation; the partial patch, diagnostics, HEAD, index and working bytes are checked. The helper uses disposable test data; no user Git operation is terminated.

Remaining: verify native light/dark layout and keyboard interaction, successful export through the dialog, failure/retry, output-folder grants, unborn/bare states, mail attachments and service failure/cancel callbacks. Verify searchable reference browsing, source command startrev/endrev presets and Log export entry points; verify Shift viewer selection and native progress cancellation. Complete the source Send Mail dialog/options rather than treating native composition alone as full parity. Signed Finder and App Store testing and screenshots remain pending.

Captured progress policy and acknowledgement

Format Patch captures AutoCloseGitProgress when export is submitted (upstream constructs CProgressDlg after options acceptance). Both success auto-close modes acknowledge automatically because this command supplies no post-actions. Manual keeps the result. Failure/cancellation stays open; Close returns the native options, where a new export can be submitted. There is no progress Retry action in the audited FormatPatchCommand.cpp. Keeping the options behind a failed result is the existing Mac ownership adaptation; upstream closes its options on OK.

ConfirmKillProcess now presents native Yes/No, default Yes, on the owned progress sheet. No keeps Git running. Yes stops the existing owned process group and retains partial patches/diagnostics. While a confirmation is pending, Close and further Abort are blocked. A success that finishes during the question waits for its answer before applying auto-close; a late Yes does not reinterpret that completed success as cancellation.

Result acknowledgement schedules one close or mail callback with the submitted Send Mail choice and generated file URLs. Duplicate acknowledgement and new exports in the scheduled handoff interval are rejected. Presets, exports and unified-diff requests cannot replace open results. Normal close/Quit includes the result and scheduled handoff; controller invalidation cancels any owned export and suppresses delayed mail/close. Directory/revision histories and mail/no-prefix preferences now accept an injected private defaults domain for native QA while production retains standard preferences. Mail remains the existing native composition service; the source Send Mail options are still unported.

Progress QA records four-Git actual Since/Number/Range exports across all policies, captured choices, duplicate acknowledgement, generated attachment URLs in a newline directory, real git am application/tree equality, private history reads, failure/new export, owned No/Yes cancellation with partial output and deferred completion. Hidden options hosting does not display a result sheet or prove UI acceptance; no actual mail service or external editor was invoked. Physical nested alerts, default focus, keyboard/layout, output grants, service callbacks and signed acceptance remain pending. Existing screenshots predate these progress changes.

Source-style live progress

The progress sheet now uses the shared read-only AppKit output view, including original Copy icons, source error/warning formatting and success/failure footer colors, locale/timing preferences and bounded UTF-8 output. Current work and percentage follow command output; ordinary format-patch emits paths without numeric progress, so no percentage estimate is invented. Close stays visible and disabled while Git runs. Abort requests the existing confirmation/cancellation flow, closes a failed result when idle, and is disabled on success. The sheet content explicitly enables its own controls while the options behind it remain locked. Escape retains the guarded cancellation/completed acknowledgement path.

Only the final format-patch command streams into progress; FETCH_HEAD and metadata probes remain internal. Returned stdout still supplies all generated attachment URLs even if visible output is truncated. Streamed command diagnostics appear once, with the actual exit-code footer. Empty ranges keep the no-patches message; cancellation retains partial files and the output-directory explanation.

Invalidating a closed owner cancels its operation, clears progress/confirmation, and suppresses late output, status refresh, completion/footer and mail/close handoffs. Busy remains true until the owned operation and child cleanup finish. Pre-start cancellation creates no output directory. A late unified-diff result also cannot open its viewer after owner closure. Physical sheet dismissal, rendered themes, keyboard/default controls, screenshots, signed sandbox grants and the complete upstream Send Mail options remain unverified.

October 10 streaming QA records system and packaged Git checks for real patch paths, completion/copy/color attributes, live output before process exit, an exit-17 diagnostic appearing once, abandoned owner/confirmation fencing, leader/child cleanup and partial-file retention, pre-start cancellation, and unchanged HEAD/index. A flooded output fixture confirms the captured 16 KiB UTF-8 display limit while both real generated patch URLs survive in the captured mail handoff. The existing progress regression also passes with both engines, including all modes/policies and actual git am tree equality. These are hidden receivers; no mail was sent or composed and no main app instance was launched.

Send Patch preparation now provides the source four-mode message foundation. The Format Patch composition consumer is not yet routed through it; a native Send Patch options foundation now exists, but caller routing and delivery remain pending.

Connected Create Patch Serial options family — October 15, 2026

The Since button now opens the shared native Browse References tree/list with its original context artwork, filter and metadata columns. It starts at the entered reference and follows gPickRef_NoTag: tags are omitted while local, remote, stash and custom namespaces survive. This replaces the reduced flat branch chooser. From/To open single-selection Log at the respective entered revision, with working-tree rows suppressed. Production callers configure the same downstream Log/reference commands used by other revision pickers.

The options window owns one revision/folder request at a time. Fields, presets, export, loading and unified preview are gated until the sheet ends. Results capture exact input spellings and options; changed drafts, duplicate answers, refused presentation and retired owners cannot replace them. The folder chooser retains its output access lease and fences delayed callbacks. Both picker types inherit the owner appearance/visibility and private preferences. Accepted or cancelled picks restore the owning window after sheet retirement.

Initial metadata and HEAD-to-working unified reads now share cancellable owned Git processing. Closing during a read cancels and waits before ordinary closure; forced closure retires callbacks while global operation accounting and repository permissions survive until processing settles. Exports also retain independent operation accounting and the captured output grant, including after owner removal. Cancel remains available during metadata/preview reads. Number mode disables OK outside the source's 1…INT_MAX range. Unified preview honors the owner's viewer preferences, remains read-only, inherits appearance/visibility and closes with its owner. The existing Send Mail handoff now uses the shared Send Patch workflow; older native-mail-service-only statements above are historical checkpoints.

The Debug build passed. Thirty affected Core tests passed; after tightening the new no-tags scope to preserve source custom/stash refs, its affected case passed again. Six grouped native runs (options, policies, streaming for system/bundled Git) passed, covering actual picker callbacks, folder acceptance/capture, real read-only preview, real Since/Number/Range exports, git-am tree equality, failure, partial-output cancellation and closed-owner process reaping. Receiver compilation now links once against current Debug objects and caches by objects, modules, fixtures and toolchain; CI groups these checks into one invocation. No full Core suite or additional Store build was repeated for this batch.

The initial compile found an ambiguous initializer and was repaired. The native fixture initially used a private selection setter and the async sheet overload; those fixture calls were corrected before all six passing runs. Existing Xcode exit-zero/no-output diagnostics and hidden-view split-layout warnings are recorded, not described as clean diagnostics. No screenshots, physical gestures, live mail or external application, signed grants, Finder or App Store acceptance are proved by these hidden checks. All receivers were reaped and independently checked absent. See options-family evidence. This remains partial source parity.

Command entry and retained drafts — October 18, 2026

Source formatpatch now reaches the native series dialog with start/end presets, including Since's inactive revision~1/revision range and end-only defaults. Repeated command entry preserves the edited review; repeated Log preset entry also keeps its existing draft rather than reapplying initial fields. Different Log ranges retain separate windows. Workspace preferences reach histories, options, picker and progress consumers. The connected export command opens ZIP export with its revision/subdirectory preset and preserved command draft. Existing source controls/icons and accepted Git operations remain. See command guide and batch evidence; focused native passes do not certify the full source layout or signed deployment.

The connected native export check exposed a lost successful acknowledgement: finish() cleared the progress flag and requested owner close before SwiftUI's sheet dismissal completed, so the native attached-sheet guard rejected Close. The owner now retains that close intent until AppKit reports the sheet ended. Options and Quit remain gated during this handoff, retirement drops the intent, and the normal close checks still run after dismissal. No artificial delay or forced dismissal bypasses the live-operation guards. The grouped command check includes actual export and native Finish/window closure.

Progress review and Send Mail continuation — October 19, 2026

The source command stores its exact format-patch invocation in ProgressDlg and waits for that modal result before calling SendPatchMail (FormatPatchCommand.cpp:78–88). Command display occurs in ProgressDlg::RunCmdList (ProgressDlg.cpp:226–233); the older constructor block at lines 145–146 is compiled out. The native progress output now receives the actual accepted executable/argv receipt from Core rather than rebuilding a second command from the editable options. Metadata and FETCH_HEAD probes remain outside the displayed export command. Raw Git stdout remains the source of patch filenames; display preambles and completion text are not interpreted as files. The actual process completion receipt supplies terminal status, timing and progress before later file discovery settles. That retained discovery still blocks Close/Quit and mail dispatch; Abort cannot cancel an already finished Git process. Forced retirement saves its action log after actual worker drain.

Progress uses the originating preferences and private output clipboard for output styling, colors, context artwork and Copy. Its owned native presentation keeps the existing progress content and option dialog layout. Successful Send Mail waits for actual progress-sheet retirement, including nested cancellation questions, before opening the existing shared Send Patch workflow. The captured output grants and repository permissions survive that transition. Successful export is independent of later mail delivery or cancellation.

Cancellation replies are one-shot and settle after actual question detachment. Completion while that question is open is retained until the decision drains. Forced owner closure, stale/duplicate replies and Quit confirmation cannot launch late mail, viewers or another export. Actual workers, chooser retirement and progress/question releases retain ownership until they drain; global Quit checks include those releases even after the owner is removed. Root command entry preserves edited live drafts and refuses a cached retiring owner.

The batch record distinguishes actual checks from physical visual/keyboard/accessibility, live mail transport, signed external grants, Finder integration and App Store acceptance. These remain open; this continuation does not certify the entire upstream dialog or port.

The grouped checkpoint passed ten affected Core methods, the Debug build after qualifying two nested callback property captures, and eight distinct native case/engine pairs cumulatively. The new workflow cases passed first with system and bundled Git. Retained Options initially crashed in objc_release; its raw fixture blocker closed with release-on-close enabled while Swift retained it. Setting isReleasedWhenClosed = false preserved every assertion and passed both options cases plus the remaining policy/streaming cases. No Core or successful workflow case was repeated for that fixture-only repair. Nine receivers including the failed attempt were reaped and their PIDs/groups independently checked absent. Existing hidden split-view and compiler warnings remain recorded.

Direct Rebase completion export — October 25, 2026

Rebase after Fetch now has a separate direct export/mail workflow. It uses the source range and repository directory without opening these editable options or inheriting their saved no-prefix choice. The shared Core export helper provides verified files from full stdout and normalizes both range endpoints for FETCH_HEAD. Ordinary Format Patch keeps its existing option/progress workflow. The connected checkpoint and remaining acceptance are recorded separately.