Version Control


Version Control

When you write applications with a few files, it is simple enough to prepare a makefile to automate the software-build process and to avoid worrying about keeping track of changes. Typically, for small projects, you might keep track of changes through comments at the beginning of a file.

This approach works well for a small project, but for larger software projects, you should use some tools that help manage different versions of your applications. In fact, you can benefit from version-control tools even if you are the sole author of a small application. Eventually, you may have trouble remembering what changes you have made. Software version control can help you track these changes.

The Linux software distribution comes with RCS and CVS. Both of these products are collections of tools that help you control software revisions.

The next few sections provide an overview of RCS through some simple examples. A later section briefly discusses CVS and lists some other sources of information about CVS.

Controlling Source Files Using RCS

Source control refers to saving a version of the source code so you can recover a specific version or revision of a file whenever you need it. Essentially, when you modify a source file, the sequence goes something like this:

  1. When you have an initial version of the source file, you archive it-place it under source control.

  2. When you want to make changes in the file, you first get a copy of the current revision. (When you get it this way, the tools should ensure that no one else can modify that revision.)

  3. You make the changes in the source file, test the code, and store the modified file as a new revision.

  4. The next time you want to make changes in the file, you start with the latest revision of the file.

RCS provides the tools that enable you to archive file revisions and update them in a controlled manner. Table 23-5 lists the tools.

Table 23-5: RCS Tools

Tool

Purpose

ci

Creates a new revision of a file, or adds a working file to an RCS file (co stands for check in)

co

Gets a working version of a file for reading. (co -l provides a working file and locks the original so you can modify the working file.) co stands for check out.

ident

Searches for identifiers in a file

merge

Incorporates changes from two files into a third file

rcsdiff

Compares a working file with its RCS file

rcsmerge

Merges different revisions of a file

rlog

Views the history of changes in a file

Suppose you have just finished developing the initial working version of an application and want to use RCS to manage the revisions from now on. The following sections outline the steps to follow to use RCS for your development effort.

Creating Initial RCS Files

The first step in managing source-file revisions by using RCS is to enable RCS to archive the current revision of your files. Follow these steps to put the source file under the control of RCS:

  1. In the directory where you keep your application's source files, create a subdirectory named RCS by typing mkdir RCS. If the RCS subdirectory exists, RCS archives file revisions in this directory.

  2. In each file you plan to place under revision control, add a comment by adding the following RCS identification keyword:

    $Id$

    In a C source file, for example, add the following:

    /*
     * $Id$
     */

    In a makefile, on the other hand, use the following:

    # $Id$

    Later, RCS expands these identifier keywords to include information about the file revision and date.

  3. Check in each file with RCS; use the ci command, and provide a brief description of each file as prompted by ci. Following is how you might check in Makefile:

    ci Makefile
    RCS/Makefile,v  <--  Makefile
    enter description, terminated with single '.' or end of file:
    NOTE: This is NOT the log message!
    >> Makefile for sample programs.
    >> .
    initial revision: 1.1
    done

    When a file is checked in, the ci command creates a corresponding RCS file in the RCS subdirectory. The RCS file's name is the same as the original file, except a ,v is appended to the name. Thus, the RCS file for Makefile is Makefile,v. Also, ci deletes the original source file after it creates the RCS file. To use or edit the file again, you have to extract it by using the co command.

After you follow these steps, all your files are safely stored in RCS files in the RCS sub-directory.

Using the Archived Files

Now suppose you want to edit one of the files (for example, to add a new feature) and rebuild the application. For starters, you need all the source files and the makefile for the compile and link step.

You should extract all these files by using the co command for read-only access (except for the file you want to change). Using the co command is straightforward. To get a working copy of Makefile for read-only use, for example, use the following command:

co Makefile
RCS/Makefile,v --> Makefile
revision 1.1
done

By default, this command looks for an RCS file named RCS/Makefile,v and creates a read-only working copy of it named Makefile.

A copy of Makefile is in the directory. Examine that copy of Makefile to see what happens to the $Id$ keyword that you add as a comment. Here's what my example Makefile shows:

# $Id: Makefile,v 1.1 2003/02/08 03:06:56 naba Exp $

As this example shows, RCS expands each identifier keyword into a string with information. The exact information depends on the identifier.

If you want to modify a file, you have to check it out by using the -l option. If you want to check out a copy of the file xmutil.c for editing, use this command:

co -l xmutil.c
RCS/xmutil.c,v --> xmutil.c
revision 1.1 (locked)
done

Compare this output with that from the previous example of co; the current output confirms that the RCS file is locked. No one else can modify the archived file until you check in the copy you have checked out for editing.

When you check out a file and put a lock on it, no one else can check out the same file for editing. However, anyone can get a copy of the file for read-only use.

After you make changes in a file, you can check it in again by using the ci command, just as you do when you create the RCS file.

Using RCS Identification Keywords

You can use RCS identification keywords-each of which is a string delimited by dollar signs ($...$)-to record information in source files. RCS expands the $Id$ keyword, for example, into summary information about the file, including the filename, revision number, date, and author. All you have to do is put the keyword in the file; RCS takes care of expanding that keyword into the appropriate information. Table 23-6 lists the identification keywords RCS supports.

Table 23-6: Identification Keywords RCS Supports

Keyword

Purpose

$Author$

Login ID of the user who checked in the revision

$Date$

Date and time when the revision was checked in

$Header$

Expands to summary information, including full path name of the RCS file, revision number, date, author, and the state of file revision

$Id$

Same as $Header$, except the RCS filename does not have a directory prefix

$Locker$

Login ID of the user who locked the file (empty if the file is not currently locked)

$Log$

Expands to a log of changes made in the file

$RCSfile$

Name of the RCS file without the directory names

$Revision$

Revision number of the RCS file

$Source$

Expands to the full path name of the RCS file

$State$

Indicates the state of the file revision (whether it is locked or not)

Insider Insight 

At minimum, you may want to use the $Id$ keyword in your files to include summary information about the latest revision.

RCS expands the identifier keywords anywhere in a file. Thus, you might mark an object file (and the executable that uses that object file) by placing the identifier keyword in a string variable. A common practice is to define a string named rcsid as follows:

static const char rcsid[] = "$Id";

Defining the rcsid string causes the object and executable file to contain a string such as the following:

$Id: xmutil.c,v 1.1 2003/02/08 03:09:56 naba Exp naba $

Viewing the Changes Made So Far

Most of the time, the ci and co commands are used to maintain file revisions with RCS. RCS, however, includes several other tools for managing various aspects of version control, such as comparing two revisions, viewing the history of changes, and examining identifiers in files.

If you have checked out a file for modification, you might want to know what changes you have made thus far. You can use the rcsdiff program to see a list of changes. If you've been editing a file named xmutil.c, for example, you can compare the working file against its RCS file, by using this command:

rcsdiff xmutil.c

The rcsdiff program runs the UNIX diff utility to find the differences between the working file and the RCS file.

If necessary, you can even find the differences between two specific revisions of a file by using a command such as the following:

rcsdiff -r1.1 -r1.2 xmutil.c

This command lists the differences between revision 1.1 and 1.2 of the file xmutil.c.

Discarding Changes Made So Far

Sometimes after making changes in a file, you realize the changes are either wrong or unnecessary. In such a case, you want to discard the changes you have made so far.

To discard changes, all you have to do is unlock the RCS file and delete the working copy of the file. To unlock an RCS file, use the rcs command with the -u (unlock) option. The following command discards the current changes in the file named xmutil.c:

rcs -u xmutil.c

Another, more convenient, way to discard changes is to overwrite the current working file with a copy of the former RCS file. To do this, use the co command with -u and -f flags:

co -f -u xmutil.c

The -u option unlocks the checked-out revision, and -f forces co to overwrite the working file with the former revision of that file.

Viewing the Change Log

As you make changes in a file and keep checking in revisions, RCS maintains a log of changes. You can view this log by using the rlog command. The following example shows how to view the log of changes for the file xmutil.c:

rlog xmutil.c

RCS file: RCS/xmutil.c,v
Working file: xmutil.c
head: 1.2
branch:
locks: strict
access list:
symbolic names:
keyword substitution: kv
total revisions: 2;     selected revisions: 2
description:
Motif utilities
----------------------------
revision 1.2
date: 2003/02/08 01:16:47;  author: naba;  state: Exp;  lines: +10 -6
Added new header file
----------------------------
revision 1.1
date: 2002/10/26 01:10:27;  author: naba;  state: Exp;
Initial revision
======================================================================

The first part of the rlog output displays some summary information about the RCS file. The lines following the description: line show the description I enter when I create the RCS file. Following this description is an entry for each revision, with the most recent revision appearing first. Each revision's entry shows the date, the author, and a brief description the author has entered.

Examining Identifier Keywords

If any identifier keywords are embedded in a file, you can view them by using the ident command. If Makefile contains the $Id$ keyword, you can examine the keyword by using this command:

ident Makefile
Makefile:
     $Id: Makefile,v 1.1 2003/02/08 01:06:26 naba Exp $

If you define a string variable with a keyword that eventually gets embedded in a binary file (an object file or an executable file), ident displays those identifiers as well. You can try ident in any binary file to see whether it contains any embedded keywords. This is what I find when I try ident on the file /usr/bin/ident (the executable program for the ident command itself):

ident /usr/bin/ident
/usr/bin/ident:
     $Id: rcsbase.h,v 5.20 1995/06/16 06:19:24 eggert Exp $
     $Id: ident.c,v 5.9 1995/06/16 06:19:24 eggert Exp $
     $Id: rcsmap.c,v 5.3 1995/06/16 06:19:24 eggert Exp $

From this output, you can tell the exact versions of source files that are used to build this version of the ident program.

Performing Concurrent Version Control with CVS

CVS is another source-control tool designed to keep track of changes made by a group of developers working on the same set of files. CVS keeps track of collections of files in a shared directory. An entire collection of files is given a module name; a developer can check out an entire collection of files by using the module name.

CVS uses RCS to save the version-control information in RCS files stored in a directory hierarchy called the repository, which is separate from the developer's working directory. Unlike RCS, CVS does not lock the files when they are checked out. In fact, CVS enables multiple developers to check out an entire collection of files. When developers commit the files back to the repository, CVS tries to merge the changes various developers have made. If CVS cannot successfully merge the changes, the developers are notified, and they have to resolve any conflicting changes manually.

To use an existing CVS repository, first set the CVSROOT environment variable to that repository. The repository can even be on a different system. For example, to work on GNOME as a developer, set CVSROOT as follows:

export CVSROOT=':pserver:anonymous@anoncvs.gnome.org:/cvs/gnome'

You also must log in to the CVS server by using this command:

cvs login
Logging in to :pserver:anonymous@anoncvs.gnome.org:2401/cvs/gnome
CVS password:  (There is no password, simply press Enter)

You will be back at the shell prompt, but you are now logged in and can use the cvs command to access this CVS repository. Use the cvs checkout command to extract the packages you need from the repository. When extracting files from a remote CVS server, you can specify a compression level with -z option (the recommended level is -z3). Thus, you might use the following command to get the gnome-xml package from the GNOME CVS repository:

cvs -z3 checkout gnome-xml

You see a whole lot of message as the copy of CVS on your system downloads from the repository the latest version of each file in the gnome-xml module, creates a directory named gnome-xml in the current directory, and places the files in that directory. You can simply type cd gnome-xml to change the directory and to begin working on these files.

If you have checked out a module to a working directory, you can use the cvs update command to bring your copy current. The cvs update command essentially merges the changes in the repository with whatever changes you may have made in the working directory. If conflicting changes exist, you have to resolve the problem manually.

After you work on these files, you can use the cvs commit command to incorporate your changes to the files in the repository. You can either commit specific files or commit the entire module.

When you no longer want your working copy of a module, use the cvs release command to indicate that you no longer need the module. To remove the files, use the cvs release -d command; the -d flag means you want to delete the files.

When you are done working with a CVS repository, type cvs logout to log out of that repository.

That, in short, is how CVS works. To learn more about CVS, consult these resources:

  • Type info cvs to browse help information on CVS.

  • Type cd /usr/share/doc/cvs* to change to the CVS documentation directory. In that directory is a file named FAQ (Frequently Asked Questions) and several PostScript files about CVS. The FAQ file may be a bit out of date, but you can still use it as a source of information about CVS.

  • Visit one of these websites to read the latest online information about CVS:

    • http://www.cvshome.org/

    • http://www.gnu.org/software/cvs/

    • http://www.gnu.org/manual/cvs/index.html