SRC 0.3 – ready for the adventurous

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

Published
Categorized as General

33 comments

  1. @esr: All great….now rename the thing. In recognition of your predilection for firearms, I suggest you call it ‘gat’.

    ….yeah, yeah,,,I know…I should git out…

    1. >Is SRC suitable for learning how to use version control systems, and if not, what is?

      Very suitable, I think, as long as you bear in mind that a real one will have real multi-file changesets rather than being file-oriented.

  2. I see there is no ‘commit –amend’, nor ‘reset’ to rewind (discard some of history, or go to older revision), nor ‘revert’ to create commit undoing work of other commit.

    There is ‘branch’ and ‘tag’, but no ‘merge’ (‘update’ doesn’t make much sense in one-user VCS), and no ‘rebase’ (though it is probably not that important in single-user version control… and hard to implement in non-DAG backend). No ‘log –graph’.

    1. >I see there is no ‘commit –amend’, nor ‘reset’ to rewind (discard some of history, or go to older revision), nor ‘revert’ to create commit undoing work of other commit.

      Oh, yeah. Amend was on my mental to-do list – just added it to the TODO in the distribution.

      Having revert makes sense. I’m dubious about reset – for the intended audience it is probably best I not issue gilt-edged invitations to shoot self in foot, so not a big fan of outright history destruction.

      If I were going to an equivalent of log -graph I’d pull the same stunt reposurgeon does – write the graph in DOT and let graphviz figure out how to render it. But I think that would be overkill – I’m not expecting people to do particularly complex change histories with this.

      I’m debating with myself about merge. I’ll stare at rcsmerge and consider it.

  3. I’d suggest “backout” instead of “revert”, both for reasons of clarity and compatibility with Mercurial. (Though if git uses “revert” for that, I won’t argue.)

  4. I was struck by “branch” taking the function of git checkout | branch. I do think that’s a sensible choice (the split is one of the annoying parts of git).

    In my humble opinion, the useful part of “git reset” is the ability to get back to a clean state (whether that be a clean “staging area” or unmodified checkout). Since (as far as I can tell) this doesn’t expose a staging area, I would expect that checkout could do everything that “reset” would be useful for.

    I notice that there’s no “-e” option for src commit. When I use git commit -m, I frequently combine it with -e. This is especially useful in wrapper scripts, when there’s a set format for commits including both a formulaic part and a descriptive part.

    1. >I notice that there’s no “-e” option for src commit.

      There is now. Also, I have added “src amend”.

  5. “git revert” is not the same thing as “svn revert”. The equivalent command is probably closer to “git reset –hard”. Having said that, git is kind of the odd duck here. I’ve read that mercurial and bazaar both use revert in much the same way as svn. According to the quick search i did to confirm my intuition, “git checkout .” is also a legitimate way of doing this.

    In my head, there’s three use cases surrounding revert…
    A) What i did in revision X was brain damaged… lets not do that.
    B) I want my workspace to look like what it did at revision X.
    C) The changes i’ve made need to just go away.

    The first one is what revert is actually supposed to handle in git (take revision X, apply inverse to working). The second one is handled by the various versions of reset however the way I really want it to work is kind of backwards to what’s in my head… the equivalent action seems to be reset –hard to revision X and then reset mixed back to the current revision. This leaves the working direct as it was at revision X but with the index saying your at HEAD (so all of those changes in reverse).
    The third one is basically the second with an implied target of the current revision.

  6. > I’m debating with myself about merge. I’ll stare at rcsmerge and consider it.

    I think that if there is branching, there should be easy merging (with automatic finding of common ancestor – that might be not easy with RCS format, without either remembering parents for a merge and thus having DAG of revisions like in Git, Mercurial, and others, or remembering what was merged in with svn:mergeinfo properties like in Subversion).

    About ‘backout’, ‘revert’ and ‘reset’ – I don’t know if for SRC intended usage creating reversion would be useful, but for single file revision control refreshing to committed version can be done with ‘src checkout [<revision>] <file>’.

  7. JonCB: I can’t speak to git, but the three cases you mention are handled by “hg backout -r “, “hg revert [-r ]”, and “hg update -c”. “hg backout” applies the inverse of the stated changeset, while hg revert rewinds to the parent of the stated changeset.

    1. >(Those who can’t build, make corrections. XD)

      And it is required of those of us who can build that we accept corrections cheerfully.

  8. Proof reading:
    src copy ‘old’ ‘new’
    Rename a file and its master. Refuses to step on existing files or masters.
    ‘cp’ is a synonym.

    s/Rename/Copy/, I presume. Also, does it copy history or not, how is its existence treated historically, etc.

    Regarding surprises, “U (unmodified)” surprised me. Subversion uses “U” to indicate there are updates on the server you don’t have locally. Unmodified files are simply not listed, which is what I would have expected to be the case here.

    Intriguing…

    1. >Also, does it copy history or not, how is its existence treated historically, etc.

      The master is copied, that implies copying the history. Hm, perhaps I shouldn’t use “master” there.

      >Subversion uses “U” to indicate there are updates on the server you don’t have locally.

      Maybe I should switch to Mercurial’s “C” for “clean”, then.

  9. >And it is required of those of us who can build that we accept corrections cheerfully.

    N.B.: I was referring only to myself. I didn’t mean that Brinton Sherwood, who’d made a previous correction, is one of those who can’t build. If it sounded that way, I apologize. I wouldn’t mock or insult another A&D commenter even if I wanted to; and, so far, I’ve never even wanted to.

  10. >Subversion uses “U” to indicate there are updates on the server you don’t have locally.

    Maybe I should switch to Mercurial’s “C” for “clean”, then.

    … and git uses “C” for “copied in index”. Examining the manual pages for subversion, hg, and git, it seems they all pick their own conflicting meanings for single-letter status codes. Sadly, it means that src can never align with the expectations of everyone used to $VCS.

    1. >Sadly, it means that src can never align with the expectations of everyone used to $VCS.

      Yes, I’ve been reaching the same conclusion. Here is part of the file header comment:

      # Top of the list of things that people will bikeshed about is the
      # letter codes returned by src status. Different VCSes have
      # cinflicting ideas about this. The universal ones are A = Added, M =
      # Modified, and ? = Untracked.  Here's a table of the ones in dispute.
      # Entries with '-' mean the VCS does not have a closely corresponding
      # status.
      #
      #                   git     hg     svn      src
      # Unmodified        ' '     'C'    ' '      'U'
      # Renamed           'R'      -      -        -
      # Deleted           'D'     'R'    'D'       -
      # Copied            'C'      -      -        -                 
      # Ignored           '!'     'I'    'I'      'I'
      # Updated/unmerged  'U'      -      -        -
      # Missing            -      '!'    '!'      '!'
      # Locked             -       -      -       'L'
      #
      # This is a bit oversimplified; it is meant not as a technical comparison
      # but rather to illustrate how bad the letter collisions are. SRC follows
      # the majority except for absolutely *not* using a space as a status code;
      # this makes the reports too hard to machine-parse. The designer judged that
      # the semantic collision wuth git's U was less bad than suggesting that an
      # unmodified file had been copied.
      

      Mike, about that U code, are you sure you meant Subversion? I don’t see it here

      http://svnbook.red-bean.com/en/1.7/svn.ref.svn.c.status.html

  11. Eric, don’t blame Mike for the ‘U’; that was me. I don’t see it in the docs either, but it’s definitely in my memory. Off to check for wetware corruption….

  12. Is there any reason that status codes need to be a single letter, other than “that’s how it’s always been done”?

    1. >Is there any reason that status codes need to be a single letter, other than “that’s how it’s always been done”?

      Not really. But I’d prefer not to be innovative in the UI, for reasons I think I’ve already explained.

  13. I am wondering, would this version control help me while I develop assembler that has lots of .asm files? At the moment, I have a batch file that archives the entire folder each time a build runs. Which is not very much fun for going back to find past changes.

  14. Random832> Is there any reason that status codes need to be a single letter, […]

    Eli> @Random832: svn has an –xml option which does not use single letter statuses.

    Git uses single^W two-letter status codes (worktree, index, HEAD) with ‘git status –short’ and ‘git status –porcelain’ (the latter for scripting); by default it uses descriptive verbose format, optionally with colors:

    # On branch master
    # Changes to be committed:
    # (use “git reset HEAD …” to unstage)
    #
    # new file: README
    # modified: hello.py

  15. I am wondering, would this version control help me while I develop assembler that has lots of .asm files? At the moment, I have a batch file that archives the entire folder each time a build runs. Which is not very much fun for going back to find past changes.

    This is not SRC’s strength. While it can be done, you are much better off using git.

  16. And if you do use git, you should probably convert your existing archived copies to a git repository instead of starting from scratch with a fresh one.

  17. Bug in src commit -m option. If you include a dash character in the comment string, src errors out with “malformed comment”. Examples:

    [38]$ src commit -m “Added ModifyStartTime to adjust program start times to half-hour boundaries” TrimChannel.plx
    src: malformed comment
    lclark@Lynns-iMac:~/bin
    [39]$ src commit -m “Added ModifyStartTime to adjust program start times to half hour boundaries” TrimChannel.plx
    lclark@Lynns-iMac:~/bin
    [40]$ src status TrimChannel.plx
    U TrimChannel.plx

    1. >Bug in src commit -m option. If you include a dash character in the comment string, src errors out with “malformed comment”.

      Upgrade, please. I fixed this yesterday.

Leave a Reply to JonCB Cancel reply

Your email address will not be published. Required fields are marked *