Skip to content

System Overview en

Won-Kyu Park edited this page Aug 4, 2026 · 2 revisions

System Overview

What SimpleRCS actually is

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).

The core idea: reverse deltas

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.

What a commit actually does

When you call commit():

  1. Read the current HEAD (full text)
  2. Compute the delta that turns the new content back into the old HEAD
  3. Overwrite the existing HEAD block with that delta (it stops being full text, becomes a delta)
  4. 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.

Text and binary, one code path

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.

Integrity: hash chain and signatures (v2)

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 diff engine that's actually wired in

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.

How it stacks up against other approaches

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.

What this isn't

  • 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.

Clone this wiki locally