Advanced CVS Commands.

How do I define a module?

A module is a name by which some portion of the repository may be referred during check-out and other operations. Therefore, the simplest way to define a module is to do nothing: one can always use a path from $CVSROOT to specify a subset of the repository.

When this is insufficient, one can explicitly define module names in the CVSROOT/Modules file. There are three kinds of modules so defined: alias modules, regular modules, and ampersand modules.

Alias modules.

Alias modules are defined by the following syntax:

    modulename -a alias ...

This creates a simple text substitution of the modules specified by alias... whenever the name modulename is encountered. Easy.

Specific subdirectories may be excluded from the module when they are named preceded by an exclamation mark:

    mymodule -a !proj/a/docs proj/a

This specifies the tree under proj/a except for the subtree proj/a/docs, since real programmers don’t read documentation, anyway. [Does this work with arbitrary module specifications, or just modules named as paths from $CVSROOT? Are the exclusion specifiers position-independent?]

Regular modules.

Regular modules are defined by the following syntax:

    modulename [ options ] dir [ files ... ]

In the simple form modulename dir, the path dir specifies the location in the repository relative to $CVSROOT, which is collapsed into the single directory modulename on the client machine.

If files ... are used, then only the specified file(s) are used from the directory.

The -d path option creates the specified client directory path instead of modulename. This can then be used to place the resulting files most anywhere convenient.

Ampersand modules.

An ampersand module is defined by prepending an ampersand before each source module name:

    modulename [ options ] &mod1 ...

As regular modules collapse the hierarchy on the client system, ampersand modules add one directory level:

    mymod &proj/a &proj/b

On checking-out “mymod,” the directory hierarchy mymod/proj/a and mymod/proj/b (with their subdirectories) will be created.

The -d name option names the client directory to name instead of modulename.

How do I create a branch?

From a codeline represented by a working copy.

To create a branch, branch_1, from where a current working copy is checked-out from (trunk or branch):

    cvs commit   # make sure nothing gets lost
                 # on trunk or source branch
    cvs tag -b branch_1
    cvs update -r branch_1

Let the commit on the first line be a warning: you want a clean working copy for this, else things start getting very confusing. The second command (note tag -b) creates the branch in the repository, the third switches the working copy to that new branch.

From an arbitrary tag in the repository.

The working copy above implicitly told CVS where to create the branch in the repository. Without reference to any working copy, one can explicitly name the branchpoint instead:

    cvs rtag -b -r rel_1-0 branch_1

This is the preferred method, as everything is explicit. In practice, one should branch from known points, anyway. This new branch can then be checked-out and worked on:

    cvs co -q -d mod-1-0-patch -r branch_1 mod

Here we're being careful to rename the top directory of module mod to mod-1-0-patch so as not to get confused with a trunk working copy.

How do I merge a branch?

    cvs -q ci             # make sure the patch is checked-in
    cvs -q update -d -A   # retrieve the head of the trunk
    cvs -q update -d -j branch_1

This shows merging back into the trunk. The same procedure works on other branches as well, if it makes sense to do so.

How do I merge a branch repeatedly?

One pattern of branch usage maintains the branch in parallel with the trunk, merging-in changes occasionally. For instance, if the trunk is used for ongoing development whereas the branch in question is used for the latest release and its patches, the patch branch will likely be merged into the development branch (the trunk) after its release so that the fix can be propagated to the mainline code. Merging works a lot better if CVS is told what the last point on the branch is where a successful merge has already taken place. To this end, the branch is given a “merge” tag, which is initially set to the start of the branch (the original release) and is moved-up to the head of the branch after each subsequent patch release.

If the merge tag is branch_1_merge and the tag defining the current patch release is branch_1_patch_2, then a patch release would conclude with:

    cvs -q update -d -A  # retrieve the head of the trunk
    cvs -q update -d -j branch_1_merge -j branch_1_patch_2
    # Resolve any conflicts on the trunk.
    # Update branch_1's merge tag:
    cvs -q tag -F -r branch_1_patch_2 branch_1_merge

How do I track 3rd-party software changes?

CVS uses vendor tags for this purpose. On the initial import of the vendor's sources, make sure the vendor tag and release tag describe the vendor and its release label:

    tar xzf gfoo-3.1.tar.gz
    cd gfoo-3.1
    cvs import -I '!' -kb -m "Import of GNU Foo 3.1" fsf/gfoo GNU gfoo_3_1

Any local modifications can then be made as usual:

    cvs -q co fsf/gfoo
    cd fsf/gfoo
    # do stuff with gfoo sources

A warning: note the -I '!' argument to import: usually one wants to import everything in the distribution tree, but CVS normally ignores files that match certain patterns (e.g., *.exe). The -I '!' clears the list of ignored files so that the import will do what one wants, except: any .cvsignore files in the distribution itself will again add the files it names back into the ignore list so they won’t be picked-up. Therefore either [a] delete all .cvsignore files from the distribution before the import, or [b] in directories containing a .cvsignore file, make sure no files match any of its patterns. (This problem is noted in the CVS 1.11 manual and may be corrected in a future version.)

Also, I’ve used -kb to set the default file type to “binary.” This is perhaps being extra-cautious for many occasions, and there may be a way of using the cvswrappers file to make this slicker, but “better safe than sorry” and all that. [TODO: Experiment with cvswrappers.] [An alternative: skip the -kb here, but check out with -ko if one knows that all files are “text.”] One can always double-back and issue a cvs admin -kkv later for those files known to be cross-platform text (the keyword substitution mode affects the file, not the branch). Therefore, after the above checkout, one can:

    find . -type f \( -iname '*.[ch]' -o -iname '*.[ch]pp' \)  \
         -exec cvs admin -kkv {} \;

Sometime later someone thinks it a good idea to update to the latest official gfoo release:

    tar xzf gfoo-3.5.tar.gz
    cd gfoo-3.5
    cvs import -I '!' -kb -m "Import of GNU Foo 3.5" fsf/gfoo GNU gfoo_3_5

Any conflicts flagged can then be dealt with by a variant of the above “repeated merging” scenario:

    cd ..    # leave import directory behind
    cvs co -j gfoo_3_1 -j gfoo_3_5 fsf/gfoo

Then resolve the conflicts (many magically go away by themselves at this point: don’t panic!) and check-in the result.

Note: the above procedure does not seem to detect deletions made by the vendor between the first and second import. Manual diff's and cvs rm’s may be necessary.