http://www.developer.com/

Back to article

A Crash Course in Subversion


April 22, 2005

If you're already familiar with version control, Subversion is reasonably simple to use. The workflow is quite similar to that of several other version control systems (notably CVS), so you shouldn't have too much trouble transitioning to Subversion. This chapter, [From the Apress book Practical Subversion] begins with a simple overview of Subversion and then dives into the specifics you need to know to use the software. Along the way, I compare Subversion commands to the equivalent commands in other version control systems, such as CVS and Perforce.

Conceptually, Subversion's design is similar to that of CVS. There is a single central repository that holds all versions of each file that is under Subversion's control. You (and others) can interact with the repository in two different ways, either by checking out a particular revision of the versioned data into a local working copy or by acting directly on the repository itself, without the need for an intermediate working copy. Generally, you'll check out a local working copy, make changes, and then commit those changes back into the central repository.

Locking vs.Nonlocking

An important difference between Subversion and many other version control systems is that like CVS, Subversion's mode of operation is nonlocking. That means that if two users have checked out working copies that contain the same file, nothing prohibits both of them from making changes to that file. For users of systems such as Visual SourceSafe, this may seem odd, as there is no way to ensure that the two users' changes to the file don't conflict with each other. In truth, this is by design.

In the vast majority of cases, the two users' changes don't conflict. Even if the two users change the same file, it's likely that they'll change separate parts of the file, and those disparate changes can easily be merged together later. In this kind of situation, allowing one user to lock the file would result in unneeded contention, with one user forced to wait until the other has completed his changes. Even worse is the situation in which the second user changes the file despite the fact that the file is locked. When the first user completes his change and unlocks the file, the second user is stuck merging the changes together manually, introducing an element of human error into something that the computer can handle far better.

Worse yet are the problems of stale locks. In a version control system that uses locks, there's always the danger of a user taking out a lock on a file and not returning it by unlocking the file when she's done. Every developer has run into something like this at some point. You begin work on a new bug or feature, and in your first stab at the solution you end up editing a file. Because you're making changes to the file, you take out the lock on it to ensure that nobody else changes it out from under you. At this point you can get into trouble in several ways. Perhaps once you get further into the solution, you realize that you were wrong to change that file, so you return the file to its previous state and move on to another solution, without unlocking the file. Perhaps your focus moves to some other issue and your work on the first problem sits there for a long period of time—and all the while you're holding the lock. Eventually, someone else is going to need to edit that same file, and to do so he'll need to find you and ask you to remove the lock before he can proceed. Worse, perhaps he'll try to work around the version control system and edit the file anyway, which leads to more complicated merging issues in the future. Even worse, what if you're on vacation or have left the company when this happens? An administrator will have to intercede and break the lock, creating an even greater chance of someone's work getting lost in the shuffle.

So in the typical case in which there are no conflicts, the nonlocking strategy used by Subversion is a clear win. But what about the rare case in which changes really do conflict? Then the first user to complete his change commits that change to the repository. When the second user tries to commit, she'll be told that her working copy is out of date and that she must update before she can commit. The act of updating will give Subversion a chance to show that the changes conflicted, and the user will be required to resolve the conflict.

This may seem similar to what would happen in the locking case, except for a couple of critical differences. First, the conflict forces the first user to stop and deal with the differences, avoiding the chance that the second user might just copy her version over the first version and destroy the first change in the process. Second, Subversion can help with the merging process by placing conflict markers in the file and providing access to the old, new, and local versions so the user can easily compare them with some other tool.

If you've never used a version control system that makes use of conflict markers, the best way to understand them is through an example. Suppose you have a file in your working copy, hello.c, that looks like this:

#include <stdio.h>
int
main (int argc,char *argv [])
{
   printf ("hello world \n");
   return 0;
}

Then say you change the hello world string to Hello World, and before checking in your changes you update your working copy and find that someone else has already changed that line of the file. The copy of hello.c in your working copy will end up looking something like this:

#include <stdio.h>
int
main (int argc, char *argv [])
{
<<<<<<<.mine
   printf ("Hello World \n");
=======
   printf ("hello world!\n");
>>>>>>>.r5
   return 0;
}

The <<<<<<<, =======, and >>>>>>> lines are used to indicate which of your changes conflicted. In this case, it means that your version of the section of hello.c that you changed looks like printf ("Hello World \n");, but in a newer version of the file that has already been checked into the repository, that line was changed to printf ("hello world!\n");.

Of course, all of this only works if the file in question is in a format that Subversion understands well enough that it can merge the changes automatically. At the moment, that means the file must be textual in nature. Changes to binary files such as image files, sound files, Word documents, and so forth can't be merged automatically. Any conflicts with such files will have to be handled manually by the user. To assist in that merging, Subversion provides you with copies of the original version of the file you checked out, your modified version, and the new version from the repository, so you can compare them using some other tool.

Note: Historically, most version control systems were designed to handle plain-text content, for example, a computer program's source code. As a result, they developed formats for storing historical data that were designed with plain text in mind. For example, RCS files work in terms of a textual file, adding or removing lines from the file in each new revision. For a binary file, which doesn't have "lines" at all, this breaks down, so systems based on these formats usually end up dealing with binary data by storing each revision separately, meaning that each time you make a change you use up space in the repository equal to the size of the file you modified. In addition, these systems often include other features, such as keyword replacement or end-of-line conversion, which not only don't make sense in terms of binary files, but also can actually damage them, because a binary file format probably won't survive intact if you replace all instances of $Id$ with a new string, or all the newline bytes with carriage return/linefeed combinations.

In addition to helping you handle the situation in which a conflict does occur, the use of a nonlocking model helps in another way: It removes the false sense of security that a locking model gives you. In the majority of cases, when you make a change to one part of a program, the effect of that change isn't isolated to just that file. For example, if you're changing a header file in a C program, you're really affecting all the files that include that header. Locking access to that one file doesn't buy much safety, because your changes can still quite easily conflict with any number of potential changes in other parts of the program. Locking gives you the illusion that it's safe to make changes, but in reality you need the same amount of communication among developers that you'd need in the non-locking mode. Locking just makes it easier to forget that.

Now, none of this is meant to imply that the only possible solution to the version control problem is a nonlocking system. There are certainly situations in which locking is a valuable tool, perhaps with files that truly shouldn't be modified except by certain key individuals, or perhaps when you're working with binary files that can't be easily merged. The Subversion developers have recognized the need for a solution to this problem, so in the future the problem will be addressed. For some clues as to what the locking system might be like, you can take a look at the locking-plan.txt file in the notes directory of the Subversion distribution. Unfortunately, the user interface and technical issues of such a feature are complex enough that the feature has been deferred until after Subversion 1.0 is released.

Other Differentiating Features

In addition to Subversion's nonlocking workflow, a couple more things differentiate it from other version control systems. First, all changes to the repository are atomic; either all parts of a change go in, or none of them do. As a result, there's a single repositorywide revision number, which describes the state of the entire tree. So when you refer to "version 247 of foo.c," it's really more correct to say "foo.c as it exists in revision 247 of the repository." This is similar to the changeset number in Perforce, and although it may seem odd to not have per-file revisions (which exist in Perforce and are the only kind of revision in CVS), thinking in terms of atomic changes that can encompass multiple separate files rather than several different changes to different files is quite easy to adjust to. In fact, it's a good idea to start thinking in those terms anyway because it helps you focus on forming coherent atomic changes, something that helps ensure the source tree is always in a usable state. Second, Subversion doesn't just keep track of changes to files, it actually versions entire directory trees. This means that directories are first-class items to Subversion, and they can be added and removed just like files can be.

Throughout the rest of this chapter, I'll cover what you need to know to be an effective Subversion user. This crash course includes how to use of the svn command-line client and the concepts you need to be aware of to make effective use of it. By the end of the chapter, you should be well prepared to start making use of Subversion in your daily development work.

The Most Important Subversion Commands

Before you get started with Subversion, it's important to be aware of a few commands that are going to prove very useful to you. Both of the major Subversion executables (svn, the command line client, and svnadmin, the repository administration tool) have built-in help for each of their commands. To access the help, just use the help command, like this:

$svn help
usage:svn <subcommand>[options ] [args ] Type

"svn help <subcommand>"for help on a specific subcommand.
Most subcommands take file and/or directory arguments,recursing
on the directories.If no arguments are supplied to such a
command,it will recurse on the current directory (inclusive)by
default.

Available subcommands:
   add
   blame (praise, annotate, ann)
   cat
   checkout (co)
   cleanup
   commit (ci)
   copy (cp)
   delete (del, remove, rm)
   diff (di)
   export
   help (?, h)
   import
   info
   list (ls)
   log
   merge
   mkdir
   move (mv, rename, ren)
   propdel (pdel, pd)
   propedit (pedit, pe)
   propget (pget, pg)
   proplist (plist, pl)
   propset (pset, ps)
   resolved
   revert
   status (stat, st)
   switch (sw)
   update (up)
Subversion is a tool for revision control.
For additional information,see http://subversion.tigris.org/
$

Many of the subcommands include both a canonical primary name, such as "diff", and some shorter aliases that appear in parentheses after the canonical primary name, such as "di". In the help output, the aliases for each subcommand are always shown in parentheses.

In everyday use, it's common to use the shorter versions of the commands. For example, if someone mentions running svn up to update his working copy, he's really talking about the svn update command. It's probably a good idea to at least become familiar with the various aliases for just this reason. In this book I use the longer names in my examples, because they tend to more clearly represent what the command actually does. The shorter names generally exist for historical reasons (i.e., if CVS uses the same alias for a similar command and there isn't a compelling reason not to support it, Subversion likely has the same alias) or because the aliases are similar to some common command-line utility. Both types of shortcuts serve to help people remember the name of the command.

The remainder of this chapter introduces the commands you're likely to use on a day-to-day basis as a Subversion user. For more specific information about a svn subcommand, just run svn help subcommand, which gives you output like this:

$svn help add
add:Put files and directories under revision control,scheduling
them for addition to repository.They will be added in next commit.
usage:add PATH [PATH [PATH ...]]
Valid options:
   --targets arg              :pass contents of file ARG as
                              :additional args
   -N [--non-recursive ]      :operate on single directory only
   -q [--quiet ]              :print as little as possible
$

svn help will quickly become your new best friend, as it provides the quick hints you need as you get used to the new commands.

For more in-depth documentation of each of the commands, please see the glossary of Subversion commands in Appendix A.

Revisions and URLs

The majority of Subversion's commands require you to refer to a particular revision of an entity in the repository, where the entity could be a file or a directory. You generally specify this entity by using a URL and possibly a revision. If, for example, your repository is located at /home/repos/projects, then the URL file:///home/repos/projects/trunk/final refers to whatever is located at /trunk/final, in this case most likely a directory, which would likely contain other items. If you're on a Windows system and you need to refer to a repository on a drive different from the one your current working directory is on, the syntax looks like this: file://C:/path/to/repos or file://C|/path/to/repos. In addition to file://-based URLs, Subversion can be built with support for two other URL schemes, each of which refers to repositories that are accessed via a different repository access layer. URLs that start with http:// or https:// refer to repositories that are accessed via ra_dav, which uses a WebDAV-based protocol. svn:// or svn+ssh:// URLs refer to repositories that are accessed via ra_ssh, a custom TCP protocol written just for Subversion. In both cases, the URLs used to access repositories via these protocols include the name of the server the repository is located on after the schema, then a path to the location of the repository, followed by the location within the repository you're interested in. When a URL is used alone, without a specific revision, it refers to the newest revision of the item at that path within the repository.

Table 2-1 breaks down the components that make up a number of different Subversion URLs.

Table 2-1. URL Breakdown

URL RA Layer Host Repository Path
http://example.org/
repos/trunk/README
ra_dav example.org /repos trunk/README
file:///path/to/repos/
branches/1.0.x/src/
main.c
ra_local Not applicable /path/to/
repos
branches/
1.0.x/src/
main.c
svn://example.org/
repos/sandbox/tags/
0.47/HACKING
ra_svn example.org /repos/
sandbox
tags/0.47/
HACKING
svn+ssh://example.org/
home/repos/dev/trunk/
include/globals.h
ra_svn over ssh example.org /home/repos/
dev
trunk/
include/
globals.h

You can specify a revision in Subversion in several different ways. First off, you can always specify the literal revision number, which is a monotonically increasing integer. Zero is the initial revision of the repository, before any data is added to it, and for each change that is made the revision increases by one. In addition to using literal revision numbers, you can use shortcuts, such as the HEAD keyword, which refers to the most recent revision in the repository. There's also BASE, which refers to the version of an item that you have in your working copy; COMMITTED, which is the revision in which the item was last changed; and PREV, which is the revision before the item was last changed. If you're looking to specify the revision the repository was at on a particular date, you can just use the date enclosed in curly braces. For example, {1978-02-05}indicates the revision the item was at on February 5, 1978. Table 2-2 shows the various ways you can specify revisions to Subversion.

Table 2-2. Revision Specifications

Revision Specification Meaning
10 Literal revision 10
10:20 A range of revisions from 10 to 20
HEAD Youngest revision in the repository
BASE Current revision of the working copy
COMMITTED Revision in which an entry was last changed
PREV Revision before the last revision in which the item was changed
{2003-03-25} Revision the item was at on March 25, 2003
{2003-02-27 10:00} Revision the item was on at 10:00 on February 27, 2003

In most cases, you specify revisions by using the --revision keyword, which can be abreviated with -r, followed by the revision (e.g., svn update --revision 10 ). If the command refers to a range of revisions, the revisions are separated by colons, as in svn diff --revision 9:10, where you're indicating that the svn diff command should start at revision 9 and end at revision 10. In a few cases, the revision can be associated with the URL directly by appending it and delimiting it with an @. For example, file:///home/repos/projects/trunk/final@4747 refers to /trunk/final as it existed in revision 4747.

Creating and Populating the Repository

Before you can really do anything with Subversion, you need to have access to a Subversion repository. Because you'll need to have the ability to commit changes to a repository to do anything interesting (which is pretty much required if you want to learn more than just the basics), you'll want to create your own repository. The svnadmin program is used for this purpose (among other things, of course, which I'll discuss further in Chapter 3). Run the svnadmin create command, and you're ready to go. It looks something like this:

$svnadmin create myrepos
$ls -l myrepos/
-rw-r--r--   1 rooneg  staff  376 May 31 17:32 README.txt
drwxr-xr-x   2 rooneg  staff   68 May 31 17:32 dav/
drwxr-xr-x  17 rooneg  staff  578 May 31 17:32 db/
-rw-r--r--   1 rooneg  staff    2 May 31 17:32 format
drwxr-xr-x   7 rooneg  staff  238 May 31 17:32 hooks/
drwxr-xr-x   3 rooneg  staff  102 May 31 17:32 locks/
$

Inside the repository you just created are a number of files and directories, each of which has a specific purpose. You'll find out more about them later, so for the moment, don't touch them. With a few exceptions, you shouldn't modify anything inside the repository by hand. Instead, you should always use the svnadmin and svn programs to access the repository (that is, unless you really know what you're doing—but in that case, why are you reading this chapter anyway?).

Now that you've created a repository, you'll need to put something in it before it's much use to anyone. The first step is to create an initial repository layout. Unlike other version control systems, Subversion uses directories (and copies of directories) as tags and branches. This means that creating a new tag or branch is exactly the same as copying a directory. The only difference is a social one. The developers agree to treat certain copies of a directory in a specific way, either by confining some kind of development to them (as in a branch) or by leaving them completely read-only (as in a tag). I'll go into greater depth on how to work with tags and branches later, but for the moment you just need to know that if you want to make use of branches or tags in the future you'll want to create some subdirectories in your repository to hold them. Traditionally, most people create three top-level directories in their repository: one named /trunk to hold the main copy of the project where most development occurs, one named /branches to hold separate lines of development (remember, those will just be copies of /trunk), and one named /tags to hold copies of the project as it exists at significant points of time, such as a release. The only difference between the copies of /trunk that end up in /tags and the ones that end up in /branches is that everyone agrees not to modify the ones in /tags—they're only there because they're of historical interest.

You might be wondering why Subversion doesn't just create these top-level directories for you. The reason is quite simple: Subversion doesn't have any idea what you're going to put in your repository or what kinds of conventions you're going to use for repository layout. If you have a single project in your repository, the traditional top-level layout makes sense, but if you want your repository to hold multiple projects, you'll probably want each project to have its own trunk, tags, and branches directories contained within a top-level directory named after the project. Chapter 3 details the issues involved in selecting a repository layout.

Before you create your top-level directories, I should mention something about how to use the svn command-line client program. All of svn's subcommands take a target as one (or more) of their arguments. In many cases, this target can be either a local path or a URL that refers to something inside a repository. For many of these commands, if you don't provide a target, the command will assume you want to use your current working directory as the target. If the command targets something in your local working copy, no change you make will be reflected in the repository until you commit it in a separate command. Conversely, any change you make that targets the repository directly will cause an immediate commit, requiring you to specify a log message as with any other commit and creating a new revision in the repository.

Now let's move on to actually create your initial repository layout. You'll need to create each top-level directory via an svn mkdir that targets the repository directly. 1 The process looks like this:

$svnadmin create repos
$svn mkdir file:///absolute/path/to/repos/trunk \
           file:///absolute/path/to/repos/branches \
           file:///absolute/path/to/repos/tags \
           -m "creating initial repository layout"
Committed revision 1.
$

Let's examine that command for a second, and see what else you can learn from it. First of all, you see the syntax for accessing a repository via a file:// URL. Whenever a Subversion command uses the file:// schema for a URL, that means it's directly accessing the repository via Berkeley DB, so the repository needs to be located on the same machine you're running the command on, and you need read-write access to the database files within the repository itself (see Chapter 3 for more details about the internals of the repository). Look closely at the URLs you used to specify the directories you created. There are three slashes after file:, which might seem a bit odd, but if you think about it, it makes sense. It's just like accessing a web page over HTTP, except the part of the URL that would contain the domain name is empty, thus it skips directly to the third slash.

Second, you used the -m flag to pass a log message for the commit in on the command line. Because you're committing this change directly to the repository, a log message is required. If you don't pass one on the command line (either via -m or in a file specified with the --file argument), the client will run your editor of choice to give you an opportunity to enter one. 2

Now that the directories in question exist in the repository, let's confirm that fact via svn list. (Most people probably know this command better as svn ls.)

$svn list file:///absolute/path/to/repos
branches/
tags/
trunk/
$

Here you can see that the top level of the repository contains the three directories you just created. Of course, empty directories aren't all that useful, so let's put some data in them. Assuming that you already have some kind of project you want to bring into Subversion, you can import it via the svn import command. This command's command-line arguments are an optional path to a directory of files you want to import (if you don't specify a path, your current working directory is imported) and a URL to indicate where in the repository to place the data you're importing. Any directories at the end of the URL that don't yet exist in the repository will be automatically created. Take a look at this example to see what I mean:

$ls my-project/
foo.c
bar.c
main.c
$svn import my-project file:///path/to/repository/trunk \
            -m "importing my project"
Adding      my-project/bar.c
Adding      my-project/foo.c
Adding      my-project/main.c
Committed revision 2.
$svn list file:///path/to/repository/trunk
foo.c
bar.c
main.c
$

This is a basic example that imports a directory of files directly into the trunk of the repository. Note that once again you have to specify a log message because you're committing a change to the repository. If you want to import the files into a subdirectory of trunk, the process looks like this:

$ls my-project/
foo.c
bar.c
main.c
$svn import file:///path/to/repository/trunk/new-directory \
            -m "importing my project into new-directory"
Adding        my-project/bar.c
Adding        my-project/foo.c
Adding        my-project/main.c
Committed revision 3.
$svn list file:///path/to/repository/trunk/
foo.c
bar.c
main.c
new-directory/
$svn list file:///path/to/repository/trunk/new-directory/
foo.c
bar.c
main.c
$svn delete file:///path/to/repository/trunk/new-directory \
   -m "get rid of extra directory because it's not very useful"
Committed revision 4.
$

Finally, you can verify that the content of the files you just imported is actually in the repository by using the svn cat command. svn cat prints the contents of a file in the repository to the screen, and it can be a quick and dirty way to see what a given revision of a file looks like.

$svn cat file:///path/to/repository/trunk/main.c
#include <stdio.h>
int
main (int argc,char *argv [])
{
   printf ("hello world \n");
   return 0;
}
$cat my-project/main.c
#include <stdio.h>
int
main (int argc,char *argv [])
{
   printf ("hello world \n");
   return 0;
}
$

As you can see, there's no difference between the contents of main.c in your original directory and the contents of main.c in the trunk directory of the repository. The import has worked as you'd expect, loading the contents of your directory tree directly into the repository.

Basic Workflow

Now that you have data in your Subversion repository, it's time to do something with it. The first step is to check out a working copy. A Subversion working copy is an ordinary directory in your filesystem. It contains any number of files and sub-directories, some of which correspond to files and directories in your Subversion repository. In addition to your files and directories, each working copy directory contains a special subdirectory named .svn/. This is known as an administrative directory, and Subversion uses it to store some bookkeeping information about the files and directories in your working copy. Note that the administrative directory's contents are under Subversion's control, and you shouldn't edit them in any way. If you do change them manually, you're likely to render your working copy unusable.

Once you have a working copy, you can edit the files, make changes to the files, and eventually commit those changes back to the repository so others can see them. In the process of making your changes, you can ask Subversion to tell you about the status of your changes, view the difference between your current version and the version you checked out (or any other revision, for that matter), or update your working copy to take into account other users' changes. These actions comprise the majority of the Subversion workflow, so let's examine exactly how they work.

To check out a working copy you use the command svn checkout. There's only one required argument: a URL that indicates what directory in the repository you're checking out. Other than that, the most interesting options are an optional path, which specifies the name of the directory you want to check out into, with the default being the name of the directory in the repository, and the -r or --revision argument, which allows you to indicate what revision you want to check out. If you don't indicate what revision you want, the HEAD revision of the repository will be used. Let's take a look at an example of checking out the most recent version of a directory in the repository and inspecting the resulting working copy.

$svn checkout file:///path/to/repository/trunk myproject
A foo.c
A bar.c
A main.c
Checked out revision 2.
$ls myproject/
foo.c bar.c main.c
$cd myproject
$svn status
$svn status -v
               2       2       rooneg    .
               2       2       rooneg    bar.c
               2       2       rooneg    foo.c
               2       2       rooneg    main.c
$svn info
Path:.
URL:file:///path/to/repository
Repository UUID:18dffd47-64c3-0310-bd18-cbeae88b449f
Revision:2
Node Kind:directory
Schedule:normal
Last Changed Author:rooneg
Last Changed Rev:2
Last Changed Date:2003-07-26 19:03:29 -0400 (Sat,26 Jul 2003)
$svn info main.c
Path:main.c
Name:main.c
URL:file:///path/to/repos/trunk/main.c
Repository UUID:18dffd47-64c3-0310-bd18-cbeae88b449f
Revision:2
Node Kind:file
Schedule:normal
Last Changed Author:rooneg
Last Changed Rev:2
Last Changed Date:2003-07-26 19:03:29 -0400 (Sat, 26 Jul 2003)
Text Last Updated:2003-07-26 19:03:37 -0400 (Sat, 26 Jul 2003)
Properties Last Updated:2003-07-26 19:03:37 -0400 (Sat, 26 Jul 2003)
Checksum:0c1ea17162fcd46023bf6712a35dba03
$

In addition to svn checkout, you've seen two other new commands, svn status and svn info, so let's take a look at them before moving on. svn status is the command you'll probably run most often while using Subversion. It prints out the current status of your working copy.

Note: In Perforce, the closest analogous command to svn status is p4 opened, but svn status shows a lot more information because a Subversion working copy is considerably more complex (at least from the client's perspective) than a Perforce client. In CVS, there really isn't a useful status command, so most people just end up running either cvs diff or cvs update to determine what local changes they've made. The first case isn't all that bad, although cvs diff produces quite a bit more information than just the status of the working copy. The second case is particularly bad because the act of updating your working copy and the act of determining what you've changed are two completely different things. When Subversion's command-line client was designed, great care was taken to make the output of svn status useful and easy to understand, so that you aren't tempted to resort to running other commands to discover information you should be getting from svn status.

When you run just svn status, with no arguments, it will tell you only about files and directories in your working copy that you've modified. In this case when you ran it the first time, there was no output because you hadn't yet modified any of the files in the working copy. Later on, you'll see some examples of the output from svn status when the working copy has some modifications. When you add the -v flag, svn status runs in verbose mode, and you can see some more information. The first eight columns of data are blank because you haven't modified any of the files, but the next ones are the current working copy revision (i.e., WORKING), followed by the revision the file last changed in (i.e., COMMITTED) and the author of the previous commit. Everything after the author is the path to the file.

svn info, as you might expect, enables you to find information about items in your working copy. For CVS users, svn info is somewhat similar to the cvs status command. Some of the more useful bits of information here that aren't available elsewhere are the URL of the item in the repository, the universally unique identifier (UUID) of the repository the item came from, the MD5 checksum of the file's contents, and the dates on which the file's text and properties were last modified. svn info isn't used especially often, but at times you'll need information about a working copy that you can't obtain in any other way, so keep it in mind.

In this example, you can see that svn info tells you that, among other things, main.c has a URL of file:///path/to/repos/trunk/main.c; it comes from a repository with a UUID of 18dffd47-64c3-0310-bd18-cbeae88b449f; it's at revision 2; it was last changed by user rooneg on Saturday July 26, 2003; and it has an MD5 checksum of 0c1ea17162fcd46023bf6712a35dba03.

Now that you have a working copy checked out, it's time to start making changes to it. Suppose you've made some changes to some files, and you want to see what you've done. Subversion provides two commands for that purpose. First, there's svn status, which you've already seen. Second, once you know that your working copy has actually been modified, you can run svn diff to see exactly what has been changed. Let's take a look at how these commands work.

$svn status
M bar.c
$svn status -v
               2      2      rooneg    .
               2      2      rooneg    bar.c
M              2      2      rooneg    foo.c
               2      2      rooneg    main.c
$svn diff
Index:bar.c
===================================================================
---bar.c       (revision 2)
+++bar.c       (working copy)
@@-1, 5 +1, 5 @@
void
bar (int a, int b)
{
-printf ("b =%d, a =%d \n", a, b);
+printf ("b =%d, a =%d \n", b, a);
}
$

svn status shows that bar.c has had its text modified because the first column in the output is an M, and svn diff shows that you changed a single new line in the file.

Assuming that you're sure that this is what you want, it's time to publish the changes for everyone else to see with the svn commit command. Running svn commit is similar to using svn mkdir to create directories directly in the repository, in that both modify the repository. This means that the commit will require a log message, which again can be either specified on the command line via the -m or --file flag, or entered into a text editor before the commit will proceed. Here's an example:

$svn commit
Sending bar.c
svn:Transaction is out of date
svn:Commit failed (details follow):
svn:out of date:'/trunk/bar.c'in txn '8'
svn:Your commit message was left in a temporary file:
svn:   '/home/rooneg/work/my-project/svn-commit.tmp'
$

OK, that's definitely not what you would expect to see. It appears that someone has made a change to bar.c since you checked it out from the repository. Let's take a look at some commands that will tell you a bit more about what has happened. You've already learned that you can use svn status to see what you've changed in your local working copy, but it's also possible to pass it the -u flag, which tells svn status to contact the repository and figure out what files have been changed in more recent revisions. You can also use svn diff -rBASE:HEAD to see the difference between your BASE revision (the version you've checked out) and HEAD (the current version in the repository), and you can use svn log -r BASE:HEAD to read the log messages for the commits that may have caused the conflicts. Let's see how this works.

$svn status -u
M     *         2    bar.c
      *         2    foo.c
?                    svn-commit.tmp
Head revision:       3
$svn diff -r BASE:HEAD
Index:foo.c
===================================================================
---foo.c       (revision 3)
+++foo.c       (working copy)
@@-1, 5 +1, 5 @@
void
foo(int a, int b)
{
-printf ("a =%d, b =%d \n", a, b);
+printf ("arguments:a =%d, b =%d \n", a, b);
}
Index:bar.c
===================================================================
---bar.c        (revision 3)
+++bar.c        (working copy)
@@-1,5 +1,5 @@
void
bar (int a, int b)
{
-printf ("b =%d,a =%d \n", a, b);
+printf ("arguments:b =%d,a =%d \n", a, b);
}
$svn log -r BASE:HEAD
-------------------------------------------------------------------
r2 |rooneg |2003-06-08 09:56:24 -0400 (Sun,08 Jun 2003)|1 line
initial import of my project
-------------------------------------------------------------------
r3 |colonr |2003-06-08 10:00:42 -0400 (Sun,08 Jun 2003)|6 lines
*foo.c
(foo):changed output.
*bar.c
(bar):ditto.
-------------------------------------------------------------------
$

svn status -u shows you that both foo.c and bar.c have been changed since you checked them out (they have a *in column 8 of the status output), and svn-commit.tmp (which holds the log message from your failed commit) isn't under Subversion's control. Diffing the BASE and HEAD revisions show you that the line in bar.c that you changed had already changed in HEAD. svn log -r BASE:HEAD shows you that in revision 3 the user colonr changed the output in both foo.c and bar.c, causing the conflict.

Well, now you know why you can't commit the change in its current state, but that doesn't change the fact that you still need to get that bug fix into the repository. To do this, you'll need to run svn update, which will result in a conflict because your changes and those committed in revision 3 by the user colonr both changed the same line of bar.c. Then you'll have to manually fix the conflict and run svn resolved to tell Subversion that you're satisfied with it. Finally, you'll be able to run svn commit to get your fix into the repository.

$svn update
U foo.c
C bar.c
Updated to revision 3.
$ls
ls
bar.c          bar.c.r2      foo.c
bar.c.mine     bar.c.r3      svn-commit.tmp
$cat bar.c
cat bar.c
void
bar (int a, int b)
{
<<<<<<.mine
printf ("b =%d, a =%d \n", b, a);
=======
printf ("arguments:b =%d, a =%d \n", a, b);
>>>>>>.r3
}
$

So here you've run svn update, and it merged the change to foo.c into the version in your working copy (which isn't all that difficult, considering that you hadn't changed foo.c locally). Subversion also noticed that revision 3's change to bar.c conflicted with your local change, so it placed bar.c in a conflicted state (note the C in the output from svn update). When a file is in conflict, several things happen. First, several copies of the file are left in your working copy, one ending with .mine, which is your original modified version; one with .rOLDVERSION (r2 in this case), which holds the original version you started modifying (the BASE revision); and one ending in .rNEWVERSION (r3 in this case), which holds the new version (HEAD from the repository). These are present so that you can easily run third-party diff and merge programs on the files, and so you can refer to the various versions of the file easily while performing a merge. Second, assuming that the file in question is a textual file, and not a raw binary format, it's modified to show both your modified version of the change and the version from the repository. Third, some bookkeeping information about the conflict is recorded in the administrative directory. The next steps are to resolve the conflict manually and to run svn resolved to tell Subversion that you're satisfied with the changes.

$vi bar.c
[ ....resolve conflict manually ...]
$cat bar.c
void
bar (int a,int b)
{
   printf ("arguments:b =%d, a =%d \n", b, a);
}
$svn resolved bar.c
Resolved conflicted state of bar.c
$svn commit --file svn-commit.tmp
Sending       bar.c
Transmitting file data .
Committed revision 5.
$

Now your change has been committed to the repository. This general sequence of events—checkout, edit, update, status, diff, possibly resolve, and commit—will make up the majority of your time spent with Subversion, so be sure to become familiar with it.

More to Come
The rest of this sample chapter will appear on our Web site starting May 6th.

End Notes

1. Well, strictly speaking, you could check out a working copy, create the directories with a local svn mkdir, and then commit them all at once, but I haven't covered how to check out a working copy and commit changes yet, so let's not put the cart before the horse here.

2. Here, the "editor of choice" is determined by checking the SVN_EDITOR environment variable, then the editor-cmd option from the helpers section of ~/.subversion/config, then the VISUAL environment variable, and finally the EDITOR environment variable. As you might guess, it took a long time to get the developers to agree on that sequence of places to try to find a text editor.

About the Author

Garrett Rooney is a software engineer at FactSet Research Systems, in Stamford, Connecticut, where he works on real-time news feeds. Rooney attended Rensselaer Polytechnic Institute, where he managed to complete 3 years of a mechanical engineering degree before coming to his senses and realizing he wanted to get a job where someone would pay him to play with computers. Since then, Rooney completed a computer science degree at RPI and has spent far too much time working on a wide variety of open source projects, most notably Subversion.

About the Book

Practical Subversion By Garrett Rooney

Published: November 2004, Softbound, 336 pages
Published by Apress
ISBN 1-59059-290-5
Retail price: Price: $34.99
This material is from Chapter 2 of the book.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date