-
Notifications
You must be signed in to change notification settings - Fork 3
System Overview en
A library for tracking the version history of a single document. That's
the whole scope. It's not a multi-file repository like Git — no branches,
no server, nothing like that. You point a SimpleRCS instance at a file
path, a BytesIO, or any file-like (BinaryIO) object, and it keeps
committing history onto that one stream.
The reason it exists: sometimes Git is overkill. If you want history for a
single wiki page, a config file, or a document sitting in a database BLOB
column, spinning up a .git directory and maintaining an object graph is
overhead you don't need. SimpleRCS just drops into that spot as one file
(or one BLOB column).
Same idea as classic RCS. HEAD (the latest version) is always stored as full text, and everything older than that is stored as a delta — "how do I get from the next newer version back to this one."
[V1 delta] [V2 delta] ... [Vn-1 delta] [Vn full text]
^^^^^^^^^^^^^
HEAD
Two things fall out of this naturally:
- Reading the latest version is always fast. That's the version people actually read most often in practice, so it's the one worth optimizing for. O(1).
- Reading an old version gets slower the further back you go. Five versions behind HEAD means applying five deltas in sequence. O(k), where k is the distance from HEAD.
When you call commit():
- Read the current HEAD (full text)
- Compute the delta that turns the new content back into the old HEAD
- Overwrite the existing HEAD block with that delta (it stops being full text, becomes a delta)
- Append the new content as full text — this is the new HEAD
So every commit only touches two things: rewrite the last block, append a new one. Everything before that stays exactly where it already was in the file. There's never a full-history rewrite. The byte-level details live in Storage Format.
Pass commit() a str and it uses an RCS-style line delta (the diff -n
family). Pass it bytes and it uses a BSDIFF40-compatible binary patch.
Callers just call the same method either way — the type alone decides
which delta algorithm runs underneath. Images, PDFs, archives, whatever —
they can all get history stored in the same .srcs format.
Starting with format v2, every block carries a hash of its own logical
content plus the hash of the block before it. Same basic idea as Git's
commit chain — tamper with anything in the past and every hash after it
stops matching, which verify() catches. On top of that there's optional
GPG-based multi-signer signing and verification (simple_rcs_gpg).
The codebase ships several diff algorithms — greedy hash matching
(StreamSequenceMatcher), pure-Python Myers implementations, and
Cython-compiled Myers SES/DMP variants. But the commit()/checkout()
path only ever uses StreamSequenceMatcher — it doesn't guarantee the
shortest edit script, but it's fast. The rest exist for benchmarking and
comparison. Why it ended up this way, and what the tradeoffs actually look
like, is covered in Diff Engines.
| SimpleRCS | Classic RCS | Git | Plain DB snapshot | |
|---|---|---|---|---|
| Storage unit | One stream (file) | One file (*,v) |
Object DB + working tree | DB row |
| Delta direction | Reverse (HEAD=full) | Reverse (HEAD=full) | Snapshot (delta at pack time) | No delta |
| Content types | Text + binary, same format | Text only | Anything (blob) | Anything |
| Integrity | v2 hash chain + GPG | None | Content-addressable | Depends on DB |
| HEAD lookup | O(1) | O(1) | O(1) | O(1) |
| History lookup | O(k) backward scan | O(N) forward scan | O(log N) | O(1) |
The full comparison table lives in the repository's README.md.
- Not a multi-file repository — one instance per file
- No branching or merging — history is always linear
- No built-in concurrent-edit conflict detection or file locking. If you're putting this behind something multi-user (a wiki, say), you have to build that layer yourself — see Wiki Backend Design (Korean only) for what that actually involves.