-
Notifications
You must be signed in to change notification settings - Fork 3
Storage Format en
What a .srcs file actually looks like on disk, and what happens
byte-by-byte when you commit. This is the code-level follow-up to the
"reverse delta" idea introduced in System Overview.
A v2 file starts with a header line:
# SimpleRCS v2.0; hash_algo=sha256; encoding=utf-8;
v1 is the older, headerless format (see "How v1 differs" below).
A block is a series of key @value@; lines, terminated by a blank line
before the next block. The @ character gets escaped as @@ inside a
value.
ver @1.2@;
date @2026-08-04T12:00:00@;
author @wkpark@;
log @fix typo@;
delta @0a1,1
> new line
@;
prev_hash @9f86d0...@;
hash @a3f5c2...@;
Field breakdown:
-
ver,date,author,log— always present -
text— this block holds full text (a text HEAD) -
delta— this block holds a delta (the instructions to go from the next newer version back to this one) -
binary— used instead oftext/deltawhen the content is binary. Format is<length>;base64,<encoded data> -
prev_hash,hash— v2 only. Previous block's hash and this block's hash -
signature— repeated once per GPG signature, if any are attached
This part is a bit subtle. The hash is computed over the version's
logical full text, not whatever is physically stored (see
codec.calculate_block_hash). The payload is
ver|date|author|log|<full text>|<previous hash>, concatenated in that
order and hashed.
The reason: when a block that used to be HEAD gets converted into a delta on the next commit, its hash value must not change. The stored representation changed, but the actual content that version held didn't — so the hash has to stay the same, or the chain breaks.
When commit() runs (inside simple_rcs.py's commit method):
- Re-encode the current HEAD as a delta block (
_format_blockwithis_delta=True) - Encode the new content as a full-text block (
is_delta=False) - Seek the stream back to where the old HEAD block started
(
stream.seek(head_info["start"])) - Write "the re-encoded old HEAD" + "the new HEAD" in a single
write()call -
truncate()to drop whatever tail is left over
self.stream.seek(self.head_info["start"])
self.stream.write(old_block_bytes + new_block_bytes)
self.stream.truncate()The code comments explain why it's one combined write() — splitting it
into two writes would leave a half-finished state (old HEAD overwritten,
new HEAD missing) if the process died in between.
Net effect: every commit rewrites exactly one block — the last one — and everything before that stays right where it already was in the file. It looks like "keep appending to the end," but it's more precisely "replace the last block, then append a new one."
Passing commit(..., snapshot=True) changes step 3: instead of a delta,
the old HEAD gets stored as full text. That breaks the delta chain at that
point — reading that version means reading full text directly, no deltas
to apply. If you know a section of history is going to get long and
frequently read, taking a snapshot there buys back lookup performance for
that stretch. It's opt-in per commit, not automatic.
checkout(ver) starts at HEAD and walks backward one block at a time,
applying each delta, until it hits the target version
(_get_prev_block). If it hits a snapshot block along the way, it jumps
straight to the full text there. It never loads the whole file into
memory — it only reads the blocks it needs, in order.
v1 was an early format that was never actually shipped — no header, and
delta blocks used the same text key as full-text blocks (no separate
delta key). No hash chain, no GPG signatures. simple_rcs.py still has
self._version < 2 branches for reading it. The v1-to-v2 migration tool
still exists in repository history (added and reverted as commits
0c7783e/90f2706) but isn't in the current tree.
-
Text: an RCS-style
diff -nscript — human-readable add/delete instructions. -
Binary: a BSDIFF40-compatible patch, base64/base85-encoded and
embedded inside a text block. Since the patch format itself is
standard, it also interoperates with the native
bsdiff/bspatchtools if you have them.
Because revisions aren't full snapshots, total file size ends up closer
to "sum of delta sizes + one full text" rather than "revision count ×
page size." A one-line edit typically only adds a few hundred bytes.
Wiki Backend Design (Korean only) has real measurements of what this means when
you're storing .srcs content in a database.