TurtleGit for Mac

View Markdown source ↗

Unicode and source text patches

Source pin: 7338078f8ddd924b8cddee35f512f2286072136d. The connected adapter batch covers decoded patch headers and hunks, target-folder matching, Review Patch file selection, Before/After comparison, recorded-base recovery, and checked application through the existing patch editor/save policies. It supplements the Git patch engine; it does not replace Git's binary or special-file handling. The dated grouped record reports actual validation scope and failures. Authored fixtures are not passing evidence.

Review and original data

The immutable SourceTextPatch receipt keeps the original bytes, decoded text, captured container encoding, file order, exact path bytes, hunks and line-format state. Copy and unedited Save retain the original patch bytes. Decoding a UTF-16 or UTF-32 patch for Review does not convert the patch file on disk or force that encoding on the files being patched. The captured Merge UTF-8/legacy-code-page policy governs detection, independently of later Settings changes.

Ordinary GNU/SVN --- and +++ file pairs do not require a diff --git header. SVN revision annotations, tab-delimited timestamps and fullwidth revision parentheses are handled. Only an index old..new record supplies recorded Git object identities; a numeric SVN revision is not a Git blob identifier. An arbitrary SVN patch without a recorded Git base cannot acquire invented three-way recovery.

Source default paths remove a/ and b/; an explicit Strip count operates on the retained raw header paths. Reverse swaps before/after paths, ranges, line roles, BOM flags and recorded identities while retaining the original document. Native quoted Git names preserve literal parentheses, escaped backslashes, octal filename bytes and Unicode composition. Parsing these quoted tokens before SVN annotation trimming is an intentional macOS adaptation: valid Git filenames must not be truncated or changed into different filesystem paths. Unquoted source Windows-style separators remain supported.

Regular executable modes and rename/copy roles are retained in the per-file receipt. They must pass through the checked multi-path application writer; text simulation alone does not authorize dropping metadata or overwriting a rename destination. Metadata-only Git changes stay with Git. Extended reverse-copy metadata also stays on the original Git route; Unicode containers that Git cannot decode do not acquire invented reverse-copy semantics from this adapter. Mixed documents containing an unsupported opaque record are refused by the text adapter as a whole, rather than applying only their text subset.

Matching follows the pinned patch algorithm

TortoiseMerge/Patch.cpp::ParsePatchFile (49–372) and PatchFile (425–582) define this compatibility behavior. Hunk counts must match before a complete receipt is returned. Unknown markers, incomplete headers, count overflow and unterminated hunks fail without publishing a partially parsed selection. Parsing and simulation are bounded and cancellable.

Removed lines compare exactly at the current new-line cursor and remove that line without advancing the cursor. Context compares the current new and old cursors. A mismatch can probe only the next new-cursor line, then the line two positions forward, then the next old-cursor line, in that order. Each hunk starts from its declared positions. There is no arbitrary backward search, global offset scoring, whitespace folding, case folding or Unicode normalization.

Insertion checks later non-added patch records against the same current target line to avoid duplicate insertion. A successful lookahead remains accepted even if a later comparison breaks the lookahead, matching the pinned implementation. The force flag used for recorded-base retry bypasses an addition's existing-file refusal; it does not bypass hunk matching.

The source's omitted-count shorthand has unusual semantics: -N means old start zero/count N, and +N means new start one/count N. The adapter preserves this source rule. Ordinary successful Git parsing retains Git's standard unified-diff semantics rather than being reinterpreted by the adapter.

Expanded-keyword matching is deliberately narrow in where it applies, but broad within that line. A case-sensitive occurrence of $LastChangedDate, $Date, $LastChangedRevision, $Rev, $LastChangedBy, $Author, $HeadURL, $URL or $Id anywhere in a patch context/removal payload bypasses that entire payload's equality check. A closing dollar sign and a matching target expansion are not required. Added-line insertion lookahead still uses exact equality. Existing context text is retained; removal deletes the actual addressed target line.

Target encoding, BOM and line endings

FileTextLines.cpp::Load and Save provide the target format rules. Existing target codecs and untouched line endings are retained. Inserted terminated patch records use the target's predominant ending, with source CRLF fallback. A missing preimage starts with the captured legacy code page, not the patch container's codec; lossless representability is required. Explicit BOM transitions can promote the result to UTF-8 BOM, including a pure addition, or remove a UTF-8 BOM when the hunk's first-line flags request that transition. This is source behavior, not a general request to transcode every target to UTF-8.

Patch payloads retain their original terminators for inspection, while any terminated inserted record has source AutoLine semantics. A leading U+FEFF is removed from a hunk payload and retained as a BOM flag. The source ignores backslash-prefixed warnings, including \ No newline at end of file: the warning does not retroactively remove the preceding record's ending. The Git engine's existing exact final-newline behavior remains separate.

A loaded nonempty target has a final NoEnding record, including an empty final record after an ending. A genuinely zero-byte file returns from source loading before that record is added. BOM-only input is nonzero-byte input and retains its empty decoded record. These states must remain distinct from absent files and from a terminated blank line. Automatic PatchSave and explicit Keep/Delete Save remain governed by the patch application/save policies.

Filesystem and engine boundaries

The pure applicator returns a result and an exact input-byte witness; it does not write a target or change the index. The repository integration retains the captured file bytes, modes and permissions, path confinement, folder-access scope, stale-input checks, generated-patch validation and publication path. For decoded output with unchanged existing Git modes, captured permissions survive Git's canonical executable mode recreation. Restoration uses a pinned root and parent/leaf descriptors, refuses symlinks, verifies exact owned output bytes and mode, and checks the opened file and named entry before changing permissions. New outputs retain their actual umask-restricted permissions in subsequent owned receipts. A failure or cancellation after publication retains the successful byte-write receipt with a distinct warning; it does not claim rollback. Native explicit Save requires a fresh read before accepting an unverified savepoint. Patch All and Patch Selected continue to use captured AutoAdd, per-file progress, cancellation and successful-file/warning receipts. Plain-folder application does not initialize an index.

Binary payloads, symlinks, gitlinks and unsupported mixed records are not decoded as ordinary text to make a failed Git command succeed. There is no blind fallback from every Git error. Existing binary/symlink/deletion whole-state recovery stays available through its existing route. Source .rej files are not a missing implemented upstream feature: Git/GitPatch.cpp records an error string and explicitly says reject files are unsupported; the rejected-hunk viewer in MainFrm.cpp::PatchFile is disabled.

Evidence and remaining acceptance

Parser and applicator fixtures cover Unicode and captured legacy codecs, multi-file SVN/GNU headers, quoted paths, source offset/keyword rules, format state, strict failures, Reverse/Strip, metadata retention, exact input witnesses and cancellation. The connected repository/native checkpoint must additionally exercise real selected/all application, recovery, mode/rename/copy publication, original-byte Copy/Save, captured preferences, unsafe/stale refusal and actual worker/window retirement with private repositories and hidden windows.

The dated record distinguishes completed checks from failures and unrun cases. This scope does not establish full upstream parser acceptance, every external producer's format, visual parity of every dialog, hosted CI success or physical Finder acceptance. Remaining implementation and evidence are tracked in the adapter gap record.

Captured target encoding after discovery — October 23, 2026

Inspection, comparison and selected/all application must honor an explicitly supplied target text policy even when discovery already selected the decoded adapter. The patch container and target encoding are separate: a captured Unicode patch retains its Unicode hunk text, while a legacy patch can be re-decoded from its original bytes under the newly selected legacy codec. Re-decoding cannot change accepted paths, record identities, rename/copy roles, modes or recorded object identities.

The original target byte, mode and permission witnesses remain authoritative. Changing an encoding choice does not refresh the review or accept later external edits. Manual recovery may show text that requires another output encoding; automatic publication still requires lossless encoding, and an unrepresentable result remains available for an explicit manual encoding choice.

The hosted Core failure that exposed this propagation boundary and the focused repair checks are recorded with the connected batch.