Working Without a Working Copy

As we described in the section called “Subversion Working Copies”, the Subversion working copy is a sort of staging area where a user can privately make changes to his or her versioned data and then—when those changes are complete and ready for sharing with others—commit them to the repository. It should come as no surprise, then, that most of the interaction you will have with Subversion will be in the form of asking your Subversion client to do something to one or more items in a local working copy. Even for those operations which don't manipulate the working copy data itself (such as svn log), it's often just easier to use a working copy file or directory as a convenient target for that operation.

Clearly, the typical approach to making changes to your versioned data is via commits from a Subversion working copy. Fortunately, it's not the only way. Users of Subversion who need to make relatively simple changes to their versioned data can do so without the overhead of checking out a working copy. We'll cover some of those supported operations in this section.

Remote command-line client operations

The Subversion command-line client supports a number of operations which can be performed directly against repository URLs in order to make simple changes without a working copy. Some of these are described elsewhere in this book, but we provide an exhaustive list of them here for your convenience.

Perhaps the most obvious remote commit-like operation is the svn import command. We describe that command in the section called “Importing Files and Directories” as part of explaining how you can easily get a whole tree of unversioned information into your Subversion repository so you can start doing version-controlled operations on it.

The svn mkdir and svn delete commands, when used with URL targets, are also remote commit-type operations. These allow the user to create one or more new versioned directories or remove (recursively) one or more versioned files or directories, respectively, without the use of a working copy. Each time you issue one of these commands, the client communicates with the server in a way that's similar to how it would describe the commit of a directory added or of an item removed from the working copy. If there's no problem or conflict detected with the requested operation, the server commits the additions or removals in a single new revision.

You can use svn copy or svn move with two URLs—a copy/move source and a destination—to commit a copies and moves of files and directories directly in the repository. These operations tend to be some of the most expensive ones when performed within a working copy, but they complete in constant time when performed remotely using repository URLs. In fact, the svn copy remote operation is commonly used to create branches in Subversion, as we discuss later in the section called “Creating a Branch”.

As with the regular svn commit command, you can supply a log message with any of these commands we've discussed so far to describe the changes you're making. Use the --file (-F) or --message (-m) option, or otherwise allow the client to prompt you for the log message.

Finally, there are a number of operations related to unversioned revision properties which can be performed directly against the repository. In fact, revision properties are somewhat unique in this context, as they aren't stored in the working copy and therefore must be modified without working copy interaction. See the section called “Properties” for a more detailed description of how to manage properties in Subversion.

Using svnmucc

One shortcoming of the remote commit operation support offered in the command-line client is that you are essentially limited to one operation—or, really, one type of operation—per commit. For example, it's perfectly natural and supported to, say, use svn delete followed by svn mkdir within a working copy to replace an existing versioned directory with a brand new one. When you commit the results of those operations, a single new revision is created in the repository, and that revision carries the full replacement of your directory. You can't really do the same thing as remote operations using the command-line client while still preserving the it-happened-in-a-single-revision-ness of the change—svn delete URL would create a new revision that removed the directory; svn mkdir URL would generate a second revision for the directory's re-creation.

Fortunately, Subversion provides a separate tool which exists solely to allow users to string together a set of remote operations and commit them as one atomic change. That tool is the svnmucc tool—the Subversion Multiple URL Command Client:

$ svnmucc --help
Subversion multiple URL command client
usage: svnmucc ACTION...

  Perform one or more Subversion repository URL-based ACTIONs, committing
  the result as a (single) new revision.

Actions:
  cp REV URL1 URL2       : copy URL1@REV to URL2
  mkdir URL              : create new directory URL
  mv URL1 URL2           : move URL1 to URL2
  rm URL                 : delete URL
  put SRC-FILE URL       : add or modify file URL with contents copied from
                           SRC-FILE (use "-" to read from standard input)
  propset NAME VAL URL   : set property NAME on URL to value VAL
  propsetf NAME VAL URL  : set property NAME on URL to value from file VAL
  propdel NAME URL       : delete property NAME from URL
…

svnmucc has been a part of the Subversion project's source code tree for many years (as mucc for most of that time), but it was only in Subversion 1.8 that it become a fully supported member of the Subversion command-line tool suite.

The svnmucc tool can perform any transformation on your versioned data that svn itself can. But unlike svn, the functionality that svnmucc offers isn't broken up into subcommands. Rather, you provide a list of actions and operands in a single command line (or from a file stream, via the --extra-args (-X) option). Some of the actions supported by svnmucc mimic those of the command-line client. You'll notice in the previous command output actions such as cp, mkdir, mv, and rm, all of which are very similar to the commands we mentioned in the section called “Remote command-line client operations”. But remember, the key difference here is that you can use any number of these actions together in a single command invocation, resulting in a single committed revision in the repository.

Let's take our previous example of trying to simply replace a remote directory. Using svnmucc, you would accomplish this as follows:

$ svnmucc rm http://svn.example.com/projects/sandbox \
          mkdir http://svn.example.com/projects/sandbox \
          -m "Replace my old sandbox with a fresh new one."
r22 committed by harry at 2013-01-15T21:45:26.442865Z
$ 

As you can see, svnmucc accomplished in a single revision what svn—without the benefit of a working copy—required two revisions to complete.

Warning

Another difference between svnmucc and svn is that the former currently will not prompt you for a commit log message if you fail to supply one via the command line. Rather, it will use a stock (that is, relatively valueless) log message.

The svnmucc tool is not limited to merely remixing actions that svn itself can perform. It introduces some additional functionality not found in the command-line client. For example, you can use the put action to add or modify a file in the repository, copying the file's intended new contents from either a file on your local machine or from data piped in via standard input. The tool also offers propset, propsetf, and propdel actions, useful for setting properties on versioned files and directories (explicitly, or by copying the property's value from a local file) and for deleting properties on the same. Those actions are unsupported in the command-line client at this time.

At this point, though, it seems prudent to discuss the difference between what can be done with svnmucc and what should be done. A pair of notable quotes comes to mind:

 

To whom much has been given, much will be expected.

 
  --Jesus
 

With great power comes great responsibility.

 
  --"Spiderman" Peter Parker's Uncle Ben

Inherent in modifications without a working copy is the loss of the very conflict detection safeguards which make the use of a working copy so valuable. When using svn in the typical way, changes are committed to the server against a specific base version of a file or directory so that you don't inadvertently overwrite contemporary changes made to the same item by another team member. The server knows what version of the file you had before you changed it, and it knows if other folks have changed that same file since that revision was created. That's all the information the server needs to deny your commit when it would clobber someone else's change, forcing you to integrate their change into your working copy and reconsider your own change. Because there is no working copy in the mix here, svnmucc really gives you the power to bypass those safeguards and to act as if the current state of the repository is precisely the base state against which you are working. But hopefully it is obvious to you that this is not a power you should cavalierly wield.

Fortunately, svnmucc allows you to be more conservative in the way you use the tool. In order to provide a safety mechanism similar to what is offered by the use of a working copy, svnmucc offers a --revision (-r) option. With this option, you can manually specify a base revision for the changes you are attempting to commit. The base revision you choose is ideally the most recent revision in your repository of which you can reasonably claim knowledge.

Warning

Users are strongly encouraged to use, and to use correctly, the --revision (-r) option to svnmucc.

Proper use of the svnmucc put action best demonstrates how this --revision (-r) option should be used. Say Harry wishes to change the contents of a versioned README file without bothering with a full checkout of a working copy. (We'll assume that there is no other value in using a working copy for this operation, such as the presence of scripts Harry should run in advance of his commit to verify that it's a reasonable one.) The first decision he has to make is which revision of the file he wants to work with. Typically, users wish to modify the most recent version of a file. So Harry queries the revision in which the file was last modified, and then uses that revision to fetch the contents of the file into a temporary local file:

$ svn info http://svn.example.com/projects/sandbox/README
Path: README
URL: http://svn.example.com/projects/sandbox/README
Relative URL: ^/sandbox/README
Repository Root: http://svn.example.com/projects
Repository UUID: 13f79535-47bb-0310-9956-ffa450edef68
Revision: 22
Node Kind: file
Last Changed Author: sally
Last Changed Rev: 14
Last Changed Date: 2012-09-02 10:34:09 -0400 (Sun, 02 Sep 2012)

$ svn cat -r 14 http://svn.example.com/projects/sandbox/README \
      > README.tmpfile
$

Harry now has a copy of the README file as it looked when it it was last modified. He makes the edits he wishes to make to this copy of the file. Naturally, when he's finished, he wishes to then commit those changes to the repository.

Now, if Harry naively uses svnmucc put … at this point to replace the contents of README in the repository with his locally modified contents, he has just abused the power that svnmucc affords. What if, just microseconds prior to his commit, Sally had also modified the README file? As with the svn program, svnmucc won't attempt some sort of server-side content merge in order to preserve both users' changes. Rather, svnmucc will happily replace the current latest version of the file with the contents specified. Harry will be oblivious. Sally will be livid.

$ svnmucc put README.tmpfile \
          http://svn.example.com/projects/sandbox/README \
          -m "Tweak the README file."
r24 committed by harry at 2013-01-21T16:21:23.100133Z
$
Message from sally@shell.example.com on pts/2 at 16:26 ...
We need to talk.  Now.
EOF

Harry should instead recall the revision he originally used as the revision on which to base his changes, supplying that revision to svnmucc via the --revision (-r) option, and thus giving the server the opportunity to bounce his commit if, by his own (perhaps ignorant) admission, he's attempting to modify an out-of-date item:

$ svnmucc -r 14 put README.tmpfile \
          http://svn.example.com/projects/sandbox/README \
          -m "Tweak the README file."
svnmucc: E170004: Item '/sandbox/README' is out of date
$

Like other svnmucc options, the --revision (-r) option operates at a scope global to the whole command—every action specified in that command. This enables you to have the same sort of safeguards you would have if you had checked out a working copy of your entire repository (and thus had a working copy entirely at a single uniform revision), made changes to that working copy, and then committed all those changes at once.

As you can see, svnmucc is a handy addition to the Subversion user's tool chest. For a complete reference of this tool's offerings, see svnmucc Reference—Subversion Multiple URL Command Client.