My low-power, low-overhead version control system, SRC, is no longer just a stake in the ground. It is still a determinedly file-oriented wrapper around RCS (and will stay that way) but every major feature except branching is implemented and it has probably crossed the border into being useful for production.
The adventurous can and should try it. You’re safe if it blows up because the histories are plain RCS files. But, as previously noted, it’s RCS behind an interface that’s actually pleasant to use. (You Emacs VC-mode users pipe down; I’m going to explain why you care in a bit.)
The main developments today include a fairly complete regression-test suite (already paying large dividends in speeding up progress) and a “src status” command that will look very familiar to Subversion/git/hg users. There’s a hack behind that status command I’m rather proud of; I’ll talk about that, too.
Presented for your perusal, some command synopses. In all of the following, A ‘revision’ is a 1-origin integer, or a tag name designating an integer revision. A revision range is a single revision or a pair of revisions separated by”-” or “..”. Unless otherwise noted under individual commands, the default revision is the tip revision on the current branch and the default range is all revisions on the current branch.
The token “–” tells the command-line interpreter that revision-specs and subcommands are done – everything after it is a filename, even if it looks like a subcommand or revision number.
src help [command] Displays help for commands. src add ['file'...] Initialize new project histories for specified files. Creates the repository directory if required. src commit [- | -m 'commentstring' | -f 'commentfile'] ['file'...] Enters a commit for specified files. Separately to each one. With '-', take comment text from stdin; with '-m' use the following string as the comment; with '-f' take from a file. ci is a synonym for commit. src checkout ['revision'] ['file'...] Refresh the working copy of the file(s) from their history files. co is a synonym for checkout. src status ['file'...] A = added, U = unmodified, M = modified, ! = missing, ? = not tracked, I = ignored. src cat [revision-range] ['file'...] Send the specified revisions of the files to standard output. src tag [list|-l|delete|del|-d|rename|-r] ['name'] ['revision'] ['file'...] Create, rename, or delete a tag. With no or only file arguments, list tags. src branch [list|-l|delete|del|-d|rename|-r] ['name'] ['revision'] ['file'...] Create, rename, switch to, or delete a branch. With no arguments, list branches; the active branch is first in the list. The default branch is 'trunk'. src list ['revision-range'] ['file'...] Sends summary information about the specified commits to standard output. In each file listing, the summary line tagged with '*' is the state that checkout would return to. src log ['revision-range'] ['file'...] Sends log information about the specified commits to standard output. src diff ['revision-range'] ['file'...] Sends a diff listing to standard output. With no revision spec, diffs the working copy against the last version checked in. With one revno, diffs the working copy against that stored revision; with a range, diff between the beginning and end of the range. src ls List all registered files. src move 'old' 'new' Rename a file and its master. Refuses to step on existing files or masters. 'mv' and 'rename' are synonyms. src copy 'old' 'new' Rename a file and its master. Refuses to step on existing files or masters. 'cp' is a synonym. src fast-export ['revision-range'] ['file'...] Export one or more projects to standard output as a git fast-import stream. The committer identification is copied from your Git configuration. src fast-import [-p] Parse a git-fast-import stream from standard input. The modifications for each individual file become a SRC history. Mark, committer and author data, and mark cross-references to parent commits, are preserved in RFC-822-style headers on log comments unless the -p (plain) option is given, in which case this metadata is discarded.
The omission of ‘src remove’ is a deliberate speed bump.
The thing is, this is it. You now know everything there is to know about SRC except some implementation details. It is intentionally an exercise in simplicity and least surprise – if anything about the above struck you as surprising or novel it was probably a design error on my part.
Yes, it really is still RCS underneath. See what can be done with a bit of care and attention to UI design? Er, not to mention a shameless willingness to crib from good examples. UI design should be egoless; if you succumb to the temptation to show off, you’re probably doing it wrong.
This is all implemented and regression-tested, except for “src branch” which does all the right parsing and sanity checks but doesn’t have back-end methods yet. I also wouldn’t lean on src fast-import too heavily, as the external tool it calls, rcs-fast-import, hasn’t been tested a lot since I wrote it.
Otherwise it’s good to go. Now I’ll explain the most subtle change in the interface from RCS and why it means VC-mode users should care. In a word: locklessness.
RCS was designed for an environment of multi-user contention. Working copies of files are read-only until they’re explictly checked out (locked, in RCS-speak) by a user. When locked, the workfile become writable (confusing, I know). When your changes are checked back in, the lock is released and the workfile goes read-only again.
This is completely inappropriate in today’s era of single-user computers. The fact that RCS workfiles are normally locked is a continuing source of friction – you go to edit, get a failure message, remember you have to do an explicit checkout, and *boom* you just lost whatever train of thought you were on.
Emacs VC mode didn’t fix this – though it did reduce the checkout friction to one key combination – because at the time I wrote it (1992, I think) locking VCSes had not given way to to merging ones. The most important thing SRC does to RCS is do away with that locking. This means that even through its VC mode (not yet written, on my list) SRC will be more pleasant to use than RCS.
And let’s not forget the nice Subversion-style plain-integer revision numbers, either. RCS revision IDs are ugly, cluttery things that ought to be hidden in any decent interface.
Another feature that will improve user experience greatly is the “src status” command. The VC mode for RCS is a complicated mess in large part because RCS has nothing like it natively, so Emacs has to simulate it by directly parsing master files. And there are two cute tricks I invented for SRC that VC mode doesn’t know.
First trick: suppose you’re trying to tell the difference between U (unmodified) and M (modified) status. I am actually no longer sure what VC does for this – lots of people have hacked on (and overcomplicated) that code since I first wrote it – but looking at the thicket of Lisp I can see it’s a kluge involving a lot of parsing of master files. That sort of thing is error-prone. I was much younger when I wrote it, and perhaps lacking in wisdom.
Here’s the simple way. When you check out the file (which SRC does immediately after each checkin so as to run lockless) you then call utimes(2) on the master to uptate its modtime. Now you can tell M from U just by checking to see if the workfile was modified after the master. This is really fast because you never have to look at the file contents, just the inode.
Another cute trick: the fast way to tell A (just added) status from U, which VC doesn’t use, is to look at the size. There’s a threshold size with all empty that any master with actual commits in it can’t get below. Again, this means we get away with just looking at the inode, not the actual file content.
In fact, I was able to write the entirety of “src status” so it never opens the workfile or master. This means good performance and responsiveness even on slow network file systems. In fact, a status check should be faster under SRC than under plain RCS!
Now I need to get branching to work. And write that VC back end. Naturally, direct support in reposurgeon is already up.