|
Copyright © 1999, 2000 Karl Fogel <kfogel@red-bean.com> This document is free software; you can redistribute and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This document is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. This manual describes how to use and administer CVS (Concurrent Versions System). It is part of a larger work entitled Open Source Development With CVS; please see the introduction for details. This is version 1.21 of this manual. Table of Contents
Node:Top, Next:Introduction, Up:(dir) Top
Node:Introduction, Next:An Overview of CVS, Previous:Top, Up:Top IntroductionThis is a set of free, online chapters about using CVS (Concurrent Versions System) for collaboration and version control. It covers everything from CVS installation and basic concepts all the way to advanced usage and administration. It is intended for anyone who uses or plans to use CVS. These chapters are excerpted from a larger work called Open Source Development With CVS (published by The Coriolis Group, ISBN 1-57610-490-7). The remainder of that book - chapters 1, 3, 5, and 7 - deals with the challenges and philosophical issues of running an Open Source project using CVS. While the free chapters here constitute a complete CVS book by themselves, we certainly hope you'll like them enough to purchase a treeware copy of the entire book! You can order it directly from the publisher, at http://www.coriolis.com/bookstore/bookdetail.cfm?id=1576104907. These chapters are released under the GNU General Public License. For more information about free software in general, visit http://www.gnu.org/, and particularly http://www.gnu.org/philosophy/free-sw.html. To submit comments or errata regarding any of this material, please send email to bug-cvsbook@red-bean.com. For news and updates, visit http://cvsbook.red-bean.com/. Node:An Overview of CVS, Next:Repository Administration, Previous:Introduction, Up:Top An Overview of CVSI can't imagine programming without it... that would be like parachuting without a parachute! This chapter introduces the fundamentals of CVS, and then provides an in-depth guided tour of everyday CVS usage. Concepts are presented sequentially, so if you're new to CVS, the best way to read this is to start at the beginning and go straight through, without skipping anything.
Node:Basic Concepts, Next:A Day With CVS, Up:An Overview of CVS Basic ConceptsIf you've never used CVS (or any version control system) before, it's easy to get tripped up by some of its underlying assumptions. What seems to cause the most initial confusion about CVS is that it is used for two apparently unrelated purposes: record keeping and collaboration. It turns out, however, that these two functions are closely connected. Record keeping became necessary because people wanted to compare a program's current state with how it was at some point in the past. For example, in the normal course of implementing a new feature, a developer may bring the program into a thoroughly broken state, where it will probably remain until the feature is mostly finished. Unfortunately, this is just the time when someone usually calls to report a bug in the last publicly released version. To debug the problem (which may also exist in the current version of the sources), the program has to be brought back to a useable state. Restoring the state poses no difficulty if the source code history is kept under CVS. The developer can simply say, in effect, "Give me the program as it was three weeks ago", or perhaps "Give me the program as it was at the time of our last public release". If you've never had this kind of convenient access to historical snapshots before, you may be surprised at how quickly you come to depend on it. Personally, I always use revision control on my coding projects now - it's saved me many times. To understand what this has to do with facilitating collaboration, we'll need to take a closer look at the mechanism that CVS provides to help numerous people work on the same project. But before we do that, let's take a look at a mechanism that CVS doesn't provide (or at least, doesn't encourage): file locking. If you've used other version control systems, you may be familiar with the lock-modify-unlock development model, wherein a developer first obtains exclusive write access (a lock) to the file to be edited, makes the changes, and then releases the lock to allow other developers access to the file. If someone else already has a lock on the file, they have to "release" it before you can lock it and start making changes (or, in some implementations, you may "steal" their lock, but that is often an unpleasant surprise for them and not good practice!). This system is workable if the developers know each other, know who's planning to do what at any given time, and can communicate with each other quickly if someone cannot work because of access contention. However, if the developer group becomes too large or too spread out, dealing with all the locking issues begins to chip away at coding time; it becomes a constant hassle that can discourage people from getting real work done. CVS takes a more mellow approach. Rather than requiring that developers coordinate with each other to avoid conflicts, CVS enables developers to edit simultaneously, assumes the burden of integrating all the changes, and keeps track of any conflicts. This process uses the copy-modify-merge model, which works as follows:
As far as CVS is concerned, all developers on a project are equal. Deciding when to update or when to commit is largely a matter of personal preference or project policy. One common strategy for coding projects is to always update before commencing work on a major change and to commit only when the changes are complete and tested so that the master copy is always in a "runnable" state. Perhaps you're wondering what happens when developers A and B, each in their own working copy, make different changes to the same area of text and then both commit their changes? This is called a conflict, and CVS notices it as soon as developer B tries to commit changes. Instead of allowing developer B to proceed, CVS announces that it has discovered a conflict and places conflict markers (easily recognizable textual flags) at the conflicting location in his copy. That location also shows both sets of changes, arranged for easy comparison. Developer B must sort it all out and commit a new revision with the conflict resolved. Perhaps the two developers will need to talk to each other to settle the issue. CVS only alerts the developers that there is a conflict; it's up to human beings to actually resolve it. What about the master copy? In official CVS terminology, it is called the project's repository. The repository is simply a file tree kept on a central server. Without going into too much detail about its structure (but see Repository Administration), let's look at what the repository must do to meet the requirements of the checkout-commit-update cycle. Consider the following scenario:
At this point, one of two things can happen. If none of the files edited by developer B have been edited by A, the commit succeeds. However, if CVS realizes that some of B's files are out of date with respect to the repository's latest copies, and those files have also been changed by B in his working copy, CVS informs B that he must do an update before committing those files. When developer B runs the update, CVS merges all of A's changes into B's local copies of the files. Some of A's work may conflict with B's uncommitted changes, and some may not. Those parts that don't are simply applied to B's copies without further complication, but the conflicting changes must be resolved by B before being committed. If developer C does an update now, she'll receive various new changes from the repository: those from A's third commit, and those from B's first successful commit (which might really come from B's second attempt to commit, assuming B's first attempt resulted in B being forced to resolve conflicts). In order for CVS to serve up changes, in the correct sequence, to developers whose working copies may be out of sync by varying degrees, the repository needs to store all commits since the project's beginning. In practice, the CVS repository stores them all as successive diffs. Thus, even for a very old working copy, CVS is able to calculate the difference between the working copy's files and the current state of the repository, and is thereby able to bring the working copy up to date efficiently. This makes it easy for developers to view the project's history at any point and to revive even very old working copies. Although, strictly speaking, the repository could achieve the same results by other means, in practice, storing diffs is a simple, intuitive means of implementing the necessary functionality. The process has the added benefit that, by using patch appropriately, CVS can reconstruct any previous state of the file tree and thus bring any working copy from one state to another. It can allow someone to check out the project as it looked at any particular time. It can also show the differences, in diff format, between two states of the tree without affecting someone's working copy. Thus, the very features necessary to give convenient access to a project's history are also useful for providing a decentralized, uncoordinated developer team with the ability to collaborate on the project. For now, you can ignore the details of setting up a repository, administering user access, and navigating CVS-specific file formats (those will be covered in Repository Administration). For the moment, we'll concentrate on how to make changes in a working copy. But first, here is a quick review of terms:
Node:A Day With CVS, Next:Other Useful CVS Commands, Previous:Basic Concepts, Up:An Overview of CVS A Day With CVSThis section describes some basic CVS operations, then follows with a sample session covering typical CVS usage. As the guided tour progresses, we'll also start to look at how CVS works internally. Although you don't need to understand every last detail of CVS's implementation to use it, a basic knowledge of how it works is invaluable in choosing the best way to achieve a given result. CVS is more like a bicycle than an automobile, in the sense that its mechanisms are entirely transparent to anyone who cares to look. As with a bicycle, you can just hop on and start riding immediately. However, if you take a few moments to study how the gears work, you'll be able to ride it much more efficiently. (In the case of CVS, I'm not sure whether transparency was a deliberate design decision or an accident, but it does seem to be a property shared by many free programs. Externally visible implementations have the advantage of encouraging the users to become contributing developers by exposing them to the system's inner workings right from the start.) Each part of the tour may make use of knowledge introduced in previous parts. Therefore, if this is your first time, I recommend that you simply start at the beginning and take the tour sequentially, without skipping over anything. The menu below is merely meant as a convenience for repeat visitors - you shouldn't use it to jump directly to a section that interests you unless you're already familiar with the material in the previous sections.
Node:Conventions Used In This Tour, Next:Invoking CVS, Up:A Day With CVS Conventions Used In This TourThe tour takes place in a Unix environment. CVS also runs on Windows and Macintosh operating systems, and Tim Endres of Ice Engineering has even written a Java client (see http://www.trustice.com/java/jcvs/), which can be run anywhere Java runs. However, I'm going to take a wild guess and assume that the majority of CVS users - present and potential - are most likely working in a Unix command-line environment. If you aren't one of these, the examples in the tour should be easy to translate to other interfaces. Once you understand the concepts, you can sit down at any CVS front end and work with it (trust me, I've done it many times). The examples in the tour are oriented toward people who will be using CVS to keep track of programming projects. However, CVS operations are applicable to all text documents, not just source code. The tour also assumes that you already have CVS installed (it's present by default on many of the popular free Unix systems, so you might already have it without knowing it) and that you have access to a repository. Even if you are not set up, you can still benefit from reading the tour. In Repository Administration, you'll learn how to install CVS and set up repositories. Assuming CVS is installed, you should take a moment to find the online CVS manual. Known familiarly as the "Cederqvist" (after Per Cederqvist, its original author), it comes with the CVS source distribution and is usually the most up-to-date reference available. It's written in Texinfo format and should be available on Unix systems in the "Info" documentation hierarchy. You can read it either with the command line info program floss$ info cvs or by pressing Ctrl+H and then typing "i" inside Emacs. If neither of these works for you, consult your local Unix guru (or see Repository Administration regarding installation issues). You'll definitely want to have the Cederqvist at your fingertips if you're going to be using CVS regularly. Node:Invoking CVS, Next:Accessing A Repository, Previous:Conventions Used In This Tour, Up:A Day With CVS Invoking CVSCVS is one program, but it can perform many different actions: updating, committing, branching, diffing, and so on. When you invoke CVS, you must specify which action you want to perform. Thus, the format of a CVS invocation is: floss$ cvs command For example, you can use floss$ cvs update floss$ cvs diff floss$ cvs commit and so on. (Don't bother to try running any of those particular commands yet, though; they won't do anything until you're in a working copy, which we'll get to shortly.) Both CVS and the command can take options. Options that affect the behavior of CVS, independently of the command being run, are called global options; command-specific options are just called command options. Global options always go to the left of the command; command options, to its right. So in floss$ cvs -Q update -p -Q is a global option, and -p is a command option. (If you're curious, -Q means "quietly"-that is, suppress all diagnostic output, and print error messages only if the command absolutely cannot be completed for some reason; -p means to send the results of update to standard output instead of to files.) Node:Accessing A Repository, Next:Starting A New Project, Previous:Invoking CVS, Up:A Day With CVS Accessing A RepositoryBefore you can do anything, you must tell CVS the location of the repository you'll be accessing. This isn't a concern if you already have a working copy checked out - any working copy knows what repository it came from, so CVS can automatically deduce the repository for a given working copy. However, let's assume you don't have a working copy yet, so you need to tell CVS explicitly where to go. This is done with the -d global option (the -d stands for "directory", an abbreviation for which there is a historical justification, although -r for "repository" might have been better), followed by the path to the repository. For example, assuming the repository is on the local machine in /usr/local/cvs (a fairly standard location): floss$ cvs -d /usr/local/cvs command In many cases, however, the repository is on another machine and must therefore be reached over the network. CVS provides a choice of network access methods; which one you'll use depends mostly on the security needs of the repository machine (hereinafter referred to as "the server"). Setting up the server to allow various remote access methods is covered in Repository Administration; here we'll deal only with the client side. Fortunately, all the remote access methods share a common invocation syntax. In general, to specify a remote repository as opposed to a local one, you just use a longer repository path. You first name the access method, delimited on each side by colons, followed by the username and the server name (joined with an @ sign), another separator colon, and finally the path to the repository directory on the server. Let's look at the pserver access method, which stands for "password-authenticated server": floss$ cvs -d :pserver:jrandom@cvs.foobar.com:/usr/local/cvs login (Logging in to jrandom@cvs.foobar.com) CVS password: (enter your CVS password here) floss$ The long repository path following -d told CVS to use the pserver access method, with the username jrandom, on the server cvs.foobar.com, which has a CVS repository in /usr/local/cvs. There's no requirement that the hostname be "cvs.something.com" by the way; that's a common convention, but it could just as easily have been: floss$ cvs -d :pserver:jrandom@fish.foobar.org:/usr/local/cvs command The command actually run was login, which verifies that you are authorized to work with this repository. It prompts for a password, then contacts the server to verify the password. Following Unix custom, cvs login returns silently if the login succeeds; it shows an error message if it fails (for instance, because the password is incorrect). You only have to log in once from your local machine to a given CVS server. After a successful login, CVS stores the password in your home directory, in a file called .cvspass. It consults that file every time a repository is contacted via the pserver method, so you only have to run login the first time you access a given CVS server from a particular client machine. Of course, you can rerun cvs login anytime if the password changes. Note: pserver is currently the only access method requiring an initial login like this; with the others, you can start running regular CVS commands immediately. Once you've stored the authentication information in your .cvspass file, you can run other CVS commands using the same command-line syntax: floss$ cvs -d :pserver:jrandom@cvs.foobar.com:/usr/local/cvs command Getting pserver to work in Windows may require an extra step. Windows doesn't have the Unix concept of a home directory, so CVS doesn't know where to put the .cvspass file. You'll have to specify a location. It's normal to designate the root of the C: drive as the home directory: C:\WINDOWS> set HOME=C: C:\WINDOWS> cvs -d :pserver:jrandom@cvs.foobar.com:/usr/local/cvs login (Logging in to jrandom@cvs.foobar.com) CVS password: (enter password here) C:\WINDOWS> Any folder in the file system will suffice. You may want to avoid network drives, though, because the contents of your .cvspass file would then be visible to anyone with access to the drive. In addition to pserver, CVS supports the ext method (which uses an external connection program, such as rsh or ssh), kserver (for the Kerberos security system version 4), and gserver (which uses the GSSAPI, or Generic Security Services API, and also handles Kerberos versions 5 and higher). These methods are similar to pserver, but each has its own idiosyncrasies. Of these, the floss$ rsh -l jrandom cvs.foobar.com Password: enter your login password here Okay, let's assume you successfully logged in and logged out of the server with rsh, so now you're back on the original client machine: floss$ CVS_RSH=rsh; export CVS_RSH floss$ cvs -d :ext:jrandom@cvs.foobar.com:/usr/local/cvs command The first line sets (in Unix Bourne shell syntax) the CVS_RSH environment variable to rsh, which tells CVS to use the rsh program to connect. The second line can be any CVS command; you will be prompted for your password so CVS can log into the server. If you're in C shell rather than in Bourne shell, try this: floss% setenv CVS_RSH rsh and for Windows, try this: C:\WINDOWS> set CVS_RSH=rsh The rest of the tour will use the Bourne syntax; translate for your environment as necessary. To use ssh (the Secure Shell) instead of rsh, just set the CVS_RSH variable appropriately: floss$ CVS_RSH=ssh; export CVS_RSH Don't get thrown by the fact that the variable's name is CVS_RSH but you're setting its value to ssh. There are historical reasons for this (the catch-all Unix excuse, I know). CVS_RSH can point to the name of any program capable of logging you into the remote server, running commands, and receiving their output. After rsh, ssh is probably the most common such program, although there are probably others. Note that this program must not modify its data stream in any way. This disqualifies the Windows NT rsh, because it converts (or attempts to convert) between the DOS and Unix line-ending conventions. You'd have to get some other rsh for Windows or use a different access method. The gserver and kserver methods are not used as often as the others and are not covered here. They're quite similar to what we've covered so far; see the Cederqvist for details. If you only use one repository and don't want to type -d repos each time, just set the CVSROOT environment variable (which perhaps should have been named CVSREPOS, but it's too late to change that now): floss$ CVSROOT=/usr/local/cvs floss$ export CVSROOT floss$ echo $CVSROOT /usr/local/cvs floss$ or maybe floss$ CVSROOT=:pserver:jrandom@cvs.foobar.com:/usr/local/cvs floss$ export CVSROOT floss$ echo $CVSROOT :pserver:jrandom@cvs.foobar.com:/usr/local/cvs floss$ The rest of this tour assumes that you've set CVSROOT to point to your repository, so the examples will not show the -d option. If you need to access many different repositories, you should not set CVSROOT and should just use -d repos when you need to specify the repository. Node:Starting A New Project, Next:Checking Out A Working Copy, Previous:Accessing A Repository, Up:A Day With CVS Starting A New ProjectIf you're learning CVS in order to work on a project that's already under CVS control (that is, it is kept in a repository somewhere), you'll probably want to skip down to the next section, "Checking Out A Working Copy." On the other hand, if you want to take existing source code and put it into CVS, this is the section for you. Note that it still assumes you have access to an existing repository; see Repository Administration if you need to set up a repository first. Putting a new project into a CVS repository is known as importing. The CVS command, as you may have guessed, is floss$ cvs import except that it needs some more options (and needs to be in the right location) to succeed. First, go into the top-level directory of your project tree: floss$ cd myproj floss$ ls README.txt a-subdir/ b-subdir/ hello.c floss$ This project has two files - README.txt and hello.c - in the top level, plus two subdirectories - a-subdir and b-subdir - plus some more files (not shown in the example) inside those subdirectories. When you import a project, CVS imports everything in the tree, starting from the current directory and working its way down. Therefore, you should make sure that the only files in the tree are ones you want to be permanent parts of the project. Any old backup files, scratch files, and so on should all be cleaned out. The general syntax of an import command is floss$ cvs import -m "log msg" projname vendortag releasetag The -m flag (for message) is for specifying a short message describing the import. This will be the first log message for the entire project; every commit thereafter will also have its own log message. These messages are mandatory; if you don't give the -m flag, CVS automatically starts up an editor (by consulting the EDITOR environment variable) for you to type a log message in. After you save the log message and exit the editor, the import then continues. The next argument is the project's name (we'll use "myproj"). This is the name under which you'll check out the project from the repository. (What actually happens is that a directory of that name gets created in the repository, but more on that in Repository Administration.) The name you choose now does not need to be the same as the name of the current directory, although in most cases it usually is. The vendortag and releasetag arguments are a bit of bookkeeping for CVS. Don't worry about them now; it hardly matters what you use. In Advanced CVS you'll learn about the rare circumstances where they're significant. For now, we'll use a username and "start" for those arguments. We're ready to run import: floss$ cvs import -m "initial import into CVS" myproj jrandom start N myproj/hello.c N myproj/README.txt cvs import: Importing /usr/local/cvs/myproj/a-subdir N myproj/a-subdir/whatever.c cvs import: Importing /usr/local/cvs/myproj/a-subdir/subsubdir N myproj/a-subdir/subsubdir/fish.c cvs import: Importing /usr/local/cvs/myproj/b-subdir N myproj/b-subdir/random.c No conflicts created by this import floss$ Congratulations! If you ran that command (or something similar), you've finally done something that affects the repository. Reading over the output of the import command, you'll notice that CVS precedes each filename with a single letter - in this case, "N" for "new file". The use of a single letter on the left to indicate the status of a file is a general pattern in CVS command output. We'll see it later in checkout and update as well. You might think that, having just imported the project, you can start working in the tree immediately. This is not the case, however. The current directory tree is still not a CVS working copy. It was the source for the import command, true, but it wasn't magically changed into a CVS working copy merely by virtue of having been imported. To get a working copy, you need to check one out from the repository. First, though, you might want to archive the current project tree. The reason is that once the sources are in CVS, you don't want to confuse yourself by accidentally editing copies that aren't in version control (because those changes won't become part of the project's history). You want to do all of your editing in a working copy from now on. However, you also don't want to remove the imported tree entirely, because you haven't yet verified that the repository actually has the files. Of course, you can be 99.999 percent certain that it does because the import command returned with no error, but why take chances? Paranoia pays, as every programmer knows. Therefore, do something like this: floss$ ls README.txt a-subdir/ b-subdir/ hello.c floss$ cd .. floss$ ls myproj/ floss$ mv myproj was_myproj floss$ ls was_myproj/ floss$ There. You still have the original files, but they're clearly named as an obsolete version, so they won't be in the way when you get a real working copy. Now you're ready to check out. Node:Checking Out A Working Copy, Next:Version Versus Revision, Previous:Starting A New Project, Up:A Day With CVS Checking Out A Working CopyThe command to check out a project is exactly what you think it is: floss$ cvs checkout myproj cvs checkout: Updating myproj U myproj/README.txt U myproj/hello.c cvs checkout: Updating myproj/a-subdir U myproj/a-subdir/whatever.c cvs checkout: Updating myproj/a-subdir/subsubdir U myproj/a-subdir/subsubdir/fish.c cvs checkout: Updating myproj/b-subdir U myproj/b-subdir/random.c floss$ ls myproj/ was_myproj/ floss$ cd myproj floss$ ls CVS/ README.txt a-subdir/ b-subdir/ hello.c floss$ Behold - your first working copy! Its contents are exactly the same as what you imported, with the addition of a subdirectory named "CVS". That's where CVS stores version control information. Actually, each directory in the project has a CVS subdirectory: floss$ ls a-subdir CVS/ subsubdir/ whatever.c floss$ ls a-subdir/subsubdir/ CVS/ fish.c floss$ ls b-subdir CVS/ random.c The fact that CVS keeps its revision information in subdirectories named CVS means that your project can never contain subdirectories of its own named CVS. In practice, I've never heard of this being a problem. Before editing any files, let's take a peek inside the black box: floss$ cd CVS floss$ ls Entries Repository Root floss$ cat Root /usr/local/cvs floss$ cat Repository myproj floss$ Nothing too mysterious there. The Root file points to repository, and the Repository file points to a project inside the repository. If that's a little confusing, let me explain. There is a longstanding confusion about terminology in CVS. The word "repository" is used to refer to two different things. Sometimes, it means the root directory of a repository (for example, /usr/local/cvs), which can contain many projects; this is what the Root file refers to. But other times, it means one particular project-specific subdirectory within a repository root (for example, /usr/local/cvs/myproj, /usr/local/cvs/yourproj, or /usr/local/cvs/fish). The Repository file inside a CVS subdirectory takes the latter meaning. In this book, "repository" generally means Root (that is, the top-level repository), although it may occasionally be used to mean a project-specific subdirectory. If the intended sense can't be figured out from the context, there will be clarifying text. Note that the Repository file may sometimes contain an absolute path to the project name instead of a relative path. This can make it slightly redundant with the Root file: floss$ cd CVS floss$ cat Root :pserver:jrandom@cvs.foobar.com:/usr/local/cvs floss$ cat Repository /usr/local/cvs/myproj floss$ The Entries file stores information about the individual files in the project. Each line deals with one file, and there are only lines for files or subdirectories in the immediate parent directory. Here's the top-level CVS/Entries file in myproj: floss$ cat Entries /README.txt/1.1.1.1/Sun Apr 18 18:18:22 1999// /hello.c/1.1.1.1/Sun Apr 18 18:18:22 1999// D/a-subdir//// D/b-subdir//// The format of each line is /filename/revision number/last modification date// and the directory lines are prefixed with "D". (CVS doesn't really keep a change history for directories, so the fields for revision number and datestamp are empty.) The datestamps record the date and time of the last update (in Universal Time, not local time) of the files in the working copy. That way, CVS can easily tell whether a file has been modified since the last checkout, update, or commit. If the file system timestamp differs from the timestamp in the CVS/Entries file, CVS knows (without even having to consult the repository) that the file was probably modified. If you take a look at the CVS/* files in one of the subdirectories floss$ cd a-subdir/CVS floss$ cat Root /usr/local/cvs floss$ cat Repository myproj/a-subdir floss$ cat Entries /whatever.c/1.1.1.1/Sun Apr 18 18:18:22 1999// D/subsubdir//// floss$ you can see that the root repository has not changed, but the Repository file spells out the location of this subdirectory of the project, and the Entries file contains different lines. Immediately after import, the revision number of every file in the project is shown as 1.1.1.1. This initial revision number is a bit of a special case, so we won't examine it in detail just yet; we'll take a closer look at revision numbers after we've committed some changes. Node:Version Versus Revision, Next:Making A Change, Previous:Checking Out A Working Copy, Up:A Day With CVS Version Versus RevisionThe internal revision number that CVS keeps for each file is unrelated to the version number of the software product of which the files are part. For example, you may have a project composed of three files, whose internal revision numbers on May 3, 1999, were 1.2, 1.7, and 2.48. On that day, you package up a new release of the software and release it as SlickoSoft Version 3. This is purely a marketing decision and doesn't affect the CVS revisions at all. The CVS revision numbers are invisible to your customers (unless you give them repository access); the only publicly visible number is the "3" in Version 3. You could have called it Version 1729 as far as CVS is concerned - the version number (or "release" number) has nothing to do with CVS's internal change tracking. To avoid confusion, I'll use the word "revision" to refer exclusively to the internal revision numbers of files under CVS control. I may still call CVS a "version control system", however, because "revision control system" just sounds too awkward. Node:Making A Change, Next:Finding Out What You (And Others) Did -- update And diff, Previous:Version Versus Revision, Up:A Day With CVS Making A ChangeThe project as it stands doesn't do much. Here are the contents of hello.c: floss$ cat hello.c #include <stdio.h> void main () { printf ("Hello, world!\n"); } Let's make the first change to the project since importing it; we'll add the line printf ("Goodbye, world!\n"); right after the Hello, world!. Invoke your favorite editor and make the change: floss$ emacs hello.c ... This was a fairly simple change, one where you're not likely to forget what you did. But in a larger, more complex project, it's quite possible you may edit a file, be interrupted by something else, and return several days later and be unable to remember exactly what you did, or even to remember if you changed anything at all. Which brings us to our first "CVS Saves Your Life" situation: comparing your working copy against the repository. Node:Finding Out What You (And Others) Did -- update And diff, Next:CVS And Implied Arguments, Previous:Making A Change, Up:A Day With CVS Finding Out What You (And Others) Did - update And diffPreviously, I've talked about updating as a way of bringing changes down from the repository into your working copy - that is, as a way of getting other people's changes. However, update is really a bit more complex; it compares the overall state of the working copy with the state of the project in the repository. Even if nothing in the repository has changed since checkout, something in the working copy may have, and update will show that, too: floss$ cvs update cvs update: Updating . M hello.c cvs update: Updating a-subdir cvs update: Updating a-subdir/subsubdir cvs update: Updating b-subdir The M next to hello.c means the file has been modified since it was last checked out, and the modifications have not yet been committed to the repository. Sometimes, merely knowing which files you've edited is all you need. However, if you want a more detailed look at the changes, you can get a full report in diff format. The diff command compares the possibly modified files in the working copy to their counterparts in the repository and displays any differences: floss$ cvs diff cvs diff: Diffing . Index: hello.c =================================================================== RCS file: /usr/local/cvs/myproj/hello.c,v retrieving revision 1.1.1.1 diff -r1.1.1.1 hello.c 6a7 > printf ("Goodbye, world!\n"); cvs diff: Diffing a-subdir cvs diff: Diffing a-subdir/subsubdir cvs diff: Diffing b-subdir That's helpful, if a bit obscure, but there's still a lot of cruft in the output. For starters, you can ignore most of the first few lines. They just name the repository file and give the number of the last checked-in revision. These are useful pieces of information under other circumstances (we'll look more closely at them later), but you don't need them when you're just trying to get a sense of what changes have been made in the working copy. A more serious impediment to reading the diff is that CVS is announcing its entry as it goes into each directory during the update. This can be useful during long updates on large projects, as it gives you a sense of how much longer the command will take, but right now it's just getting in the way of reading the diff. Let's tell CVS to be quiet about where it's working, with the -Q global option: floss$ cvs -Q diff Index: hello.c =================================================================== RCS file: /usr/local/cvs/myproj/hello.c,v retrieving revision 1.1.1.1 diff -r1.1.1.1 hello.c 6a7 > printf ("Goodbye, world!\n"); Better - at least some of the cruft is gone. However, the diff is still hard to read. It's telling you that at line 6, a new line was added (that is, what became line 7), whose contents were: printf ("Goodbye, world!\n"); The preceding ">" in the diff tells you that this line is present in the newer version of the file but not in the older one. The format could be made even more readable, however. Most people find "context" diff format easier to read because it displays a few lines of context on either side of a change. Context diffs are generated by passing the -c flag to diff: floss$ cvs -Q diff -c Index: hello.c =================================================================== RCS file: /usr/local/cvs/myproj/hello.c,v retrieving revision 1.1.1.1 diff -c -r1.1.1.1 hello.c *** hello.c 1999/04/18 18:18:22 1.1.1.1 --- hello.c 1999/04/19 02:17:07 *************** *** 4,7 **** ---4,8 -- main () { printf ("Hello, world!\n"); + printf ("Goodbye, world!\n"); } Now that's clarity! Even if you're not used to reading context diffs, a glance at the preceding output will probably make it obvious what happened: a new line was added (the + in the first column signifies an added line) between the line that prints Hello, world! and the final curly brace. We don't need to be able to read context diffs perfectly (that's patch's job), but it's worth taking the time to acquire at least a passing familiarity with the format. The first two lines (after the introductory cruft) are *** hello.c 1999/04/18 18:18:22 1.1.1.1 --- hello.c 1999/04/19 02:17:07 and they tell you what is being diffed against what. In this case, revision 1.1.1.1 of hello.c is being compared against a modified version of the same file (thus, there's no revision number for the second line, because only the working copy's changes haven't been committed to the repository yet). The lines of asterisks and dashes identify sections farther down in the diff. Later on, a line of asterisks, with a line number range embedded, precedes a section from the original file. Then a line of dashes, with a new and potentially different line number range embedded, precedes a section from the modified file. These sections are organized into contrasting pairs (known as "hunks"), one side from the old file and the other side from the new. Our diff has one hunk: *************** *** 4,7 **** --- 4,8 -- main () { printf ("Hello, world!\n"); + printf ("Goodbye, world!\n"); } The first section of the hunk is empty, meaning that no material was removed from the original file. The second section shows that, in the corresponding place in the new file, one line has been added; it's marked with a "+". (When diff quotes excerpts from files, it reserves the first two columns on the left for special codes, such as "+" so the entire excerpt appears to be indented by two spaces. This extra indentation is stripped off when the diff is applied, of course.) The line number ranges show the hunk's coverage, including context lines. In the original file, the hunk was in lines 4 through 7; in the new file, it's lines 4 through 8 (because a line has been added). Note that the diff didn't need to show any material from the original file because nothing was removed; it just showed the range and moved on to the second half of the hunk. Here's another context diff, from an actual project of mine: floss$ cvs -Q diff -c Index: cvs2cl.pl =================================================================== RCS file: /usr/local/cvs/kfogel/code/cvs2cl/cvs2cl.pl,v retrieving revision 1.76 diff -c -r1.76 cvs2cl.pl *** cvs2cl.pl 1999/04/13 22:29:44 1.76 --- cvs2cl.pl 1999/04/19 05:41:37 *************** *** 212,218 **** # can contain uppercase and lowercase letters, digits, '-', # and '_'. However, it's not our place to enforce that, so # we'll allow anything CVS hands us to be a tag: ! /^\s([^:]+): ([0-9.]+)$/; push (@{$symbolic_names{$2}}, $1); } } -- 212,218 -- # can contain uppercase and lowercase letters, digits, '-', # and '_'. However, it's not our place to enforce that, so # we'll allow anything CVS hands us to be a tag: ! /^\s([^:]+): ([\d.]+)$/; push (@{$symbolic_names{$2}}, $1); } } The exclamation point shows that the marked line differs between the old and new files. Since there are no "+" or "-" signs, we know that the total number of lines in the file has remained the same. Here's one more context diff from the same project, slightly more complex this time: floss$ cvs -Q diff -c Index: cvs2cl.pl =================================================================== RCS file: /usr/local/cvs/kfogel/code/cvs2cl/cvs2cl.pl,v retrieving revision 1.76 diff -c -r1.76 cvs2cl.pl *** cvs2cl.pl 1999/04/13 22:29:44 1.76 --- cvs2cl.pl 1999/04/19 05:58:51 *************** *** 207,217 **** } else # we're looking at a tag name, so parse & store it { - # According to the Cederqvist manual, in node "Tags", "Tag - # names must start with an uppercase or lowercase letter and - # can contain uppercase and lowercase letters, digits, '-', - # and '_'. However, it's not our place to enforce that, so - # we'll allow anything CVS hands us to be a tag: /^\s([^:]+): ([0-9.]+)$/; push (@{$symbolic_names{$2}}, $1); } - 207,212 -- *************** *** 223,228 **** --- 218,225 -- if (/^revision (\d\.[0-9.]+)$/) { $revision = "$1"; } + + # This line was added, I admit, solely for the sake of a diff example. # If have file name but not time and author, and see date or # author, then grab them: This diff has two hunks. In the first, five lines were removed (these lines are only shown in the first section of the hunk, and the second section's line count shows that it has five fewer lines). An unbroken line of asterisks forms the boundary between hunks, and in the second hunk we see that two lines have been added: a blank line and a pointless comment. Note how the line numbers compensate for the effect of the previous hunk. In the original file, the second hunk's range of the area was lines 223 through 228; in the new file, because of the deletion that took place in the first hunk, the range is in lines 218 through 225. Congratulations, you are probably now as expert as you'll ever need to be at reading diffs. Node:CVS And Implied Arguments, Next:Committing, Previous:Finding Out What You (And Others) Did -- update And diff, Up:A Day With CVS CVS And Implied ArgumentsIn each of the CVS commands so far, you may have noticed that no files were specified on the command line. We ran floss$ cvs diff instead of floss$ cvs diff hello.c and floss$ cvs update instead of floss$ cvs update hello.c The principle at work here is that if you don't name any files, CVS acts on all files for which the command could possibly be appropriate. This even includes files in subdirectories beneath the current directory; CVS automatically descends from the current directory through every subdirectory in the tree. For example, if you modified b-subdir/random.c and a-subdir/subsubdir/fish.c, running update may result in this: floss$ cvs update cvs update: Updating . M hello.c cvs update: Updating a-subdir cvs update: Updating a-subdir/subsubdir M a-subdir/subsubdir/fish.c cvs update: Updating b-subdir M b-subdir/random.c floss$ or better yet: floss$ cvs -q update M hello.c M a-subdir/subsubdir/fish.c M b-subdir/random.c floss$ Note: The -q flag is a less emphatic version of -Q. Had we used -Q, the command would have printed out nothing at all, because the modification notices are considered nonessential informational messages. Using the lowercase -q is less strict; it suppresses the messages we probably don't want, while allowing certain, more useful messages to pass through. You can also name specific files for the update: floss$ cvs update hello.c b-subdir/random.c M hello.c M b-subdir/random.c floss$ and CVS will only examine those files, ignoring all others. In truth, it's more common to run update without restricting it to certain files. In most situations, you'll want to update the entire directory tree at once. Remember, the updates we're doing here only show that some files have been locally modified, because nothing has changed yet in the repository. When other people are working on the project with you, there's always the chance that running update will pull some new changes down from the repository and incorporate them into your local files. In that case, you may find it slightly more useful to name which files you want updated. The same principle can be applied to other CVS commands. For example, with diff, you can choose to view the changes one file at a time floss$ cvs diff -c b-subdir/random.c Index: b-subdir/random.c =================================================================== RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v retrieving revision 1.1.1.1 diff -c -r1.1.1.1 random.c *** b-subdir/random.c 1999/04/18 18:18:22 1.1.1.1 --- b-subdir/random.c 1999/04/19 06:09:48 *************** *** 1 **** ! /* A completely empty C file. */ --- 1,8 -- ! /* Print out a random number. */ ! ! #include <stdio.h> ! ! void main () ! { ! printf ("a random number\n"); ! } or see all the changes at once (hang on to your seat, this is going to be a big diff): floss$ cvs -Q diff -c Index: hello.c =================================================================== RCS file: /usr/local/cvs/myproj/hello.c,v retrieving revision 1.1.1.1 diff -c -r1.1.1.1 hello.c *** hello.c 1999/04/18 18:18:22 1.1.1.1 --- hello.c 1999/04/19 02:17:07 *************** *** 4,7 **** --- 4,8 -- main () { printf ("Hello, world!\n"); + printf ("Goodbye, world!\n"); } Index: a-subdir/subsubdir/fish.c =================================================================== RCS file: /usr/local/cvs/myproj/a-subdir/subsubdir/fish.c,v retrieving revision 1.1.1.1 diff -c -r1.1.1.1 fish.c *** a-subdir/subsubdir/fish.c 1999/04/18 18:18:22 1.1.1.1 --- a-subdir/subsubdir/fish.c 1999/04/19 06:08:50 *************** *** 1 **** ! /* A completely empty C file. */ --- 1,8 -- ! #include <stdio.h> ! ! void main () ! { ! while (1) { ! printf ("fish\n"); ! } ! } Index: b-subdir/random.c =================================================================== RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v retrieving revision 1.1.1.1 diff -c -r1.1.1.1 random.c *** b-subdir/random.c 1999/04/18 18:18:22 1.1.1.1 --- b-subdir/random.c 1999/04/19 06:09:48 *************** *** 1 **** ! /* A completely empty C file. */ --- 1,8 -- ! /* Print out a random number. */ ! ! #include <stdio.h> ! ! void main () ! { ! printf ("a random number\n"); ! } Anyway, as you can see from these diffs, this project is clearly ready for prime time. Let's commit the changes to the repository. Node:Committing, Next:Revision Numbers, Previous:CVS And Implied Arguments, Up:A Day With CVS CommittingThe commit command sends modifications to the repository. If you don't name any files, a commit will send all changes to the repository; otherwise, you can pass the names of one or more files to be committed (other files would be ignored, in that case). Here, we commit one file by name and two by inference: floss$ cvs commit -m "print goodbye too" hello.c Checking in hello.c; /usr/local/cvs/myproj/hello.c,v <-- hello.c new revision: 1.2; previous revision: 1.1 done floss$ cvs commit -m "filled out C code" cvs commit: Examining . cvs commit: Examining a-subdir cvs commit: Examining a-subdir/subsubdir cvs commit: Examining b-subdir Checking in a-subdir/subsubdir/fish.c; /usr/local/cvs/myproj/a-subdir/subsubdir/fish.c,v <-- fish.c new revision: 1.2; previous revision: 1.1 done Checking in b-subdir/random.c; /usr/local/cvs/myproj/b-subdir/random.c,v <-- random.c new revision: 1.2; previous revision: 1.1 done floss$ Take a moment to read over the output carefully. Most of what it says is pretty self-explanatory. One thing you may notice is that revision numbers have been incremented (as expected), but the original revisions are listed as 1.1 instead of 1.1.1.1 as we saw in the Entries file earlier. There is an explanation for this discrepancy, but it's not very important. It concerns a special meaning that CVS attaches to revision 1.1.1.1. For most purposes, we can just say that files receive a revision number of 1.1 when imported, but the number is displayed - for reasons known only to CVS - as 1.1.1.1 in the Entries file, until the first commit. Node:Revision Numbers, Next:Detecting And Resolving Conflicts, Previous:Committing, Up:A Day With CVS Revision NumbersEach file in a project has its own revision number. When a file is committed, the last portion of the revision number is incremented by one. Thus, at any given time, the various files comprising a project may have very different revision numbers. This just means that some files have been changed (committed) more often than others. (You may be wondering, what's the point of the part to the left of the decimal point, if only the part on the right ever changes? Actually, although CVS never automatically increments the number on the left, that number can be incremented on request by a user. This is a rarely used feature, and we won't cover it in this tour.) In the example project that we've been using, we just committed changes to three files. Each of those files is now revision 1.2, but the remaining files in the project are still revision 1.1. When you check out a project, you get each file at its highest revision so far. Here is what qsmith would see if he checked out myproj right now and looked at the revision numbers for the top-level directory: paste$ cvs -q -d :pserver:qsmith@cvs.foobar.com:/usr/local/cvs co myproj U myproj/README.txt U myproj/hello.c U myproj/a-subdir/whatever.c U myproj/a-subdir/subsubdir/fish.c U myproj/b-subdir/random.c paste$ cd myproj/CVS paste$ cat Entries /README.txt/1.1.1.1/Sun Apr 18 18:18:22 1999// /hello.c/1.2/Mon Apr 19 06:35:15 1999// D/a-subdir//// D/b-subdir//// paste$ The file hello.c (among others) is now at revision 1.2, while README.txt is still at the initial revision (revision 1.1.1.1, also known as 1.1). If he adds the line printf ("between hello and goodbye\n"); to hello.c and commit it, the file's revision number will be incremented once more: paste$ cvs ci -m "added new middle line" cvs commit: Examining . cvs commit: Examining a-subdir cvs commit: Examining a-subdir/subsubdir cvs commit: Examining b-subdir Checking in hello.c; /usr/local/cvs/myproj/hello.c,v <-- hello.c new revision: 1.3; previous revision: 1.2 done paste$ Now hello.c is revision 1.3, fish.c and random.c still are revision 1.2, and every other file is revision 1.1. Note: that the command was given as cvs ci instead of cvs commit. Most
CVS commands have short forms, to make typing easier. For checkout,
update, and commit, the abbreviated versions are co, up, and ci,
respectively. You can get a list of all of the short forms by running
the command You can usually ignore a file's revision number. In most situations, the numbers are just internal bookkeeping that CVS handles automatically. However, being able to find and compare revision numbers is extremely handy when you have to retrieve (or diff against) an earlier copy of a file. Examining the Entries file isn't the only way to discover a revision number. You can also use the status command paste$ cvs status hello.c =================================================================== File: hello.c Status: Up-to-date Working revision: 1.3 Tue Apr 20 02:34:42 1999 Repository revision: 1.3 /usr/local/cvs/myproj/hello.c,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none) which, if invoked without any files being named, shows the status of every file in the project: paste$ cvs status cvs status: Examining. =================================================================== File: README.txt Status: Up-to-date Working revision: 1.1.1.1 Sun Apr 18 18:18:22 1999 Repository revision: 1.1.1.1 /usr/local/cvs/myproj/README.txt,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none) =================================================================== File: hello.c Status: Up-to-date Working revision: 1.3 Tue Apr 20 02:34:42 1999 Repository revision: 1.3 /usr/local/cvs/myproj/hello.c,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none) cvs status: Examining a-subdir =================================================================== File: whatever.c Status: Up-to-date Working revision: 1.1.1.1 Sun Apr 18 18:18:22 1999 Repository revision: 1.1.1.1 /usr/local/cvs/myproj/a-subdir/whatever.c,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none) cvs status: Examining a-subdir/subsubdir =================================================================== File: fish.c Status: Up-to-date Working revision: 1.2 Mon Apr 19 06:35:27 1999 Repository revision: 1.2 /usr/local/cvs/myproj/ a-subdir/subsubdir/fish.c,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none) cvs status: Examining b-subdir =================================================================== File: random.c Status: Up-to-date Working revision: 1.2 Mon Apr 19 06:35:27 1999 Repository revision: 1.2 /usr/local/cvs/myproj/b-subdir/random.c,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none) paste$ Just ignore the parts of that output that you don't understand. In fact, that's generally good advice with CVS. Often, the one little bit of information you're looking for will be accompanied by reams of information that you don't care about at all, and maybe don't even understand. This situation is normal. Just pick out what you need, and don't worry about the rest. In the previous example, the parts we care about are the first three
lines (not counting the blank line) of each file's status output. The
first line is the most important; it tells you the file's name, and its
status in the working copy. All of the files are currently in sync with
the repository, so they all say =================================================================== File: random.c Status: Locally Modified Working revision: 1.2 Mon Apr 19 06:35:27 1999 Repository revision: 1.2 /usr/local/cvs/myproj/b-subdir/random.c,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none) The Working revision and Repository revision tell you whether the file is out of sync with the repository. Returning to our original working copy (jrandom's copy, which hasn't seen the new change to hello.c yet), we see: floss$ cvs status hello.c =================================================================== File: hello.c Status: Needs Patch Working revision: 1.2 Mon Apr 19 02:17:07 1999 Repository revision: 1.3 /usr/local/cvs/myproj/hello.c,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none) floss$ This tells us that someone has committed a change to hello.c, bringing the repository copy to revision 1.3, but that this working copy is still on revision 1.2. The line Status: Needs Patch means that the next update will retrieve those changes from the repository and "patch" them into the working copy's file. Let's pretend for the moment that we don't know anything about qsmith's change to hello.c, so we don't run status or update. Instead, we just start editing the file, making a slightly different change at the same location. This brings us to our first conflict. Node:Detecting And Resolving Conflicts, Next:Finding Out Who Did What (Browsing Log Messages), Previous:Revision Numbers, Up:A Day With CVS Detecting And Resolving ConflictsDetecting a conflict is easy enough. When you run update, CVS tells you, in no uncertain terms, that there's a conflict. But first, let's create the conflict. We edit hello.c to insert the line printf ("this change will conflict\n"); right where qsmith committed this: printf ("between hello and goodbye\n"); At this point, the status of our copy of hello.c is floss$ cvs status hello.c =================================================================== File: hello.c Status: Needs Merge Working revision: 1.2 Mon Apr 19 02:17:07 1999 Repository revision: 1.3 /usr/local/cvs/myproj/hello.c,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none) floss$ meaning that there are changes both in the repository and the working copy, and these changes need to be merged. (CVS isn't aware that the changes will conflict, because we haven't run update yet.) When we do the update, we see this: floss$ cvs update hello.c RCS file: /usr/local/cvs/myproj/hello.c,v retrieving revision 1.2 retrieving revision 1.3 Merging differences between 1.2 and 1.3 into hello.c rcsmerge: warning: conflicts during merge cvs update: conflicts found in hello.c C hello.c floss$ The last line of output is the giveaway. The C in the left margin next to the filename indicates that changes have been merged, but that they conflict. The contents of hello.c now shows both changes: #include <stdio.h> void main () { printf ("Hello, world!\n"); <<<<<<< hello.c printf ("this change will conflict\n"); ======= printf ("between hello and goodbye\n"); >>>>>>> 1.3 printf ("Goodbye, world!\n"); } Conflicts are always shown delimited by conflict markers, in the following format: <<<<<<< (filename) the uncommitted changes in the working copy blah blah blah ======= the new changes that came from the repository blah blah blah and so on >>>>>>> (latest revision number in the repository) The Entries file also shows that the file is in a halfway state at the moment: floss$ cat CVS/Entries /README.txt/1.1.1.1/Sun Apr 18 18:18:22 1999// D/a-subdir//// D/b-subdir//// /hello.c/1.3/Result of merge+Tue Apr 20 03:59:09 1999// floss$ The way to resolve the conflict is to edit the file so that it contains whatever text is appropriate, removing the conflict markers in the process, and then to commit. This doesn't necessarily mean choosing one change over another; you could decide neither change is sufficient and rewrite the conflicting section (or indeed the whole file) completely. In this case, we'll adjust in favor of the first change, but with capitalization and punctuation slightly different from qsmith's: floss$ emacs hello.c (make the edits...) floss$ cat hello.c #include <stdio.h> void main () { printf ("Hello, world!\n"); printf ("BETWEEN HELLO AND GOODBYE.\n"); printf ("Goodbye, world!\n"); } floss$ cvs ci -m "adjusted middle line" cvs commit: Examining . cvs commit: Examining a-subdir cvs commit: Examining a-subdir/subsubdir cvs commit: Examining b-subdir Checking in hello.c; /usr/local/cvs/myproj/hello.c,v <- hello.c new revision: 1.4; previous revision: 1.3 done floss$ Node:Finding Out Who Did What (Browsing Log Messages), Next:Examining And Reverting Changes, Previous:Detecting And Resolving Conflicts, Up:A Day With CVS Finding Out Who Did What (Browsing Log Messages)By now, the project has undergone several changes. If you're trying to get an overview of what has happened so far, you don't necessarily want to examine every diff in detail. Browsing the log messages would be ideal, and you can accomplish this with the log command: floss$ cvs log (pages upon pages of output omitted) The log output tends to be a bit verbose. Let's look at the log messages for just one file: floss$ cvs log hello.c RCS file: /usr/local/cvs/myproj/hello.c,v Working file: hello.c head: 1.4 branch: locks: strict access list: symbolic names: start: 1.1.1.1 jrandom: 1.1.1 keyword substitution: kv total revisions: 5; selected revisions: 5 description: -------------- revision 1.4 date: 1999/04/20 04:14:37; author: jrandom; state: Exp; lines: +1 -1 adjusted middle line -------------- revision 1.3 date: 1999/04/20 02:30:05; author: qsmith; state: Exp; lines: +1 -0 added new middle line -------------- revision 1.2 date: 1999/04/19 06:35:15; author: jrandom; state: Exp; lines: +1 -0 print goodbye too -------------- revision 1.1 date: 1999/04/18 18:18:22; author: jrandom; state: Exp; branches: 1.1.1; Initial revision -------------- revision 1.1.1.1 date: 1999/04/18 18:18:22; author: jrandom; state: Exp; lines: +0 -0 initial import into CVS ========================================================================= floss$ As usual, there's a lot of information at the top that you can just ignore. The good stuff comes after each line of dashes, in a format that is self-explanatory. When many files are sent in the same commit, they all share the same log message; a fact that can be useful in tracing changes. For example, remember back when we committed fish.c and random.c simultaneously? It was done like this: floss$ cvs commit -m "filled out C code" Checking in a-subdir/subsubdir/fish.c; /usr/local/cvs/myproj/a-subdir/subsubdir/fish.c,v <- fish.c new revision: 1.2; previous revision: 1.1 done Checking in b-subdir/random.c; /usr/local/cvs/myproj/b-subdir/random.c,v <- random.c new revision: 1.2; previous revision: 1.1 done floss$ The effect of this was to commit both files with the same log message: "Filled out C code." (As it happened, both files started at revision 1.1 and went to 1.2, but that's just a coincidence. If random.c had been at revision 1.29, it would have moved to 1.30 with this commit, and its revision 1.30 would have had the same log message as fish.c's revision 1.2.) When you run cvs log on them, you'll see the shared message: floss$ cvs log a-subdir/subsubdir/fish.c b-subdir/random.c RCS file: /usr/local/cvs/myproj/a-subdir/subsubdir/fish.c,v Working file: a-subdir/subsubdir/fish.c head: 1.2 branch: locks: strict access list: symbolic names: start: 1.1.1.1 jrandom: 1.1.1 keyword substitution: kv total revisions: 3; selected revisions: 3 description: -------------- revision 1.2 date: 1999/04/19 06:35:27; author: jrandom; state: Exp; lines: +8 -1 filled out C code -------------- revision 1.1 date: 1999/04/18 18:18:22; author: jrandom; state: Exp; branches: 1.1.1; Initial revision -------------- revision 1.1.1.1 date: 1999/04/18 18:18:22; author: jrandom; state: Exp; lines: +0 -0 initial import into CVS ========================================================================= RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v Working file: b-subdir/random.c head: 1.2 branch: locks: strict access list: symbolic names: start: 1.1.1.1 jrandom: 1.1.1 keyword substitution: kv total revisions: 3; selected revisions: 3 description: -------------- revision 1.2 date: 1999/04/19 06:35:27; author: jrandom; state: Exp; lines: +8 -1 filled out C code -------------- revision 1.1 date: 1999/04/18 18:18:22; author: jrandom; state: Exp; branches: 1.1.1; Initial revision -------------- revision 1.1.1.1 date: 1999/04/18 18:18:22; author: jrandom; state: Exp; lines: +0 -0 initial import into CVS ========================================================================= floss$ From this output, you'll know that the two revisions were part of the same commit (the fact that the timestamps on the two revisions are the same, or very close, is further evidence). Browsing log messages is a good way to get a quick overview of what's been going on in a project or to find out what happened to a specific file at a certain time. There are also free tools available to convert raw cvs log output to more concise and readable formats (such as GNU ChangeLog style); we won't cover those tools in this tour, but they'll be introduced in Third-Party Tools. Node:Examining And Reverting Changes, Next:The Slow Method Of Reverting, Previous:Finding Out Who Did What (Browsing Log Messages), Up:A Day With CVS Examining And Reverting ChangesSuppose that, in the course of browsing the logs, qsmith sees that jrandom made the most recent change to hello.c: revision 1.4 date: 1999/04/20 04:14:37; author: jrandom; state: Exp; lines: +1 -1 adjusted middle line and wonders what jrandom did? In formal terms, the question that qsmith is asking is, "What's the difference between my revision (1.3) of hello.c, and jrandom's revision right after it (1.4)?" The way to find out is with the diff command, but this time by comparing two past revisions using the -r command option to specify both of them: paste$ cvs diff -c -r 1.3 -r 1.4 hello.c Index: hello.c =========================================================== RCS file: /usr/local/cvs/myproj/hello.c,v retrieving revision 1.3 retrieving revision 1.4 diff -c -r1.3 -r1.4 *** hello.c 1999/04/20 02:30:05 1.3 --- hello.c 1999/04/20 04:14:37 1.4 *************** *** 4,9 **** main () { printf ("Hello, world!\n"); ! printf ("between hello and goodbye\n"); printf ("Goodbye, world!\n"); } --- 4,9 -- main () { printf ("Hello, world!\n"); ! printf ("BETWEEN HELLO AND GOODBYE.\n"); printf ("Goodbye, world!\n"); } paste$ The change is pretty clear, when viewed this way. Because the revision numbers are given in chronological order (usually a good idea), the diff shows them in order. If only one revision number is given, CVS uses the revision of the current working copy for the other. When qsmith sees this change, he instantly decides he likes his way better and resolves to "undo"-that is, to step back by one revision. However, this doesn't mean that he wants to lose his revision 1.4. Although, in an absolute technical sense, it's probably possible to achieve that effect in CVS, there's almost never any reason to do so. It's much preferable to keep revision 1.4 in the history and make a new revision 1.5 that looks exactly like 1.3. That way the undo event itself is part of the file's history. The only question is, how can you retrieve the contents of revision 1.3 and put them into 1.5? In this particular case, because the change is a very simple one, qsmith can probably just edit the file by hand to mirror revision 1.3 and then commit. However, if the changes are more complex (as they usually are in a real-life project), trying to re-create the old revision manually will be hopelessly error-prone. Therefore, we'll have qsmith use CVS to retrieve and recommit the older revision's contents. There are two equally good ways to do this: the slow, plodding way and the fast, fancy way. We'll examine the slow, plodding way first. Node:The Slow Method Of Reverting, Next:The Fast Method Of Reverting, Previous:Examining And Reverting Changes, Up:A Day With CVS The Slow Method Of RevertingThis method involves passing the -p flag to update, in conjunction with -r. The -p option sends the contents of the named revision to standard output. By itself, this isn't terribly helpful; the contents of the file fly by on the display, leaving the working copy unchanged. However, by redirecting the standard output into the file, the file will now hold the contents of the older revision. It's just as though the file had been hand-edited into that state. First, though, qsmith needs to get up to date with respect to the repository: paste$ cvs update cvs update: Updating . U hello.c cvs update: Updating a-subdir cvs update: Updating a-subdir/subsubdir cvs update: Updating b-subdir paste$ cat hello.c #include <stdio.h> void main () { printf ("Hello, world!\n"); printf ("BETWEEN HELLO AND GOODBYE.\n"); printf ("Goodbye, world!\n"); } paste$ Next, he runs update -p to make sure that the revision 1.3 is the one he wants: paste$ cvs update -p -r 1.3 hello.c =================================================================== Checking out hello.c RCS: /usr/local/cvs/myproj/hello.c,v VERS: 1.3 *************** #include <stdio.h> void main () { printf ("Hello, world!\n"); printf ("between hello and goodbye\n"); printf ("Goodbye, world!\n"); } Oops, there are a few lines of cruft at the beginning. They aren't actually being sent to standard output, but rather to standard error, so they're harmless. Nevertheless, they make reading the output more difficult and can be suppressed with -Q: paste$ cvs -Q update -p -r 1.3 hello.c #include <stdio.h> void main () { printf ("Hello, world!\n"); printf ("between hello and goodbye\n"); printf ("Goodbye, world!\n"); } paste$ There - that's exactly what qsmith was hoping to retrieve. The next step is to put that content into the working copy's file, using a Unix redirect (that's what the ">" does): paste$ cvs -Q update -p -r 1.3 hello.c > hello.c paste$ cvs update cvs update: Updating . M hello.c cvs update: Updating a-subdir cvs update: Updating a-subdir/subsubdir cvs update: Updating b-subdir paste$ Now when update is run, the file is listed as modified, which makes sense because its contents have changed. Specifically, it has the same content as the old revision 1.3 (not that CVS is aware of its being identical to a previous revision - it just knows the file has been modified). If qsmith wants to make extra sure, he can do a diff to check: paste$ cvs -Q diff -c Index: hello.c =================================================================== RCS file: /usr/local/cvs/myproj/hello.c,v retrieving revision 1.4 diff -c -r1.4 hello.c *** hello.c 1999/04/20 04:14:37 1.4 --- hello.c 1999/04/20 06:02:25 *************** *** 4,9 **** main () { printf ("Hello, world!\n"); ! printf ("BETWEEN HELLO AND GOODBYE.\n"); printf ("Goodbye, world!\n"); } --- 4,9 -- main () { printf ("Hello, world!\n"); ! printf ("between hello and goodbye\n"); printf ("Goodbye, world!\n"); } paste$ Yes, that's exactly what he wanted: a pure reversion - in fact, it is the reverse of the diff he previously obtained. Satisfied, he commits: paste$ cvs ci -m "reverted to 1.3 code" cvs commit: Examining . cvs commit: Examining a-subdir cvs commit: Examining a-subdir/subsubdir cvs commit: Examining b-subdir Checking in hello.c; /usr/local/cvs/myproj/hello.c,v <- hello.c new revision: 1.5; previous revision: 1.4 done paste$ Node:The Fast Method Of Reverting, Previous:The Slow Method Of Reverting, Up:A Day With CVS The Fast Method Of RevertingThe fast, fancy way of reverting is to use the -j (for "join") flag to the update command. This flag is like -r in that it takes a revision number, and you can use up to two -j's at once. CVS calculates the difference between the two named revisions and applies that difference as a patch to the file in question (so the order in which you give the revisions is important). Thus, assuming qsmith's copy is up to date, he can just do this: paste$ cvs update -j 1.4 -j 1.3 hello.c RCS file: /usr/local/cvs/myproj/hello.c,v retrieving revision 1.4 retrieving revision 1.3 Merging differences between 1.4 and 1.3 into hello.c paste$ cvs update cvs update: Updating . M hello.c cvs update: Updating a-subdir cvs update: Updating a-subdir/subsubdir cvs update: Updating b-subdir paste$ cvs ci -m "reverted to 1.3 code" hello.c Checking in hello.c; /usr/local/cvs/myproj/hello.c,v <-- hello.c new revision: 1.5; previous revision: 1.4 done paste$ When you only need to revert one file, there's not really much difference between the plodding and fast methods. Later in the book, you'll see how the fast method is much better for reverting multiple files at once. In the meantime, use whichever way you're more comfortable with. Reverting Is Not A Substitute For CommunicationIn all likelihood, what qsmith did in our example was quite rude. When you're working on a real project with other people and you think that someone has committed a bad change, the first thing you should do is talk to him or her about it. Maybe there's a good reason for the change, or maybe he or she just didn't think things through. Either way, there's no reason to rush and revert. A full record of everything that happens is stored permanently in CVS, so you can always revert to a previous revision after consulting with whoever made the changes. If you're a project maintainer facing a deadline or you feel you have the right and the need to revert the change unconditionally, then do so - but follow it immediately with an email to the author whose change was reverted, explaining why you did it and what needs to be fixed to recommit the change. Node:Other Useful CVS Commands, Next:Branches, Previous:A Day With CVS, Up:An Overview of CVS Other Useful CVS CommandsAt this point, you should be pretty comfortable with basic CVS usage. I'll abandon the tour narrative and introduce a few more useful commands in summarized form.
Node:Adding Files, Next:Adding Directories, Up:Other Useful CVS Commands Adding FilesAdding a file is a two-step process: First you run the add command on it, then commit. The file won't actually appear in the repository until commit is run: floss$ cvs add newfile.c cvs add: scheduling file 'newfile.c' for addition cvs add: use 'cvs commit' to add this file permanently floss$ cvs ci -m "added newfile.c" newfile.c RCS file: /usr/local/cvs/myproj/newfile.c,v done Checking in newfile.c; /usr/local/cvs/myproj/newfile.c,v <- newfile.c initial revision: 1.1 done floss$ Node:Adding Directories, Next:CVS And Binary Files, Previous:Adding Files, Up:Other Useful CVS Commands Adding DirectoriesUnlike adding a file, adding a new directory is done in one step; there's no need to do a commit afterwards: floss$ mkdir c-subdir floss$ cvs add c-subdir Directory /usr/local/cvs/myproj/c-subdir added to the repository floss$ If you look inside the new directory in the working copy, you'll see that a CVS subdirectory was created automatically by add: floss$ ls c-subdir CVS/ floss$ ls c-subdir/CVS Entries Repository Root floss$ Now you can add files (or new directories) inside it, as with any other working copy directory. Node:CVS And Binary Files, Next:Removing Files, Previous:Adding Directories, Up:Other Useful CVS Commands CVS And Binary FilesUntil now, I've left unsaid the dirty little secret of CVS, which is that it doesn't handle binary files very well (well, there are other dirty little secrets, but this definitely counts as one of the dirtiest). It's not that CVS doesn't handle binaries at all; it does, just not with any great panache. All the files we've been working with until now have been plain text files. CVS has some special tricks for text files. For example, when it's working between a Unix repository and a Windows or Macintosh working copy, it converts file line endings appropriately for each platform. For example, Unix convention is to use a linefeed (LF) only, whereas Windows expects a carriage return/linefeed (CRLF) sequence at the end of each line. Thus, the files in a working copy on a Windows machine will have CRLF endings, but a working copy of the same project on a Unix machine will have LF endings (the repository itself is always stored in LF format). Another trick is that CVS detects special strings, known as RCS keyword strings, in text files and replaces them with revision information and other useful things. For example, if your file contains this string $Revision$ CVS will expand on each commit to include the revision number. For example, it may get expanded to $Revision: 1.3 $ CVS will keep that string up to date as the file is developed. (The various keyword strings are documented in Advanced CVS and Third-Party Tools.) This string expansion is a very useful feature in text files, as it allows you to see the revision number or other information about a file while you're editing it. But what if the file is a JPG image? Or a compiled executable program? In those kinds of files, CVS could do some serious damage if it blundered around expanding any keyword string that it encountered. In a binary, such strings may even appear by coincidence. Therefore, when you add a binary file, you have to tell CVS to turn off both keyword expansion and line-ending conversion. To do so, use -kb: floss$ cvs add -kb filename floss$ cvs ci -m "added blah" filename (etc) Also, in some cases (such as text files that are likely to contain spurious keyword strings), you may wish to disable just the keyword expansion. That's done with -ko: floss$ cvs add -ko filename floss$ cvs ci -m "added blah" filename (etc) (In fact, this chapter is one such document, because of the
Note that you can't meaningfully run Node:Removing Files, Next:Removing Directories, Previous:CVS And Binary Files, Up:Other Useful CVS Commands Removing FilesRemoving a file is similar to adding one, except there's an extra step: You have to remove the file from the working copy first: floss$ rm newfile.c floss$ cvs remove newfile.c cvs remove: scheduling 'newfile.c' for removal cvs remove: use 'cvs commit' to remove this file permanently floss$ cvs ci -m "removed newfile.c" newfile.c Removing newfile.c; /usr/local/cvs/myproj/newfile.c,v <- newfile.c new revision: delete; previous revision: 1.1 done floss$ Notice how, in the second and third commands, we name newfile.c explicitly even though it doesn't exist in the working copy anymore. Of course, in the commit, you don't absolutely need to name the file, as long as you don't mind the commit encompassing any other modifications that may have taken place in the working copy. Node:Removing Directories, Next:Renaming Files And Directories, Previous:Removing Files, Up:Other Useful CVS Commands Removing DirectoriesAs I said before, CVS doesn't really keep directories under version control. Instead, as a kind of cheap substitute, it offers certain odd behaviors that in most cases do the "right thing". One of these odd behaviors is that empty directories can be treated specially. If you want to remove a directory from a project, you first remove all the files in it floss$ cd dir floss$ rm file1 file2 file3 floss$ cvs remove file1 file2 file3 (output omitted) floss$ cvs ci -m "removed all files" file1 file2 file3 (output omitted) and then run update in the directory above it with the -P flag: floss$ cd .. floss$ cvs update -P (output omitted) The -P option tells update to "prune" any empty directories - that is, to remove them from the working copy. Once that's done, the directory can be said to have been removed; all of its files are gone, and the directory itself is gone (from the working copy, at least, although there is actually still an empty directory in the repository). An interesting counterpart to this behavior is that when you run a plain update, CVS does not automatically bring new directories from the repository into your working copy. There are a couple of different justifications for this, none really worth going into here. The short answer is that from time to time you should run update with the -d flag, telling it to bring down any new directories from the repository. Node:Renaming Files And Directories, Next:Avoiding Option Fatigue, Previous:Removing Directories, Up:Other Useful CVS Commands Renaming Files And DirectoriesRenaming a file is equivalent to creating it under the new name and removing it under the old. In Unix, the commands are: floss$ cp oldname newname floss$ rm oldname Here's the equivalent in CVS: floss$ mv oldname newname floss$ cvs remove oldname (output omitted) floss$ cvs add newname (output omitted) floss$ cvs ci -m "renamed oldname to newname" oldname newname (output omitted) floss$ For files, that's all there is to it. Renaming directories is not done very differently: create the new directory, cvs add it, move all the files from the old directory to the new one, cvs remove them from the old directory, cvs add them in the new one, cvs commit so everything takes effect, and then do cvs update -P to make the now-empty directory disappear from the working copy. That is to say: floss$ mkdir newdir floss$ cvs add newdir floss$ mv olddir/* newdir mv: newdir/CVS: cannot overwrite directory floss$ cd olddir floss$ cvs rm foo.c bar.txt floss$ cd ../newdir floss$ cvs add foo.c bar.txt floss$ cd .. floss$ cvs commit -m "moved foo.c and bar.txt from olddir to newdir" floss$ cvs update -P Note: the warning message after the third command. It's telling you that it can't copy olddir's CVS/ subdirectory into newdir because newdir already has a directory of that name. This is fine, because you want olddir to keep its CVS/ subdirectory anyway. Obviously, moving directories around can get a bit cumbersome. The best policy is to try to come up with a good layout when you initially import your project so you won't have to move directories around very often. Later, you'll learn about a more drastic method of moving directories that involves making the change directly in the repository. However, that method is best saved for emergencies; whenever possible, it's best to handle everything with CVS operations inside working copies. Node:Avoiding Option Fatigue, Next:Getting Snapshots (Dates And Tagging), Previous:Renaming Files And Directories, Up:Other Useful CVS Commands Avoiding Option FatigueMost people tire pretty quickly of typing the same option flags with every command. If you know that you always want to pass the -Q global option or you always want to use -c with diff, why should you have to type it out each time? There is help, fortunately. CVS looks for a .cvsrc file in your home directory. In that file, you can specify default options to apply to every invocation of CVS. Here's an example .cvsrc: diff -c update -P cvs -q If the leftmost word on a line matches a CVS command (in its unabbreviated form), the corresponding options are used for that command every time. For global options, you just use cvs. So, for example, every time that user runs cvs diff, the -c flag is automatically included. Node:Getting Snapshots (Dates And Tagging), Next:Acceptable Date Formats, Previous:Avoiding Option Fatigue, Up:Other Useful CVS Commands Getting Snapshots (Dates And Tagging)Let's return to the example of the program that's in a broken state when a bug report comes in. The developer suddenly needs access to the entire project as it was at the time of the last release, even though many files may have been changed since then, and each file's revision number differs from the others. It would be far too time-consuming to look over the log messages, figure out what each file's individual revision number was at the time of release, and then run update (specifying a revision number with -r) on each one of them. In medium- to large-sized projects (tens to hundreds of files), such a process would be too unwieldy to attempt. CVS, therefore, provides a way to retrieve previous revisions of the files in a project en masse. In fact, it provides two ways: by date, which selects the revisions based on the time that they were committed, and by tag, which retrieves a previously marked "snapshot" of the project. Which method you use depends on the situation. The date-based retrievals are done by passing update the -D flag, which is similar to -r but takes dates instead of revision numbers: floss$ cvs -q update -D "1999-04-19" U hello.c U a-subdir/subsubdir/fish.c U b-subdir/random.c floss$ With the -D option, update retrieves the highest revision of each file as of the given date, and it will revert the files in the working copy to prior revisions if necessary. When you give the date, you can, and often should, include the time. For example, the previous command ended up retrieving revision 1.1 of everything (only three files showed changes, because all of the others are still at revision 1.1 anyway). Here's the status of hello.c to prove it: floss$ cvs -Q status hello.c =================================================================== File: hello.c Status: Up-to-date Working revision: 1.1.1.1 Sat Apr 24 22:45:03 1999 Repository revision: 1.1.1.1 /usr/local/cvs/myproj/hello.c,v Sticky Date: 99.04.19.05.00.00 floss$ But a glance back at the log messages from earlier in this chapter shows that revision 1.2 of hello.c was definitely committed on April 19, 1999. So why did we now get revision 1.1 instead of 1.2? The problem is that the date "1999-04-19" was interpreted as meaning "the midnight that begins 1999-04-19" - that is, the very first instant on that date. This is probably not what you want. The 1.2 commit took place later in the day. By qualifying the date more precisely, we can retrieve revision 1.2: floss$ cvs -q update -D "1999-04-19 23:59:59" U hello.c U a-subdir/subsubdir/fish.c U b-subdir/random.c floss$ cvs status hello.c =================================================================== File: hello.c Status: Locally Modified Working revision: 1.2 Sat Apr 24 22:45:22 1999 Repository revision: 1.2 /usr/local/cvs/myproj/hello.c,v Sticky Tag: (none) Sticky Date: 99.04.20.04.59.59 Sticky Options: (none) floss$ We're almost there. If you look closely at the date/time on the Sticky Date line, it seems to indicate 4:59:59 A.M., not 11:59 as the command requested (later we'll get to what the "sticky" means). As you may have guessed, the discrepancy is due to the difference between local time and Universal Coordinated Time (also known as "Greenwich mean time"). The repository always stores dates in Universal Time, but CVS on the client side usually assumes the local system time zone. In the case of -D, this is rather unfortunate because you're probably most interested in comparing against the repository time and don't care about the local system's idea of time. You can get around this by specifying the GMT zone in the command: floss$ cvs -q update -D "1999-04-19 23:59:59 GMT" U hello.c floss$ cvs -q status hello.c =================================================================== File: hello.c Status: Up-to-date Working revision: 1.2 Sun Apr 25 22:38:53 1999 Repository revision: 1.2 /usr/local/cvs/myproj/hello.c,v Sticky Tag: (none) Sticky Date: 99.04.19.23.59.59 Sticky Options: (none) floss$ There - that brought the working copy back to the final commits from April 19 (unless there were any commits during the last second of the day, which there weren't). What happens now if you run update? floss$ cvs update cvs update: Updating . cvs update: Updating a-subdir cvs update: Updating a-subdir/subsubdir cvs update: Updating b-subdir floss$ Nothing happens at all. But you know that there are more recent versions of at least three files. Why aren't these included in your working copy? That's where the "sticky" comes in. Updating ("downdating"?) with the -D flag causes the working copy to be restricted permanently to that date or before. In CVS terminology, the working copy has a "sticky date" set. Once a working copy has acquired a sticky property, it stays sticky until told otherwise. Therefore, subsequent updates will not automatically retrieve the most recent revision. Instead, they'll stay restricted to the sticky date. Stickiness can be revealed by running cvs status or by directly examining the CVS/Entries file: floss$ cvs -q update -D "1999-04-19 23:59:59 GMT" U hello.c floss$ cat CVS/Entries D/a-subdir//// D/b-subdir//// D/c-subdir//// /README.txt/1.1.1.1/Sun Apr 18 18:18:22 1999//D99.04.19.23.59.59 /hello.c/1.2/Sun Apr 25 23:07:29 1999//D99.04.19.23.59.59 floss$ If you were to modify hello.c and then try to commit floss$ cvs update M hello.c floss$ cvs ci -m "trying to change the past" cvs commit: cannot commit with sticky date for file 'hello.c' cvs [commit aborted]: correct above errors first! floss$ CVS would not permit the commit to happen because that would be like allowing you to go back and change the past. CVS is all about record keeping and, therefore, will not allow you to do that. This does not mean CVS is unaware of all the revisions that have been committed since that date, however. You can still compare the sticky-dated working copy against other revisions, including future ones: floss$ cvs -q diff -c -r 1.5 hello.c Index: hello.c =================================================================== RCS file: /usr/local/cvs/myproj/hello.c,v retrieving revision 1.5 diff -c -r1.5 hello.c *** hello.c 1999/04/24 22:09:27 1.5 --- hello.c 1999/04/25 00:08:44 *************** *** 3,9 **** void main () { printf ("Hello, world!\n"); - printf ("between hello and goodbye\n"); printf ("Goodbye, world!\n"); } --- 3,9 -- void main () { + /* this line was added to a downdated working copy */ printf ("Hello, world!\n"); printf ("Goodbye, world!\n"); } This diff reveals that, as of April 19, 1999, the between hello and goodbye line had not yet been added. It also shows the modification that we made to the working copy (adding the comment shown in the preceding code snippet). You can remove a sticky date (or any sticky property) by updating with the -A flag (-A stands for "reset", don't ask me why), which brings the working copy back to the most recent revisions: floss$ cvs -q update -A U hello.c floss$ cvs status hello.c =================================================================== File: hello.c Status: Up-to-date Working revision: 1.5 Sun Apr 25 22:50:27 1999 Repository revision: 1.5 /usr/local/cvs/myproj/hello.c,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none) floss$ Node:Acceptable Date Formats, Next:Marking A Moment In Time (Tags), Previous:Getting Snapshots (Dates And Tagging), Up:Other Useful CVS Commands Acceptable Date FormatsCVS accepts a wide range of syntaxes to specify dates. You'll never go wrong if you use ISO 8601 format (that is, the International Standards Organization standard #8601, see also www.saqqara.demon.co.uk/datefmt.htm), which is the format used in the preceding examples. You can also use Internet email dates as described in RFC 822 and RFC 1123 (see www.rfc-editor.org/rfc/). Finally, you can use certain unambiguous English constructs to specify dates relative to the current date. You will probably never need all of the formats available, but here are some more examples to give you an idea of what CVS accepts: floss$ cvs update -D "19 Apr 1999" floss$ cvs update -D "19 Apr 1999 20:05" floss$ cvs update -D "19/04/1999" floss$ cvs update -D "3 days ago" floss$ cvs update -D "5 years ago" floss$ cvs update -D "19 Apr 1999 23:59:59 GMT" floss$ cvs update -D "19 Apr" The double quotes around the dates are there to ensure that the Unix shell treats the date as one argument even if it contains spaces. The quotes will do no harm if the date doesn't contain spaces, so it's probably best to always use them. Node:Marking A Moment In Time (Tags), Previous:Acceptable Date Formats, Up:Other Useful CVS Commands Marking A Moment In Time (Tags)Retrieving by date is useful when the mere passage of time is your main concern. But more often what you really want to do is retrieve the project as it was at the time of a specific event - perhaps a public release, a known stable point in the software's development, or the addition or removal of some major feature. Trying to remember the date when that event took place or deducing the date from log messages would be a tedious process. Presumably, the event, because it was important, was marked as such in the formal revision history. The method CVS offers for making such marks is known as tagging. Tags differ from commits in that they don't record any particular textual change to files, but rather a change in the developers' attitude about the files. A tag gives a label to the collection of revisions represented by one developer's working copy (usually, that working copy is completely up to date so the tag name is attached to the "latest and greatest" revisions in the repository). Setting a tag is as simple as this: floss$ cvs -q tag Release-1999_05_01 T README.txt T hello.c T a-subdir/whatever.c T a-subdir/subsubdir/fish.c T b-subdir/random.c floss$ That command associates the symbolic name "Release-1999_05_01" with the snapshot represented by this working copy. Defined formally, snapshot means a set of files and associated revision numbers from the project. Those revision numbers do not have to be the same from file to file and, in fact, usually aren't. For example, assuming that tag was done on the same myproj directory that we've been using throughout this chapter and that the working copy was completely up to date, the symbolic name "Release-1999_05_01" will be attached to hello.c at revision 1.5, to fish.c at revision 1.2, to random.c at revision 1.2, and to everything else at revision 1.1. It may help to visualize a tag as a path or string linking various revisions of files in the project. In Figure 2.1, an imaginary string passes through the tagged revision number of each file in a project. File A File B File C File D File E ------ ------ ------ ------ ------ 1.1 1.1 1.1 1.1 1.1 ----1.2-. 1.2 1.2 1.2 1.2 1.3 | 1.3 1.3 1.3 1.3 \ 1.4 .-1.4-. 1.4 1.4 \ 1.5 / 1.5 \ 1.5 1.5 \ 1.6 / 1.6 | 1.6 1.6 \ 1.7 / | 1.7 1.7 \ 1.8 / | 1.8 .-1.8-------> \ 1.9 / | 1.9 / 1.9 `1.10' | 1.10 / 1.10 1.11 | 1.11 | | 1.12 | | 1.13 | \ 1.14 | \ 1.15 / \ 1.16 / `-1.17-' [Figure 2.1: How a tag might stand in relation to files's revisions.] But if you pull the string taut and sight directly along it, you'll see a particular moment in the project's history - namely, the moment that the tag was set (Figure 2.2). File A File B File C File D File E ------ ------ ------ ------ ------ 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.1 1.8 1.2 1.9 1.3 1.10 1.1 1.4 1.11 1.2 1.5 1.12 1.3 1.6 1.13 1.4 1.7 1.1 1.14 1.5 1.8 1.2 1.15 1.6 1.1 1.9 1.3 1.16 1.7 ----1.2---------1.10--------1.4---------1.17--------1.8-------> 1.3 1.11 1.5 1.17 1.9 1.6 1.17 1.10 [Figure 2.2: The same tag as a "straight sight" through the revision history.] As you continue to edit files and commit changes, the tag will not move along with the increasing revision numbers. It stays fixed, "stickily", at the revision number of each file at the time the tag was made. Given their importance as descriptors, it's a bit unfortunate that log messages can't be included with tags or that the tags themselves can't be full paragraphs of prose. In the preceding example, the tag is fairly obviously stating that the project was in a releasable state as of a certain date. However, sometimes you may want to make snapshots of a more complex state, which can result in ungainly tag names such as: floss$ cvs tag testing-release-3_pre-19990525-public-release As a general rule, you should try to keep tags as terse as possible while still including all necessary information about the event that you're trying to record. When in doubt, err on the side of being overly descriptive - you'll be glad later when you're able to tell from some verbose tag name exactly what circumstance was recorded. You've probably noticed that no periods or spaces were used in the tag names. CVS is rather strict about what constitutes a valid tag name. The rules are that it must start with a letter and contain letters, digits, hyphens ("-"), and underscores ("_"). No spaces, periods, colons, commas, or any other symbols may be used. To retrieve a snapshot by tag name, the tag name is used just like a revision number. There are two ways to retrieve snapshots: You can check out a new working copy with a certain tag, or you can switch an existing working copy over to a tag. Both result in a working copy whose files are at the revisions specified by the tag. Most of the time, what you're trying to do is take a look at the project as it was at the time of the snapshot. You may not necessarily want to do this in your main working copy, where you presumably have uncommitted changes and other useful states built up, so let's assume you just want to check out a separate working copy with the tag. Here's how (but make sure to invoke this somewhere other than in your existing working copy or its parent directory!): floss$ cvs checkout -r Release-1999_05_01 myproj cvs checkout: Updating myproj U myproj/README.txt U myproj/hello.c cvs checkout: Updating myproj/a-subdir U myproj/a-subdir/whatever.c cvs checkout: Updating myproj/a-subdir/subsubdir U myproj/a-subdir/subsubdir/fish.c cvs checkout: Updating myproj/b-subdir U myproj/b-subdir/random.c cvs checkout: Updating myproj/c-subdir We've seen the -r option before in the update command, where it preceded a revision number. In many ways a tag is just like a revision number because, for any file, a given tag corresponds to exactly one revision number (it's illegal, and generally impossible, to have two tags of the same name in the same project). In fact, anywhere you can use a revision number as part of a CVS command, you can use a tag name instead (as long as the tag has been set previously). If you want to diff a file's current state against its state at the time of the last release, you can do this: floss$ cvs diff -c -r Release-1999_05_01 hello.c And if you want to revert it temporarily to that revision, you can do this: floss$ cvs update -r Release-1999_05_01 hello.c The interchangeability of tags and revision numbers explains some of the strict rules about valid tag names. Imagine if periods were legal in tag names; you could have a tag named "1.3" attached to an actual revision number of "1.47". If you then issued the command floss$ cvs update -r 1.3 hello.c how would CVS know whether you were referring to the tag named "1.3", or the much earlier revision 1.3 of hello.c? Thus, restrictions are placed on tag names so that they can always be easily distinguished from revision numbers. A revision number has a period; a tag name doesn't. (There are reasons for the other restrictions, too, mostly having to do with making tag names easy for CVS to parse.) As you've probably guessed by this point, the second method of retrieving a snapshot - that is, switching an existing working directory over to the tagged revisions-is also done by updating: floss$ cvs update -r Release-1999_05_01 cvs update: Updating . cvs update: Updating a-subdir cvs update: Updating a-subdir/subsubdir cvs update: Updating b-subdir cvs update: Updating c-subdir floss$ The preceding command is just like the one we used to revert hello.c to
Note that no files appear to have changed when we updated. The working copy was completely up to date when we tagged, and no changes had been committed since the tagging. However, this does not mean that nothing changed at all. The working copy now knows that it's at a tagged revision. When you make a change and try to commit it (let's assume we modified hello.c): floss$ cvs -q update M hello.c floss$ cvs -q ci -m "trying to commit from a working copy on a tag" cvs commit: sticky tag 'Release-1999_05_01' for file 'hello.c' is not a branch cvs [commit aborted]: correct above errors first! floss$ CVS does not permit the commit to happen. (Don't worry about the exact meaning of that error message yet - we'll cover branches next in this chapter.) It doesn't matter whether the working copy got to be on a tag via a checkout or an update. Once it is on a tag, CVS views the working copy as a static snapshot of a moment in history, and CVS won't let you change history, at least not easily. If you run cvs status or look at the CVS/Entries files, you'll see that there is a sticky tag set on each file. Here's the top level Entries file, for example: floss$ cat CVS/Entries D/a-subdir//// D/b-subdir//// D/c-subdir//// /README.txt/1.1.1.1/Sun Apr 18 18:18:22 1999//TRelease-1999_05_01 /hello.c/1.5/Tue Apr 20 07:24:10 1999//TRelease-1999_05_01 floss$ Tags, like other sticky properties, are removed with the -A flag to update: floss$ cvs -q update -A M hello.c floss$ The modification to hello.c did not go away, however; CVS is still aware that the file changed with respect to the repository: floss$ cvs -q diff -c hello.c Index: hello.c =================================================================== RCS file: /usr/local/cvs/myproj/hello.c,v retrieving revision 1.5 diff -c -r1.5 hello.c *** hello.c 1999/04/20 06:12:56 1.5 --- hello.c 1999/05/04 20:09:17 *************** *** 6,9 **** --- 6,10 -- printf ("Hello, world!\n"); printf ("between hello and goodbye\n"); printf ("Goodbye, world!\n"); + /* a comment on the last line */ } floss$ Now that you've reset with update, CVS will accept a commit: floss$ cvs ci -m "added comment to end of main function" cvs commit: Examining . cvs commit: Examining a-subdir cvs commit: Examining a-subdir/subsubdir cvs commit: Examining b-subdir cvs commit: Examining c-subdir Checking in hello.c; /usr/local/cvs/myproj/hello.c,v <- hello.c new revision: 1.6; previous revision: 1.5 done floss$ The tag floss$ cvs -q status hello.c =================================================================== File: hello.c Status: Up-to-date Working revision: 1.6 Tue May 4 20:09:17 1999 Repository revision: 1.6 /usr/local/cvs/myproj/hello.c,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none) floss$ cvs -q update -r Release-1999_05_01 U hello.c floss$ cvs -q status hello.c =================================================================== File: hello.c Status: Up-to-date Working revision: 1.5 Tue May 4 20:21:12 1999 Repository revision: 1.5 /usr/local/cvs/myproj/hello.c,v Sticky Tag: Release-1999_05_01 (revision: 1.5) Sticky Date: (none) Sticky Options: (none) floss$ Now, having just told you that CVS doesn't let you change history, I'll show you how to change history. Node:Branches, Previous:Other Useful CVS Commands, Up:An Overview of CVS BranchesWe've been viewing CVS as a kind of intelligent, coordinating library. However, it can also be thought of as a time machine (thanks to Jim Blandy for the analogy). So far, we've only seen how you can examine the past with CVS, without affecting anything. Like all good time machines, CVS also allows you to go back in time to change the past. What do you get then? Science fiction fans know the answer to that question: an alternate universe, running parallel to ours, but diverging from ours at exactly the point where the past was changed. A CVS branch splits a project's development into separate, parallel histories. Changes made on one branch do not affect the other.
Node:Branching Basics, Next:Merging Changes From Branch To Trunk, Up:Branches Branching BasicsWhy are branches useful? Let's return for a moment to the scenario of the developer who, in the midst of working on a new version of the program, receives a bug report about an older released version. Assuming the developer fixes the problem, she still needs a way to deliver the fix to the customer. It won't help to just find an old copy of the program somewhere, patch it up without CVS's knowledge, and ship it off. There would be no record of what was done; CVS would be unaware of the fix; and later if something was discovered to be wrong with the patch, no one would have a starting point for reproducing the problem. It's even more ill-advised to fix the bug in the current, unstable version of the sources and ship that to the customer. Sure, the reported bug may be solved, but the rest of the code is in a half-implemented, untested state. It may run, but it's certainly not ready for prime time. Because the last released version is thought to be stable, aside from this one bug, the ideal solution is to go back and correct the bug in the old release - that is, to create an alternate universe in which the last public release includes this bug fix. That's where branches come in. The developer splits off a branch, rooted in the main line of development (the trunk) not at its most recent revisions, but back at the point of the last release. Then she checks out a working copy of this branch, makes whatever changes are necessary to fix the bug, and commits them on that branch, so there's a record of the bug fix. Now she can package up an interim release based on the branch and ship it to the customer. Her change won't have affected the code on the trunk, nor would she want it to without first finding out whether the trunk needs the same bug fix or not. If it does, she can merge the branch changes into the trunk. In a merge, CVS calculates the changes made on the branch between the point where it diverged from the trunk and the branch's tip (its most recent state), then applies those differences to the project at the tip of the trunk. The difference between the branch's root and its tip works out, of course, to be precisely the bug fix. Another good way to think of a merge is as a special case of updating. The difference is that in a merge, the changes to be incorporated are derived by comparing the branch's root and tip, instead of by comparing the working copy against the repository. The act of updating is itself similar to receiving patches directly from their authors and applying them by hand. In fact, to do an update, CVS calculates the difference (that's "difference" as in the diff program) between the working copy and the repository and then applies that diff to the working copy just as the patch program would. This mirrors the way in which a developer takes changes from the outside world, by manually applying patch files sent in by contributors. Thus, merging the bug fix branch into the trunk is just like accepting some outside contributor's patch to fix the bug. The contributor would have made the patch against the last released version, just as the branch's changes are against that version. If that area of code in the current sources hasn't changed much since the last release, the merge will succeed with no problems. If the code is now substantially different, however, the merge will fail with conflict (that is, the patch will be rejected), and some manual fiddling will be necessary. Usually this is accomplished by reading the conflicting area, making the necessary changes by hand, and committing. Figure 2.3 shows a picture of what happens in a branch and merge. (branch on which bug was fixed) .---------------->---------------. / | / | / | / | / V (<------ point of merge) ====*===================================================================> (main line of development) [Figure 2.3: A branch and then a merge. Time flows left to right.] We'll now walk through the steps necessary to make this picture happen. Remember that it's not really time that's flowing from left to right in the diagram, but rather the revision history. The branch will not have been made at the time of the release, but is created later, rooted back at the release's revisions. In our case, let's assume the files in the project have gone through
many revisions since they were tagged as One way to do this is to first check out a working copy based on that tag, then create the branch by re-tagging with the -b (branch) option: floss$ cd .. floss$ ls myproj/ floss$ cvs -q checkout -d myproj_old_release -r Release-1999_05_01 myproj U myproj_old_release/README.txt U myproj_old_release/hello.c U myproj_old_release/a-subdir/whatever.c U myproj_old_release/a-subdir/subsubdir/fish.c U myproj_old_release/b-subdir/random.c floss$ ls myproj/ myproj_old_release/ floss$ cd myproj_old_release floss$ ls CVS/ README.txt a-subdir/ b-subdir/ hello.c floss$ cvs -q tag -b Release-1999_05_01-bugfixes T README.txt T hello.c T a-subdir/whatever.c T a-subdir/subsubdir/fish.c T b-subdir/random.c floss$ Take a good look at that last command. It may seem somewhat arbitrary
that tag is used to create branches, but there's actually a reason for
it: The tag name will serve as a label by which the branch can be
retrieved later. Branch tags do not look any different from non-branch
tags, and are subject to the same naming restrictions. Some people like
to always include the word branch in the tag name itself (for example,
(And while we're at it, note the -d myproj_old_release option to checkout in the first CVS command. This tells checkout to put the working copy in a directory called myproj_old_release, so we won't confuse it with the current version in myproj. Be careful not to confuse this use of -d with the global option of the same name, or with the -d option to update.) Of course, merely running the tag command does not switch this working copy over to the branch. Tagging never affects the working copy; it just records some extra information in the repository to allow you to retrieve that working copy's revisions later on (as a static piece of history or as a branch, as the case may be). Retrieval can be done one of two ways (you're probably getting used to this motif by now!). You can check out a new working copy on the branch floss$ pwd /home/whatever floss$ cvs co -d myproj_branch -r Release-1999_05_01-bugfixes myproj or switch an existing working copy over to it: floss$ pwd /home/whatever/myproj floss$ cvs update -r Release-1999_05_01-bugfixes The end result is the same (well, the name of the new working copy's top-level directory may be different, but that's not important for CVS's purposes). If your current working copy has uncommitted changes, you'll probably want to use checkout instead of update to access the branch. Otherwise, CVS attempts to merge your changes into the working copy as it switches it over to the branch. In that case, you might get conflicts, and even if you didn't, you'd still have an impure branch. It won't truly reflect the state of the program as of the designated tag, because some files in the working copy will contain modifications made by you. Anyway, let's assume that by one method or another you get a working copy on the desired branch: floss$ cvs -q status hello.c =================================================================== File: hello.c Status: Up-to-date Working revision: 1.5 Tue Apr 20 06:12:56 1999 Repository revision: 1.5 /usr/local/cvs/myproj/hello.c,v Sticky Tag: Release-1999_05_01-bugfixes (branch: 1.5.2) Sticky Date: (none) Sticky Options: (none) floss$ cvs -q status b-subdir/random.c =================================================================== File: random.c Status: Up-to-date Working revision: 1.2 Mon Apr 19 06:35:27 1999 Repository revision: 1.2 /usr/local/cvs/myproj/b-subdir/random.c,v Sticky Tag: Release-1999_05_01-bugfixes (branch: 1.2.2) Sticky Date: (none) Sticky Options: (none) floss$ (The contents of those floss$ cvs -q update M hello.c M b-subdir/random.c floss$ cvs ci -m "fixed old punctuation bugs" cvs commit: Examining . cvs commit: Examining a-subdir cvs commit: Examining a-subdir/subsubdir cvs commit: Examining b-subdir Checking in hello.c; /usr/local/cvs/myproj/hello.c,v <- hello.c new revision: 1.5.2.1; previous revision: 1.5 done Checking in b-subdir/random.c; /usr/local/cvs/myproj/b-subdir/random.c,v <- random.c new revision: 1.2.2.1; previous revision: 1.2 done floss$ you'll notice that there's something funny going on with the revision numbers: floss$ cvs -q status hello.c b-subdir/random.c =================================================================== File: hello.c Status: Up-to-date Working revision: 1.5.2.1 Wed May 5 00:13:58 1999 Repository revision: 1.5.2.1 /usr/local/cvs/myproj/hello.c,v Sticky Tag: Release-1999_05_01-bugfixes (branch: 1.5.2) Sticky Date: (none) Sticky Options: (none) =================================================================== File: random.c Status: Up-to-date Working revision: 1.2.2.1 Wed May 5 00:14:25 1999 Repository revision: 1.2.2.1 /usr/local/cvs/myproj/b-subdir/random.c,v Sticky Tag: Release-1999_05_01-bugfixes (branch: 1.2.2) Sticky Date: (none) Sticky Options: (none) floss$ They now have four digits instead of two! A closer look reveals that each file's revision number is just the
branch number (as shown on the What you're seeing is a little bit of CVS's inner workings. Although
you almost always use a branch to mark a project-wide divergence, CVS
actually records the branch on a per-file basis. This project had five
files in it at the point of the branch, so five individual branches were
made, all with the same tag name: Most people consider this per-file scheme a rather inelegant implementation on CVS's part. It's a bit of the old RCS legacy showing through-RCS didn't know how to group files into projects, and even though CVS does, it still uses code inherited from RCS to handle branches. Ordinarily, you don't need to be too concerned with how CVS is keeping track of things internally, but in this case, it helps to understand the relationship between branch numbers and revision numbers. Let's look at the hello.c file; everything I'm about to say about hello.c applies to the other files in the branch (with revision/branch numbers adjusted accordingly). The hello.c file was on revision 1.5 at the point where the branch was
rooted. When we created the branch, a new number was tacked onto the
end to make a branch number (CVS chooses the first unused even, nonzero
integer). Thus, the branch number in this case became However, when we ran that first CVS status in a branched working copy,
hello.c's revision number showed up as only Once we had committed a new revision, hello.c was no longer the same on
both trunk and branch - the branch incarnation of the file had changed,
while the trunk remained the same. Accordingly, hello.c was assigned
its first branch revision number. We saw this in the status output
after the commit, where its revision number is clearly The same story applies to the random.c file. Its revision number at the
time of branching was There is no numeric relationship between floss$ cvs update -r 1.5.2.1 hello.c U hello.c floss$ it is almost always ill-advised. You would be mixing the branch revision of one file with non-branch revisions of the others. Who knows what losses may result? It is better to use the branch tag to refer to the branch and do all files at once by not specifying any particular file. That way you don't have to know or care what the actual branch revision number is for any particular file. It is also possible to have branches that sprout off other branches, to
any level of absurdity. For example, if a file has a revision number of
1.1 | 1.2 | 1.3 | 1.4 | 1.5 / \ / \ / \ (1.5.2) (1.5.4) <--- (these are branch numbers) / \ 1.5.2.1 1.5.4.1 | | 1.5.2.2 1.5.4.2 | | (etc) (...) <--- (collapsed 34 revisions for brevity) | 1.5.4.37 / / (1.5.4.37.2) <--- (this is also a branch number) / / 1.5.4.37.2.1 | 1.5.4.37.2.2 | 1.5.4.37.2.3 [Figure 2.4: An unusually high degree of branching. Time flows downward.] Admittedly, only the rarest circumstances make such a branching depth
necessary, but isn't it nice to know that CVS will go as far as you're
willing to take it? Nested branches are created the same way as any
other branch: Check out a working copy on branch Node:Merging Changes From Branch To Trunk, Next:Multiple Merges, Previous:Branching Basics, Up:Branches Merging Changes From Branch To TrunkNow that the bug fix has been committed on the branch, let's switch the working copy over to the highest trunk revisions and see if the bug fix needs to be done there, too. We'll move the working copy off the branch by using update -A (branch tags are like other sticky properties in this respect) and then diffing against the branch we just left: floss$ cvs -q update -d -A U hello.c U b-subdir/random.c floss$ cvs -q diff -c -r Release-1999_05_01-bugfixes Index: hello.c =================================================================== RCS file: /usr/local/cvs/myproj/hello.c,v retrieving revision 1.5.2.1 retrieving revision 1.6 diff -c -r1.5.2.1 -r1.6 *** hello.c 1999/05/05 00:15:07 1.5.2.1 --- hello.c 1999/05/04 20:19:16 1.6 *************** *** 4,9 **** main () { printf ("Hello, world!\n"); ! printf ("between hello and good-bye\n"); printf ("Goodbye, world!\n"); } --- 4,10 -- main () { printf ("Hello, world!\n"); ! printf ("between hello and goodbye\n"); printf ("Goodbye, world!\n"); + /* a comment on the last line */ } Index: b-subdir/random.c =================================================================== RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v retrieving revision 1.2.2.1 retrieving revision 1.2 diff -c -r1.2.2.1 -r1.2 *** b-subdir/random.c 1999/05/05 00:15:07 1.2.2.1 --- b-subdir/random.c 1999/04/19 06:35:27 1.2 *************** *** 4,8 **** void main () { ! printf ("A random number.\n"); } --- 4,8 -- void main () { ! printf ("a random number\n"); } floss$ The diff shows that good-bye is spelled with a hyphen in the branch revision of hello.c, and that the trunk revision of that file has a comment near the end that the branch revision doesn't have. Meanwhile, in random.c, the branch revision has a capital "A" and a period, whereas the trunk doesn't. To actually merge the branch changes into the current working copy, run update with the -j flag (the same j for "join" that we used to revert a file to an old revision before): floss$ cvs -q update -d -j Release-1999_05_01-bugfixes RCS file: /usr/local/cvs/myproj/hello.c,v retrieving revision 1.5 retrieving revision 1.5.2.1 Merging differences between 1.5 and 1.5.2.1 into hello.c RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v retrieving revision 1.2 retrieving revision 1.2.2.1 Merging differences between 1.2 and 1.2.2.1 into random.c floss$ cvs -q update M hello.c M b-subdir/random.c floss$ cvs -q ci -m "merged from branch Release-1999_05_01-bugfixes" Checking in hello.c; /usr/local/cvs/myproj/hello.c,v <- hello.c new revision: 1.7; previous revision: 1.6 done Checking in b-subdir/random.c; /usr/local/cvs/myproj/b-subdir/random.c,v <- random.c new revision: 1.3; previous revision: 1.2 done floss$ This takes the changes from the branch's root to its tip and merges them into the current working copy (which subsequently shows those modifications just as though the files had been hand-edited into that state). The changes are then committed onto the trunk, since nothing in the repository changed when a working copy underwent a merge. Although no conflicts were encountered in this example, it's quite possible (even probable) that there would be some in a normal merge. If that happens, they need to be resolved like any other conflict, and then committed. Node:Multiple Merges, Next:Creating A Tag Or Branch Without A Working Copy, Previous:Merging Changes From Branch To Trunk, Up:Branches Multiple MergesSometimes a branch will continue to be actively developed even after the trunk has undergone a merge from it. For example, this can happen if a second bug in the previous public release is discovered and has to be fixed on the branch. Maybe someone didn't get the joke in random.c, so on the branch, you have to add a line explaining it floss$ pwd /home/whatever/myproj_branch floss$ cat b-subdir/random.c /* Print out a random number. */ #include <stdio.h> void main () { printf ("A random number.\n"); printf ("Get the joke?\n"); } floss$ and commit. If that bug fix also needs to be merged into the trunk, you might be tempted to try the same update command as before in the trunk working copy to "re-merge": floss$ cvs -q update -d -j Release-1999_05_01-bugfixes RCS file: /usr/local/cvs/myproj/hello.c,v retrieving revision 1.5 retrieving revision 1.5.2.1 Merging differences between 1.5 and 1.5.2.1 into hello.c RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v retrieving revision 1.2 retrieving revision 1.2.2.2 Merging differences between 1.2 and 1.2.2.2 into random.c rcsmerge: warning: conflicts during merge floss$ As you can see, that didn't have quite the desired effect-we got a conflict, even though the trunk copy hadn't been modified there and, therefore, no conflict was expected. The trouble was that the update command behaved exactly as described: It tried to take all the changes between the branch's root and tip and merge them into the current working copy. The only problem is, some of those changes had already been merged into this working copy. That's why we got the conflict: floss$ pwd /home/whatever/myproj floss$ cat b-subdir/random.c /* Print out a random number. */ #include <stdio.h void main () { <<<<<<< random.c printf ("A random number.\n"); ======= printf ("A random number.\n"); printf ("Get the joke?\n"); >>>>>>> 1.2.2.2 } floss$ You could go through resolving all such conflicts by hand-it's usually not hard to tell what you need to do in each file. Nevertheless, it is even better to avoid a conflict in the first place. By passing two -j flags instead of one, you'll get only those changes from where you last merged to the tip instead of all of the changes on the branch, from root to tip. The first -j gives the starting point on the branch, and the second is just the plain branch name (which implies the tip of the branch). The question then is, how can you specify the point on the branch from which you last merged? One way is to qualify by using a date along with the branch tag name. CVS provides a special syntax for this: floss$ cvs -q update -d -j "Release-1999_05_01-bugfixes:2 days ago" \ -j Release-1999_05_01-bugfixes RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v retrieving revision 1.2.2.1 retrieving revision 1.2.2.2 Merging differences between 1.2.2.1 and 1.2.2.2 into random.c floss$ If the branch tag name is followed by a colon and then a date (in any of the usual CVS date syntaxes), CVS will include only changes later than that date. So if you knew that the original bug fix was committed on the branch three days ago, the preceding command would merge the second bug fix only. A better way, if you plan ahead, is to tag the branch after each bug fix (just a regular tag - we're not starting a new branch here or anything like that). Suppose after fixing the bug in the branch and committing, you do this in the branch's working copy: floss$ cvs -q tag Release-1999_05_01-bugfixes-fix-number-1 T README.txt T hello.c T a-subdir/whatever.c T a-subdir/subsubdir/fish.c T b-subdir/random.c floss$ Then, when it's time to merge the second change into the trunk, you can use that conveniently placed tag to delimit the earlier revision: floss$ cvs -q update -d -j Release-1999_05_01-bugfixes-fix-number-1 \ -j Release-1999_05_01-bugfixes RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v retrieving revision 1.2.2.1 retrieving revision 1.2.2.2 Merging differences between 1.2.2.1 and 1.2.2.2 into random.c floss$ This way, of course, is much better than trying to recall how long ago
you made one change versus another, but it only works if you remember to
tag the branch every time it is merged to the trunk. The lesson,
therefore, is to tag early and tag often! It's better to err on the side
of too many tags (as long as they all have descriptive names) than to
have too few. In these last examples, for instance, there was no
requirement that the new tag on the branch have a name similar to the
branch tag itself. Although I named it
Node:Creating A Tag Or Branch Without A Working Copy, Previous:Multiple Merges, Up:Branches Creating A Tag Or Branch Without A Working CopyAs stated earlier, tagging affects the repository, not the working copy. That begs the question: Why require a working copy at all when tagging? The only purpose that it serves is to designate which project and which revisions of the various files in the project are being tagged. If you could specify the project and revisions independently of the working copy, no working copy would be necessary. There is such a way: the rtag command (for "repository tag"). It's very
similar to tag; a couple of examples will explain its usage. Let's go
back to the moment when the first bug report came in and we needed to
create a branch rooted at the last public release. We checked out a
working copy at the release tag and then ran floss$ cvs tag -b Release-1999_05_01-bugfixes This created a branch rooted at floss$ cvs rtag -b -r Release-1999_05_01 Release-1999_05_01-bugfixes myproj That's all there is to it. That command can be issued from anywhere, inside or outside a working copy. However, your CVSROOT environment variable would have to point to the repository, of course, or you can specify it with the global -d option. It works for non-branch tagging, too, but it's less useful that way because you have to specify each file's revision number, one by one. (Or you can refer to it by tag, but then you'd obviously already have a tag there, so why would you want to set a second one on the exact same revisions?) You now know enough to get around in CVS and probably enough to start working with other people on a project. There are still a few minor features that haven't been introduced, as well as some unmentioned but useful options to features already seen. These will all be presented as appropriate in chapters to come, in scenarios that will demonstrate both how and why to use them. When in doubt, don't hesitate to consult the Cederqvist manual; it is an indispensable resource for serious CVS users. Node:Repository Administration, Next:Advanced CVS, Previous:An Overview of CVS, Up:Top Repository AdministrationIn An Overview of CVS, you learned enough CVS to use it effectively as a project participant. If you're going to be a project maintainer, however, you'll need to know how to install CVS and administer repositories. In this chapter, we'll throw back the curtain and look in detail at how the repository is structured, and how CVS uses it. You'll learn all the major steps CVS goes through during updates and commits, and how you can modify its behavior. By understanding how CVS works, you'll also be able to trace problems to their causes, and fix them in maintainable ways. This may sound very involved, but remember that CVS has already proven quite long-lived, and will probably be around for many years to come. Whatever you learn now will be useful for a long time. CVS also tends to become more indispensable the more you use it. If you're going to be that dependent on something (and trust me, you are), it's worth really getting to know it. With that in mind, let's begin at the beginning: putting CVS on your system.
Node:Getting And Installing CVS, Next:Anatomy Of A CVS Distribution, Up:Repository Administration Getting And Installing CVSIn many cases, you won't have to go out and get CVS, because it will already be on your system. If you run one of the major Linux or FreeBSD distributions, it's probably already installed in /usr/bin or some other likely location. If not, Red Hat Linux users can usually find an RPM (Red Hat Package) for the latest, or nearly latest, version of CVS in their distributions. And Debian users can install the latest Debian package with these commands: floss$ apt-get update floss$ apt-get install cvs If CVS isn't already on your machine, you'll probably have to build it from source. If you're a non-Unix user, you'll probably find it easier to get a prebuilt binary for your operating system (more on that later). Fortunately, CVS is fully autoconfiscated - that is, it uses the GNU autoconfiguration mechanism, making compilation from source surprisingly easy.
Node:Getting And Building CVS Under Unix, Next:Getting And Installing CVS Under Windows, Up:Getting And Installing CVS Getting And Building CVS Under UnixAs of this writing, there are two canonical sites from which you can download CVS. One is the Free Software Foundation's FTP site, ftp://ftp.gnu.org/gnu/cvs/, which offers CVS as an official GNU tool. The other is Cyclic Software's download site. Cyclic Software is, if not the maintainer of CVS, then the "maintainer of the maintainers", by providing a repository server and download access for users and developers. They distribute releases from http://download.cyclic.com/pub/. Either location is fine. In the following example, I use Cyclic Software's site. If you point your FTP client (probably your Web browser) there, you'll see a list of directories, something like this: Index of /pub cvs-1.10.5/ 18-Feb-99 21:36 - cvs-1.10.6/ 17-May-99 10:34 - cvs-1.10/ 09-Dec-98 17:26 - macintosh/ 23-Feb-99 00:53 - os2/ 09-Dec-98 17:26 - packages/ 09-Dec-98 17:26 - rcs/ 09-Dec-98 17:26 - tkcvs/ 09-Dec-98 17:26 - training/ 09-Dec-98 17:26 - unix/ 09-Dec-98 17:26 - vms/ 09-Dec-98 17:26 - Pay attention to the directories beginning with "cvs-" (you can ignore most of the others). There are three such directories, which means that you're already faced with a choice: Get the designated "stable" release, or go with a newer (but less-tested) interim release. The stable releases have only one decimal point, as in "cvs-1.10", whereas the interim releases have minor version increments tacked on the end, as in "1.10.5". The GNU site usually only offers the major releases, not the interim ones, so you won't see all of this if you get CVS from there. In general, the interim releases have been pretty safe, and sometimes contain fixes to bugs that were found in the major release. Your best policy is to go with the highest interim release, but if you encounter any problems with it, be prepared to drop back to the previous release, as many times as necessary. The highest release listed in the earlier example is cvs-1.10.6. Entering that directory, we see this: Index of /pub/cvs-1.10.6 cvs-1.10.6.tar.gz 17-May-99 08:44 2.2M That's it - the full source code to CVS. Just download it to your machine, and you're ready to build. At this point, if you're already familiar with the standard build process for GNU tools, you know what to do and probably don't need to read anything between here and the section Anatomy Of A CVS Distribution. On the other hand, if you're not sure how to proceed, then read on.... The following compilation instructions and examples assume that you have a fairly standard distribution of Unix. Any of the free versions of Unix (for example, FreeBSD or Linux) should work with no problem, as should the major commercial Unix versions (such as SunOS/Solaris, AIX, HP-UX, or Ultrix). Even if these instructions don't work for you exactly as written, don't give up hope. Although covering the details of compiling on every operating system is beyond the scope of this book, I'll give some pointers to other help resources later in this chapter. Anyway, to proceed with the compilation, first unpack the tar file using GNU gunzip and tar (if you don't have these installed on your system, you can get gunzip from ftp://ftp.gnu.org/gnu/gzip/ and GNU's version of tar from ftp://ftp.gnu.org/gnu/tar/): floss$ gunzip cvs-1.10.6.tar.gz floss$ tar xvf cvs-1.10.6.tar You'll see a lot of file names fly by on your screen. Now you have a new directory on your machine - cvs-1.10.6 - and it is populated with the CVS source code. Go into that directory and configure CVS for your system, by using the provided configure script: floss$ cd cvs-1.10.6 floss$ ./configure creating cache ./config.cache checking for gcc... gcc checking whether we are using GNU C... yes checking whether gcc accepts -g... yes checking how to run the C preprocessor... gcc -E (etc) When the configure command finishes, the source tree will know everything it needs to know about compiling on your machine. The next step is to type: floss$ make You'll see lots of output fly by, then type: floss$ make install You'll see yet more output fly by; when it's all over, CVS will be installed on your system. (You will probably need to do that last step as the superuser.) By default, the CVS executable will end up as If you want CVS to install to a location other than /usr/local/bin, you should change how you run the initial configuration step. For example, floss$ ./configure --prefix=/usr results in CVS being installed as /usr/bin/cvs (it always ends up as PREFIX/bin/cvs). The default prefix is /usr/local, which is fine for most installations. Note To Experienced Users: Although older versions of CVS consisted of more than just an executable in that they depended on having RCS installed as well, this has not been the case since Version 1.10. Therefore, you don't need to worry about any libraries or executables other than cvs itself. If you just intend to use CVS to access remote repositories, the preceding is all you need to do. If you also plan to serve a repository from this system, a few additional steps are necessary, which are covered later in this chapter. Node:Getting And Installing CVS Under Windows, Next:Getting And Installing CVS On A Macintosh, Previous:Getting And Building CVS Under Unix, Up:Getting And Installing CVS Getting And Installing CVS Under WindowsUnless you're truly religious about having the source code to your executable, you don't need to compile CVS from source on your Windows box. Unlike Unix, the necessary compilation tools probably do not already exist on your system, so a source build would involve first going out and getting those tools. Because such a project is beyond the scope of this book, I'll just give instructions for getting a precompiled CVS binary. First, note that Windows binary distributions of CVS are usually made only for major releases of CVS - not for the interim releases - and are not found on the GNU FTP site. So you'll need to go to Cyclic Software's download site, where in the major version directory, http://download.cyclic.com/pub/cvs-1.10/, you'll see an extra subdirectory Index of /pub/cvs-1.10 cvs-1.10.tar.gz 14-Aug-98 09:35 2.4M windows/ inside of which is a ZIP file: Index of /pub/cvs-1.10/windows cvs-1.10-win.zip 14-Aug-98 10:10 589k This ZIP file contains a binary distribution of CVS. Download and extract that ZIP file: floss$ unzip cvs-1.10-win.zip Archive: cvs-1.10-win.zip inflating: cvs.html inflating: cvs.exe inflating: README inflating: FAQ inflating: NEWS inflating: patch.exe inflating: win32gnu.dll The README there contains detailed instructions. For most
installations, they can be summarized as follows: Put all of the EXE and
DLL files in a directory in your PATH. Additionally, if you're going to
be using the pserver method to access a remote repository, you may need
to put the following in your set HOME=C: This tells CVS where to store the .cvspass file. CVS running under Windows cannot currently serve repositories to remote machines; it can be a client (connecting to remote repositories), and operate in local mode (using a repository on the same machine). For the most part, this book assumes that CVS under Windows is operating as a client. However, it shouldn't be too hard to set up a local repository under Windows after reading the Unix-oriented instructions in the rest of this chapter. If you are only accessing remote repositories, you may not even need to run CVS. There is a tool called WinCvs that implements only the client-side portion of CVS. It is distributed separately from CVS itself but, like CVS, is freely available under the GNU General Public License. More information is available from http://www.wincvs.org. Node:Getting And Installing CVS On A Macintosh, Next:Limitations Of The Windows And Macintosh Versions, Previous:Getting And Installing CVS Under Windows, Up:Getting And Installing CVS Getting And Installing CVS On A MacintoshCVS is available for the Macintosh, but not as part of the main distribution. At the moment, there are actually three separate Macintosh CVS clients available:
Frankly, I have no idea which one is best. Try them all, not necessarily in the order given, and see which one you like. MacCVS Pro seems to be under active development. MacCvs is apparently a companion project of WinCVS and shares a home page with it. (As of this writing, a notice on the WinCVS page states, "Development of MacCvs will be resumed soon.", whatever that means.) Node:Limitations Of The Windows And Macintosh Versions, Previous:Getting And Installing CVS On A Macintosh, Up:Getting And Installing CVS Limitations Of The Windows And Macintosh VersionsThe Windows and Macintosh distributions of CVS are generally limited in functionality. They can all act as clients, meaning that they can contact a repository server to obtain a working copy, commit, update, and so on. But they can't serve repositories themselves. If you set it up right, the Windows port can use a local-disk repository, but it still can't serve projects from that repository to other machines. In general, if you want to have a network-accessible CVS repository, you must run the CVS server on a Unix box. Node:Anatomy Of A CVS Distribution, Next:Starting A Repository, Previous:Getting And Installing CVS, Up:Repository Administration Anatomy Of A CVS DistributionThe preceding instructions are designed to get you up and running quickly, but there's a lot more inside a CVS source distribution than just the code. Here's a quick road map to the source tree, so you'll know which parts are useful resources and which can be ignored.
Node:Informational Files, Next:Subdirectories, Up:Anatomy Of A CVS Distribution Informational FilesIn the top level of the distribution tree, you'll find several files containing useful information (and pointers to further information). They are, in approximate order of importance:
Node:Subdirectories, Next:The Cederqvist Manual, Previous:Informational Files, Up:Anatomy Of A CVS Distribution SubdirectoriesThe CVS distribution contains a number of subdirectories. In the course of a normal installation, you won't have to navigate among them, but if you want to go poking around in the sources, it's nice to know what each one does. Here they are: contrib/ diff/ doc/ emx/ lib/ man/ os2/ src/ tools/ vms/ windows-NT/ zlib/ The majority of these can be ignored. The emx/, os2/, vms/, and windows-NT/ subdirectories all contain operating-system-specific source code, which you would only need if you're actually trying to debug a code-level problem in CVS (an unlikely situation, though not unheard of). The diff/ and zlib/ subdirectories contain CVS's internal implementations of the diff program and the GNU gzip compression library, respectively. (CVS uses the latter to reduce the number of bits it has to send over the network when accessing remote repositories.) The contrib/ and tools/ subdirectories contain free third-party software meant to be used with CVS. In contrib/, you will find an assortment of small, specialized shell scripts (read contrib/README to find out what they do). The tools/ subdirectory used to contain contributed software, but now contains a README file, which says in part: This subdirectory formerly contained tools that can be used with CVS. In particular, it used to contain a copy of pcl-cvs version 1.x. Pcl-cvs is an Emacs interface to CVS. If you are looking for pcl-cvs, we'd suggest pcl-cvs version 2.x, at: ftp://ftp.weird.com/pub/local/ The PCL-CVS package it's referring to is very handy, and I'll have more to say about it in Third-Party Tools. The src/ and lib/ subdirectories contain the bulk of the CVS source code, which involves the CVS internals. The main data structures and commands are implemented in src/, whereas lib/ contains small code modules of general utility that CVS uses. The man/ subdirectory contains the CVS man pages (intended for the Unix online manual system). When you ran make install, they were incorporated into your Unix system's regular man pages, so you can type floss$ man cvs and get a rather terse introduction and subcommand reference to CVS. Although useful as a quick reference, the man pages may not be as up to date or complete as the Cederqvist manual (see the next section); however, the man pages are more likely to be incomplete than actually wrong, if it's any comfort. Node:The Cederqvist Manual, Next:Other Sources Of Information, Previous:Subdirectories, Up:Anatomy Of A CVS Distribution The Cederqvist ManualThat leaves the doc/ subdirectory, whose most important inhabitant is the famed Cederqvist. These days, it's probably a stretch to call it "the Cederqvist". Although Per Cederqvist (of Signum Support, Linkoping Sweden, www.signum.se) wrote the first version around 1992, it has been updated since then by many other people. For example, when contributors add a new feature to CVS, they usually also document it in the Cederqvist. The Cederqvist manual is written in Texinfo format, which is used by the GNU project because it's relatively easy to produce both online and printed output from it (in Info and PostScript formats, respectively). The Texinfo master file is doc/cvs.texinfo, but CVS distributions come with the Info and PostScript pregenerated, so you don't have to worry about running any Texinfo tools yourself. Although the Cederqvist can be used as an introduction and tutorial, it
is probably most useful as a reference document. For that reason, most
people browse it online instead of printing it out (although the
PostScript file is The Info files (doc/cvs.info, doc/cvs.info-1, doc/cvs.info-2, and so on) were installed for you when you ran make install. Although the files were copied into the system's Info tree, you may still have to add a line for CVS to the Info table of contents, the "Top" node. (This will only be necessary if this is the first time CVS has been installed on your system; otherwise, the entry from previous installations should already be in the table of contents.) If you've added new Info documentation before, you may be familiar with the process. First figure out where the Info pages were installed. If you used the default installation (in /usr/local/), then the Info files are /usr/local/info/cvs.info*. If you installed using floss$ ./configure --prefix=/usr the files ended up as /usr/info/cvs.*. After you locate the files, you'll need to add a line for CVS to the Info table of contents, which is in a file named dir in that directory (so in the latter case, it would be /usr/info/dir). If you don't have root access, ask your system administrator to do it. Here is an excerpt from dir before the reference to CVS documentation was added: * Bison: (bison). The Bison parser generator. * Cpp: (cpp). The GNU C preprocessor. * Flex: (flex). A fast scanner generator And here is the same region of dir afterwards: * Bison: (bison). The Bison parser generator. * Cpp: (cpp). The GNU C preprocessor. * Cvs: (cvs). Concurrent Versions System * Flex: (flex). A fast scanner generator The format of the line is very important. You must include the
asterisk, spaces, and colon in Once the manual is installed and referred to from the table of contents, you can read it with any Info-compatible browser. The ones most likely to be installed on a typical Unix system are either the command-line Info reader, which can be invoked this way if you want to go straight to the CVS pages floss$ info cvs and the one within Emacs, which is invoked by typing M-x info or C-h i Take whatever time is necessary to get the Cederqvist set up properly on your system when you install CVS; it will pay off many times down the road when you need to look something up. Node:Other Sources Of Information, Previous:The Cederqvist Manual, Up:Anatomy Of A CVS Distribution Other Sources Of InformationIn addition to the Cederqvist, the FAQ, and the other files in the distribution itself, there are Internet resources devoted to CVS. If you're going to administrate a CVS server, you'll probably want to join the info-cvs mailing list. To subscribe, send email to info-cvs-request@gnu.org (the list itself is info-cvs@gnu.org). Traffic can be medium to heavy, around 10 to 20 emails a day, most of them questions seeking answers. The majority of these can be deleted without reading (unless you want to help people by answering their questions, which is always nice), but every now and then someone will announce the discovery of a bug or announce a patch that implements some feature you've been wanting. You can also join the formal bug report mailing list, which includes every bug report sent in. This probably isn't necessary, unless you intend to help fix the bugs, which would be great, or you're terrifically paranoid and want to know about every problem other people find with CVS. If you do want to join, send email to bug-cvs-request@gnu.org. There's also a Usenet newsgroup, Finally, there are at least three Web sites devoted to CVS. Cyclic Software's http://www.cyclic.com has been CVS's informal home site for a few years, and probably will continue to be for the foreseeable future. Cyclic Software also provides server space and Net access for the repository where the CVS sources are kept. The Cyclic Web pages contain comprehensive links to experimental patches for CVS, third-party tools that work with CVS, documentation, mailing list archives, and just about everything else. If you can't find what you need in the distribution, http://www.cyclic.com is the place to start looking. Two other good sites are Pascal Molli's http://www.loria.fr/~molli/cvs-index.html and Sean Dreilinger's http://durak.org/cvswebsites/. The biggest attraction at Molli's site is, of course, the FAQ, but it also has links to CVS-related tools and mailing list archives. Dreilinger's site specializes in information about using CVS to manage Web documents and also has a CVS-specific search engine. Node:Starting A Repository, Next:The Password-Authenticating Server, Previous:Anatomy Of A CVS Distribution, Up:Repository Administration Starting A RepositoryOnce the CVS executable is installed on your system, you can start using it right away as a client to access remote repositories, following the procedures described in An Overview of CVS. However, if you want to serve revisions from your machine, you have to create a repository there. The command to do that is floss$ cvs -d /usr/local/newrepos init where The command will return silently after it is run. Let's examine the new directory: floss$ ls -ld /usr/local/newrepos drwxrwxr-x 3 root root 1024 Jun 19 17:59 /usr/local/newrepos/ floss$ cd /usr/local/newrepos floss$ ls CVSROOT floss$ cd CVSROOT floss$ ls checkoutlist config,v history notify taginfo,v checkoutlist,v cvswrappers loginfo notify,v verifymsg commitinfo cvswrappers,v loginfo,v rcsinfo verifymsg,v commitinfo,v editinfo modules rcsinfo,v config editinfo,v modules,v taginfo floss$ The single subdirectory in the new repository - CVSROOT/ - contains various administrative files that control CVS's behavior. Later on, we'll examine those files one by one; for now, the goal is just to get the repository working. In this case, "working" means users can import, check out, update, and commit projects. Don't confuse the CVSROOT environment variable introduced in An Overview of CVS with this CVSROOT subdirectory in the repository. They
are unrelated - it is an unfortunate coincidence that they share the
same name. The former is a way for users to avoid having to type
Once the repository is created, you must take care of its permissions. CVS does not require any particular, standardized permission or file ownership scheme; it merely needs write access to the repository. However - partly for security reasons, but mainly for your own sanity as an administrator - I highly recommend that you take the following steps:
Now any of the users listed in that group can start a project by running
If your repository is intended to serve projects to the general public, where contributors won't necessarily have accounts on the repository machine, you should set up the password-authenticating server now (see The Password-Authenticating Server). It's necessary for anonymous read-only access, and it's also probably the easiest way to grant commit access to certain people without giving them full accounts on the machine. Node:The Password-Authenticating Server, Next:Anonymous Access, Previous:Starting A Repository, Up:Repository Administration The Password-Authenticating ServerBefore running through the steps needed to set up the password server,
let's examine how such connections work in the abstract. When a remote
CVS client uses the The CVS server is not actually waiting for connections at that port - the server won't get started up until a connection actually arrives. Instead, the Unix inetd (InterNET Daemon) program is listening on that port, and needs to know that when it receives a connection request there, it should start up the CVS server and connect it to the incoming client. This is accomplished by modifying inetd's configuration files:
First, put a line like this into /etc/services (after checking to make sure it isn't already there): cvspserver 2401/tcp Then in /etc/inetd.conf, put this: cvspserver stream tcp nowait root /usr/local/bin/cvs cvs \ --allow-root=/usr/local/newrepos pserver (In the actual file, this should be all one long line, with no backslash.) If your system uses tcpwrappers, you may want to use something like this instead: cvspserver stream tcp nowait root /usr/sbin/tcpd /usr/local/bin/cvs \ --allow-root=/usr/local/newrepos pserver Now, restart inetd so it notices the changes to its configuration files (if you don't know how to restart the daemon, just reboot the machine - that will work too). That's enough to permit connections, but you'll also want to set up special CVS passwords - separate from the users' regular login passwords - so people can access the repository without compromising overall system security. The CVS password file is CVSROOT/passwd in the repository. It was not created by default when you ran cvs init, because CVS doesn't know for sure that you'll be using pserver. Even if the password file had been created, CVS would have no way of knowing what usernames and passwords to create. So, you'll have to create one yourself; here's a sample CVSRoot/passwd file: kfogel:rKa5jzULzmhOo anonymous:XR4EZcEs0szik melissa:tGX1fS8sun6rY:pubcvs The format is as simple as it looks. Each line is: <USERNAME>:<ENCRYPTED_PASSWORD>:<OPTIONAL_SYSTEM_USERNAME> The extra colon followed by an optional system username tells CVS that connections authenticated with USERNAME should run as the system account SYSTEM_USERNAME - in other words, that CVS session would only be able to do things in the repository that someone logged in as SYSTEM_USERNAME could do. If no system username is given, USERNAME must match an actual login account name on the system, and the session will run with that user's permissions. In either case, the encrypted password should not be the same as the user's actual login password. It should be an independent password used only for CVS pserver connections. The password is encrypted using the same algorithm as the standard Unix system passwords stored in /etc/passwd. You may be wondering at this point, how does one acquire an encrypted version of a password? For Unix system passwords, the passwd command takes care of the encryption in /etc/passwd for you. Unfortunately, there is no corresponding cvs passwd command (it has been proposed several times, but no one's gotten around to writing it - perhaps you'll do it?). This is an inconvenience, but only a slight one. If nothing else, you can always temporarily change a regular user's system password using passwd, cut and paste the encrypted text from /etc/passwd into CVSROOT/passwd, and then restore the old password (note that on some systems, the encrypted passwords are found in /etc/shadow and are readable only by root.) That scheme is workable but rather cumbersome. It would be much easier to have a command-line utility that takes a plain text password as its argument and outputs the encrypted version. Here is such a tool, written in Perl: #!/usr/bin/perl srand (time()); my $randletter = "(int (rand (26)) + (int (rand (1) + .5) % 2 ? 65 : 97))"; my $salt = sprintf ("%c%c", eval $randletter, eval $randletter); my $plaintext = shift; my $crypttext = crypt ($plaintext, $salt); print "${crypttext}\n"; I keep the preceding script in floss$ ls -l /usr/local/bin/cryptout.pl -rwxr-xr-x 1 root root 265 Jun 14 20:41 /usr/local/bin/cryptout.pl floss$ cryptout.pl "some text" sB3A79YDX5L4s floss$ If I took the output of this example and used it to create the following entry in CVSROOT/passwd jrandom:sB3A79YDX5L4s:craig then someone could connect to the repository with the following command: remote$ cvs -d :pserver:jrandom@floss.red-bean.com:/usr/local/newrepos login They could then type If someone attempts to authenticate with a username and password that don't appear in CVSROOT/passwd, CVS will check to see if that username and password are present in /etc/passwd. If they are (and if the password matches, of course), CVS will grant access. It behaves this way for the administrator's convenience, so that separate CVSROOT/passwd entries don't have to be set up for regular system users. However, this behavior is also a security hole, because it means that if one of those users does connect to the CVS server, her regular login password will have crossed over the network in cleartext, potentially vulnerable to the eyes of password sniffers. A bit further on, you'll learn how to turn off this "fallback" behavior, so that CVS consults only its own passwd file. Whether you leave it on or off, you should probably force any CVS users who also have login accounts to maintain different passwords for the two functions. Although the passwd file authenticates for the whole repository, with a little extra work you can still use it to grant project-specific access. Here's one method: Suppose you want to grant some remote developers access to project
Here's the relevant excerpt from /etc/passwd cvs-foo:*:600:600:Public CVS Account for Project Foo:/usr/local/cvs:/bin/false cvs-bar:*:601:601:Public CVS Account for Project Bar:/usr/local/cvs:/bin/false and from /etc/group cvs-foo:*:600:cvs-foo cvs-bar:*:601:cvs-bar and, finally, CVSROOT/passwd: kcunderh:rKa5jzULzmhOo:cvs-foo jmankoff:tGX1fS8sun6rY:cvs-foo brebard:cAXVPNZN6uFH2:cvs-foo xwang:qp5lsf7nzRzfs:cvs-foo dstone:JDNNF6HeX/yLw:cvs-bar twp:glUHEM8KhcbO6:cvs-bar ffranklin:cG6/6yXbS9BHI:cvs-bar yyang:YoEqcCeCUq1vQ:cvs-bar Some of the CVS usernames map onto the system user account
Node:Anonymous Access, Next:Repository Structure, Previous:The Password-Authenticating Server, Up:Repository Administration Anonymous AccessSo far we've only seen how to use the password-authenticating server to
grant normal full access to the repository (although admittedly one can
restrict that access through carefully arranged Unix file permissions).
Turning this into anonymous, read-only access is a simple step: You just
have to add a new file, or possibly two, in CVSROOT/. The files' names
are If you list a username in CVSROOT/readers, that user will have only read access to all projects in the repository. If you list a username in CVSROOT/writers, that user will have write access, and every pserver user not listed in writers will have read-only access (that is, if the writers file exists at all, it implies read-only access for all those not listed in it). If the same username is listed in both files, CVS resolves the conflict in the more conservative way: the user will have read-only access. The format of the files is very simple: one user per line (don't forget to put a newline after the last user). Here is a sample readers file: anonymous splotnik guest jbrowse Note that the files apply to CVS usernames, not system usernames. If you use user aliasing in the CVSROOT/passwd file (putting a system username after a second colon), the leftmost username is the one to list in a readers or writers file. Just to be painfully accurate about it, here is a formal description of the server's behavior in deciding whether to grant read-only or read-write access: If a readers file exists and this user is listed in it, then she gets read-only access. If a writers file exists and this user is not listed in it, then she also gets read-only access (this is true even if a readers file exists but that person is not listed there). If that person is listed in both, she gets read-only access. In all other cases, that person gets full read-write access. Thus, a typical repository with anonymous CVS access has this (or something like it) in CVSROOT/passwd anonymous:XR4EZcEs0szik this (or something like it) in /etc/passwd anonymous:!:1729:105:Anonymous CVS User:/usr/local/newrepos:/bin/false and this in CVSROOT/readers: anonymous And, of course, the aforementioned setup in /etc/services and /etc/inetd.conf. That's all there is to it! Note that some older Unix systems don't support usernames longer than
eight characters. One way to get around this would be to call the user
anonymous:XR4EZcEs0szik:cvsanon (and then of course use cvs -d :pserver:anonymous@cvs.foobar.com:/usr/local/newrepos (etc...) would actually run on the server as cvsanon (or whatever). But they wouldn't need to know or care about how things are set up on the server side - they'd only see the published address. Node:Repository Structure, Next:RCS Format, Previous:Anonymous Access, Up:Repository Administration Repository StructureThe new repository still has no projects in it. Let's re-run the initial import from An Overview of CVS, watching what happens to the repository. (For simplicity's sake, all commands will assume that the CVSROOT environment variable has been set to /usr/local/newrepos, so there's no need to specify the repository with -d on imports and checkouts.) floss$ ls /usr/local/newrepos CVSROOT/ floss$ pwd /home/jrandom/src/ floss$ ls myproj/ floss$ cd myproj floss$ cvs import -m "initial import into CVS" myproj jrandom start N myproj/README.txt N myproj/hello.c cvs import: Importing /usr/local/newrepos/myproj/a-subdir N myproj/a-subdir/whatever.c cvs import: Importing /usr/local/newrepos/myproj/a-subdir/subsubdir N myproj/a-subdir/subsubdir/fish.c cvs import: Importing /usr/local/newrepos/myproj/b-subdir N myproj/b-subdir/random.c No conflicts created by this import floss$ ls /usr/local/newrepos CVSROOT/ myproj/ floss$ cd /usr/local/newrepos/myproj floss$ ls README.txt,v a-subdir/ b-subdir/ hello.c,v floss$ cd a-subdir floss$ ls subsubdir/ whatever.c,v floss$ cd .. floss$ Before the import, the repository contained only its administrative
area, CVSROOT. After the import, a new directory - Node:RCS Format, Next:What Happens When You Remove A File, Previous:Repository Structure, Up:Repository Administration RCS FormatYou do not need to know any of the RCS format to use CVS (although there
is an excellent writeup included with the source distribution, see
doc/RCSFILES). However, a basic understanding of the format can be of
immense help in troubleshooting CVS problems, so we'll take a brief peek
into one of the files, head 1.1; branch 1.1.1; access ; symbols start:1.1.1.1 jrandom:1.1.1; locks ; strict; comment @ * @; 1.1 date 99.06.20.17.47.26; author jrandom; state Exp; branches 1.1.1.1; next; 1.1.1.1 date 99.06.20.17.47.26; author jrandom; state Exp; branches ; next; desc @@ 1.1 log @Initial revision @ text @#include <stdio.h> void main () { printf ("Hello, world!\n"); } @ 1.1.1.1 log @initial import into CVS @ text @@ Whew! Most of that you can ignore; don't worry about the relationship between 1.1 and 1.1.1.1, for example, or the implied 1.1.1 branch - they aren't really significant from a user's or even an administrator's point of view. What you should try to grok is the overall format. At the top is a collection of header fields: head 1.1; branch 1.1.1; access ; symbols start:1.1.1.1 jrandom:1.1.1; locks ; strict; comment @ * @; Farther down in the file are groups of meta-information about each revision (but still not showing the contents of that revision), such as: 1.1 date 99.06.20.17.47.26; author jrandom; state Exp; branches 1.1.1.1; next ; And finally, the log message and text of an actual revision: 1.1 log @Initial revision @ text @#include <stdio.h> void main () { printf ("Hello, world!\n"); } @ 1.1.1.1 log @initial import into CVS @ text @@ If you look closely, you'll see that the first revision's contents are
stored under the heading 1.1, but that the log message there is "Initial
revision", whereas the log message we actually used at import time was
"initial import into CVS", which appears farther down, under
The file becomes even more revealing after we commit the first modification to hello.c: floss$ cvs -Q co myproj floss$ cd myproj floss$ emacs hello.c (make some changes to the file) floss$ cvs ci -m "print goodbye too" cvs commit: Examining . cvs commit: Examining a-subdir cvs commit: Examining a-subdir/subsubdir cvs commit: Examining b-subdir Checking in hello.c; /usr/local/newrepos/myproj/hello.c,v <-- hello.c new revision: 1.2; previous revision: 1.1 done If you look at hello.c,v in the repository now, you can see the effect of the commit: head 1.2; access; symbols start:1.1.1.1 jrandom:1.1.1; locks; strict; comment @ * @; 1.2 date 99.06.21.01.49.40; author jrandom; state Exp; branches; next 1.1; 1.1 date 99.06.20.17.47.26; author jrandom; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 99.06.20.17.47.26; author jrandom; state Exp; branches; next ; desc @@ 1.2 log @print goodbye too @ text @#include <stdio.h> void main () { printf ("Hello, world!\n"); printf ("Goodbye, world!\n"); } @ 1.1 log @Initial revision @ text @d7 1 @ 1.1.1.1 log @initial import into CVS @ text @@ Now the full contents of Revision 1.2 are stored in the file, and the text for Revision 1.1 has been replaced with the cryptic formula: d7 1 The This demonstrates the basic principle of RCS format: It stores only the differences between revisions, thereby saving a lot of space compared with storing each revision in full. To go backwards from the most recent revision to the previous one, it patches the later revision using the stored diff. Of course, this means that the further back you travel in the revision history, the more patch operations must be performed (for example, if the file is on Revision 1.7 and CVS is asked to retrieve Revision 1.4, it has to produce 1.6 by patching backwards from 1.7, then 1.5 by patching 1.6, then 1.4 by patching 1.5). Fortunately, old revisions are also the ones least often retrieved, so the RCS system works out pretty well in practice: The more recent the revision, the cheaper it is to obtain. As for the header information at the top of the file, you don't need to know what all of it means. However, the effects of certain operations show up very clearly in the headers, and a passing familiarity with them may prove useful. When you commit a new revision on the trunk, the floss$ cvs add -kb foo.jpg cvs add: scheduling file 'foo.jpg' for addition cvs add: use 'cvs commit' to add this file permanently floss$ cvs -q commit -m "added a random image; ask jrandom@red-bean.com why" RCS file: /usr/local/newrepos/myproj/foo.jpg,v done Checking in foo.jpg; /usr/local/newrepos/myproj/foo.jpg,v <-- foo.jpg initial revision: 1.1 done floss$ cvs tag some_random_tag foo.jpg T foo.jpg floss$ cvs tag ANOTHER-TAG foo.jpg T foo.jpg floss$ Now examine the header section of foo.jpg,v in the repository: head 1.1; access; symbols ANOTHER-TAG:1.1 some_random_tag:1.1; locks; strict; comment @# @; expand @b@; Notice the b in the expand line at the end - it's due to our having used the -kb flag when adding the file, and means the file won't undergo any keyword or newline expansions, which would normally occur during checkouts and updates if it were a regular text file. The tags appear in the symbols section, one tag per line - both of them are attached to the first revision, since that's what was tagged both times. (This also helps explain why tag names can only contain letters, numbers, hyphens, and underscores. If the tag itself contained colons or dots, the RCS file's record of it might be ambiguous, because there would be no way to find the textual boundary between the tag and the revision to which it is attached.) RCS Format Always Quotes @ SignsThe "added a random image; ask jrandom@red-bean.com why" which is stored in foo.jpg,v like this: 1.1 log @added a random image; ask jrandom@@red-bean.com why @ The @ sign in jrandom@@red-bean.com will be automatically unquoted whenever CVS retrieves the log message: floss$ cvs log foo.jpg RCS file: /usr/local/newrepos/myproj/foo.jpg,v Working file: foo.jpg head: 1.1 branch: locks: strict access list: symbolic names: ANOTHER-TAG: 1.1 some_random_tag: 1.1 keyword substitution: b total revisions: 1; selected revisions: 1 description: ---------------------------- revision 1.1 date: 1999/06/21 02:56:18; author: jrandom; state: Exp; added a random image; ask jrandom@red-bean.com why ============================================================================ floss$ The only reason you should care is that if you ever find yourself hand-editing RCS files (a rare circumstance, but not unheard of), you must remember to use double @ signs in revision contents and log messages. If you don't, the RCS file will be corrupt and will probably exhibit strange and undesirable behaviors. Speaking of hand-editing RCS files, don't be fooled by the permissions in the repository: floss$ ls -l total 6 -r--r--r-- 1 jrandom users 410 Jun 20 12:47 README.txt,v drwxrwxr-x 3 jrandom users 1024 Jun 20 21:56 a-subdir/ drwxrwxr-x 2 jrandom users 1024 Jun 20 21:56 b-subdir/ -r--r--r-- 1 jrandom users 937 Jun 20 21:56 foo.jpg,v -r--r--r-- 1 jrandom users 564 Jun 20 21:11 hello.c,v floss$ (For those not fluent in Unix ls output, the floss$ ls -ld . drwxrwxr-x 4 jrandom users 1024 Jun 20 22:16 ./ floss$ The myproj/ directory itself - and its subdirectories - are all writeable by the owner (jrandom) and the group (users). This means that CVS (running as jrandom, or as anyone in the users group) can create and delete files in those directories, even if it can't directly edit files already present. CVS edits an RCS file by making a separate copy of it, so you should also make all of your changes in a temporary copy, and then replace the existing RCS file with the new one. (But please don't ask why the files themselves are read-only - there are historical reasons for that, having to do with the way RCS works when run as a standalone program.) Incidentally, having the files' group be floss$ cd /usr/local/newrepos floss$ chgrp -R cvs myproj The usual Unix file-creation rules govern which group is assigned to new
files that appear in the repository, so once in a while you may need to
run chgrp or chmod on certain files or directories in the repository
(setting the SGID bit with Node:What Happens When You Remove A File, Next:The CVSROOT/ Administrative Directory, Previous:RCS Format, Up:Repository Administration What Happens When You Remove A FileWhen you remove a file from a project, it doesn't just disappear. CVS
must be able to retrieve such files when you request an old snapshot of
the project. Instead, the file gets put in the floss$ pwd /home/jrandom/src/myproj floss$ ls /usr/local/newrepos/myproj/ README.txt,v a-subdir/ b-subdir/ foo.jpg,v hello.c,v floss$ rm foo.jpg floss$ cvs rm foo.jpg cvs remove: scheduling 'foo.jpg' for removal cvs remove: use 'cvs commit' to remove this file permanently floss$ cvs ci -m "Removed foo.jpg" foo.jpg Removing foo.jpg; /usr/local/newrepos/myproj/foo.jpg,v <-- foo.jpg new revision: delete; previous revision: 1.1 done floss$ cd /usr/local/newrepos/myproj/ floss$ ls Attic/ README.txt,v a-subdir/ b-subdir/ hello.c,v floss$ cd Attic floss$ ls foo.jpg,v floss$ In each repository directory of a project, the presence of an
1.2 date 99.06.21.03.38.07; author jrandom; state dead; branches; next 1.1; If the file is later brought back to life, CVS has a way of recording that it was dead at some point in the past and is now alive again. This means that if you want to restore a removed file, you can't just take it out of the Attic/ and put it back into the project. Instead, you have to do something like this in a working copy: floss$ pwd /home/jrandom/src/myproj floss$ cvs -Q update -p -r 1.1 foo.jpg > foo.jpg floss$ ls CVS/ README.txt a-subdir/ b-subdir/ foo.jpg hello.c floss$ cvs add -kb foo.jpg cvs add: re-adding file foo.jpg (in place of dead revision 1.2) cvs add: use 'cvs commit' to add this file permanently floss$ cvs ci -m "revived jpg image" foo.jpg Checking in foo.jpg; /usr/local/newrepos/myproj/foo.jpg,v <-- foo.jpg new revision: 1.3; previous revision: 1.2 done floss$ cd /usr/local/newrepos/myproj/ floss$ ls Attic/ a-subdir/ foo.jpg,v README.txt,v b-subdir/ hello.c,v floss$ ls Attic/ floss$ There's a lot more to know about RCS format, but this is sufficient for a CVS adminstrator to maintain a repository. It's quite rare to actually edit an RCS file; you'll usually just have to tweak file permissions in the repository, at least if my own experience is any guide. Nevertheless, when CVS starts behaving truly weirdly (rare, but not completely outside the realm of possibility), you may want to actually look inside the RCS files to figure out what's going on. Node:The CVSROOT/ Administrative Directory, Next:Commit Emails, Previous:What Happens When You Remove A File, Up:Repository Administration The CVSROOT/ Administrative DirectoryThe files in newrepos/CVSROOT/ are not part of any project, but are used to control CVS's behavior in the repository. The best way to edit those files is to check out a working copy of CVSROOT, just like a regular project: floss$ cvs co CVSROOT cvs checkout: Updating CVSROOT U CVSROOT/checkoutlist U CVSROOT/commitinfo U CVSROOT/config U CVSROOT/cvswrappers U CVSROOT/editinfo U CVSROOT/loginfo U CVSROOT/modules U CVSROOT/notify U CVSROOT/rcsinfo U CVSROOT/taginfo U CVSROOT/verifymsg floss$ We'll take the files in their approximate order of importance. Note
that each of the files comes with an explanatory comment at the
beginning (the comment convention is the same across all of them: A
If you're extremely security conscious, you may want to arrange the Unix-level permissions on CVSROOT to be different from permissions elsewhere in the repository, in order to have fine-grained control over who can commit changes to CVSROOT. As you'll see a little later, being able to modify the files in CVSROOT essentially gives any CVS user - even remote ones - the ability to run arbitrary commands on the repository machine.
Node:The config File, Next:The modules File, Up:The CVSROOT/ Administrative Directory The config FileThe config file allows you to configure certain global behavioral parameters. It follows a very strict format PARAMETER=VALUE (etc) with no extra spaces allowed. For example, here is a possible config file: SystemAuth=yes TopLevelAdmin=no PreservePermissions=no (An absent entry would be equivalent to The
There are no other parameters at this time, but future versions of CVS may add new ones; you should always check the Cederqvist or the distribution config file itself for updates. Node:The modules File, Next:The commitinfo And loginfo And rcsinfo Files, Previous:The config File, Up:The CVSROOT/ Administrative Directory The modules FileIn modules, you can define aliases and alternate groupings for projects in the repository. The most basic module line is of the form: MODULE_NAME DIRECTORY_IN_REPOSITORY for example, mp myproj asub myproj/a-subdir (The paths given on the right are relative to the top of the repository.) This gives developers an alternate name by which to check out a project or a portion of a project: floss$ cvs co mp cvs checkout: Updating mp U mp/README.txt U mp/foo.jpg U mp/hello.c cvs checkout: Updating mp/a-subdir U mp/a-subdir/whatever.c cvs checkout: Updating mp/a-subdir/subsubdir U mp/a-subdir/subsubdir/fish.c cvs checkout: Updating mp/b-subdir U mp/b-subdir/random.c or floss$ cvs -d /usr/local/newrepos/ co asub cvs checkout: Updating asub U asub/whatever.c cvs checkout: Updating asub/subsubdir U asub/subsubdir/fish.c Notice how in both cases the module's name became the name of the directory created for the working copy. In the case of asub, it didn't even bother with the intermediate myproj/ directory, but created a top-level asub/ instead, even though it came from myproj/a-subdir in the repository. Updates, commits, and all other CVS commands will behave normally in those working copies - the only thing unusual about them are their names. By putting file names after the directory name, you can define a module consisting of just some of the files in a given repository directory. For example readme myproj README.txt and no-readme myproj hello.c foo.jpg would permit the following checkouts, respectively: floss$ cvs -q co readme U readme/README.txt floss$ cvs -q co no-readme U no-readme/hello.c U no-readme/foo.jpg floss$ You can define a module that will include multiple repository
directories by using the -a (for twoproj -a myproj yourproj would allow you to do this (assuming that both myproj/ and yourproj/ are in the repository): floss$ cvs co twoproj U myproj/README.txt U myproj/foo.jpg U myproj/hello.c U myproj/a-subdir/whatever.c U myproj/a-subdir/subsubdir/fish.c U myproj/b-subdir/random.c U yourproj/README U yourproj/foo.c U yourproj/some-subdir/file1.c U yourproj/some-subdir/file2.c U yourproj/some-subdir/another-subdir/blah.c The name Modules can even refer to other modules, by prefixing them with an ampersand: mp myproj asub myproj/a-subdir twoproj -a myproj yourproj tp &twoproj Doing a checkout of There are a few other tricks you can do with modules, most of them less frequently used than the ones just presented. See the node modules in the Cederqvist for information about them. Node:The commitinfo And loginfo And rcsinfo Files, Next:The verifymsg And rcsinfo Files, Previous:The modules File, Up:The CVSROOT/ Administrative Directory The commitinfo And loginfo And rcsinfo FilesMost of the other administrative files provide programmatic hooks into various parts of the commit process (for example, the ability to validate log messages or file states before permitting the commit, or the ability to notify a group of developers whenever a commit happens in a certain directory of the repository). The files generally share a common syntax. Each line is of the form: REGULAR_EXPRESSION PROGRAM_TO_RUN The regular expression will be tested against the directory into which the commit is taking place (with the directory name relative to the top of the repository). If it matches, the designated program will be run. The program will be passed the names of each of the files in the commit; it can do whatever it likes with those names, including opening up the files and examining their contents. If the program returns with a nonzero exit status, the commit is prevented from taking place. (Regular expressions are a system for concisely describing classes
of strings. If you aren't familiar with regular expressions, you can
get by with the following short summary: The commitinfo file is for generic hooks you want run on every commit. Here are some example commitinfo lines: ^a-subdir* /usr/local/bin/check-asubdir.sh ou /usr/local/bin/validate-project.pl So any commit into myproj/a-subdir/ would match the first line, which
would then run the check-asubdir.sh script. A commit in any project
whose name (actual repository directory name, not necessarily module
name) contained the string In place of a regular expression, the word The file names passed to the program do not refer to RCS files - they point to normal files, whose contents are exactly the same as the working-copy files being committed. The only unusual aspect is that CVS has them temporarily placed inside the repository, so they'll be available to programs running on the machine where the repository is located. The loginfo file is similar to commitinfo, except that instead of acting on the files' contents, it acts on the log message. The left side of the loginfo file contains regular expressions, including possibly DEFAULT and ALL lines. The program invoked on the right side receives the log message on its standard input; it can do whatever it wants with that input. The program on the right side can also take an arbitrary number of
command-line arguments. One of those arguments can be a special
%s ------> name(s) of the file(s) being committed %V ------> revision number(s) before the commit %v ------> revision number(s) after the commit The expansion always begins with the repository subdirectory (relative
to the top of the repository), followed by the per-file information.
For example, if the files committed were foo, bar, and baz, all in
myproj/a-subdir foo bar baz whereas myproj/a-subdir 1.7 1.134 1.12 and myproj/a-subdir 1.8 1.135 1.13 You can combine myproj/a-subdir foo,1.8 bar,1.135 baz,1.13 and myproj/a-subdir foo,1.7,1.8 bar,1.134,1.135 baz,1.12,1.13 (You may have to look carefully to distinguish the commas from the periods in those examples.) Here is a sample loginfo file: ^myproj$ /usr/local/newrepos/CVSROOT/log.pl -m myproj-devel@foobar.com %s ou /usr/local/bin/ou-notify.pl %{sv} DEFAULT /usr/local/bin/default-notify.pl %{sVv} In the first line, any commit in the myproj subdirectory of the
repository invokes In the second line, any commit in a repository subdirectory containing
the string The third line invokes the (equally imaginary) Node:The verifymsg And rcsinfo Files, Next:The taginfo File, Previous:The commitinfo And loginfo And rcsinfo Files, Up:The CVSROOT/ Administrative Directory The verifymsg And rcsinfo FilesSometimes you may just want a program to automatically verify that the
log message conforms to a certain standard and to stop the commit if
that standard is not met. This can be accomplished by using
The verifymsg file is the usual combination of regular expressions and programs. The program receives the log message on standard input; presumably it runs some checks to verify that the log message meets certain criteria, then it exits with status zero or nonzero. If the latter, the commit will fail. Meanwhile, the left side of rcsinfo has the usual regular expressions, but the right side points to template files instead of programs. A template file might be something like this Condition: Fix: Comments: or some other collection of fields that a developer is supposed to fill out to form a valid log message. The template is not very useful if everyone commits using the -m option explicitly, but many developers prefer not to do that. Instead, they run floss$ cvs commit and wait for CVS to automatically fire up a text editor (as specified in the EDITOR environment variable). There they write a log message, then save the file and exit the editor, after which CVS continues with the commit. In that scenario, an rcsinfo template would insert itself into the
editor before the user starts typing, so the fields would be displayed
along with a reminder to fill them in. Then when the user commits, the
appropriate program in As an aid to the verification programs, the path to the template from
the rcsinfo file is appended as the last argument to the program command
line in Note that when someone checks out a working copy to a remote machine, the appropriate rcsinfo template file is sent to the client as well (it's stored in the CVS/ subdirectory of the working copy). However, this means that if the rcsinfo file on the server is changed after that, the client won't see the changes without re-checking out the project (merely doing an update won't work). Note also that in the verifymsg file, the ALL keyword is not supported (although DEFAULT still is). This is to make it easier to override default verification scripts with subdirectory-specific ones. Node:The taginfo File, Next:The cvswrappers File, Previous:The verifymsg And rcsinfo Files, Up:The CVSROOT/ Administrative Directory The taginfo FileWhat loginfo does for log messages, taginfo does for tags. The left side of taginfo is regular expressions, as usual, and the right side is programs. Each program is automatically handed arguments when CVS tag is invoked, in this order: arg 1: tag name arg 2: operation ("add" => tag, "mov" => tag -F, "del" => tag -d) arg 3: repository arg 4, 5, etc: file revision [file revision ...] If the program returns nonzero, the tag is aborted. We haven't covered the -F option to tag before now, but it's exactly
what the above implies: a way to move a tag from one revision to
another. For example, if the tag cvs tag -r 1.11 -F Known_Working foo.c which removes the tag from 1.7, or wherever it was previously in that file, and puts it at 1.11. Node:The cvswrappers File, Next:The editinfo File, Previous:The taginfo File, Up:The CVSROOT/ Administrative Directory The cvswrappers FileThe redundantly-named cvswrappers file gives you a way to specify that certain files should be treated as binary, based on their file name. CVS does not assume that all .jpg files are JPG image data, for example, so it doesn't automatically use -kb when adding JPG files. Nonetheless, certain projects would find it very useful to simply designate all JPG files as binary. Here is a line in cvswrappers to do that: *.jpg -k 'b' The There are a few other modes that can be specified from the wrappers file, but they're for such rare situations that they're probably not worth documenting here (translation: your author has never had to use them). See the node Wrappers in the Cederqvist if you're curious. Node:The editinfo File, Next:The notify File, Previous:The cvswrappers File, Up:The CVSROOT/ Administrative Directory The editinfo FileThis file is obsolete, even though it's still included in distributions. Just ignore it. Node:The notify File, Next:The checkoutlist File, Previous:The editinfo File, Up:The CVSROOT/ Administrative Directory The notify FileThis file is used in conjunction with CVS's Node:The checkoutlist File, Previous:The notify File, Up:The CVSROOT/ Administrative Directory The checkoutlist FileIf you look inside CVSROOT/, you'll see that working copies of the files exist side by side with their RCS revision files: floss$ ls /usr/local/newrepos/CVSROOT checkoutlist config,v history notify taginfo checkoutlist,v cvswrappers loginfo notify,v taginfo,v commitinfo cvswrappers,v loginfo,v passwd verifymsg commitinfo,v editinfo modules rcsinfo verifymsg,v config editinfo,v modules,v rcsinfo,v floss$ CVS only pays attention to the working versions, not the RCS files, when it's looking for guidance on how to behave. Therefore, whenever you commit your working copy of CVSROOT/ (which might, after all, even be checked out to a different machine), CVS automatically updates any changed files in the repository itself. You will know that this has happened because CVS will print a message at the end of such commits: floss$ cvs ci -m "added mp and asub modules" modules Checking in modules; /usr/local/newrepos/CVSROOT/modules,v <-- modules new revision: 1.2; previous revision: 1.1 done cvs commit: Rebuilding administrative file database CVS automatically knows about the standard administrative files, and will rebuild them in CVSROOT/ as necessary. If you decide to put custom files in CVSROOT/ (such as programs or rcsinfo template files), you'll have to tell CVS explicitly to treat them the same way. That's the purpose of the checkoutlist file. It has a different format from most of the files we've looked at so far FILENAME ERROR_MESSAGE_IF_FILE_CANNOT_BE_CHECKED_OUT for example, log.pl unable to check out / update log.pl in CVSROOT bugfix.tmpl unable to check out / update bugfix.tmpl in CVSROOT Certain files in CVSROOT are traditionally not kept under revision
control. One such is the history file, which keeps a running
record of all actions in the repository, for use by the Note: sometimes the history file is the cause of permission problems, and the easiest way to solve them is to either make it world-writeable or just remove it. Another Two final notes about the CVSROOT/ directory: It is possible, if you make a big enough mistake, to commit an administrative file that is broken in such a way as to prevent any commits from happening at all. If you do that, naturally you won't be able to commit a fixed version of the administrative file! The solution is to go in and hand-edit the repository's working copy of the administrative file to correct the problem; the whole repository may stay inaccessible until you do that. Also, for security's sake, make sure your CVSROOT/ directory is only
writeable by users you trust (by Node:Commit Emails, Next:Finding Out More, Previous:The CVSROOT/ Administrative Directory, Up:Repository Administration Commit EmailsThe loginfo file is how one sets up commit emails - automated emails
that go out to everyone working on a project whenever a commit takes
place. (It may seem counterintuitive that this is done in loginfo
instead of commitinfo, but the point is that one wants to include the
log message in the email). The program to do the mailing -
You may need to edit $mailcmd = "| Mail -s 'CVS update: $modulepath'"; to invoke your preferred mailer, which may or may not be named
listerizer CVSROOT/log.pl %s -f CVSROOT/commitlog -m listerizer@red-bean.com RoadMail CVSROOT/log.pl %s -f CVSROOT/commitlog -m roadmail@red-bean.com bk/*score CVSROOT/log.pl %s -f CVSROOT/commitlog -m \ bkscore-devel@red-bean.com The Node:Finding Out More, Previous:Commit Emails, Up:Repository Administration Finding Out MoreAlthough this chapter tries to give a complete introduction to installing and administering CVS, I've left out things that are either too rarely used to be worth mentioning or already well documented in the Cederqvist manual. The latter category includes setting up the other remote access methods: RSH/SSH, kserver (Kerberos 4), and GSSAPI (which includes Kerberos 5, among other things). It should be noted that nothing special needs to be done for RSH/SSH connections, other than making sure that the user in question can log into the repository machine using RSH or SSH. If they can and CVS is installed on both client and server, and they have the right permissions to use the repository directly from the server machine, then they should be able to access the repository remotely via the :ext: method. Descriptions of certain specialized features of CVS have been deferred to later chapters, so they can be introduced in contexts where their usefulness is obvious. General CVS troubleshooting tips are found in Tips And Troubleshooting. Although it's not necessary to read the entire Cederqvist manual, you should familiarize yourself with it; it will be an invaluable reference tool. If for some reason you don't have Info working on your machine and don't want to print the manual, you can browse it online at http://durak.org/cvswebsites/doc/ or http://www.loria.fr/~molli/cvs/doc/cvs_toc.html. Node:Advanced CVS, Next:Tips And Troubleshooting, Previous:Repository Administration, Up:Top Advanced CVSNow that we've covered the basic concepts of CVS usage and repository administration, we'll look at how CVS can be incorporated into the entire process of development. The fundamental CVS working cycle - checkout, update, commit, update, commit, and so on - was demonstrated by the examples in An Overview of CVS. This chapter elaborates on the cycle and discusses how CVS can be used to help developers communicate, give overviews of project activity and history, isolate and reunite different branches of development, and automate frequently performed tasks. Some of the techniques covered introduce new CVS commands, but many merely explain better ways to use commands that you already know.
Node:Watches (CVS As Telephone), Next:Log Messages And Commit Emails, Up:Advanced CVS Watches (CVS As Telephone)A major benefit of using CVS on a project is that it can function as a communications device as well as a record-keeper. This section concentrates on how CVS can be used to keep participants informed about what's going on in a project. As is true with other aspects of CVS, these features reward cooperation. The participants must want to be informed; if people choose not to use the communications features, there's nothing CVS can do about it.
Node:How Watches Work, Next:Enabling Watches In The Repository, Up:Watches (CVS As Telephone) How Watches WorkIn its default behavior, CVS treats each working copy as an isolated sandbox. No one knows what you're doing in your working copy until you commit your changes. In turn, you don't know what others are doing in theirs - except via the usual methods of communication, such as shouting down the hallway, "Hey, I'm going to work on parse.c now. Let me know if you're editing it so we can avoid conflicts!" This informality works for projects where people have a general idea of who's responsible for what. However, this process can break down when a large number of developers are active in all parts of a code base and want to avoid conflicts. In such cases, they frequently have to cross each others' areas of responsibility but can't shout down the hallway at each other because they're geographically distributed. A feature of CVS called To use watches, you must modify one or two files in the repository
administrative area, and developers must add some extra steps to the
usual checkout/update/commit cycle. The changes on the repository side
are fairly simple: You may need to edit the On the working copy side, developers have to tell CVS which files they want to watch so that CVS can send them notifications when someone else starts editing those files. They also need to tell CVS when they start or stop editing a file, so CVS can send out notifications to others who may be watching. The following commands are used to implement these extra steps:
The command In the following example, we'll look at how to turn on watches in the repository and then how to use watches from the developer's side. The two example users, jrandom and qsmith, each have their own separate working copies of the same project; the working copies may even be on different machines. As usual, all examples assume that the $CVSROOT environment variable has already been set, so there's no need to pass -d <REPOS> to any CVS commands. Node:Enabling Watches In The Repository, Next:Using Watches In Development, Previous:How Watches Work, Up:Watches (CVS As Telephone) Enabling Watches In The RepositoryFirst, the CVSROOT/notify file must be edited to turn on email notification. One of the developers can do this, or the repository administrator can if the developers don't have permission to change the repository's administrative files. In any case, the first thing to do is check out the administrative area and edit the notify file: floss$ cvs -q co CVSROOT U CVSROOT/checkoutlist U CVSROOT/commitinfo U CVSROOT/config U CVSROOT/cvswrappers U CVSROOT/editinfo U CVSROOT/loginfo U CVSROOT/modules U CVSROOT/notify U CVSROOT/rcsinfo U CVSROOT/taginfo U CVSROOT/verifymsg floss$ cd CVSROOT floss$ emacs notify ... When you edit the notify file for the first time, you'll see something like this: # The "notify" file controls where notifications from watches set by # "cvs watch add" or "cvs edit" are sent. The first entry on a line is # a regular expression which is tested against the directory that the # change is being made to, relative to the $CVSROOT. If it matches, # then the remainder of the line is a filter program that should contain # one occurrence of %s for the user to notify, and information on its # standard input. # # "ALL" or "DEFAULT" can be used in place of the regular expression. # # For example: # ALL mail %s -s "CVS notification" All you really need to do is uncomment the last line by removing the
initial To specify email notification, the line ALL mail %s -s "CVS notification" should work on any standard Unix machine. This command causes
notifications to be sent as emails with the subject line floss$ cvs ci -m "turned on watch notification" cvs commit: Examining . Checking in notify; /usr/local/newrepos/CVSROOT/notify,v <-- notify new revision: 1.2; previous revision: 1.1 done cvs commit: Rebuilding administrative file database floss$ Editing the notify file in this way may be all that you'll need to do
for watches in the repository. However, if there are remote developers
working on the project, you may need to edit the CVS_USERNAME:EMAIL_ADDRESS For example, qsmith:quentinsmith@farawayplace.com The CVS username at the beginning of the line corresponds to a CVS
username in Unfortunately, as of this writing, the users file does not exist in the
stock CVS distribution. Because it's an administrative file, you must
not only create, cvs add, and commit it in the usual way, but also add
it to Here is a sample session demonstrating this: floss$ emacs checkoutlist ... (add the line for the users file) ... floss$ emacs users ... (add the line for qsmith) ... floss$ cvs add users floss$ cvs ci -m "added users to checkoutlist, qsmith to users" cvs commit: Examining . Checking in checkoutlist; /usr/local/newrepos/CVSROOT/checkoutlist,v <-- checkoutlist new revision: 1.2; previous revision: 1.1 done Checking in users; /usr/local/newrepos/CVSROOT/users,v <-- users new revision: 1.2; previous revision: 1.1 done cvs commit: Rebuilding administrative file database floss$ It's possible to use expanded-format email addresses in
qsmith:"Quentin Q. Smith <quentinsmith@farawayplace.com>" or qsmith:'Quentin Q. Smith <quentinsmith@farawayplace.com>' However, this will not work: qsmith:"Quentin Q. Smith" <quentinsmith@farawayplace.com> When in doubt, you should test by running the command line given in the
notify file manually. Just replace the mail %s -s "CVS notification" with what you have following the colon in users. If it works when you run it at a command prompt, it should work in the users file, too. When it's over, the checkout file will look like this: # The "checkoutlist" file is used to support additional version controlled # administrative files in $CVSROOT/CVSROOT, such as template files. # # The first entry on a line is a filename which will be checked out from # the corresponding RCS file in the $CVSROOT/CVSROOT directory. # The remainder of the line is an error message to use if the file cannot # be checked out. # # File format: # # [<whitespace>]<filename><whitespace><error message><end-of-line> # # comment lines begin with '#' users Unable to check out 'users' file in CVSROOT. The users file will look like this: qsmith:quentinsmith@farawayplace.com Now that the repository is set up for watches, let's look at what developers need to do in their working copies. Node:Using Watches In Development, Next:Ending An Editing Session, Previous:Enabling Watches In The Repository, Up:Watches (CVS As Telephone) Using Watches In DevelopmentFirst, a developer checks out a working copy and adds herself to the list of watchers for one of the files in the project: floss$ whoami jrandom floss$ cvs -q co myproj U myproj/README.txt U myproj/foo.gif U myproj/hello.c U myproj/a-subdir/whatever.c U myproj/a-subdir/subsubdir/fish.c U myproj/b-subdir/random.c floss$ cd myproj floss$ cvs watch add hello.c floss$ The last command, cvs watch add hello.c, tells CVS to notify jrandom if anyone else starts working on hello.c (that is, it adds jrandom to hello.c's watch list). For CVS to send notifications as soon as a file is being edited, the user who is editing it has to announce the fact by running cvs edit on the file first. CVS has no other way of knowing when someone starts working on a file. Once checkout is done, CVS isn't usually invoked until the next update or commit, which happens after the file has already been edited: paste$ whoami qsmith paste$ cvs -q co myproj U myproj/README.txt U myproj/foo.gif U myproj/hello.c U myproj/a-subdir/whatever.c U myproj/a-subdir/subsubdir/fish.c U myproj/b-subdir/random.c paste$ cd myproj paste$ cvs edit hello.c paste$ emacs hello.c ... When qsmith runs cvs edit hello.c, CVS looks at the watch list for hello.c, sees that jrandom is on it, and sends email to jrandom telling her that qsmith has started editing the file. The email even appears to come from qsmith: From: qsmith Subject: CVS notification To: jrandom Date: Sat, 17 Jul 1999 22:14:43 -0500 myproj hello.c -- Triggered edit watch on /usr/local/newrepos/myproj By qsmith Furthermore, every time that qsmith (or anyone) commits a new revision of hello.c, jrandom will receive another email: myproj hello.c -- Triggered commit watch on /usr/local/newrepos/myproj By qsmith After receiving these emails, jrandom may want to update hello.c immediately to see what qsmith has done, or perhaps she'll email qsmith to find out why he's working on that file. Note that nothing forced qsmith to remember to run cvs edit - presumably he did it because he wanted jrandom to know what he was up to (anyway, even if he forgot to do cvs edit, his commits would still trigger notifications). The reason to use cvs edit is that it notifies watchers before you start to work on a file. The watchers can contact you if they think there may be a conflict, before you've wasted a lot of time. CVS assumes that anyone who runs cvs edit on a file wants to be added to the file's watch list, at least temporarily, in case someone else starts to edit it. When qsmith ran cvs edit, he became a watcher of hello.c. Both he and jrandom would have received notification if a third party had run cvs edit on that file (or committed it). However, CVS also assumes that the person editing the file only wants to be on its watch list while he or she is editing it. Such users are taken off the watch list when they're done editing. If they prefer to be permanent watchers of the file, they would have to run cvs watch add. CVS makes a default assumption that someone is done editing when he or she commits a file (until the next time, anyway). Anyone who gets on a file's watch list solely by virtue of having run
CVS's assumption that the first commit ends the editing session is only a best guess, of course, because CVS doesn't know how many commits the person will need to finish their changes. The guess is probably accurate for one-off changes - changes where someone just needs to make one quick fix to a file and commit it. For more prolonged editing sessions involving several commits, users should add themselves permanently to the file's watch list: paste$ cvs watch add hello.c paste$ cvs edit hello.c paste$ emacs hello.c ... paste$ cvs commit -m "print hello in Sanskrit" Even after the commit, qsmith remains a watcher of hello.c because he ran watch add on it. (By the way, qsmith will not receive notification of his own edits; only other watchers will. CVS is smart enough not to notify you about actions that you took.) Node:Ending An Editing Session, Next:Controlling What Actions Are Watched, Previous:Using Watches In Development, Up:Watches (CVS As Telephone) Ending An Editing SessionIf you don't want to commit but want to explicitly end an editing session, you can do so by running cvs unedit: paste$ cvs unedit hello.c But beware! This does more than just notify all watchers that you're done editing - it also offers to revert any uncommitted changes that you've made to the file: paste$ cvs unedit hello.c hello.c has been modified; revert changes? y paste$ If you answer Node:Controlling What Actions Are Watched, Next:Finding Out Who Is Watching What, Previous:Ending An Editing Session, Up:Watches (CVS As Telephone) Controlling What Actions Are WatchedBy default, watchers are notified about three kinds of action: edits, commits, and unedits. However, if you only want to be notified about, say, commits, you can restrict notifications by adjusting your watch with the -a flag (a for action): floss$ cvs watch add -a commit hello.c Or if you want to watch edits and commits but don't care about unedits, you could pass the -a flag twice: floss$ cvs watch add -a edit -a commit hello.c Adding a watch with the -a flag will never cause any of your existing watches to be removed. If you were watching for all three kinds of actions on hello.c, running floss$ cvs watch add -a commit hello.c has no effect - you'll still be a watcher for all three actions. To remove watches, you should run floss$ cvs watch remove hello.c which is similar to add in that, by default, it removes your watches for all three actions. If you pass -a arguments, it removes only the watches you specify: floss$ cvs watch remove -a commit hello.c This means that you want to stop receiving notifications about commits but continue to receive notifications about edits and unedits (assuming you were watching edits and unedits to begin with, that is). There are two special actions you can pass to the -a flag: all or none. The former means all actions that are eligible for watching (edits, commits, and unedits, as of this writing), and the latter means none of these. Because CVS's default behavior, in the absence of -a, is to watch all actions, and because watching none is the same as removing yourself from the watch list entirely, it's hard to imagine a situation in which it would be useful to specify either of these two special actions. However, cvs edit also takes the -a option, and in this case, it can be useful to specify all or none. For example, someone working on a file very briefly may not want to receive any notifications about what other people do with the file. Thus, this command paste$ whoami qsmith paste$ cvs edit -a none README.txt causes watchers of README.txt to be notified that qsmith is about to work on it, but qsmith would not be added as a temporary watcher of README.txt during his editing session (which he normally would have been), because he asked not to watch any actions. Remember that you can only affect your own watches with the cvs watch command. You may stop watching a certain file yourself, but that won't change anyone else's watches. Node:Finding Out Who Is Watching What, Next:Reminding People To Use Watches, Previous:Controlling What Actions Are Watched, Up:Watches (CVS As Telephone) Finding Out Who Is Watching WhatSometimes you may want to know who's watching before you even run cvs edit or want to see who is editing what without adding yourself to any watch lists. Or you may have forgotten exactly what your own status is. After setting and unsetting a few watches and committing some files, it's easy to lose track of what you're watching and editing. CVS provides two commands to show who's watching and who's editing files - cvs watchers and cvs editors: floss$ whoami jrandom floss$ cvs watch add hello.c floss$ cvs watchers hello.c hello.c jrandom edit unedit commit floss$ cvs watch remove -a unedit hello.c floss$ cvs watchers hello.c hello.c jrandom edit commit floss$ cvs watch add README.txt floss$ cvs watchers README.txt jrandom edit unedit commit hello.c jrandom edit commit floss$ Notice that the last cvs watchers command doesn't specify any files and, therefore, shows watchers for all files (all those that have watchers, that is). All of the watch and edit commands have this behavior in common with other CVS commands. If you specify file names, they act on those files. If you specify directory names, they act on everything in that directory and its subdirectories. If you don't specify anything, they act on the current directory and everything underneath it, to as many levels of depth as are available. For example (continuing with the same session): floss$ cvs watch add a-subdir/whatever.c floss$ cvs watchers README.txt jrandom edit unedit commit hello.c jrandom edit commit a-subdir/whatever.c jrandom edit unedit commit floss$ cvs watch add floss$ cvs watchers README.txt jrandom edit unedit commit foo.gif jrandom edit unedit commit hello.c jrandom edit commit unedit a-subdir/whatever.c jrandom edit unedit commit a-subdir/subsubdir/fish.c jrandom edit unedit commit b-subdir/random.c jrandom edit unedit commit floss$ The last two commands made jrandom a watcher of every file in the
project and then showed the watch list for every file in the project,
respectively. The output of [FILENAME] [whitespace] WATCHER [whitespace] ACTIONS-BEING-WATCHED... Now watch what happens when qsmith starts to edit one of the files: paste$ cvs edit hello.c paste$ cvs watchers README.txt jrandom edit unedit commit foo.gif jrandom edit unedit commit hello.c jrandom edit commit unedit qsmith tedit tunedit tcommit a-subdir/whatever.c jrandom edit unedit commit a-subdir/subsubdir/fish.c jrandom edit unedit commit b-subdir/random.c jrandom edit unedit commit The file hello.c has acquired another watcher: qsmith himself (note that
the file name is not repeated but is left as white space at the
beginning of the line - this would be important if you ever wanted to
write a program that parses watchers output). Because he's editing
hello.c, qsmith has a temporary watch on the file; it goes away as
soon as he commits a new revision of hello.c. The prefix paste$ cvs watch add hello.c README.txt jrandom edit unedit commit foo.gif jrandom edit unedit commit hello.c jrandom edit commit unedit qsmith tedit tunedit tcommit edit unedit commit a-subdir/whatever.c jrandom edit unedit commit a-subdir/subsubdir/fish.c jrandom edit unedit commit b-subdir/random.c jrandom edit unedit commit he is listed as both a temporary watcher and a permanent watcher. You may think that the permanent watch status would simply override the temporary, so that the line would look like this: qsmith edit unedit commit However, CVS can't just replace the temporary watches because it doesn't know in what order things happen. Will qsmith remove himself from the permanent watch list before ending his editing session, or will he finish the edits while still remaining a watcher? If the former, the edit/unedit/commit actions disappear while the tedit/tunedit/tcommit ones remain; if the latter, the reverse would happen. Anyway, that side of the watch list is usually not of great concern. Most of the time, what you want to do is run floss$ cvs watchers or floss$ cvs editors from the top level of a project and see who's doing what. You don't really need to know the details of who cares about what actions: the important things are people and files. Node:Reminding People To Use Watches, Next:What Watches Look Like In The Repository, Previous:Finding Out Who Is Watching What, Up:Watches (CVS As Telephone) Reminding People To Use WatchesYou've probably noticed that the watch features are utterly dependent on the cooperation of all the developers. If someone just starts editing a file without first running cvs edit, no one else will know about it until the changes get committed. Because cvs edit is an additional step, not part of the normal development routine, people can easily forget to do it. Although CVS can't force someone to use cvs edit, it does have a mechanism for reminding people to do so - the watch on command: floss$ cvs -q co myproj U myproj/README.txt U myproj/foo.gif U myproj/hello.c U myproj/a-subdir/whatever.c U myproj/a-subdir/subsubdir/fish.c U myproj/b-subdir/random.c floss$ cd myproj floss$ cvs watch on hello.c floss$ By running cvs watch on hello.c, jrandom causes future checkouts of myproj to create hello.c read-only in the working copy. When qsmith tries to work on it, he'll discover that it's read-only and be reminded to run cvs edit first: paste$ cvs -q co myproj U myproj/README.txt U myproj/foo.gif U myproj/hello.c U myproj/a-subdir/whatever.c U myproj/a-subdir/subsubdir/fish.c U myproj/b-subdir/random.c paste$ cd myproj paste$ ls -l total 6 drwxr-xr-x 2 qsmith users 1024 Jul 19 01:06 CVS/ -rw-r--r-- 1 qsmith users 38 Jul 12 11:28 README.txt drwxr-xr-x 4 qsmith users 1024 Jul 19 01:06 a-subdir/ drwxr-xr-x 3 qsmith users 1024 Jul 19 01:06 b-subdir/ -rw-r--r-- 1 qsmith users 673 Jun 20 22:47 foo.gif -r--r--r-- 1 qsmith users 188 Jul 18 01:20 hello.c paste$ When he does so, the file becomes read-write. He can then edit it, and when he commits, it becomes read-only again: paste$ cvs edit hello.c paste$ ls -l hello.c -rw-r--r-- 1 qsmith users 188 Jul 18 01:20 hello.c paste$ emacs hello.c ... paste$ cvs commit -m "say hello in Aramaic" hello.c Checking in hello.c; /usr/local/newrepos/myproj/hello.c,v <-- hello.c new revision: 1.12; previous revision: 1.11 done paste$ ls -l hello.c -r--r--r-- 1 qsmith users 210 Jul 19 01:12 hello.c paste$ His edit and commit will send notification to all watchers of hello.c. Note that jrandom isn't necessarily one of them. By running cvs watch on hello.c, jrandom did not add herself to the watch list for that file; she merely specified that it should be checked out read-only. People who want to watch a file must remember to add themselves to its watch list - CVS cannot help them with that. Turning on watches for a single file may be the exception. Generally, it's more common to turn on watches project-wide: floss$ cvs -q co myproj U myproj/README.txt U myproj/foo.gif U myproj/hello.c U myproj/a-subdir/whatever.c U myproj/a-subdir/subsubdir/fish.c U myproj/b-subdir/random.c floss$ cd myproj floss$ cvs watch on floss$ This action amounts to announcing a policy decision for the entire project: "Please use cvs edit to tell watchers what you're working on, and feel free to watch any file you're interested in or responsible for." Every file in the project will be checked out read-only, and thus people will be reminded that they're expected to use cvs edit before working on anything. Curiously, although checkouts of watched files make them read-only, updates do not. If qsmith had checked out his working copy before jrandom ran cvs watch on, his files would have stayed read-write, remaining so even after updates. However, any file he commits after jrandom turns watching on will become read-only. If jrandom turns off watches floss$ cvs watch off qsmith's read-only files do not magically become read-write. On the other hand, after he commits one, it will not revert to read-only again (as it would have if watches were still on). It's worth noting that qsmith could, were he truly devious, make files
in his working copy writeable by using the standard Unix paste$ chmod u+w hello.c or if he wanted to get everything in one fell swoop: paste$ chmod -R u+w . There is nothing CVS can do about this. Working copies by their nature are private sandboxes - the watch features can open them up to public scrutiny a little bit, but only as far as the developer permits. Only when a developer does something that affects the repository (such as commits) is her privacy unconditionally lost. The relationship among watch add, watch remove, watch on, and watch off
probably seems a bit confusing. It may help to summarize the overall
scheme: All of this may seem a little inconsistent. In a sense, using watches works against the grain of CVS. It deviates from the idealized universe of multiple developers editing freely in their working copies, hidden from each other until they choose to commit. With watches, CVS gives developers convenient shortcuts for informing each other of what's going on in their working copies; however, it has no way to enforce observation policies, nor does it have a definitive concept of what constitutes an editing session. Nevertheless, watches can be helpful in certain circumstances if developers work with them. Node:What Watches Look Like In The Repository, Previous:Reminding People To Use Watches, Up:Watches (CVS As Telephone) What Watches Look Like In The RepositoryIn the interests of stamping out black boxes and needless mystery, let's take a quick look at how watches are implemented in the repository. We'll only take a quick look, though, because it's not pretty. When you set a watch floss$ pwd /home/jrandom/myproj floss$ cvs watch add hello.c floss$ cvs watchers hello.c jrandom edit unedit commit floss$ CVS records it in the special file, floss$ cd /usr/local/newrepos floss$ ls CVSROOT/ myproj/ floss$ cd myproj floss$ ls CVS/ a-subdir/ foo.gif,v README.txt,v b-subdir/ hello.c,v floss$ cd CVS floss$ ls fileattr floss$ cat fileattr Fhello.c _watchers=jrandom>edit+unedit+commit floss$ The fact that fileattr is stored in a CVS subdirectory in the repository
does not mean that the repository has become a working copy. It's
simply that the name I won't describe the format of floss$ cvs watch add hello.c floss$ cat /usr/local/newrepos/myproj/CVS/fileattr Fhello.c _watchers=jrandom>edit+unedit+commit floss$ cvs watch add README.txt floss$ cat /usr/local/newrepos/myproj/CVS/fileattr Fhello.c _watchers=jrandom>edit+unedit+commit FREADME.txt _watchers=jrandom>edit+unedit+commit floss$ cvs watch on hello.c floss$ cat /usr/local/newrepos/myproj/CVS/fileattr Fhello.c _watchers=jrandom>edit+unedit+commit;_watched= FREADME.txt _watchers=jrandom>edit+unedit+commit floss$ cvs watch remove hello.c floss$ cat /usr/local/newrepos/myproj/CVS/fileattr Fhello.c _watched= FREADME.txt _watchers=jrandom>edit+unedit+commit floss$ cvs watch off hello.c floss$ cat /usr/local/newrepos/myproj/CVS/fileattr FREADME.txt _watchers=jrandom>edit+unedit+commit floss$ Edit records are stored in fileattr, too. Here's what happens when qsmith adds himself as an editor: paste$ cvs edit hello.c floss$ cat /usr/local/newrepos/myproj/CVS/fileattr Fhello.c _watched=;_editors=qsmith>Tue Jul 20 04:53:23 1999 GMT+floss\ +/home/qsmith/myproj;_watchers=qsmith>tedit+tunedit+tcommit FREADME.txt _watchers=jrandom>edit+unedit+commit Finally, note that CVS removes fileattr and the CVS subdirectory when there are no more watchers or editors for any of the files in that directory: paste$ cvs unedit floss$ cvs watch off floss$ cvs watch remove floss$ cat /usr/local/newrepos/myproj/CVS/fileattr cat: /usr/local/newrepos/myproj/CVS/fileattr: No such file or directory floss$ It should be clear after this brief exposure that the details of parsing fileattr format are better left to CVS. The main reason to have a basic understanding of the format - aside from the inherent satisfaction of knowing what's going on behind the curtain - is if you try to write an extension to the CVS watch features or debug some problem in them. It's sufficient to know that you shouldn't be alarmed if you see CVS/ subdirectories popping up in your repository. They're the only safe place CVS has to store meta-information such as watch lists. Node:Log Messages And Commit Emails, Next:Changing A Log Message After Commit, Previous:Watches (CVS As Telephone), Up:Advanced CVS Log Messages And Commit EmailsCommit emails are notices sent out at commit time, showing the log message and files involved in the commit. They usually go to all project participants and sometimes to other interested parties. The details of setting up commit emails were covered in Repository Administration, so I won't repeat them here. I have noticed, however, that commit emails can sometimes result in unexpected side effects to projects, effects that you may want to take into account if you set up commit emails for your project. First, be prepared for the messages to be mostly ignored. Whether people read them depends, at least partly, on the frequency of commits in your project. Do developers tend to commit one big change at the end of the day, or many small changes throughout the day? The closer your project is to the latter, the thicker the barrage of tiny commit notices raining down on the developers all day long, and the less inclined they will be to pay attention to each message. This doesn't mean the notices aren't useful, just that you shouldn't count on every person reading every message. It's still a convenient way for people to keep tabs on who's doing what (without the intrusiveness of watches). When the emails go to a publicly subscribable mailing list, they are a wonderful mechanism for giving interested users (and future developers!) a chance to see what happens in the code on a daily basis. You may want to consider having a designated developer who watches all log messages and has an overview of activity across the entire project (of course, a good project leader will probably be doing this anyway). If there are clear divisions of responsibility - say, certain developers are "in charge of" certain subdirectories of the project - you could do some fancy scripting in CVSROOT/loginfo to see that each responsible party receives specially marked notices of changes made in their area. This will help ensure that the developers will at least read the email that pertains to their subdirectories. A more interesting side effect happens when commit emails aren't ignored. People start to use them as a realtime communications method. Here's the kind of log message that can result: Finished feedback form; fixed the fonts and background colors on the home page. Whew! Anyone want to go to Mon Lung for lunch? There's nothing wrong with this, and it makes the logs more fun to read over later. However, people need to be aware that log messages, such as the following, are not only distributed by email but is also preserved forever in the project's history. For example, griping about customer specifications is a frequent pastime among programmers; it's not hard to imagine someone committing a log message like this one, knowing that the other programmers will soon see it in their email: Truncate four-digit years to two-digits in input. What the customer wants, the customer gets, no matter how silly & wrong. Sigh. This makes for an amusing email, but what happens if the customer reviews the logs someday? (I'll bet similar concerns have led more than one site to set up CVSROOT/loginfo so that it invokes scripts to guard against offensive words in log messages!) The overall effect of commit emails seems to be that people become less willing to write short or obscure log messages, which is probably a good thing. However, they may need to be reminded that their audience is anyone who might ever read the logs, not just the people receiving commit emails. Node:Changing A Log Message After Commit, Next:Getting Rid Of A Working Copy, Previous:Log Messages And Commit Emails, Up:Advanced CVS Changing A Log Message After CommitJust in case someone does commit a regrettable log message, CVS enables you to rewrite logs after they've been committed. It's done with the -m option to the admin command (this command is covered in more detail later in this chapter) and allows you to change one log message (per revision, per file) at a time. Here's how it works: floss$ cvs admin -m 1.7:"Truncate four-digit years to two in input." date.c RCS file: /usr/local/newrepos/someproj/date.c,v done floss$ The original, offensive log message that was committed with revision 1.7 has been replaced with a perfectly innocent - albeit duller - message. Don't forget the colon separating the revision number from the new log message. If the new log message consists of multiple lines, put it in a file and do this: floss$ cvs admin -m 1.7:"`cat new-log-message.txt`" date.c (This example was sent in by Peter Ross <peter.ross@miscrit.be>; note that it only works for Unix users.) If the bad message was committed into multiple files, you'll have to run cvs admin separately for each one, because the revision number is different for each file. Therefore, this is one of the few commands in CVS that requires you to pass a single file name as argument: floss$ cvs admin -m 1.2:"very boring log message" hello.c README.txt foo.gif cvs admin: while processing more than one file: cvs [admin aborted]: attempt to specify a numeric revision floss$ Confusingly, you get the same error if you pass no file names (because CVS then assumes all the files in the current directory and below are implied arguments): floss$ cvs admin -m 1.2:"very boring log message" cvs admin: while processing more than one file: cvs [admin aborted]: attempt to specify a numeric revision floss$ (As is unfortunately often the case with CVS error messages, you have to see things from CVS's point of view before the message makes sense!) Invoking Although its name might seem to imply that only the designated CVS
administrator can use it, in fact anyone can run Node:Getting Rid Of A Working Copy, Next:History -- A Summary Of Repository Activity, Previous:Changing A Log Message After Commit, Up:Advanced CVS Getting Rid Of A Working CopyIn typical CVS usage, the way to get rid of a working copy directory tree is to remove it like any other directory tree: paste$ rm -rf myproj However, if you eliminate your working copy this way, other developers will not know that you have stopped using it. CVS provides a command to relinquish a working copy explicitly. Think of release as the opposite of checkout - you're telling the repository that you're done with the working copy now. Like checkout, release is invoked from the parent directory of the tree: paste$ pwd /home/qsmith/myproj paste$ cd .. paste$ ls myproj paste$ cvs release myproj You have [0] altered files in this repository. Are you sure you want to release directory 'myproj': y paste$ If there are any uncommitted changes in the repository, the release fails, meaning that it just lists the modified files and otherwise has no effect. Assuming the tree is clean (totally up to date), release records in the repository that the working copy has been released. You can also have release automatically delete the working tree for you, by passing the -d flag: paste$ ls myproj paste$ cvs release -d myproj You have [0] altered files in this repository. Are you sure you want to release (and delete) directory 'myproj: y paste$ ls paste$ As of CVS version 1.10.6, the release command is not able to deduce the
repository's location by examining the working copy (this is because
release is invoked from above the working copy, not within it). You
must pass the The Cederqvist claims that if you use release instead of just deleting
the working tree, people with watches set on the released files will be
notified just as if you had run Node:History -- A Summary Of Repository Activity, Next:Annotations -- A Detailed View Of Project Activity, Previous:Getting Rid Of A Working Copy, Up:Advanced CVS History - A Summary Of Repository ActivityIn Repository Administration, I briefly mentioned the cvs history command. This command displays a summary of all checkouts, commits, updates, rtags, and releases done in the repository (at least, since logging was enabled by the creation of the CVSROOT/history file in the repository). You can control the format and contents of the summary with various options. The first step is to make sure that logging is enabled in your repository. The repository administrator should first make sure there is a history file floss$ cd /usr/local/newrepos/CVSROOT floss$ ls -l history ls: history: No such file or directory floss$ and if there isn't one, create it, as follows: floss$ touch history floss$ ls -l history -rw-r--r-- 1 jrandom cvs 0 Jul 22 14:57 history floss$ This history file also needs to be writeable by everyone who uses the repository, otherwise they'll get an error every time they try to run a CVS command that modifies that file. The easiest way is simply to make the file world-writeable: floss$ chmod a+rw history floss$ ls -l history -rw-rw-rw- 1 jrandom cvs 0 Jul 22 14:57 history floss$ If the repository was created with the The rest of these examples assume that history logging has been enabled for a while, so that data has had time to accumulate in the history file. The output of cvs history is somewhat terse (it's probably intended to be parsed by programs rather than humans, although it is readable with a little study). Let's run it once and see what we get: paste$ pwd /home/qsmith/myproj paste$ cvs history -e -a O 07/25 15:14 +0000 qsmith myproj =mp= ~/* M 07/25 15:16 +0000 qsmith 1.14 hello.c myproj == ~/mp U 07/25 15:21 +0000 qsmith 1.14 README.txt myproj == ~/mp G 07/25 15:21 +0000 qsmith 1.15 hello.c myproj == ~/mp A 07/25 15:22 +0000 qsmith 1.1 goodbye.c myproj == ~/mp M 07/25 15:23 +0000 qsmith 1.16 hello.c myproj == ~/mp M 07/25 15:26 +0000 qsmith 1.17 hello.c myproj == ~/mp U 07/25 15:29 +0000 qsmith 1.2 goodbye.c myproj == ~/mp G 07/25 15:29 +0000 qsmith 1.18 hello.c myproj == ~/mp M 07/25 15:30 +0000 qsmith 1.19 hello.c myproj == ~/mp O 07/23 03:45 +0000 jrandom myproj =myproj= ~/src/* F 07/23 03:48 +0000 jrandom =myproj= ~/src/* F 07/23 04:06 +0000 jrandom =myproj= ~/src/* M 07/25 15:12 +0000 jrandom 1.13 README.txt myproj == ~/src/myproj U 07/25 15:17 +0000 jrandom 1.14 hello.c myproj == ~/src/myproj M 07/25 15:18 +0000 jrandom 1.14 README.txt myproj == ~/src/myproj M 07/25 15:18 +0000 jrandom 1.15 hello.c myproj == ~/src/myproj U 07/25 15:23 +0000 jrandom 1.1 goodbye.c myproj == ~/src/myproj U 07/25 15:23 +0000 jrandom 1.16 hello.c myproj == ~/src/myproj U 07/25 15:26 +0000 jrandom 1.1 goodbye.c myproj == ~/src/myproj G 07/25 15:26 +0000 jrandom 1.17 hello.c myproj == ~/src/myproj M 07/25 15:27 +0000 jrandom 1.18 hello.c myproj == ~/src/myproj C 07/25 15:30 +0000 jrandom 1.19 hello.c myproj == ~/src/myproj M 07/25 15:31 +0000 jrandom 1.20 hello.c myproj == ~/src/myproj M 07/25 16:29 +0000 jrandom 1.3 whatever.c myproj/a-subdir == ~/src/myproj paste$ There, isn't that clear? Before we examine the output, notice that the invocation included two options: -e and -a. When you run history, you almost always want to pass options telling it what data to report and how to report it. In this respect, it differs from most other CVS commands, which usually do something useful when invoked without any options. In this example, the two flags meant "everything" (show every kind of event that happened) and "all" (for all users), respectively. Another way that history differs from other commands is that, although
it is usually invoked from within a working copy, it does not restrict
its output to that working copy's project. Instead, it shows all
history events from all projects in the repository - the working copy
merely serves to tell CVS from which repository to retrieve the history
data. (In the preceding example, the only history data in that
repository is for the The general format of the output is: CODE DATE USER [REVISION] [FILE] PATH_IN_REPOSITORY ACTUAL_WORKING_COPY_NAME The code letters refer to various CVS operations, as shown in Table 6.1. For operations (such as checkout) that are about the project as a whole rather than about individual files, the revision and file are omitted, and the repository path is placed between the equal signs. Although the output of the history command was designed to be compact, parseable input for other programs, CVS still gives you a lot of control over its scope and content. The options shown in Table 6.2 control what types of events get reported. Table 6.1 The meaning of the code letters. Letter Meaning ====== ========================================================= O Checkout T Tag F Release W Update (no user file, remove from entries file) U Update (file overwrote unmodified user file) G Update (file was merged successfully into modified user file) C Update (file was merged, but conflicts w/ modified user file) M Commit (from modified file) A Commit (an added file) R Commit (the removal of a file) E Export Table 6.2 Options to filter by event type. Option Meaning ========== ========================================================= -m MODULE Show historical events affecting MODULE. -c Show commit events. -o Show checkout events. -T Show tag events. -x CODE(S) Show all events of type CODE (one or more of OTFWUGCMARE). -e Show all types of events, period. Once you have selected what type of events you want reported, you can filter further with the options shown in Table 6.3. Table 6.3 Options to filter by user. Option Meaning ========== ========================================================= -a Show actions taken by all users -w Show only actions taken from within this working copy -l Show only the last time this user took the action -u USER Show records for USER Node:Annotations -- A Detailed View Of Project Activity, Next:Annotations And Branches, Previous:History -- A Summary Of Repository Activity, Up:Advanced CVS Annotations - A Detailed View Of Project ActivityThe annotate CommandIf the history command gives an overview of project activity, the
annotate command is a way of attaching a zoom lens to the view.
With floss$ cvs annotate Annotations for README.txt *************** 1.14 (jrandom 25-Jul-99): blah 1.13 (jrandom 25-Jul-99): test 3 for history 1.12 (qsmith 19-Jul-99): test 2 1.11 (qsmith 19-Jul-99): test 1.10 (jrandom 12-Jul-99): blah 1.1 (jrandom 20-Jun-99): Just a test project. 1.4 (jrandom 21-Jun-99): yeah. 1.5 (jrandom 21-Jun-99): nope. Annotations for hello.c *************** 1.1 (jrandom 20-Jun-99): #include <stdio.h> 1.1 (jrandom 20-Jun-99): 1.1 (jrandom 20-Jun-99): void 1.1 (jrandom 20-Jun-99): main () 1.1 (jrandom 20-Jun-99): { 1.15 (jrandom 25-Jul-99): /* another test for history */ 1.13 (qsmith 19-Jul-99): /* random change number two */ 1.10 (jrandom 12-Jul-99): /* test */ 1.21 (jrandom 25-Jul-99): printf ("Hellooo, world!\n"); 1.3 (jrandom 21-Jun-99): printf ("hmmm\n"); 1.4 (jrandom 21-Jun-99): printf ("double hmmm\n"); 1.11 (qsmith 18-Jul-99): /* added this comment */ 1.16 (qsmith 25-Jul-99): /* will merge these changes */ 1.18 (jrandom 25-Jul-99): /* will merge these changes too */ 1.2 (jrandom 21-Jun-99): printf ("Goodbye, world!\n"); 1.1 (jrandom 20-Jun-99): } Annotations for a-subdir/whatever.c *************** 1.3 (jrandom 25-Jul-99): /* A completely non-empty C file. */ Annotations for a-subdir/subsubdir/fish.c *************** 1.2 (jrandom 25-Jul-99): /* An almost completely empty C file. */ Annotations for b-subdir/random.c *************** 1.1 (jrandom 20-Jun-99): /* A completely empty C file. */ floss$ The output of annotate is pretty intuitive. On the left are the revision number, developer, and date on which the line in question was added or last modified. On the right is the line itself, as of the current revision. Because every line is annotated, you can actually see the entire contents of the file, pushed over to the right by the annotation information. If you specify a revision number or tag, the annotations are given as of that revision, meaning that it shows the most recent modification to each line at or before that revision. This is probably the most common way to use annotations - examining a particular revision of a single file to determine which developers were active in which parts of the file. For example, in the output of the previous example, you can see that the most recent revision of hello.c is 1.21, in which jrandom did something to the line: printf ("Hellooo, world!\n"); One way to find out what she did is to diff that revision against the previous one: floss$ cvs diff -r 1.20 -r 1.21 hello.c Index: hello.c =================================================================== RCS file: /usr/local/newrepos/myproj/hello.c,v retrieving revision 1.20 retrieving revision 1.21 diff -r1.20 -r1.21 9c9 < printf ("Hello, world!\n"); -- > printf ("Hellooo, world!\n"); floss$ Another way to find out, while still retaining a file-wide view of everyone's activity, is to compare the current annotations with the annotations from a previous revision: floss$ cvs annotate -r 1.20 hello.c Annotations for hello.c *************** 1.1 (jrandom 20-Jun-99): #include <stdio.h> 1.1 (jrandom 20-Jun-99): 1.1 (jrandom 20-Jun-99): void 1.1 (jrandom 20-Jun-99): main () 1.1 (jrandom 20-Jun-99): { 1.15 (jrandom 25-Jul-99): /* another test for history */ 1.13 (qsmith 19-Jul-99): /* random change number two */ 1.10 (jrandom 12-Jul-99): /* test */ 1.1 (jrandom 20-Jun-99): printf ("Hello, world!\n"); 1.3 (jrandom 21-Jun-99): printf ("hmmm\n"); 1.4 (jrandom 21-Jun-99): printf ("double hmmm\n"); 1.11 (qsmith 18-Jul-99): /* added this comment */ 1.16 (qsmith 25-Jul-99): /* will merge these changes */ 1.18 (jrandom 25-Jul-99): /* will merge these changes too */ 1.2 (jrandom 21-Jun-99): printf ("Goodbye, world!\n"); 1.1 (jrandom 20-Jun-99): } floss$ Although the diff reveals the textual facts of the change more concisely, the annotation may be preferable because it places them in their historical context by showing how long the previous incarnation of the line had been present (in this case, all the way since revision 1.1). That knowledge can help you decide whether to look at the logs to find out the motivation for the change: floss$ cvs log -r 1.21 hello.c RCS file: /usr/local/newrepos/myproj/hello.c,v Working file: hello.c head: 1.21 branch: locks: strict access list: symbolic names: random-tag: 1.20 start: 1.1.1.1 jrandom: 1.1.1 keyword substitution: kv total revisions: 22; selected revisions: 1 description: ---------------------------- revision 1.21 date: 1999/07/25 20:17:42; author: jrandom; state: Exp; lines: +1 -1 say hello with renewed enthusiasm ============================================================================ floss$ In addition to -r, you can also filter annotations using the -D DATE option: floss$ cvs annotate -D "5 weeks ago" hello.c Annotations for hello.c *************** 1.1 (jrandom 20-Jun-99): #include <stdio.h> 1.1 (jrandom 20-Jun-99): 1.1 (jrandom 20-Jun-99): void 1.1 (jrandom 20-Jun-99): main () 1.1 (jrandom 20-Jun-99): { 1.1 (jrandom 20-Jun-99): printf ("Hello, world!\n"); 1.1 (jrandom 20-Jun-99): } floss$ cvs annotate -D "3 weeks ago" hello.c Annotations for hello.c *************** 1.1 (jrandom 20-Jun-99): #include <stdio.h> 1.1 (jrandom 20-Jun-99): 1.1 (jrandom 20-Jun-99): void 1.1 (jrandom 20-Jun-99): main () 1.1 (jrandom 20-Jun-99): { 1.1 (jrandom 20-Jun-99): printf ("Hello, world!\n"); 1.3 (jrandom 21-Jun-99): printf ("hmmm\n"); 1.4 (jrandom 21-Jun-99): printf ("double hmmm\n"); 1.2 (jrandom 21-Jun-99): printf ("Goodbye, world!\n"); 1.1 (jrandom 20-Jun-99): } floss$ Node:Annotations And Branches, Next:Using Keyword Expansion, Previous:Annotations -- A Detailed View Of Project Activity, Up:Advanced CVS Annotations And BranchesBy default, annotation always shows activity on the main trunk of
development. Even when invoked from a branch working copy, it shows
annotations for the trunk unless you specify otherwise. (This tendency
to favor the trunk is either a bug or a feature, depending on your point
of view.) You can force CVS to annotate a branch by passing the branch
tag as an argument to -r. Here is an example from a working copy in
which hello.c is on a branch named floss$ cvs status hello.c =================================================================== File: hello.c Status: Up-to-date Working revision: 1.10.2.2 Sun Jul 25 21:29:05 1999 Repository revision: 1.10.2.2 /usr/local/newrepos/myproj/hello.c,v Sticky Tag: Brancho_Gratuito (branch: 1.10.2) Sticky Date: (none) Sticky Options: (none) floss$ cvs annotate hello.c Annotations for hello.c *************** 1.1 (jrandom 20-Jun-99): #include <stdio.h> 1.1 (jrandom 20-Jun-99): 1.1 (jrandom 20-Jun-99): void 1.1 (jrandom 20-Jun-99): main () 1.1 (jrandom 20-Jun-99): { 1.10 (jrandom 12-Jul-99): /* test */ 1.1 (jrandom 20-Jun-99): printf ("Hello, world!\n"); 1.3 (jrandom 21-Jun-99): printf ("hmmm\n"); 1.4 (jrandom 21-Jun-99): printf ("double hmmm\n"); 1.2 (jrandom 21-Jun-99): printf ("Goodbye, world!\n"); 1.1 (jrandom 20-Jun-99): } floss$ cvs annotate -r Brancho_Gratuito hello.c Annotations for hello.c *************** 1.1 (jrandom 20-Jun-99): #include <stdio.h> 1.1 (jrandom 20-Jun-99): 1.1 (jrandom 20-Jun-99): void 1.1 (jrandom 20-Jun-99): main () 1.1 (jrandom 20-Jun-99): { 1.10 (jrandom 12-Jul-99): /* test */ 1.1 (jrandom 20-Jun-99): printf ("Hello, world!\n"); 1.10.2.2 (jrandom 25-Jul-99): printf ("hmmmmm\n"); 1.4 (jrandom 21-Jun-99): printf ("double hmmm\n"); 1.10.2.1 (jrandom 25-Jul-99): printf ("added this line"); 1.2 (jrandom 21-Jun-99): printf ("Goodbye, world!\n"); 1.1 (jrandom 20-Jun-99): } floss$ You can also pass the branch number itself: floss$ cvs annotate -r 1.10.2 hello.c Annotations for hello.c *************** 1.1 (jrandom 20-Jun-99): #include <stdio.h> 1.1 (jrandom 20-Jun-99): 1.1 (jrandom 20-Jun-99): void 1.1 (jrandom 20-Jun-99): main () 1.1 (jrandom 20-Jun-99): { 1.10 (jrandom 12-Jul-99): /* test */ 1.1 (jrandom 20-Jun-99): printf ("Hello, world!\n"); 1.10.2.2 (jrandom 25-Jul-99): printf ("hmmmmm\n"); 1.4 (jrandom 21-Jun-99): printf ("double hmmm\n"); 1.10.2.1 (jrandom 25-Jul-99): printf ("added this line"); 1.2 (jrandom 21-Jun-99): printf ("Goodbye, world!\n"); 1.1 (jrandom 20-Jun-99): } floss$ or a full revision number from the branch: floss$ cvs annotate -r 1.10.2.1 hello.c Annotations for hello.c *************** 1.1 (jrandom 20-Jun-99): #include <stdio.h> 1.1 (jrandom 20-Jun-99): 1.1 (jrandom 20-Jun-99): void 1.1 (jrandom 20-Jun-99): main () 1.1 (jrandom 20-Jun-99): { 1.10 (jrandom 12-Jul-99): /* test */ 1.1 (jrandom 20-Jun-99): printf ("Hello, world!\n"); 1.3 (jrandom 21-Jun-99): printf ("hmmm\n"); 1.4 (jrandom 21-Jun-99): printf ("double hmmm\n"); 1.10.2.1 (jrandom 25-Jul-99): printf ("added this line"); 1.2 (jrandom 21-Jun-99): printf ("Goodbye, world!\n"); 1.1 (jrandom 20-Jun-99): } floss$ If you do this, remember that the numbers are only valid for that particular file. In general, it's probably better to use the branch name wherever possible. Node:Using Keyword Expansion, Next:Going Out On A Limb (How To Work With Branches And Survive), Previous:Annotations And Branches, Up:Advanced CVS Using Keyword ExpansionYou may recall a brief mention of $Author$ then when updating the file to a given revision, CVS will expand it to the username of the person who committed that revision: $Author: jrandom $ CVS is also sensitive to keywords in their expanded form, so that once expanded, they continue to be updated as appropriate. Although keywords don't actually offer any information that's not available by other means, they give people a convenient way to see revision control facts embedded in the text of the file itself, rather than by invoking some arcane CVS operation. Here are a few other commonly used keywords: $Date$ ==> date of last commit, expands to ==> $Date: 1999/07/26 06:39:46 $ $Id$ ==> filename, revision, date, and author; expands to ==> $Id: hello.c,v 1.11 1999/07/26 06:39:46 jrandom Exp $ $Revision$ ==> exactly what you think it is, expands to ==> $Revision: 1.11 $ $Source$ ==> path to corresponding repository file, expands to ==> $Source: /usr/local/newrepos/tossproj/hello.c,v $ $Log$ ==> accumulating log messages for the file, expands to ==> $Log: hello.c,v $ Revision 1.2 1999/07/26 06:47:52 jrandom ...and this is the second log message. Revision 1.1 1999/07/26 06:39:46 jrandom This is the first log message... The $Log$ keyword is the only one of these that expands to cover multiple lines, so its behavior is unique. Unlike the others, it does not replace the old expansion with the new one, but instead inserts the latest expansion, plus an additional blank line, right after the keyword (thereby pushing any previous expansions downward). Furthermore, any text between the beginning of the line and $Log is used as a prefix for the expansions (this is done to ensure that the log messages stay commented in program code). For example, if you put this into the file // $Log$ it will expand to something like this on the first commit: // $Log: hello.c,v $ // Revision 1.14 1999/07/26 07:03:20 jrandom // this is the first log message... // this on the second: // $Log: hello.c,v $ // Revision 1.15 1999/07/26 07:04:40 jrandom // ...and this is the second log message... // // Revision 1.14 1999/07/26 07:03:20 jrandom // this is the first log message... // and so on: // $Log: hello.c,v $ // Revision 1.16 1999/07/26 07:05:34 jrandom // ...and this is the third! // // Revision 1.15 1999/07/26 07:04:40 jrandom // ...and this is the second log message... // // Revision 1.14 1999/07/26 07:03:20 jrandom // this is the first log message... // You may not want to keep your entire log history in the file all the time; if you do, you can always remove the older sections when it starts to get too lengthy. It's certainly more convenient than running cvs log, and it may be worthwhile in projects where people must constantly read over the logs. A more common technique may be to include $Revision$ in a file and use it as the version number for the program. This can work if the project consists of essentially one file or undergoes frequent releases and has at least one file that is guaranteed to be modified between every release. You can even use an RCS keyword as a value in program code: VERSION = "$Revision: 1.114 $"; CVS expands that keyword just like any other; it has no concept of the programming language's semantics and does not assume that the double quotes protect the string in any way. A complete list of keywords (there are a few more, rather obscure ones) is given in CVS Reference. Node:Going Out On A Limb (How To Work With Branches And Survive), Next:Tracking Third-Party Sources (Vendor Branches), Previous:Using Keyword Expansion, Up:Advanced CVS Going Out On A Limb (How To Work With Branches And Survive)Branches are simultaneously one of the most important and most easily misused features of CVS. Isolating risky or disruptive changes onto a separate line of development until they stabilize can be immensely helpful. If not properly managed, however, branches can quickly propel a project into confusion and cascading chaos, as people lose track of what changes have been merged when.
Node:Some Principles For Working With Branches, Next:Merging Repeatedly Into The Trunk, Up:Going Out On A Limb (How To Work With Branches And Survive) Some Principles For Working With BranchesTo work successfully with branches, your development group should adhere to these principles:
With those principles in mind, let's take a look at a typical branch development scenario. We'll have jrandom on the trunk and qsmith on the branch, but note that there could just as well be multiple developers on the trunk and/or on the branch. Regular development along either line can involve any number of people; however, the tagging and merging are best done by one person on each side, as you'll see. Node:Merging Repeatedly Into The Trunk, Next:The Dovetail Approach -- Merging In And Out Of The Trunk, Previous:Some Principles For Working With Branches, Up:Going Out On A Limb (How To Work With Branches And Survive) Merging Repeatedly Into The TrunkLet's assume qsmith needs to do development on a branch for a while, to avoid destabilizing the trunk that he shares with jrandom. The first step is to create the branch. Notice how qsmith creates a regular (non-branch) tag at the branch point first, and then creates the branch: paste$ pwd /home/qsmith/myproj paste$ cvs tag Root-of-Exotic_Greetings cvs tag: Tagging . T README.txt T foo.gif T hello.c cvs tag: Tagging a-subdir T a-subdir/whatever.c cvs tag: Tagging a-subdir/subsubdir T a-subdir/subsubdir/fish.c cvs tag: Tagging b-subdir T b-subdir/random.c paste$ cvs tag -b Exotic_Greetings-branch cvs tag: Tagging . T README.txt T foo.gif T hello.c cvs tag: Tagging a-subdir T a-subdir/whatever.c cvs tag: Tagging a-subdir/subsubdir T a-subdir/subsubdir/fish.c cvs tag: Tagging b-subdir T b-subdir/random.c paste$ The point of tagging the trunk first is that it may be necessary someday to retrieve the trunk as it was the moment the branch was created. If you ever need to do that, you'll have to have a way of referring to the trunk snapshot without referring to the branch itself. Obviously, you can't use the branch tag because that would retrieve the branch, not the revisions in the trunk that form the root of the branch. The only way to do it is to make a regular tag at the same revisions the branch sprouts from. (Some people stick to this rule so faithfully that I considered listing it as "Branching Principle Number 4: Always create a non-branch tag at the branch point." However, many sites don't do it, and they generally seem to do okay, so it's really a matter of taste.) From here on, I will refer to this non-branch tag as the branch point tag. Notice also that a naming convention is being adhered to: The branch
point tag begins with Of course, I'm being extra pedantic here. In smallish projects, where everyone knows who's doing what and confusion is easy to recover from, these conventions don't have to be used. Whether you use a branch point tag or have a strict naming convention for your tags depends on the complexity of the project and the branching scheme. (Also, don't forget that you can always go back later and update old tags to use new conventions by retrieving an old tagged version, adding the new tag, and then deleting the old tag.) Now, qsmith is ready to start working on the branch: paste$ cvs update -r Exotic_Greetings-branch cvs update: Updating . cvs update: Updating a-subdir cvs update: Updating a-subdir/subsubdir cvs update: Updating b-subdir paste$ He makes some changes to a couple of files and commits them on the branch: paste$ emacs README.txt a-subdir/whatever.c b-subdir/random.c ... paste$ cvs ci -m "print greeting backwards, etc" cvs commit: Examining . cvs commit: Examining a-subdir cvs commit: Examining a-subdir/subsubdir cvs commit: Examining b-subdir Checking in README.txt; /usr/local/newrepos/myproj/README.txt,v <-- README.txt new revision: 1.14.2.1; previous revision: 1.14 done Checking in a-subdir/whatever.c; /usr/local/newrepos/myproj/a-subdir/whatever.c,v <-- whatever.c new revision: 1.3.2.1; previous revision: 1.3 done Checking in b-subdir/random.c; /usr/local/newrepos/myproj/b-subdir/random.c,v <-- random.c new revision: 1.1.1.1.2.1; previous revision: 1.1.1.1 done paste$ Meanwhile, jrandom is continuing to work on the trunk. She modifies two of the three files that qsmith touched. Just for kicks, we'll have her make changes that conflict with qsmith's work: floss$ emacs README.txt whatever.c ... floss$ cvs ci -m "some very stable changes indeed" cvs commit: Examining . cvs commit: Examining a-subdir cvs commit: Examining a-subdir/subsubdir cvs commit: Examining b-subdir Checking in README.txt; /usr/local/newrepos/myproj/README.txt,v <-- README.txt new revision: 1.15; previous revision: 1.14 done Checking in a-subdir/whatever.c; /usr/local/newrepos/myproj/a-subdir/whatever.c,v <-- whatever.c new revision: 1.4; previous revision: 1.3 done floss$ The conflict is not apparent yet, of course, because neither developer has tried to merge branch and trunk. Now, jrandom does the merge: floss$ cvs update -j Exotic_Greetings-branch cvs update: Updating . RCS file: /usr/local/newrepos/myproj/README.txt,v retrieving revision 1.14 retrieving revision 1.14.2.1 Merging differences between 1.14 and 1.14.2.1 into README.txt rcsmerge: warning: conflicts during merge cvs update: Updating a-subdir RCS file: /usr/local/newrepos/myproj/a-subdir/whatever.c,v retrieving revision 1.3 retrieving revision 1.3.2.1 Merging differences between 1.3 and 1.3.2.1 into whatever.c rcsmerge: warning: conflicts during merge cvs update: Updating a-subdir/subsubdir cvs update: Updating b-subdir RCS file: /usr/local/newrepos/myproj/b-subdir/random.c,v retrieving revision 1.1.1.1 retrieving revision 1.1.1.1.2.1 Merging differences between 1.1.1.1 and 1.1.1.1.2.1 into random.c floss$ cvs update cvs update: Updating . C README.txt cvs update: Updating a-subdir C a-subdir/whatever.c cvs update: Updating a-subdir/subsubdir cvs update: Updating b-subdir M b-subdir/random.c floss$ Two of the files conflict. No big deal; with her usual savoir-faire, jrandom resolves the conflicts, commits, and tags the trunk as successfully merged: floss$ emacs README.txt a-subdir/whatever.c ... floss$ cvs ci -m "merged from Exotic_Greetings-branch (conflicts resolved)" cvs commit: Examining . cvs commit: Examining a-subdir cvs commit: Examining a-subdir/subsubdir cvs commit: Examining b-subdir Checking in README.txt; /usr/local/newrepos/myproj/README.txt,v <-- README.txt new revision: 1.16; previous revision: 1.15 done Checking in a-subdir/whatever.c; /usr/local/newrepos/myproj/a-subdir/whatever.c,v <-- whatever.c new revision: 1.5; previous revision: 1.4 done Checking in b-subdir/random.c; /usr/local/newrepos/myproj/b-subdir/random.c,v <-- random.c new revision: 1.2; previous revision: 1.1 done floss$ cvs tag merged-Exotic_Greetings cvs tag: Tagging . T README.txt T foo.gif T hello.c cvs tag: Tagging a-subdir T a-subdir/whatever.c cvs tag: Tagging a-subdir/subsubdir T a-subdir/subsubdir/fish.c cvs tag: Tagging b-subdir T b-subdir/random.c floss$ Meanwhile, qsmith needn't wait for the merge to finish before continuing development, as long as he makes a tag for the batch of changes from which jrandom merged (later, jrandom will need to know this tag name; in general, branches depend on frequent and thorough developer communications): paste$ cvs tag Exotic_Greetings-1 cvs tag: Tagging . T README.txt T foo.gif T hello.c cvs tag: Tagging a-subdir T a-subdir/whatever.c cvs tag: Tagging a-subdir/subsubdir T a-subdir/subsubdir/fish.c cvs tag: Tagging b-subdir T b-subdir/random.c paste$ emacs a-subdir/whatever.c ... paste$ cvs ci -m "print a randomly capitalized greeting" cvs commit: Examining . cvs commit: Examining a-subdir cvs commit: Examining a-subdir/subsubdir cvs commit: Examining b-subdir Checking in a-subdir/whatever.c; /usr/local/newrepos/myproj/a-subdir/whatever.c,v <-- whatever.c new revision: 1.3.2.2; previous revision: 1.3.2.1 done paste$ And of course, qsmith should tag those changes once he's done: paste$ cvs -q tag Exotic_Greetings-2 T README.txt T foo.gif T hello.c T a-subdir/whatever.c T a-subdir/subsubdir/fish.c T b-subdir/random.c paste$ While all this is going on, jrandom makes a change in a different file, one that qsmith hasn't touched in his new batch of edits: floss$ emacs README.txt ... floss$ cvs ci -m "Mention new Exotic Greeting features" README.txt Checking in README.txt; /usr/local/newrepos/myproj/README.txt,v <-- README.txt new revision: 1.17; previous revision: 1.16 done floss$ At this point, qsmith has committed a new change on the branch, and jrandom has committed a nonconflicting change in a different file on the trunk. Watch what happens when jrandom tries to merge from the branch again: floss$ cvs -q update -j Exotic_Greetings-branch RCS file: /usr/local/newrepos/myproj/README.txt,v retrieving revision 1.14 retrieving revision 1.14.2.1 Merging differences between 1.14 and 1.14.2.1 into README.txt rcsmerge: warning: conflicts during merge RCS file: /usr/local/newrepos/myproj/a-subdir/whatever.c,v retrieving revision 1.3 retrieving revision 1.3.2.2 Merging differences between 1.3 and 1.3.2.2 into whatever.c rcsmerge: warning: conflicts during merge RCS file: /usr/local/newrepos/myproj/b-subdir/random.c,v retrieving revision 1.1 retrieving revision 1.1.1.1.2.1 Merging differences between 1.1 and 1.1.1.1.2.1 into random.c floss$ cvs -q update C README.txt C a-subdir/whatever.c floss$ There are conflicts! Is that what you expected? The problem lies in the semantics of merging. Back in An Overview of CVS, I explained that when you run floss$ cvs update -j BRANCH in a working copy, CVS merges into the working copy the differences between BRANCH's root and its tip. The trouble with that behavior, in this situation, is that most of those changes had already been incorporated into the trunk the first time that jrandom did a merge. When CVS tried to merge them in again (over themselves, as it were), it naturally registered a conflict. What jrandom really wanted to do was merge into her working copy the changes between the branch's most recent merge and its current tip. You can do this by using two -j flags to update, as you may recall from An Overview of CVS, as long as you know what revision to specify with each flag. Fortunately, qsmith made a tag at exactly the last merge point (hurrah for planning ahead!), so this will be no problem. First, let's have jrandom restore her working copy to a clean state, from which she can redo the merge: floss$ rm README.txt a-subdir/whatever.c floss$ cvs -q update cvs update: warning: README.txt was lost U README.txt cvs update: warning: a-subdir/whatever.c was lost U a-subdir/whatever.c floss$ Now she's ready to do the merge, this time using qsmith's conveniently placed tag: floss$ cvs -q update -j Exotic_Greetings-1 -j Exotic_Greetings-branch RCS file: /usr/local/newrepos/myproj/a-subdir/whatever.c,v retrieving revision 1.3.2.1 retrieving revision 1.3.2.2 Merging differences between 1.3.2.1 and 1.3.2.2 into whatever.c floss$ cvs -q update M a-subdir/whatever.c floss$ Much better. The change from qsmith has been incorporated into whatever.c; jrandom can now commit and tag: floss$ cvs -q ci -m "merged again from Exotic_Greetings (1)" Checking in a-subdir/whatever.c; /usr/local/newrepos/myproj/a-subdir/whatever.c,v <-- whatever.c new revision: 1.6; previous revision: 1.5 done floss$ cvs -q tag merged-Exotic_Greetings-1 T README.txt T foo.gif T hello.c T a-subdir/whatever.c T a-subdir/subsubdir/fish.c T b-subdir/random.c floss$ Even if qsmith had forgotten to tag at the merge point, all hope would not be lost. If jrandom knew approximately when qsmith's first batch of changes had been committed, she could try filtering by date: floss$ cvs update -j Exotic_Greetings-branch:3pm -j Exotic_Greetings_branch Although useful as a last resort, filtering by date is less than ideal because it selects the changes based on people's recollections rather than dependable developer designations. If qsmith's first mergeable set of changes had happened over several commits instead of in one commit, jrandom may mistakenly choose a date or time that would catch some of the changes, but not all of them. There's no reason why each taggable point in qsmith's changes needs to be sent to the repository in a single commit - it just happens to have worked out that way in these examples. In real life, qsmith may make several commits between tags. He can work on the branch in isolation, as he pleases. The point of the tags is to record successive points on the branch where he considers the changes to be mergeable into the trunk. As long as jrandom always merges using two -j flags and is careful to use qsmith's merge tags in the right order and only once each, the trunk should never experience the double-merge problem. Conflicts may occur, but they will be the unavoidable kind that requires human resolution - situations in which both branch and trunk made changes to the same area of code. Node:The Dovetail Approach -- Merging In And Out Of The Trunk, Next:The Flying Fish Approach -- A Simpler Way To Do It, Previous:Merging Repeatedly Into The Trunk, Up:Going Out On A Limb (How To Work With Branches And Survive) The Dovetail Approach - Merging In And Out Of The TrunkMerging repeatedly from branch to trunk is good for the people on the trunk, because they see all of their own changes and all the changes from the branch. However, the developer on the branch never gets to incorporate any of the work being done on the trunk. To allow that, the branch developer needs to add an extra step every now and then (meaning whenever he feels like merging in recent trunk changes and dealing with the inevitable conflicts): paste$ cvs update -j HEAD The special reserved tag The branch developer can likewise use the trunk's merge tags as boundaries, allowing the branch to merge exactly those trunk changes between the last merge and the trunk's current state (the same way the trunk does merges). For example, supposing jrandom had made some changes to hello.c after merging from the branch: floss$ emacs hello.c ... floss$ cvs ci -m "clarify algorithm" hello.c Checking in hello.c; /usr/local/newrepos/myproj/hello.c,v <-- hello.c new revision: 1.22; previous revision: 1.21 done floss$ Then, qsmith can merge those changes into his branch, commit, and, of course, tag: paste$ cvs -q update -j merged-Exotic_Greetings-1 -j HEAD RCS file: /usr/local/newrepos/myproj/hello.c,v retrieving revision 1.21 retrieving revision 1.22 Merging differences between 1.21 and 1.22 into hello.c paste$ cvs -q update M hello.c paste$ cvs -q ci -m "merged trunk, from merged-Exotic_Greetings-1 to HEAD" Checking in hello.c; /usr/local/newrepos/myproj/hello.c,v <-- hello.c new revision: 1.21.2.1; previous revision: 1.21 done paste$ cvs -q tag merged-merged-Exotic_Greetings-1 T README.txt T foo.gif T hello.c T a-subdir/whatever.c T a-subdir/subsubdir/fish.c T b-subdir/random.c paste$ Notice that jrandom did not bother to tag after committing the changes to hello.c, but qsmith did. The principle at work here is that although you don't need to tag after every little change, you should always tag after a merge or after committing your line of development up to a mergeable state. That way, other people - perhaps on other branches - have a reference point against which to base their own merges. Node:The Flying Fish Approach -- A Simpler Way To Do It, Next:Branches And Keyword Expansion -- Natural Enemies, Previous:The Dovetail Approach -- Merging In And Out Of The Trunk, Up:Going Out On A Limb (How To Work With Branches And Survive) The Flying Fish Approach - A Simpler Way To Do ItThere is a simpler, albeit slightly limiting, variant of the preceding. In it, the branch developers freeze while the trunk merges, and then the trunk developers create an entirely new branch, which replaces the old one. The branch developers move onto that branch and continue working. The cycle continues until there is no more need for branch development. It goes something like this (in shorthand - we'll assume jrandom@floss has the trunk and qsmith@paste has the branch, as usual): floss$ cvs tag -b BRANCH-1 paste$ cvs checkout -r BRANCH-1 myproj Trunk and branch both start working; eventually, the developers confer and decide it's time to merge the branch into the trunk: paste$ cvs ci -m "committing all uncommitted changes" floss$ cvs update -j BRANCH-1 All the changes from the branch merge in; the branch developers stop working while the trunk developers resolve any conflicts, commit, tag, and create a new branch: floss$ cvs ci -m "merged from BRANCH-1" floss$ cvs tag merged-from-BRANCH-1 floss$ cvs tag -b BRANCH-2 Now the branch developers switch their working copies over to the new branch; they know they won't lose any uncommitted changes by doing so, because they were up-to-date when the merge happened, and the new branch is coming out of a trunk that has incorporated the changes from the old branch: paste$ cvs update -r BRANCH-2 And the cycle continues in that way, indefinitely; just substitute BRANCH-2 for BRANCH-1 and BRANCH-3 for BRANCH-2. I call this the Flying Fish technique, because the branch is constantly emerging from the trunk, traveling a short distance, then rejoining it. The advantages of this approach are that it's simple (the trunk always merges in all the changes from a given branch) and the branch developers never need to resolve conflicts (they're simply handed a new, clean branch on which to work each time). The disadvantage, of course, is that the branch people must sit idle while the trunk is undergoing merge (which can take an arbitrary amount of time, depending on how many conflicts need to be resolved). Another minor disadvantage is that it results in many little, unused branches laying around instead of many unused non-branch tags. However, if having millions of tiny, obsolete branches doesn't bother you, and you anticipate fairly trouble-free merges, Flying Fish may be the easiest way to go in terms of mental bookkeeping. Whichever way you do it, you should try to keep the separations as short as possible. If the branch and the trunk go too long without merging, they could easily begin to suffer not just from textual drift, but semantic drift as well. Changes that conflict textually are the easiest ones to resolve. Changes that conflict conceptually, but not textually, often prove hardest to find and fix. The isolation of a branch, so freeing to the developers, is dangerous precisely because it shields each side from the effects of others' changes...for a time. When you use branches, communication becomes more vital than ever: Everyone needs to make extra sure to review each others' plans and code to ensure that they're all staying on the same track. Node:Branches And Keyword Expansion -- Natural Enemies, Previous:The Flying Fish Approach -- A Simpler Way To Do It, Up:Going Out On A Limb (How To Work With Branches And Survive) Branches And Keyword Expansion - Natural EnemiesIf your files contain RCS keywords that expand differently on branch and trunk, you're almost guaranteed to get spurious conflicts on every merge. Even if nothing else changed, the keywords are overlapping, and their expansions won't match. For example, if README.txt contains this on the trunk $Revision: 1.14 $ and this on the branch $Revision: 1.14.2.1 $ then when the merge is performed, you'll get the following conflict: floss$ cvs update -j Exotic_Greetings-branch RCS file: /usr/local/newrepos/myproj/README.txt,v retrieving revision 1.14 retrieving revision 1.14.2.1 Merging differences between 1.14 and 1.14.2.1 into README.txt rcsmerge: warning: conflicts during merge floss$ cat README.txt ... <<<<<<< README.txt key $Revision: 1.14 $ ======= key $Revision: 1.14.2.1 $ >>>>>>> 1.14.2.1 ... floss$ To avoid this, you can temporarily disable expansion by passing the -kk option (I don't know what it stands for; "kill keywords" maybe?) when you do the merge: floss$ cvs update -kk -j Exotic_Greetings-branch RCS file: /usr/local/newrepos/myproj/README.txt,v retrieving revision 1.14 retrieving revision 1.14.2.1 Merging differences between 1.14 and 1.14.2.1 into README.txt floss$ cat README.txt ... $Revision$ ... floss$ There is one thing to be careful of, however: If you use -kk, it overrides whatever other keyword expansion mode you may have set for that file. Specifically, this is a problem for binary files, which are normally -kb (which suppresses all keyword expansion and line-end conversion). So if you have to merge binary files in from a branch, don't use -kk. Just deal with the conflicts by hand instead. Node:Tracking Third-Party Sources (Vendor Branches), Next:Exporting For Public Distribution, Previous:Going Out On A Limb (How To Work With Branches And Survive), Up:Advanced CVS Tracking Third-Party Sources (Vendor Branches)Sometimes a site will make local changes to a piece of software received from an outside source. If the outside source does not incorporate the local changes (and there might be many legitimate reasons why it can't), the site has to maintain its changes in each received upgrade of the software. CVS can help with this task, via a feature known as vendor branches. In fact, vendor branches are the explanation behind the puzzling (until now) final two arguments to cvs import: the vendor tag and release tag that I glossed over in An Overview of CVS. Here's how it works. The initial import is just like any other initial import of a CVS project (except that you'll want to choose the vendor tag and release tag with a little care): floss$ pwd /home/jrandom/theirproj-1.0 floss$ cvs import -m "Import of TheirProj 1.0" theirproj Them THEIRPROJ_1_0 N theirproj/INSTALL N theirproj/README N theirproj/src/main.c N theirproj/src/parse.c N theirproj/src/digest.c N theirproj/doc/random.c N theirproj/doc/manual.txt No conflicts created by this import floss$ Then you check out a working copy somewhere, make your local modifications, and commit: floss$ cvs -q co theirproj U theirproj/INSTALL U theirproj/README U theirproj/doc/manual.txt U theirproj/doc/random.c U theirproj/src/digest.c U theirproj/src/main.c U theirproj/src/parse.c floss$ cd theirproj floss$ emacs src/main.c src/digest.c ... floss$ cvs -q update M src/digest.c M src/main.c floss$ cvs -q ci -m "changed digestion algorithm; added comment to main" Checking in src/digest.c; /usr/local/newrepos/theirproj/src/digest.c,v <-- digest.c new revision: 1.2; previous revision: 1.1 done Checking in src/main.c; /usr/local/newrepos/theirproj/src/main.c,v <-- main.c new revision: 1.2; previous revision: 1.1 done floss$ A year later, the next version of the software arrives from Them, Inc., and you must incorporate your local changes into it. Their changes and yours overlap slightly. They've added one new file, modified a couple of files that you didn't touch, but also modified two files that you modified. First you must do another import, this time from the new sources. Almost everything is the same as it was in the initial import - you're importing to the same project in the repository, and on the same vendor branch. The only thing different is the release tag: floss$ pwd /home/jrandom/theirproj-2.0 floss$ cvs -q import -m "Import of TheirProj 2.0" theirproj Them THEIRPROJ_2_0 U theirproj/INSTALL N theirproj/TODO U theirproj/README cvs import: Importing /usr/local/newrepos/theirproj/src C theirproj/src/main.c U theirproj/src/parse.c C theirproj/src/digest.c cvs import: Importing /usr/local/newrepos/theirproj/doc U theirproj/doc/random.c U theirproj/doc/manual.txt 2 conflicts created by this import. Use the following command to help the merge: cvs checkout -jThem:yesterday -jThem theirproj floss$ My goodness - we've never seen CVS try to be so helpful. It's actually telling us what command to run to merge the changes. And it's almost right, too! Actually, the command as given works (assuming that you adjust yesterday to be any time interval that definitely includes the first import but not the second), but I mildly prefer to do it by release tag instead: floss$ cvs checkout -j THEIRPROJ_1_0 -j THEIRPROJ_2_0 theirproj cvs checkout: Updating theirproj U theirproj/INSTALL U theirproj/README U theirproj/TODO cvs checkout: Updating theirproj/doc U theirproj/doc/manual.txt U theirproj/doc/random.c cvs checkout: Updating theirproj/src U theirproj/src/digest.c RCS file: /usr/local/newrepos/theirproj/src/digest.c,v retrieving revision 1.1.1.1 retrieving revision 1.1.1.2 Merging differences between 1.1.1.1 and 1.1.1.2 into digest.c rcsmerge: warning: conflicts during merge U theirproj/src/main.c RCS file: /usr/local/newrepos/theirproj/src/main.c,v retrieving revision 1.1.1.1 retrieving revision 1.1.1.2 Merging differences between 1.1.1.1 and 1.1.1.2 into main.c U theirproj/src/parse.c floss$ Notice how the import told us that there were two conflicts, but the merge only seems to claim one conflict. It seems that CVS's idea of a conflict is a little different when importing than at other times. Basically, import reports a conflict if both you and the vendor modified a file between the last import and this one. However, when it comes time to merge, update sticks with the usual definition of "conflict" - overlapping changes. Changes that don't overlap are merged in the usual way, and the file is simply marked as modified. A quick diff verifies that only one of the files actually has conflict markers: floss$ cvs -q update C src/digest.c M src/main.c floss$ cvs diff -c Index: src/digest.c =================================================================== RCS file: /usr/local/newrepos/theirproj/src/digest.c,v retrieving revision 1.2 diff -c -r1.2 digest.c *** src/digest.c 1999/07/26 08:02:18 1.2 -- src/digest.c 1999/07/26 08:16:15 *************** *** 3,7 **** -- 3,11 ---- void digest () { + <<<<<<< digest.c printf ("gurgle, slorp\n"); + ======= + printf ("mild gurgle\n"); + >>>>>>> 1.1.1.2 } Index: src/main.c =================================================================== RCS file: /usr/local/newrepos/theirproj/src/main.c,v retrieving revision 1.2 diff -c -r1.2 main.c *** src/main.c 1999/07/26 08:02:18 1.2 -- src/main.c 1999/07/26 08:16:15 *************** *** 7,9 **** -- 7,11 ---- { printf ("Goodbye, world!\n"); } + + /* I, the vendor, added this comment for no good reason. */ floss$ From here, it's just a matter of resolving the conflicts as with any other merge: floss$ emacs src/digest.c src/main.c ... floss$ cvs -q update M src/digest.c M src/main.c floss$ cvs diff src/digest.c cvs diff src/digest.c Index: src/digest.c =================================================================== RCS file: /usr/local/newrepos/theirproj/src/digest.c,v retrieving revision 1.2 diff -r1.2 digest.c 6c6 < printf ("gurgle, slorp\n"); -- > printf ("mild gurgle, slorp\n"); floss$ Then commit the changes floss$ cvs -q ci -m "Resolved conflicts with import of 2.0" Checking in src/digest.c; /usr/local/newrepos/theirproj/src/digest.c,v <-- digest.c new revision: 1.3; previous revision: 1.2 done Checking in src/main.c; /usr/local/newrepos/theirproj/src/main.c,v <-- main.c new revision: 1.3; previous revision: 1.2 done floss$ and wait for the next release from the vendor. (Of course, you'll also want to test that your local modifications still work!) ------------------------------------------------------------- Node:Exporting For Public Distribution, Next:The Humble Guru, Previous:Tracking Third-Party Sources (Vendor Branches), Up:Advanced CVS Exporting For Public DistributionCVS is a good distribution mechanism for developers, but most users will obtain the software through a downloadable package instead. This package is generally not a CVS working copy - it's just a source tree that can be easily configured and compiled on the user's system. However, CVS does offer a mechanism to help you create that package,
namely the The floss$ pwd /home/jrandom/myproj floss$ cvs -q tag R_1_0 T README.txt T hello.c T a-subdir/whatever.c T a-subdir/subsubdir/fish.c T b-subdir/random.c floss$ cd .. floss$ cvs -d /usr/local/newrepos -q export -r R_1_0 -d myproj-1.0 myproj U myproj-1.0/README.txt U myproj-1.0/hello.c U myproj-1.0/a-subdir/whatever.c U myproj-1.0/a-subdir/subsubdir/fish.c U myproj-1.0/b-subdir/random.c floss$ cd myproj-1.0 floss$ ls README.txt a-subdir b-subdir hello.c Notice how, since the After the exported copy is created, as in the above example, the following might be sufficient to complete the release, if the project is a simple one: floss$ tar cf myproj-1.0.tar myproj-1.0 floss$ gzip --best myproj-1.0.tar floss$ ls myproj/ myproj-1.0/ myproj-1.0.tar.gz floss$ rm -rf myproj-1.0 floss$ mv myproj-1.0.tar.gz /home/ftp/pub/myproj/ Of course, running all of these commands by hand is rare. More often,
Node:The Humble Guru, Previous:Exporting For Public Distribution, Up:Advanced CVS The Humble GuruIf you read and understood (and better yet, experimented with) everything in this chapter, you may rest assured that there are no big surprises left for you in CVS - at least until someone adds a major new feature to CVS. Everything you need to know to use CVS on a major project has been presented. Before that goes to your head, let me reiterate the suggestion, first made in Chapter 4, that you subscribe to the info-cvs@gnu.org mailing list. Despite having the impoverished signal-to-noise ratio common to most Internet mailing lists, the bits of signal that do come through are almost always worth the wait. I was subscribed during the entire time I wrote this chapter (indeed, for all previous chapters as well), and you would be amazed to know how many important details I learned about CVS's behavior from reading other people's posts. If you're going to be using CVS seriously, and especially if you're the CVS administrator for a group of developers, you can benefit a lot from the shared knowledge of all the other serious users out there. Node:Tips And Troubleshooting, Next:CVS Reference, Previous:Advanced CVS, Up:Top Tips And TroubleshootingI've said in earlier chapters that CVS is not "black box" software. Black boxes don't let you peek inside; they don't give you internal access so that you can fix (or break) things. The premise is that the black box usually doesn't need to be fixed. Most of the time, the software should work perfectly, so users don't need internal access. But when black boxes do fail, they tend to fail completely. Any problem at all is a showstopper, because there aren't many options for repair. CVS is more like a perfectly transparent box - except without the box. Its moving parts are exposed directly to the environment, not hermetically sealed off, and bits of that environment (unexpected file permissions, interrupted commands, competing processes, whatever) can sometimes get inside the mechanism and gum up the gears. But even though CVS does not always work perfectly, it rarely fails completely, either. It has the advantage of graceful degradation; the degree to which it doesn't work is usually proportional to the number and severity of problems in its environment. If you know enough about what CVS is trying to do - and how it's trying to do it - you'll know what to do when things go wrong. Although I can't list all of the problems that you might encounter, I've included some of the more common ones here. This chapter is divided into two sections: The first describes those parts of the environment to which CVS is most sensitive (mainly repository permissions and the working copy administrative area), and the second describes some of the most frequently encountered problems and their solutions. By seeing how to handle these common situations, you will get a feeling for how to approach any unexpected problem in CVS.
Node:The Usual Suspects, Next:General Troubleshooting Tips, Up:Tips And Troubleshooting The Usual SuspectsAs a CVS administrator (read "field doctor"), you will find that 90 percent of your users' problems are caused by inconsistent working copies, and the other 90 percent by incorrect repository permissions. Therefore, before looking at any specific situations, I'll give a quick overview of the working copy administrative area and review a few important things about repository permissions. Node:The Working Copy Administrative Area, Next:Repository Permissions, Up:The Usual Suspects The Working Copy Administrative AreaYou've already seen the basics of working copy structure in An Overview of CVS; in this section, we'll go into a bit more detail. Most of the details concern the files in the CVS/ administrative subdirectories. You already know about Entries, Root, and Repository, but the CVS/ subdirectory can also contain other files, depending on the circumstances. I'll describe those other files here, partly so they don't surprise you when you encounter them, and partly so you can fix them if they ever cause trouble.
|
|||||||||||||||||
With any suggestions or questions please feel free to contact us |