patchette

docs: home the replay algorithm in REPLAYING.md; message directives live inside the flanks

commit e3554c656162133cc0ca78bc32cebead7064f316 · parent 61ee4d1792 · browse this tree
Ximianth, 2026-10-18 23:17:39 UTC

The replay steps describe what a tool does, not what the format is,
so they move out of FORMAT.md into their own hacking doc.

The message-no-final-newline declaration moves from the header area into
the message block itself: directive lines sit directly after the opening
'----', before any content.  Directives-then-content mirrors how the
header block precedes its content, and makes a strict rule: bare
unprefixed lines are legal only in that position, and only if recognized.
tip is now optional per the format rewrite; REPLAYING.md says what that
means for verification.  The tool does not conform to these three points
yet; that lands next, on this branch.
diff --git a/docs/hacking/FORMAT.md b/docs/hacking/FORMAT.md
index bae83d0..96950bd 100644
--- a/docs/hacking/FORMAT.md
+++ b/docs/hacking/FORMAT.md
@@ -165,6 +165,12 @@ In detail:
 	  (Git headers cannot contain an empty line, so this is unambiguous.)
 - **Commit message**:
 	- Flanked by literal `----` lines.
+	- Directive lines may appear directly after the opening `----`, before any message content.
+	  These are bare lines (no prefix); any unrecognized line in this position is an error.
+	  Currently defined:
+		- `message-no-final-newline` -- declares that the message does not end with a newline character.
+		  (Possible at the git object level, though porcelain tools essentially never produce it.
+		  But lossless means lossless.)
 	- Each line of body between the `----` boundary markers carries a prefix.
 	  (This ensures that commit messages containing a line of dashes cannot produce a fuss.)
 		- Two prefix styles are supported: {`> `+line} and {`\t`+line}.
diff --git a/docs/hacking/REPLAYING.md b/docs/hacking/REPLAYING.md
new file mode 100644
index 0000000..ee5bc68
--- /dev/null
+++ b/docs/hacking/REPLAYING.md
@@ -0,0 +1,26 @@
+Replaying: rebuilding branches from patch files
+===============================================
+
+The FORMAT document describes what the files *are*;
+this one describes how a tool turns them back into git commits, exactly.
+Any implementation following these steps reproduces the stored branch SHA-exact.
+
+For each patch name in series order, maintaining a map from reference to replayed SHA
+(seeded with `BASE` -> the STACK's base):
+
+1. Parse the patch file.  Resolve each `parents` reference through the map;
+   an unknown reference is an error (series order guarantees parents were replayed first).
+2. Read the first parent's tree into a scratch index (no worktree is required, ever).
+3. If the diff section is non-empty, apply it to that index
+   (`git apply --cached --binary --whitespace=nowarn`; the explicit whitespace flag
+   keeps a target repo's own `apply.whitespace` from rewriting or refusing recorded bytes).
+   An apply failure is an error: the recorded base is the only supported target.
+4. Write the resulting tree.
+5. Reassemble the raw commit object: `tree` line, one `parent` line per resolved parent,
+   then the stored header block verbatim, a blank line, and the message bytes
+   (dropping the final newline if the `message-no-final-newline` directive was declared).
+   Hash and store it (`git hash-object -t commit -w`).  Record name -> SHA in the map.
+
+The final SHA is the replayed branch tip.
+When the STACK records a `tip`, verification compares against it;
+a STACK without one can still be replayed, just not cross-checked.