SRC 0.9: Ready for the less adventurous now

I just shipped SRC 0.9, and you no longer need to be adventurous to try it. It has a regression-test suite and real users.

Remarkably, SRC has had real users since 0.3, two days after it was born. Even more remarkably, the count of crash reports and botched operations from those users is zero. Zero. This is what you can gain from keeping code simple – I have has a couple of bug reports but they were both about filename quoting in the fast-export code, which is not a central feature.

Next, I’ll make a couple of what I think are important points about writing for zero defects. Then I’ll talk about a subtle issue or two in the design, and our one known behavioral glitch.

If you read the source code for SRC, you are rather likely to think “Well, no wonder it has not had any reported defects. This is trivial! There’s nothing here!” If that is your reaction, you are skirting a large truth. The precisely important thing about SRC is that it adds a lot of value to RCS at the same time that it is so simple that you can easily see all the way down to the bottom of the code. Achieving that took harder work and deeper thought than would have been required for a program that was fussier, more complex – and more bug-prone.

Too often programmers succumb to the temptation to be clever rather than simple. Edsger Dijkstra used to bang on about this, and he was right. In SRC I strove for simplicity, not just in implementation (which is why I re-used RCS rather than writing my own storage manager) but in design as well, all the way out to the level of the user interface.

The result is, alas, not perfect. There is at least one wart, and arguably a couple more, in the UI.

Early on I made the basic decision that the commands would have the general form “src verb [modifiers] [range] [filename…]”; this, of course, is a deliberate imitation of CVS/SVN/hg/git designed to make the UI feel instantly comfortable to their users. Unfortunately, this clashed with two other premises: range specifications being optional and named tags being available in range specifications. Consider this:

tag foo bar baz

OK, so we can tell by its position that ‘foo’ is a tag name. But is ‘bar’ a tag naming a revision (the command reads “create a tag named ‘foo’ at the location named by ‘bar’ in file ‘baz’) or is it a filename (‘create a tag named ‘foo’ at the default branch tips in files ‘bar’ and ‘baz’)?

Syntactically there’s no way to tell, so I had to introduce a special prefix ‘@’ meaning “this is a revision specifier even though it looks like a filename”. A wart.

Suppose you have, for some reason, a file named “23”. To say “this is a filename even though it looks like it’s a revision specfier” you could write this:

tag foo -- 23 baz

This seems less warty, if only because there’s precedent in Git and elsewhere for using “–” to mean “options end here, normal operands start afterwards”.

Most of the serious ambiguities were in the ‘tag’ and ‘branch’ commands. Reluctantly, I ended up requiring a qualifier on these commands – in 0.9 you have to say “tag create”, “tag delete” and “tag list”. Otherwise the rules for when a token would be interpreted as a qualifier, when as a named tag, and when as filename just got too easy to screw up. The alternative would have been to drop back to a much more verbose RCS-like UI with switches, switches everywhere.

I still have one serious implementation issue. SRC uses utime(2) to touch both a workfile and then its master on checkout, so we can test for modified status just by comparing modification dates afterwards.

The problem is that utime(2) seems to be prone to flaky failures. Mike Swanson and I have been poking at this and we can’t figure out where the problem is. Python? The kernel? My on-line research has turned up a lot of bug reports about utime(2) that seem vaguely relevant; they hint that the problem might even be in an (unknown) glibc bug that’s tripping up Python.

To be further investigated. I’m seriously thinking about porting the code to another language (Go is the leading candidate – better fit to SRC’s abstractions than Rust) to find out of the bug replicates.

40 thoughts on “SRC 0.9: Ready for the less adventurous now

  1. Solid fast-export support isn’t a central feature, you say? It should be a core feature of any modern version control system. How can you trust a VCS that doesn’t let you pick up your history and walk away?

  2. > Reluctantly, I ended up requiring a qualifier on these commands – in 0.9 you have to say “tag create”, “tag delete” and “tag list”.

    One problem that SRC has that full-tree VCS doesn’t have is that in SRC you _need_ to give at least one file argument for most commands, while in full-tree VCS the repository is derived from context – current directory.

    > Early on I made the basic decision that the commands would have the general form “src verb [modifiers] [range] [filename…]“; this, of course, is a deliberate imitation of CVS/SVN/hg/git designed to make the UI feel instantly comfortable to their users. Unfortunately, this clashed with two other premises […] The alternative would have been to drop back to a much more verbose RCS-like UI with switches, switches everywhere.

    This is possibly one of reasons why Git UI is a bit of mess with respect of using options (switches) like “git checkout -b”, “git tag –list” versus using subcommands like “git remote add”, “git bundle create”.

  3. >One problem that SRC has that full-tree VCS doesn’t have is that in SRC you _need_ to give at least one file argument for most commands, while in full-tree VCS the repository is derived from context – current directory.

    That is at the root of the problem, yes.

  4. >Solid fast-export support isn’t a central feature, you say? It should be a core feature of any modern version control system. How can you trust a VCS that doesn’t let you pick up your history and walk away?

    I agree in principle, and have made that argument in public with a persistence that some VCS designers probably find obnoxious. I wrote rcs-fast-import to shame them.

    In this case, however, it’s not a feature my real users have had a lot of actual use for. SRC occupies an odd niche that way.

  5. These might be stupid questions, but under SRC, if I make a trivial edit to a file, save, undo my edits, and save again (with no commits after any step), will SRC still believe the file’s been modified? Why not just run diff -q between the file and the master copy?

  6. >These might be stupid questions, but under SRC, if I make a trivial edit to a file, save, undo my edits, and save again (with no commits after any step), will SRC still believe the file’s been modified?

    It will. It only goes by modification times, not content. I am aware that this is a flaw but have not thought of any way to fix it that wouldn’t hurt performance rather badly. Other VCSes have the same issue for the same reason.

    >Why not just run diff -q between the file and the master copy?

    Because that’s not the expensive part. The expensive part is getting the “master copy” out of the storage manager.

  7. What is the nature of utime’s flaky failures? Times don’t stick? Rounding errors?

    Filesystems have timestamp precision ranging from picoseconds down to “sometime in this minute plus or minus an hour,” and the combination of buffered output, NFS, and futimens() can be almost entirely random.

  8. >> These might be stupid questions, but under SRC, if I make a trivial edit to a file, save, undo my edits, and save again (with no commits after any step), will SRC still believe the file’s been modified?

    > It will. It only goes by modification times, not content. I am aware that this is a flaw but have not thought of any way to fix it that wouldn’t hurt performance rather badly. Other VCSes have the same issue for the same reason.

    Git stores metadata that can be used to detect modification: mtime, ctime, inode, size, etc., but it is used only as cache, to decide if more costly examination of contents is needed (and then it compares SHA-1, I think).

    >> Why not just run diff -q between the file and the master copy?

    > Because that’s not the expensive part. The expensive part is getting the “master copy” out of the storage manager.

    You can always use Subversion trick of storing “pristine copy”. Anyway, doesn’t RCS file format store pristine copy at beginning of file and older versions as deltas? You can use Mercurial (and Git for pack files) trick of separate index file to versions.

  9. >What is the nature of utime’s flaky failures?

    False M statuses. When the failure occurs, it looks looks as though utime(2) wasn’t called from Python; the master’s modtime isn’t touched, so it looks like the workfile was modified when it had not yet been.

    (I described this backwards first time I typed it. It’s a confusing error.)

  10. >Git stores metadata that can be used to detect modification: mtime, ctime, inode, size, etc., but it is used only as cache, to decide if more costly examination of contents is needed (and then it compares SHA-1, I think).

    Yes, I’m contemplating doing something much like that myself. The only thing that has stopped me is that I’d [refer not to violate the present property that the RCS files are the only metadata SRC has.

    >You can always use Subversion trick of storing “pristine copy”.

    Unnecessary. An MD5 hash would be just as good and take up far less space.

  11. > An MD5 hash would be just as good and take up far less space.

    I have a collection of different files with the same MD5 hash which I might want to keep in SRC. I’d recommend using a stronger algorithm. ;)

    On the other hand, now you have another to keep hashes of the revisions in the master file.

  12. >I have a collection of different files with the same MD5 hash which I might want to keep in SRC. I’d recommend using a stronger algorithm. ;)

    Noted. :-)

  13. >On the other hand, now you have another to keep hashes of the revisions in the master file.

    I presume the word “reason” was supposed to be between “another” and “to”.

    Yes. I’m considering stuffing the RCS description field with a JSON structure dedicated to file metadata, including a hash, length etc. for the currently checked out version.

  14. “Too often programmers succumb to the temptation to be clever rather than simple.”

    I suspect that the culture of Open Source culture tends to push programmers in that direction. Sure, a member’s contributions are the ultimate measure of his worth to the community, but the apparent cleverness of his code — even if it is less effective than less-clever code might be — likely increases his status and reputation.

  15. I could have used this for a situation i had last week. The file seemed trivial. It’s just a couple of commands of which I use variations in my workflow. Normally there are no problems but it got borked when i restarted my computer. Spent time re-researching how to restart my local varnish server.

  16. esr> In this case, however, it’s not a feature my real users have had a lot of actual use for. SRC occupies an odd niche that way.

    I’m sure they haven’t had a lot of use for it yet, with SRC being less than two weeks old. But in my experience, subsets of the scripts in my ~/bin directory tend to grow or coagulate into bigger units that do benefit from Git’s or Mercurial’s worldview about changesets. That’s when solid fast-export into these VCSs becomes a central feature, because without it your file sets are trapped in a tool they have outgrown.

    Obviously, two weeks is too short for this to have happened already. But doesn’t it make sense to design for it now? Not accusing, just interested.

  17. >But doesn’t it make sense to design for it now?

    Of course it does. That’s why fast-export was on my initial feature list, and was actually working before branch support was fully implemented. I’m puzzled as to what disagreement you think we have.

  18. esr> I’m puzzled as to what disagreement you think we have.

    I guess I instinctively interpreted your phrase “not a central feature” as a euphemism for “a feature I care little about”. This heuristic is usually accurate in big engineering corporations like the one I used to work for throughout most of my career. I shouldn’t have relied on it here.

  19. How do I operate on a file with a hyphen in the name? There’s “@” to force interpretation of an argument as a revision, but I don’t see any way to do the reverse.

  20. >How do I operate on a file with a hyphen in the name? There’s “@” to force interpretation of an argument as a revision, but I don’t see any way to do the reverse.

    The magic token “–” says “everything after this is a filename even if it looks like a range (or switch, or modifier)”.

  21. Hm, tried both — and – for that:

    $ src ci -m’initial checkin’ — file-name.txt
    src: commit doesn’t take a revision spec

    $ src ci -m’initial checkin’ – file-name.txt
    src: commit doesn’t take a revision spec

    I opened a pull request that assumes anything with a slash is a filename so you could do src ci ./file-name.txt, but if – is supposed to work, that would be better. :)

  22. >$ src ci -m’initial checkin’ — file-name.txt

    OK, this is funny. The parsing actually worked – the actual problem was that you were thinking of RCS-style options, from before getopt(3) became standard, and ran the -m option together with its string argument. They need to be separated by a space.

    I’ll add a check for this. I also took your merge request

  23. I wonder why file-level VCS doesn’t have line-history of a file command: blame aka praise aka annotate. ‘cvs annotate’ (which uses RCS) can be a code base / example, while ‘git blame’ can be features example (with optional detection of code movement and copying within file, and ignoring whitespace differences, and selecting line range and revision range to blame, and incremental output for GUI tools such as ‘git gui blame’)…

    …though it is 0.9, not 2.0.

  24. “I have a collection of different files with the same MD5 hash which I might want to keep in SRC. I’d recommend using a stronger algorithm. ;)”

    “Keep your pathological cases out of my design parameters!”

  25. > the actual problem was that you were thinking of RCS-style options, from before getopt(3) became standard

    Maybe, but that’s how I’ve always used git, too, and it works fine there. :)

  26. > Maybe, but that’s how I’ve always used git, too, and it works fine there. :)

    Yeah, but git uses its own parseopt mini library (or Getopt::Long for Perl scripts), while SRC uses i think standard Python option-parsing module… though I think there are other modules with more capabilities like sticky arguments.

    OTOH sticky arguments can cause ambiguity problems when coupled with optional arguments.

  27. >Maybe, but that’s how I’ve always used git, too, and it works fine there. :)

    And now it does in SRC 0.10 too – turned out to be trivial to support this, though I haven’t documented it.

  28. esr> turned out to be trivial to support this […]

    Was it extra code, or just an option to option-parsing function?

  29. > OK, this is funny. The parsing actually worked – the actual problem was that you were thinking of RCS-style options, from before getopt(3) became standard, and ran the -m option together with its string argument. They need to be separated by a space.

    Actually, getopt(3) allows this. You’re correct, though, that the version with the space is the “more canonical” form [except for utilities that specifically document the other form], per the unix standard’s Utility Syntax Guideline 6. Utilities are not required to use getopt internally or to support a form other than the one they document, and new utilities are supposed to use the form with the space.

    A far worse offense, though, is having “optional” arguments, where -c and -c1 both mean something, and a space is not allowed – which is specifically recommended against by the Utility Syntax Guidelines. And the less said about ps and tar, the better.

  30. >Was it extra code, or just an option to option-parsing function?

    Four lines of code. I’m not using getopt.

  31. This shows off my noobness, but I wasn’t able to figure out how to install src. I use a Mac for development at work. First problem I had was that I cannot clone the git repository. I get the following error “Permission denied (publickey).
    fatal: Could not read from remote repository.

    Please make sure you have the correct access rights”

    The main issue though is that I cannot figure out how to compile the source code. At least I presume that is what I’m supposed to do but there are no instructions. There doesn’t seem to be the normal configure or autogen.sh script. When I skip that step and just run “make” in the directory I get “make: src: Command not found
    make: src: Command not found
    make: src: Command not found”

    Apparently other people can figure it out. I figured I’d report this because if this is intended for people with only moderate experience compiling projects from source code, they may run into similar issues.

  32. >First problem I had was that I cannot clone the git repository.

    Please report the command you tried to use to clone.

    >The main issue though is that I cannot figure out how to compile the source code. At least I presume that is what I’m supposed to do but there are no instructions.

    It’s a Python script. No compilation is required – all you have to do is put src somewhere your command interpreter can see it. I’ll add an INSTALL file to the distribution.

  33. The fact that srctest adds $(pwd) to the front of PATH should mean that he shouldn’t have gotten that error, though… unless python isn’t installed, or isn’t called “python2”, or /usr/bin/env doesn’t exist.

  34. Wait, no, I missed VERS=$(shell src version)

    I’m pretty sure this should use ./src

  35. >Please report the command you tried to use to clone.
    I tried ‘git clone git@gitorious.org:src-vcs/src-vcs.git’. I have since used the https version and that worked.

    >It’s a Python script.
    Ok. That makes sense. It wasn’t apparent that it was a Python script since there was no setup.py or other .py files.

  36. >> Was it extra code, or just an option to option-parsing function?

    Do you think that “A concesssion to the Git folks.” (sic!) is a good commit message that you and other people will be able to understand and know the hows and whys of commit a year later?

    > Four lines of code. I’m not using getopt.

    Why not? NIH with “batteries included” philosophy of Python?

  37. >Why not? NIH with “batteries included” philosophy of Python?

    It would interact badly with the CommandContext class as it is now. It’s an option I’m bearing in mind if I rewrite the command parsing, which could happen – I’m not entirely happy with the present code.

  38. > Why not? NIH with “batteries included” philosophy of Python?

    The main issue, I suspect, is that he’s using –– to differentiate between filenames and other kinds of non-option operands (revspecs). Git does the same thing, and getopt can’t support this behavior (I don’t know if python’s argument parsing modules can).

  39. Some typos in http://www.catb.org/~esr/src/src.html:

    1. Add period to this sentence: “@ by itself means the current (checked-out) revision”

    2. “…either give the -a swidth (sic) or specify…” s/swidth/switch/

    3. “…of the status comand, …” s/comand/command/

    4. “src sratus” s/sratus/status/

  40. Ok. I finally had time to deal with the “env: python2: No such file or directory” error I got after I placed src in the $PATH. The problem is that the binary for python is called “python” instead of “python2”. I don’t know if it’s worth adding code to take into account the possibility that some people’s systems are set up this way. I ended up using the not advised fix here http://stackoverflow.com/questions/11390206/usr-bin-env-python2-no-such-file-or-directory to add a symbolic link for python2. I’ve used other python scripts, mainly the mysql command line tools, with no issue.

    Note this is on my work’s development box which is a mac. Of the two choices (Mac or Windows) it was the better option but I noticed it tends to get a little weird when trying to use tools developed for *nix.

    One other minor note is that the INSTALL file is not listed on the project page.

Leave a Reply

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