![]()
|
Table of Contents
List of Figures
List of Tables List of Examples Table of Contents This will be the start of the preface. The source code examples are just that—examples. While they will compile with the proper compiler incantations, they are intended to illustrate the problem at hand. So don't be surprised if an example is lacking error checking code and other bits that you would expect from production-quality code. Table of Contents The intended audience of this book is anyone who has used a version control system before, although perhaps not Subversion or CVS. It assumes that the reader is computer-literate, and reasonably comfortable at a Unix command-line. People familiar with CVS may want to skip some of the introductory sections that describe Subversion's concurrent versioning model. Also, there is a quick guide for CVS users attached as an appendix Appendix A Subversion is a free/open-source version control system. That is, Subversion manages files over time. The files are placed into a central repository. The repository is much like an ordinary file server, except that it remembers every change ever made to your files. This allows you to recover older versions of your files, or browse the history of how your files changed. Many people think of a version control system as a sort of ``time machine.'' Some version control systems are also software configuration management (SCM) systems. These systems are specifically tailored to manage trees of source code, and have many features that are specific to software development (such as natively understanding programming languages). Subversion, however, is not one of these systems; it is a general system that can be used to manage any sort of collection of files, including source code. Subversion aims to be the successor to the Concurrent Versions System (CVS). You can find more information about CVS at http://www.cvshome.org/. At the time of writing, CVS is the standard Free version control system used by the open-source community. It has a hard-earned, well-deserved reputation as stable and useful software, and has a design that makes it perfect for open-source development. However, it also has a number of problems that are difficult to fix. Subversion's original designers settled on a few simple goals. First, it was decided that Subversion should be a functional replacement for CVS. That is, it should do everything that CVS does, preserving the same development model while fixing the most obvious flaws. Secondly, with existing CVS users as the first target audience, Subversion should be written such that any CVS user should be able to start using it with little effort. Collabnet http://www.collab.net/ provided the initial funding in 2000 to begin development work, and the effort has now blossomed into a large, open-source project backed by a community of free software developers. What sort of things does Subversion do better than CVS? Here's a short list to whet your appetite:
Table of Contents This chapter is a short, casual introduction to Subversion. If you're new to version control, this chapter is definitely for you. We begin with a discussion of general version control concepts, work our way into the specific ideas behind Subversion, and show some simple examples of Subversion in use. Even though the examples in this chapter show people sharing collections of program source code, keep in mind that Subversion can manage any sort of file collection -- it's not limited to helping computer programmers. Subversion is a centralized system for sharing information. At its core is a repository, which is a central store of data. The repository stores information in the form of a filesystem tree -- a typical hierarchy of files and directories. Any number of clients connect to the repository, and then read or write to these files. By writing data, a client makes the information available to others; by reading data, the client receives information from others. So why is this interesting? So far, this sounds like the definition of a typical file server. And indeed, the repository is a kind of file server, but it's not your usual breed. What makes the Subversion repository special is that it remembers every change ever written to it: every change to every file, and even changes to the directory tree itself, such as the addition, deletion, and rearrangement of files and directories. When a client reads data from the repository, it normally sees only the latest version of the filesystem tree. But the client also has the ability to view previous states of the filesystem. For example, a client can ask historical questions like, "what did this directory contain last Wednesday?", or "who was the last person to change this file, and what changes did they make?" These are the sorts of questions that are at the heart of any version control system: systems that are designed to record and track changes to data over time. All version control systems have to solve the same fundamental problem: how will the system allow users to share information, but prevent them from accidentally stepping on each other's feet? It's all too easy for users to accidentally overwrite each other's changes in the repository. Consider this scenario: suppose we have two co-workers, Jane and Joe. They each decide to edit the same repository file at the same time. If Joe saves his changes to the repository first, then it's possible that (a few moments later) Jane could accidentally overwrite them with her own new version of the file. While Joe's version of the file won't be lost forever (because the system remembers every change), any changes Joe made won't be present in Jane's newer version of the file, because she never saw Joe's changes to begin with. Joe's work is still effectively lost—or at least missing from the latest version of the file—and probably by accident. This is definitely a situation we want to avoid! Many version control systems use a lock-modify-unlock model to address this problem, which is a very simple solution. In such a system, the repository allows only one person to change a file at a time. First Joe must "lock" the file before he can begin making changes to it. Locking a file is a lot like borrowing a book from the library; if Joe has locked a file, then Jane cannot make any changes to it. If she tries to lock the file, the repository will deny the request. All she can do is read the file, and wait for Joe to finish his changes and release his lock. After Joe unlocks the file, his turn is over, and now Jane can take her turn by locking and editing. The problem with the lock-modify-unlock model is that it's a bit restrictive, and often becomes a roadblock for users:
Subversion, CVS, and other version control systems use a copy-modify-merge model as an alternative to locking. In this model, each user's client reads the repository and creates a personal working copy of the file or project. Users then work in parallel, modifying their private copies. Finally, the private copies are merged together into a new, final version. The version control system often assists with the merging, but ultimately a human being is responsible for making it happen correctly. Here's an example. Say that Joe and Jane each create working copies of the same project, copied from the repository. They work concurrently, and make changes to the same file "A" within their copies. Jane saves her changes to the repository first. When Joe attempts to save his changes later, the repository informs him that his file A is out-of-date. In other words, that file A in the repository has somehow changed since he last copied it. So Joe asks his client to merge any new changes from the repository into his working copy of file A. Chances are that Jane's changes don't overlap with his own; so once he has both sets of changes integrated, he saves his working copy back to the repository. But what if Jane's changes do overlap with Joe's changes? What then? This situation is called a conflict, and it's usually not much of a problem. When Joe asks his client to merge the latest repository changes into his working copy, his copy of file A is somehow flagged as being in a state of conflict: he'll be able to see both sets of conflicting changes, and manually choose between them. Note that software can't automatically resolve conflicts; only humans are capable of understanding and making intelligent choices. Once Joe has manually resolved the overlapping changes (perhaps by discussing the conflict with Jane!), he can safely save the hand-merged file back to the repository. The copy-modify-merge model may sound a bit chaotic, but in practice, it runs extremely smoothly. Users can work in parallel, never waiting for one another. When they work on the same files, it turns out that most of their concurrent changes don't overlap at all; conflicts are infrequent. And the amount of time it takes to resolve conflicts is far less than the time lost by a locking system. In the end, it all comes down to one critical factor: user communication. When users communicate poorly, both syntactic and semantic conflicts increase. No system can force users to communicate perfectly, and no system can detect semantic conflicts. So there's no point in being lulled into a false promise that a locking system will somehow prevent conflicts; in practice, locking seems to inhibit productivity more than anything else. You've already read about working copies; now we'll demonstrate how the Subversion client creates and uses them. A Subversion working copy is an ordinary directory tree on your local system, containing a collection of files. You can edit these files however you wish, and if they're source code files, you can compile your program from them in the usual way. Your working copy is your own private work area: Subversion will never incorporate other people's changes, nor make your own changes available to others, until you explicitly tell it to do so. After you've made some changes to the files in your working copy and verified that they work properly, Subversion provides you with commands to "publish" your changes to the other people working with you on your project (by writing to the repository). If other people publish their own changes, Subversion provides you with commands to merge those changes into your working directory (by reading from the repository). A working copy also contains some extra files, created and maintained by Subversion, to help it carry out these commands. In particular, each directory in your working copy contains a subdirectory named .svn, also known as the working copy administrative directory. The files in each administrative directory help Subversion recognize which files contain unpublished changes, and which files are out-of-date with respect to others' work. A typical Subversion repository often holds the files (or source code) for several projects; usually, each project is a subdirectory in the repository's filesystem tree. In this arrangement, a user's working copy will usually correspond to a particular subtree of the repository. For example, suppose you have a repository that contains two software projects. In other words, the repository's root directory has two subdirectories: paint and write. To get a working copy, you must check out some subtree of the repository. (The term "check out" may sound like it has something to do with locking or reserving resources, but it doesn't; it simply creates a private copy of the project for you.) For example, If you check out /write, you will get a working copy like this: $ svn checkout http://svn.example.com/repos/write A write A write/Makefile A write/document.c A write/search.c $ ls -a write Makefile document.c search.c .svn/ Your working copy is a personal copy of the repository's /write directory, with one additional entry—.svn—which holds the extra information needed by Subversion, as mentioned earlier. Suppose you make changes to search.c. Since the .svn directory remembers the file's modification date and original contents, Subversion can tell that you've changed the file. However, Subversion does not make your changes public until you explicitly tell it to. The act of publishing your changes is more commonly known as committing or (checking in) changes to the repository. To publish your changes to others, you can use Subversion's commit command: $ svn commit search.c Sending search.c Transmitting file data.. Committed revision 57. Now your changes to search.c have been committed to the repository; if another user checks out a working copy of /write, they will see your changes in the latest version of the file. Suppose you have a collaborator, Felix, who checked out a working copy of /write at the same time you did. When you commit your change to search.c, Felix's working copy is left unchanged; Subversion only modifies working directories at the user's request. To bring his project up to date, Felix can ask Subversion to update his working copy, by using the Subversion update command. This will incorporate your changes into his working copy, as well as any others that have been committed since he checked it out. $ pwd /home/felix/write $ ls -a .svn/ Makefile document.c search.c $ svn update U search.c The output from the svn update command indicates that Subversion updated the contents of search.c. Note that Felix didn't need to specify which files to update; Subversion uses the information in the .svn directory, and further information in the repository, to decide which files need to be brought up to date. A svn commit operation can publish changes to any number of files and directories as a single atomic transaction. In your working copy, you can change files' contents, create, delete, rename and copy files and directories, and then commit the complete set of changes as a unit. In the repository, each commit is treated as an atomic transaction: either all the commit's changes take place, or none of them take place. Subversion tries to retain this atomicity in the face of program crashes, system crashes, network problems, and other users' actions. Each time the repository accepts a commit, this creates a new state of the filesystem tree, called a revision. Each revision is assigned a unique natural number, one greater than the number of the previous revision. The initial revision of a freshly created repository is numbered zero, and consists of nothing but an empty root directory. A nice way to visualize the repository is as a series of trees. Imagine an array of revision numbers, starting at 0, stretching from left to right. Each revision number has a filesystem tree hanging below it, and each tree is a "snapshot" of the way the repository looked after each commit. It's important to note that working copies do not always correspond to any single revision in the repository; they may contain files from several different revisions. For example, suppose you check out a working copy from a repository whose most recent revision is 4:
write/Makefile:4
document.c:4
search.c:4
At the moment, this working directory corresponds exactly to revision 4 in the repository. However, suppose you make a change to search.c, and commit that change. Assuming no other commits have taken place, your commit will create revision 5 of the repository, and your working copy will now look like this:
write/Makefile:4
document.c:4
search.c:5
Suppose that, at this point, Felix commits a change to document.c, creating revision 6. If you use svn update to bring your working copy up to date, then it will look like this:
write/Makefile:6
document.c:6
search.c:6
Felix's changes to document.c will appear in your working copy, and your change will still be present in search.c. In this example, the text of Makefile is identical in revisions 4, 5, and 6, but Subversion will mark your working copy of Makefile with revision 6 to indicate that it is still current. So, after you do a clean update at the top of your working copy, it will generally correspond to exactly one revision in the repository. For each file in a working directory, Subversion records two essential pieces of information in the .svn/ administrative area:
Given this information, by talking to the repository, Subversion can tell which of the following four states a working file is in:
This may sound like a lot to keep track of, but as you will learn in a later chapter, the svn status command will show you the state of any item in your working copy. We've covered a number of fundamental Subversion concepts in this chapter:
At this point, you should have a good idea of how Subversion works in the most general sense. Armed with this knowledge, you should now be ready to jump into the next chapter, which is a detailed tour of Subversion's commands and features. Table of Contents Now we will go into the details of using Subversion in your day to day work. By the time you reach the end of this chapter, you will be able to perform almost all the tasks you need to use Subversion in a normal day's work. You'll start with an initial checkout of your code, and walk through making changes and examining those changes. You'll also see how to bring changes made by others into your working copy, examine them, and work through any conflicts that might arise. Note that this chapter is not meant to be an exhaustive list of all Subversion's commands—rather, it's a conversational introduction to the most common Subversion tasks you'll encounter. For a complete reference of all commands, see Chapter 8. Before reading on, here is the most important command you'll ever need when using Subversion: svn help. The Subversion command-line client tries to be self-documenting—at any time, a quick svn help <subcommand> will describe the syntax, switches, and behavior of the subcommand. svn import is used to import a new project into a Subversion repository. While this is most likely the very first thing you will do when you set up your Subversion server, it's not something that happens very often. For a detailed description of import, see Chapter 5. Most of the time, you will start using a Subversion repository by doing a checkout of your project. “Checking out” will provide you with a local copy of the HEAD (latest revision) of the Subversion repository that you specify on the command line.
$ svn co http://svn.collab.net/repos/svn/trunk
A trunk/subversion.dsw
A trunk/svn_check.dsp
A trunk/COMMITTERS
A trunk/configure.in
A trunk/IDEAS
…
Checked out revision 2499.
Although the above example checks out the trunk directory, you can just as easily checkout any deep subdirectory of a repository by specifying the subdirectory in the checkout URL:
$ svn co http://svn.collab.net/repos/svn/trunk/doc/book/tools
A tools/readme-dblite.html
A tools/fo-stylesheet.xsl
A tools/svnbook.el
A tools/dtd
A tools/dtd/dblite.dtd
…
Checked out revision 3678.
Since Subversion uses a “copy-modify-merge” model instead of “lock-modify-unlock,” (See Chapter 2) you're now ready to start making changes to the files that you've checked out, known collectively as your working copy. You can even delete the entire working copy and forget about it—there's no need to notify the Subversion server unless you're ready to check in changes, a new file, or even a directory. While you can certainly check out a working copy with the url of the repository as the only argument, you can also specify a directory after your repository url. This places your working copy into the new directory that you name. For example:
$ svn co http://svn.collab.net/repos/svn/trunk subv
A subv/subversion.dsw
A subv/svn_check.dsp
A subv/COMMITTERS
A subv/configure.in
A subv/IDEAS
…
Checked out revision 2499.
That will place your working copy in a directory named subv instead of a directory named trunk as was the case above. Subversion has numerous features, options, bells and whistles, but on a day-to-day basis, odds are that you will only use a few of them. In this section we'll run through the most common things that you might find yourself doing with Subversion in the course of a day's work. The typical work cycle looks like this
When working on a project with a team, you'll want to update your working copy: that is, receive any changes from other developers on the project. svn update brings your working copy in sync with the latest revision in the repository.
$ svn up
U ./foo.c
U ./bar.c
Updated to revision 2.
In this case, someone else checked in modifications to both foo.c and bar.c since the last time you updated, and Subversion has updated your working copy to include those changes. Let's examine the output of svn update a bit more. When the server sends changes to your working copy, a letter code is displayed next to each item to let you know what actions Subversion performed to bring your working copy up to date:
Now you can to get to work and make changes in your working copy. It's usually most convenient to create a “task” for yourself, such as writing a new feature, fixing a bug, etc. The Subversion commands that you will use here are svn add, svn delete, svn copy, and svn move. However, if you are merely editing a file (or files) that is already in Subversion, you may not need to use any of these commands until you commit. Changes you can make to your working copy:
To make file changes, just use your text editor, word processor, or graphics program—whatever tool you would normally use. A file needn't be in text-format; Subversion handles binary files just as easily as it handles text file (and just as efficiently too). Here is an overview of the four Subversion subcommands that you'll use most often to make tree changes (we'll cover svn import and svn mkdir later).
Once you've finished making changes, you need to commit them to the repository, but before you do so, it's usually a good idea to take a look at exactly what you've changed. By examining your changes before you commit, you can not only make a more accurate log message, but you may discover that you've inadvertently changed a file, and this gives you a chance to revert those changes before committing. You can see exactly what changes you've made by using svn status, svn diff, and svn revert to find out what files have changed where and possibly remove these changes. Subversion has been optimized to help you with this task, and is able to do many things without communicating with the repository. In particular, your working copy contains a secret cached “pristine” copy of each version controlled file within the .svn area. Because of this, Subversion can quickly show you how your working files have changed, or even allow you to undo your changes without contacting the repository. You'll probably use the svn status command more than any other Subversion command. If you run svn status at the top of your working copy with no arguments, it will detect all file and tree changes you've made. This example is designed to show all the different status codes that svn status can return. Note that the text in [] is not printed by svn status.
$ svn status
L ./abc.c [svn has a lock in its .svn directory for abc.c]
M ./bar.c [the content in bar.c has local modifications]
M ./baz.c [baz.c has property but no content modifications]
? ./foo.o [svn doesn't manage foo.o]
! ./foo.c [svn knows foo.c but a non-svn program deleted it]
~ ./qux [versioned as dir, but is file, or vice versa]
A + ./moved_dir [added with history of where it came from]
M + ./moved_dir/README [added with history and has local modifications]
D ./stuff/fish.c [this file is scheduled for deletion]
A ./stuff/things/bloo.h [this file is scheduled for addition]
In this output format svn status prints four columns of characters followed by several whitespace characters followed by a file or directory name. The first column tells the status of a file or directory and/or its contents. The codes printed here are:
The second column tells the status of a file or directory's properties. If an M appears in the second column, then the properties have been modified, otherwise a whitespace will be printed. The third column will only show whitespace or an L which means that Subversion has locked the item in the.svn working area. You will see an L if you run svn status in a directory where an svn commit is in progress—perhaps when you are editing the log message. If Subversion is not running, then presumably Subversion was forcibly quit or died and the lock needs to be cleaned up by running svn cleanup (more about that later in this chapter). Locks typically appear if a Subversion command is interrupted before completion. The fourth column will only show whitespace or a + which means that the file or directory is scheduled to be added or modified with additional attached history. This typically happens when you svn move or svn copy a file or directory. If you see A +, this means the item is scheduled for addition-with-history. It could be a file, or the root of a copied directory. “ +” means the item is part of a subtree scheduled for addition-with-history, i.e. some parent got copied, and it's just coming along for the ride. M + means the item is part of a subtree scheduled for addition-with-history, and it has local modifications. When you commit, first the parent will be added-with-history (copied), which means this file will automatically exist in the copy. Then the local modifications will be uploaded into the copy. If you pass a specific path to svn status, it gives you information about that item alone:
$ svn status stuff/fish.c
D stuff/fish.c
svn status also has a --verbose (-v) switch, which will show you the status of every item in your working copy, even if it has not been changed in your working copy:
$ svn status --verbose
M 44 23 sally ./README
44 30 sally ./INSTALL
M 44 20 harry ./bar.c
44 18 ira ./stuff
44 35 harry ./stuff/trout.c
D 44 19 ira ./stuff/fish.c
44 21 sally ./stuff/things
A 0 ? ? ./stuff/things/bloo.h
44 36 harry ./stuff/things/gloo.c
This is the “long form” output of svn status. The first column remains same, but the second column shows the working-revision of the item. The third and fourth columns show the revision in which the item last changed, and who changed it. None of the above invocations to svn status contact the repository, they work only locally by comparing the metadata in the .svn directory with the working copy. Finally, there is the --show-updates (-u) switch, which contacts the repository and adds information about things that are out-of-date:
$ svn status --show-updates --verbose
M * 44 23 sally ./README
M 44 20 harry ./bar.c
* 44 35 harry ./stuff/trout.c
D 44 19 ira ./stuff/fish.c
A 0 ? ? ./stuff/things/bloo.h
Notice the two asterisks: if you were to run svn update at this point, you would receive changes toREADME andtrout.c. This tells you some very useful information—you'll need to update and get the server-changes onREADME before you commit, or the repository will reject your commit for being out-of-date. (More on this subject later). Another way to examine your changes is with the svn diff command. You can find out exactly how you've modified things by running svn diff with no arguments, which prints out file changes in unified diff format:
$ svn diff
Index: ./bar.c
===================================================================
--- ./bar.c
+++ ./bar.c Mon Jul 15 17:58:18 2002
@@ -1,7 +1,12 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <stdio.h>
int main(void) {
- printf("Sixty-four slices of American Cheese...\n");
+ printf("Sixty-five slices of American Cheese...\n");
return 0;
}
Index: ./README
===================================================================
--- ./README
+++ ./README Mon Jul 15 17:58:18 2002
@@ -193,3 +193,4 @@
+Note to self: pick up laundry.
Index: ./stuff/fish.c
===================================================================
--- ./stuff/fish.c
+++ ./stuff/fish.c Mon Jul 15 17:58:18 2002
-Welcome to the file known as 'fish'.
-Information on fish will be here soon.
Index: ./stuff/things/bloo.h
===================================================================
--- ./stuff/things/bloo.h
+++ ./stuff/things/bloo.h Mon Jul 15 17:58:18 2002
+Here is a new file to describe
+things about bloo.
The svn diff command produces this output by comparing your working files against the cached “pristine” copies within the .svn area. Files scheduled for addition are displayed as all added-text, and files scheduled for deletion are displayed as all deleted text. Output is displayed in unified diff format. That is, removed lines are prefaced with a - and added lines are prefaced with a +. svn diff also prints filename and offset information useful to the patch program, so you can generate “patches” by redirecting the diff output to a file:
$ svn diff > patchfile
You could, for example, email the patchfile to another developer for review or testing prior to commit. Now suppose you see the above diff output, and realize that your changes to README are a mistake; perhaps you accidentally typed that text into the wrong file in your editor. This is a perfect opportunity to use svn revert.
$ svn revert README
Reverted ./README
Subversion reverts the file to its pre-modified state by overwriting it with the cached “pristine” copy from the .svn area. But also note that svn revert can undo any scheduled operations—for example, you might decide that you don't want to add a new file after all:
$ svn status foo
? foo
$ svn add foo
A foo
$ svn revert foo
Reverted foo
$ svn status foo
? foo
Or perhaps you mistakenly removed a file from version control:
$ svn status README
README
$ svn delete README
D README
$ svn revert README
Reverted README
$ svn status README
README
We've already seen how svn status -u can predict conflicts. Suppose you run svn update and some interesting things occur:
$ svn update
U ./INSTALL
G ./README
C ./bar.c
The U and G codes are no cause for concern; those files cleanly absorbed changes from the repository. The files marked with U contained no local changes but were Updated with changes from the repository. The G stands for merGed, which means that the file had local changes to begin with, but the changes coming from the repository didn't overlap in any way. But the C stands for conflict. This means that the changes from the server overlapped with your own, and now you have to manually choose between them. Whenever a conflict occurs, your Subversion client does the following:
For example, Sally makes changes to the file sandwich in the repository. Harry has just changed the file in his working copy and checked it in. Sally updates her working copy before checking in and she gets a conflict:
$ svn update
C sandwich
Updated to revision 2.
$ ls -1
sandwich
sandwich.59528.00001.r2
sandwich.59524.00001.r1
sandwich.59532.00001.mine
$
At this point, Subversion will not allow you to commit the file sandwichuntil the three temporary files are removed.
$ svn ci --message "Add a few more things"
subversion/libsvn_client/commit.c:654: (apr_err=155015, src_err=0)
svn: A conflict in working copy obstructs the current operation
svn: Commit failed (details follow):
subversion/libsvn_client/commit_util.c:180: (apr_err=155015, src_err=0)
svn: Aborting commit: '/home/sally/svn-work/sandwich' remains in conflict.
If you get a conflict, you need to either (1) hand-merge the conflicted text (by examining and editing the conflict markers within the file), (2) copy one of the tmpfiles on top of your working file, or (3) run svn revert <filename> to throw away all of your local changes.
$ cp sandwich.59532.00001.mine sandwich
Once you've resolved the conflict, you need to let Subversion know by removing the three temporary files. (The svn resolve command, by the way, is a shortcut that does nothing but automatically remove the three temporary files for you.) When those files are gone, Subversion no longer considers the file to be in a state of conflict anymore.
$ svn resolve sandwich
Resolved conflicted state of sandwich
Now you're ready to check in your changes
$ svn ci --message "Add a few more things"
Sending sandwich
Transmitting file data .
Committed revision 3.
Note thatsvn resolve, unlike most of the other commands we've dealt with in this chapter, requires an argument. In any case, you want to be careful and only run svn resolve when you're certain that you've fixed the conflict in your file—once the temporary files are removed, Subversion will even let you check in a file with conflict markers. Finally! Your edits are finished, you've merged all updates from the server, and you're ready to commit your changes to the repository. The svn commit command sends all (or, if you specify files or directories, some) of your changes to the repository. When you commit a change, you need to supply a log message, describing your change. Your log message will be attached to the new revision you create. If your log message is brief, you may wish to supply it on the command line using the --message (or -m):
$ svn commit --message "Corrected number of cheese slices."
Sending sandwich
Transmitting file data .
Committed revision 3.
However, if you've been composing your log message as you work, you may want to tell Subversion to get the message from a file by passing the filename with the --file switch:
svn ci --file logmsg
Sending sandwich
Transmitting file data .
Committed revision 4.
If you fail to specify either the --message or --file switch, then Subversion will automatically launch your favorite editor (As defined in the environment variable $EDITOR) for composing a log message. The repository doesn't know or care if your changes make any sense as a whole; it only checks to make sure that nobody else has changed any of the same files that you did when you weren't looking. If somebody has done that, the entire commit will fail with a message informing you that one or more of your files is out-of-date: $ svn commit --message "Add another rule" Sending rules.txt subversion/libsvn_client/commit.c:655: (apr_err=160028, src_err=0) svn: Transaction is out of date svn: Commit failed (details follow): subversion/libsvn_repos/commit.c:112: (apr_err=160028, src_err=0) svn: out of date: `rules.txt' in txn `g' $ At this point, you need to run svn update, deal with any merges or conflicts that result, and attempt your commit again. That covers the basic work cycle for using Subversion. There are many other features in Subversion that you can use to manage your repository and working copy, but you can get by quite easily using only the commands that we've discussed so far in this chapter. As we mentioned earlier, the repository is like a time machine. It keeps a record of every revision ever committed, and allows you to explore this history by examining previous versions of files and directories as well as the metadata that accompanies them. With a single Subversion command, you can check out (or restore an existing working copy) the repository exactly as it was at any date or revision number in the past. However, sometimes you just want to peer into the past instead of going into the past. There are several commands that can provide you with historical data from the repository. svn log shows you broad information: log messages attached to revisions, and which paths changed in each revision. svn diff, on the other hand, can show you the specific details of how a file changed over time. Finally, svn cat can be used to retrieve any file as it existed in a particular revision number and display it on your screen. To find out information about the history of a file or directory, use the svn log command. svn log will provide you with a record of who made changes to a file or directory, at what revision it changed, the time and date of that revision, and, if it was provided, the log message that accompanied the commit.
$ svn log
------------------------------------------------------------------------
rev 3: sally | Mon, 15 Jul 2002 18:03:46 -0500 | 1 line
Added include lines and corrected # of cheese slices.
------------------------------------------------------------------------
rev 2: harry | Mon, 15 Jul 2002 17:47:57 -0500 | 1 line
Added main() methods.
------------------------------------------------------------------------
rev 1: sally | Mon, 15 Jul 2002 17:40:08 -0500 | 2 lines
Initial import
------------------------------------------------------------------------
Note that the log messages are printed in reverse chronological order by default. If you wish to see a different range of revisions in a particular order, or just a single revision, pass the --revision (-r) switch:
$ svn log --revision 5:19
… # shows logs 5 through 19 in chronological order
$ svn log -r 19:5
… # shows logs 5 through 19 in reverse order
$ svn log -r 8
…
You can also examine the log history of a single file or directory. For example:
$ svn log foo.c
…
$ svn log http://foo.com/svn/trunk/code/foo.c
…
These will display log messages only for those revisions in which the working file (or URL) changed. If you want even more information about a file or directory, svn log also takes a --verbose (-v) switch. Because Subversion allows you to move and copy files and directories, it is important to be able to track path changes in the filesystem, so in verbose mode, svn log will include a list of changed-paths in a revision in its output:
$ svn log -r 8 -v
------------------------------------------------------------------------
rev 8: sally | 2002-07-14 08:15:29 -0500 | 1 line
Changed paths:
U /trunk/code/foo.c
U /trunk/code/bar.h
A /trunk/code/doc/README
Frozzled the sub-space winch.
------------------------------------------------------------------------
We've already seen svn diff above—it displays file differences in unified diff format; it was used to show the local modifications made to our working copy before committing to the repository. In fact, it turns out that there are three distinct uses of svn diff:
As we saw above, invoking svn diff with no switches will compare your working files to the cached “pristine” copies in the.svn area:
$ svn diff
Index: rules.txt
===================================================================
--- rules.txt (revision 3)
+++ rules.txt (working copy)
@@ -1,4 +1,5 @@
Be kind to others
Freedom = Responsibility
Everything in moderation
-Chew with your mouth closed
+Chew with your mouth closed
+Listen when others are speaking
$
If a single --revision (-r) number is passed, then your working copy is compared to the specified revision in the repository.
$ svn diff --revision 3 rules.txt
Index: rules.txt
===================================================================
--- rules.txt (revision 3)
+++ rules.txt (working copy)
@@ -1,4 +1,5 @@
Be kind to others
Freedom = Responsibility
Everything in moderation
-Chew with your mouth closed
+Chew with your mouth closed
+Listen when others are speaking
$
If two revision numbers, separated by a colon, are passed via --revision (-r), then the two revisions are directly compared.
$ svn diff --revision 2:3 rules.txt
Index: rules.txt
===================================================================
--- rules.txt (revision 2)
+++ rules.txt (revision 3)
@@ -1,4 +1,4 @@
Be kind to others
-Freedom = Chocolate Ice Cream
+Freedom = Responsibility
Everything in moderation
Chew with your mouth closed
$
Not only can you use svn diff to compare files in your working copy to the repository, but if you supply a URL argument, you can examine the differences between items in the repository without even having a working copy. This is especially useful if you wish to inspect changes in a file when you don't have a working copy on your local machine:
$ svn diff --revision 4:5 http://svn.red-bean.com/repos/example/trunk/text/rules.txt
…
$
If you want to examine an earlier version of a file and not necessarily the differences between two files, you can use svn cat:
$ svn cat --revision 2 rules.txt
Be kind to others
Freedom = Chocolate Ice Cream
Everything in moderation
Chew with your mouth closed
$
You can also redirect the output directly into a file:
$ svn cat --revision 2 rules.txt > rules.txt.v2
$
Now you're probably wondering why we don't just use svn update --revision to update the file to the older revision. Well, there are several reasons that we might prefer to use svn cat. First, you may want to see the differences between 2 revisions of a file using an external diff program (perhaps a graphical one, or perhaps your file is in such a format that the output ofunified diff is nonsensical). In this case, you'll need to grab a copy of the old revision, redirect it to a file, and pass both that and the file in your working copy to your external diff program. Second, you may have local changes to the file and don't want to lose them. Finally, sometimes it's easier to look at an older revision in its entirety as opposed to just the differences between it and another revision. As you've probably noticed by now, many Subversion commands can take a --revision (-r) switch. Here we describe some special ways to specify revisions. The Subversion client understands a number of revision keywords. These keywords can be used instead of integer arguments to the --revision switch, and are resolved into specific revision numbers by Subversion:
Here are some examples of revision keywords in action:
$ svn diff --revision PREV:COMMITTED foo.c
# shows the last change committed to foo.c
$ svn log --revision HEAD
# shows log message for the latest repository commit
$ svn diff --revision HEAD
# compares your working file (with local mods) to the latest version
# in the repository.
$ svn diff --revision BASE:HEAD foo.c
# compares your “pristine” foo.c (no local mods) with the
# latest version in the repository
$ svn log --revision BASE:HEAD
# shows all commit logs since you last updated
$ svn update --revision PREV foo.c
# rewinds the last change on foo.c.
# (foo.c's working revision is decreased.)
These keywords may not seem terribly important, but they do allow you to perform many common (and helpful) operations without having to look up specific revision numbers or remember the exact revision of your working copy. When Subversion modifies your working copy (or any information within .svn), it tries to do so as safely as possible. Before changing anything, it writes its intentions to a logfile, executes the commands in the logfile, then removes the logfile (This is similar in design to a journaled filesystem). If a Subversion operation is interrupted (If you hit Control-C, or if the machine crashes, for example), the logfiles remain on disk. By re-executing the logfiles, Subversion can complete the previously started operation, and your working copy can get itself back into a consistent state. And this is exactly what svn cleanup does: it searches your working copy and runs any leftover logs, removing locks in the process. If Subversion ever tells you that some part of your working copy is “locked”, then this is the command that you should run. Also, svn status will display an L next to locked items:
$ svn status
L ./somedir
M ./somedir/foo.c
$ svn cleanup
$ svn status
M ./somedir/foo.c
In general, we try to discourage you from directly reading the .svn/entries file used to track items in a Subversion working copy. Instead, you can get tons of information about an item in your working copy by using the svn info command to display most of the tracked information:
$ svn info rules.txt
Path: rules.txt
Name: rules.txt
Url: http://svn.red-bean.com/repos/example/trunk/text/rules.txt
Revision: 8
Node Kind: file
Schedule: normal
Last Changed Author: harry
Last Changed Rev: 7
Last Changed Date: 2002-11-15 23:03:54 -0500 (Fri, 15 Nov 2002)
Text Last Updated: 2002-11-16 08:48:04 -0500 (Sat, 16 Nov 2002)
Properties Last Updated: 2002-11-16 08:48:03 -0500 (Sat, 16 Nov 2002)
Checksum: 8sfaU+5dqyOgkhuSdyxGrQ==
The import command is a quick way to move an unversioned tree of files into a repository. There are two ways to use this command:
$ svnadmin create /usr/local/svn/newrepos
$ svn import file:///usr/local/svn/newrepos mytree
Adding mytree/foo.c
Adding mytree/bar.c
Adding mytree/subdir
Adding mytree/subdir/quux.h
Transmitting file data....
Committed revision 1.
The above example places the contents of directory mytree directly into the root of the repository:
/foo.c
/bar.c
/subdir
/subdir/quux.h
If you give svn import a third argument, it will use the argument as the name of a new subdirectory to create within the URL.
$ svnadmin create /usr/local/svn/newrepos
$ svn import file:///usr/local/svn/newrepos mytree fooproject
Adding mytree/foo.c
Adding mytree/bar.c
Adding mytree/subdir
Adding mytree/subdir/quux.h
Transmitting file data....
Committed revision 1.
The repository would now look like this:
/fooproject/foo.c
/fooproject/bar.c
/fooproject/subdir
/fooproject/subdir/quux.h
The export command is a quick way to create an unversioned tree of files from a repository directory—a tree that looks like a typical working copy, but doesn't contain the .svn directories:
$ svn export file:///usr/local/svn/newrepos/fooproject
A fooproject/foo.c
A fooproject/bar.c
A fooproject/subdir
A fooproject/subdir/quux.h
Checked out revision 3.
The resulting directory will not contain any .svn administrative areas, and all property metadata will be lost. (Hint: don't use this technique for backing up; it's probably better for rolling source distributions.) The svn list command shows you what files are in a repository directory without actually downloading the files to your local machine:
$ svn list http://svn.collab.net/repos/svn
README
branches/
clients/
tags/
trunk/
If you want a more detailed listing, pass the --verbose (-v) flag to get output like this.
$ svn list --verbose http://svn.collab.net/repos/svn
_ 2755 kfogel 1331 Jul 28 02:07 README
_ 2773 sussman 0 Jul 29 15:07 branches/
_ 2769 cmpilato 0 Jul 29 12:07 clients/
_ 2698 rooneg 0 Jul 24 18:07 tags/
_ 2785 brane 0 Jul 29 19:07 trunk/
The columns tell you if a file has any properties (“P” if it does, “_” if it doesn't), the revision it was last updated at, the user who last updated it, the size if it is a file, the date it was last updated, and the item's name. This is another convenience command, and it has two uses. First, it can be used to simultaneously create a new working copy directory and schedule it for addition:
$ svn mkdir new-dir
A new-dir
Or, it can be used to instantly create a directory in a repository (no working copy needed):
$ svn mkdir file:///usr/local/svn/newrepos/branches --message "made new dir"
Committed revision 1123.
Again, this is a form of immediate commit, so some sort of log message is required. Now we've covered all of the Subversion client commands with the exception of those dealing with branching and merging (see Chapter 4) and properties (see the section called “Properties”). However, you may want to take a moment to skim through Chapter 8 to get an idea of the many different ways that you can use the commands that we covered above. [1] Of course, nothing is ever totally deleted from the repository—just from the HEAD of the repository. You can get back anything you delete by checking out (or updating your working copy) to a revision earlier than the one in which you deleted it. Table of Contents Branching, tagging, and merging are concepts common to almost all version control systems. If you're not familiar with these ideas, we provide a good introduction in this chapter. If you are familiar, then hopefully you'll find it interesting to see how Subversion implements these ideas. Either way, branching is a fundamental part of version control. If you're going to allow Subversion to manage your data, then this is a feature you'll eventually come to depend on. A good way to explain the idea of a "branch" is with an example. Suppose it's your job to maintain a document for a division in your company, a handbook of some sort. One day a different division asks you for the same handbook, but with a few parts 'tweaked' for them, since they do things slightly differently. What do you do in this situation? You do the obvious thing: you make a second copy of your document, and begin maintaining the two copies separately. As each department asks you to make small changes, you incorporate them into one copy or the other. Of course, often you want to make the same change to both copies. For example, if you discover a typo in the first copy, it's very likely that the same typo exists in the second copy. The two documents are almost the same, after all; they only differ in small, specific ways. This is the basic concept of a branch — namely, a line of development that exists independently of another line, yet still shares a common history if you look far enough back in time. A branch always begins life as a copy of something, and moves on from there, generating its own history. Subversion has commands to help you maintain parallel branches of your files and directories. It allows you to create branches by copying your data, and remembers that the copies are related to one another. It also helps you duplicate changes from one branch to another. Finally, it can make portions of your working copy reflect different branches, so that you can "mix and match" different lines of development in your daily work. At this point, you should understand how each commit creates an entire new filesystem tree (called a "revision") in the repository. If not, go back and read about revisions in the section called “Revisions”. For this chapter, we'll go back to the same example from Chapter 2. Remember that you and your collaborator, Felix, are sharing a repository that contains two projects, paint and write. Notice, however, that this time somebody has created two new top-level directories in the repository, called trunk and branches. The projects themselves are subdirectories of trunk, and the reason for this will become clearer later on. As before, assume that you and Felix both have working copies of the /trunk/write project. Let's say that you've been given the task of performing a radical reorganization of the project. Not only is it a wide change (it will affect all the files in the project), but it's a very large change (it will take a long time to write.) The problem here is that you don't want to interfere with Felix, who is in the process of fixing small bugs here and there. He's depending on the fact that the latest version of the project is always usable. If you start committing your changes bit-by-bit, you'll surely break things for Felix. One strategy is to crawl into a hole: you and Felix can stop sharing information for a week or two. That is, start gutting and reorganizing all the files in your working copy, but don't commit or update until you're completely finished with the task. There are a number of problems with this, though. First, it's not very safe. Most people like to save their work to the repository frequently, should something bad accidentally happen to their working copy. Second, it's not very flexible. If you do your work on different computers (perhaps you have a working copy of /trunk/write on two different machines), you'll need to manually copy your changes back and forth, or just do all the work on a single computer. Finally, when you're finished, you might find it very difficult to commit your changes. Felix (or others) may have made many other changes in the repository that are difficult to merge into your working copy -- especially all at once. The better solution, of course, is to create your own branch, or line of development, in the repository. This allows you to save your half-broken work frequently without interfering with others, yet you can still selectively share information with your collaborators. You'll see exactly how this works later on. So how do we create a branch? Very simple -- you make a copy of the project in the repository using the svn copy command. Subversion is not only able to copy single files, but whole directories as well. In this case, you want to make a copy of the /trunk/write directory. Where should the new copy live? It doesn't really matter; Subversion doesn't care. It's a matter of project policy. Let's say that your team has a policy of creating branches in the /branches/write area of the repository, and you want to name your branch "my-write-branch". So you want to create a new directory, /branches/write/my-write-branch, which starts as a copy of /trunk/write. There are two different ways to make a copy. We'll demonstrate the messy way first, just to make the concept clear. To begin, check out a working copy of the root (/) of the repository: $ svn checkout http://svn.example.com/repos bigwc A bigwc/branches/ A bigwc/branches/write A bigwc/branches/paint A bigwc/trunk/ A bigwc/trunk/write A bigwc/trunk/write/Makefile A bigwc/trunk/write/document.c A bigwc/trunk/write/search.c A bigwc/trunk/paint A bigwc/trunk/paint/Makefile A bigwc/trunk/paint/canvas.c A bigwc/trunk/paint/brush.c Checked out revision 340. And now it's simply a matter of giving two working-copy paths to the svn copy command: $ cd bigwc $ svn copy trunk/write branches/write/my-write-branch $ svn status A + branches/write/my-write-branch In this case, the svn copy command recursively copied the trunk/write working directory to a new working directory, branches/write/my-write-branch. As you can see from the svn status command, the new directory is now scheduled for addition to the repository. But also notice the '+' next to the letter A. This indicates that the scheduled addition is a copy of something, not something new. When you commit your changes, Subversion will create /branches/write/my-write-branch in the repository by copying /trunk/write, rather than resending all of the working copy data over the network: $ svn commit -m "Creating a private branch of /trunk/write." Adding branches/write/my-write-branch Committed revision 341. And now the easier method of creating a branch, which we should have told you about in first place: the svn copy is able to operate on two URLs.
$ svn copy http://svn.example.com/repos/trunk/write \
http://svn.example.com/repos/branches/write/my-write-branch \
-m "Creating a private branch of /trunk/write"
Committed revision 341.
There's really no difference between these two methods. Both procedures create a new directory in revision 341, and the new directory is a copy of /trunk/write. Notice that the second method, however, performs an immediate commit. It's an easier procedure, because it doesn't require you to check out a large mirror of the repository. In fact, this technique doesn't even require you to have a working copy at all! Now that you've created a new branch of the project, you can check out a new working copy to start using it: $ svn checkout http://svn.example.com/repos/branches/write/my-write-branch A my-write-branch/Makefile A my-write-branch/document.c A my-write-branch/search.c Checked out revision 341. There's nothing special about this working copy; it simply mirrors a different location of the repository. When you commit changes, however, Felix won't ever see them when he updates. His working copy is of /trunk/write. Let's pretend that a week goes by, and the following commits happen:
Things get interesting when you look at the history of changes made to your copy of document.c: $ pwd /home/user/my-write-branch $ svn log document.c ------------------------------------------------------------------------ rev 343: user | 2002-11-07 15:27:56 -0600 (Thu, 07 Nov 2002) | 2 lines * document.c: frozzled the wazjub. ------------------------------------------------------------------------ rev 303: felix | 2002-10-29 21:14:35 -0600 (Tue, 29 Oct 2002) | 2 lines * document.c: changed a docstring. ------------------------------------------------------------------------ rev 98: felix | 2002-02-22 15:35:29 -0600 (Fri, 22 Feb 2002) | 2 lines * document.c: adding this file to the project. ------------------------------------------------------------------------ Notice that Subversion is tracing the history of your document.c all the way back through time, traversing the point where it was copied. (Remember that your branch was created in revision 341.) Now look what happens when Felix runs the same command on his copy of the file: $ pwd /home/felix/write $ svn log document.c ------------------------------------------------------------------------ rev 344: felix | 2002-11-07 15:27:56 -0600 (Thu, 07 Nov 2002) | 2 lines * document.c: fix a bunch of spelling errors. ------------------------------------------------------------------------ rev 303: felix | 2002-10-29 21:14:35 -0600 (Tue, 29 Oct 2002) | 2 lines * document.c: changed a docstring. ------------------------------------------------------------------------ rev 98: felix | 2002-02-22 15:35:29 -0600 (Fri, 22 Feb 2002) | 2 lines * document.c: adding this file to the project. ------------------------------------------------------------------------ Felix sees his own revision 344 change, but not the change you made in revision 343. As far as Subversion is concerned, these two commits affected different files in different repository locations. However, Subversion does show that the two files share a common history. Before the branch-copy was made in revision 341, they used to be the same file. That's why you and Felix both see revisions 303 and 98. There are now two independent lines of development happening. A diagram makes it easier to visualize: There are two important lessons that you should remember from this section.
Okay, so now you and Felix are working on parallel branches of the project. The good news is that you're not interfering with each other. The bad news is that it's very easy to drift too far apart. Remember that one of the problems with the "crawl in a hole" strategy is that by the time you're finished with your branch, it may be near-impossible to merge your changes back into the main branch. Instead, you and Felix should continue to share changes as you work. It's up to you to decide which changes are worth sharing; Subversion can gives you the ability to selectively "copy" changes between branches. Here's how. In the previous section, we mentioned that both you and Felix made changes to document.c on different branches. If you look at Felix's log message for revision 344, you can see that he fixed some spelling errors. No doubt, your copy of the same file still has the same spelling errors. It's likely that your future changes to this file will be affecting the same areas that have the spelling errors, so you're in for some potential conflicts when you merge your branches someday. It's better, then, to receive Felix's change now, before you start working too heavily in the same places. Enter the svn merge command. This command, it turns out, is a very close cousin to the svn diff command (which you read about in Chapter 3.) Both commands are able to compare any two objects in the repository and describe the differences. For example, you can ask svn diff to show you the exact change made by Felix in revision 344:
$ svn diff -r343:344 http://svn.example.com/repos/trunk/write
Index: document.c
===================================================================
--- document.c (revision 343)
+++ document.c (revision 344)
@@ -147,7 +147,7 @@
case 6: sprintf(info->operating_system, "HPFS (OS/2 or NT)"); break;
case 7: sprintf(info->operating_system, "Macintosh"); break;
case 8: sprintf(info->operating_system, "Z-System"); break;
- case 9: sprintf(info->operating_system, "CPM"); break;
+ case 9: sprintf(info->operating_system, "CP/M"); break;
case 10: sprintf(info->operating_system, "TOPS-20"); break;
case 11: sprintf(info->operating_system, "NTFS (Windows NT)"); break;
case 12: sprintf(info->operating_system, "QDOS"); break;
@@ -164,7 +164,7 @@
low = (unsigned short) read_byte(gzfile); /* read LSB */
high = (unsigned short) read_byte(gzfile); /* read MSB */
high = high << 8; /* interpret MSB correctly */
- total = low + high; /* add them togethe for correct total */
+ total = low + high; /* add them together for correct total */
info->extra_header = (unsigned char *) my_malloc(total);
fread(info->extra_header, total, 1, gzfile);
@@ -241,7 +241,7 @@
Store the offset with ftell() ! */
if ((info->data_offset = ftell(gzfile))== -1) {
- printf("error: ftell() retturned -1.\n");
+ printf("error: ftell() returned -1.\n");
exit(1);
}
@@ -249,7 +249,7 @@
printf("I believe start of compressed data is %u\n", info->data_offset);
#endif
- /* Set postion eight bytes from the end of the file. */
+ /* Set position eight bytes from the end of the file. */
if (fseek(gzfile, -8, SEEK_END)) {
printf("error: fseek() returned non-zero\n");
The svn merge is almost exactly the same. Instead of printing the differences to your terminal, however, it applies them directly to your working copy as local modifications: $ svn merge -r343:344 http://svn.example.com/repos/trunk/write U document.c $ svn status M document.c Your working copy now contains Felix's change — it has been "copied" from his branch to your working copy. At this point, it's up to you to review the local modifications and make sure they're all good. In another scenario, it's possible that things may not have gone so well, and that document.c may have entered a conflicted state. You might need to resolve the conflict by hand, or if you're really disgusted, simply give up and svn revert the local change. But assuming the changes are working and you're confident that the merge was goo |