Essential CVS/CVS Administration/Project Management

From WikiContent

Jump to: navigation, search
Essential CVS

The team leader responsible for a project needs to know more about the project's tools than most other developers, or he needs to have a team member who knows about the tools. This chapter is for the team leader and the CVS specialist on the team. It concentrates on the files and directories that make up a project and how they are stored and managed in CVS, rather than on managing the development process itself.

The topics covered include initial creation of a project's CVS files, configuring a project as a module, exporting a project, integrating CVS with build systems and bug trackers, and configuring scripts to run when certain CVS commands are used.

This chapter also covers a special type of branch called a vendor branch, and concludes with a discussion of strategies and practices for using CVS that you may find helpful.


Creating a Project

A project is any group of files and directories that exist beneath one project root directory. Every project must include at least one directory, because CVS needs to be able to create a subdirectory to store metadata about the project.

Most often, a project represents one program, one set of related configuration files, or one web site. Sometimes, a project is a collection of related items. For instance, I store all my articles for in one project, with each topic grouped under a subdirectory. Sometimes a project is a subsection of something larger.

If you want to create a project right now with default settings and minimal reading, read Section 2.3 in Chapter 2. That section walks you through the process of importing a project.

Preparing a Project

To create a project in CVS, you need to have a copy of the files and directories you want to import, or an idea of which files and directories you want to make. You may modify this copy of the project, so make a copy in a temporary directory to use for importing.

If you have an existing set of files and directories, check whether you have files that can be generated from other files, such as compiled code. Files that can be generated easily do not usually need to be stored in CVS and can be removed from the copy of the project. It is good practice to store the commands that generate these files in CVS as part of the project, usually as a build script or as part of the installation document.

If you have a set of files stored in another version control system and want to save their revision history, see Section 7.1.5 in this chapter. If you don't need to retain their revision history, export the files from the other system using a command that gives you a clean copy of the project with no administrative files, and use that copy to import from.

If you don't have any existing files, you can import a single directory with the project name. If you are more comfortable with a structure to work from, create an initial directory structure for your project and import that.

If you cannot easily fit the task you are trying to accomplish into a single project, you can use the modules feature of CVS to give each development team a smaller section to work from, or you can create several projects and use a module to combine projects into a larger unit when needed. Modules are explained in Section 7.3.3 and Section 7.6.3.

Once you have at least one directory ready to add to the repository, you need to decide how you will add it. There are two methods, which are explained in Section 7.1.2 and Section 7.1.4.

Importing a Project

The cvs import command is used for both creating a new project and updating the vendor branch. Vendor branches are explained in Section 7.1.3 of this chapter.

To create a new project, make your initial directory structure and place in it the initial files for your project, or set out your existing code that is ready for importing, as described in Section 7.1.1. Run the cvs import command from within the root directory of that structure. CVS takes the files and directories from the initial directory structure and copies them into the repository. The syntax for cvs import is:

cvs [cvs-options] import [command-options] project_name 

cvs import requires a log message and calls an editor if it is not invoked with the -m option. You'll then be able to type a message into the editor to record the purpose of the import. See Chapter 2 for more information about the log message.

Check out a sandbox to test whether the import command has worked. If the files are in the sandbox correctly, you can remove or store the original files or directories as you see fit; CVS no longer needs them. cvs import doesn't change the files it imported from, and doesn't convert them into a sandbox.

Example 7-1 shows how to import a project and create an initial project sandbox. The ellipsis indicates where CVS calls the editor so you can enter a log message. Note that I change directories before checking out the test sandbox.

Example III-18. Importing with cvs import

bash-2.05a$ cvs -d cvs:/var/lib/cvs import wizzard wizzard ver_1-0
"/tmp/cvsfaQoRz" 5L, 287C written
N wizzard/Makefile
N wizzard/README
N wizzard/TODO
No conflicts created by this import
bash-2.05a$ cd ~/cvs
bash-2.05a$ cvs -d cvs:/var/lib/cvs checkout wizzard     
cvs server: Updating wizzard

cvs import saves the data from the files to both the project trunk and to the special branch known as the vendor branch. Unless specified otherwise with -b, the vendor branch is given the ID 1.1.1. The vendor tag parameter is used for the branch tag, and the release tag tags the branch at the current (just-imported) revision. These tags can consist of only alphanumeric characters, hyphens, and underscores.

If you need to import binary files or other files that are not plain text, see the information on binary files in Chapter 3 before adding them to the repository.

The cvs import command honors cvsignore and cvswrappers files and recognizes the standard -k, -I, and -W options (also used by cvs checkout and cvs update) explained in Chapter 3 and Chapter 10. The -d option causes CVS to record the time of import based on a file's last modification time, rather than the current time.

Like cvs update, cvs import reports on the status of files it adds to the repository. These are the output codes for cvs import:

C file
The file already exists in the repository and has been modified locally. A conflict must be resolved.

This code is usually seen when a project is reimported (see Section 7.1.3).

I file
The file has matched a cvsignore pattern and has been ignored.
L file
The file is a symbolic link and has been ignored.
N file
The file is new and has been added to the repository.

U file
The file already exists in the repository and has not been locally modified. A new revision has been created.

This code is usually seen when a project is reimported (see Section 7.1.3).

Vendor Branches

A vendor branch'is a special type of branch that CVS creates automatically when you cvs import a project. If your project is based on code external to the project itself, such as in-house patches to a vendor's code base, the vendor branch provides a way to merge updates from the external source with your own code.

If you intend to use vendor branches, use cvs import to create your project in CVS, using the vendor's code as the initial directory for the project. Make your changes on the main trunk and run the project normally; the vendor'branch is used only when the vendor releases a new set of code.

When the vendor provides a new release, use the cvs import command to add the new code to the same CVS project with the same vendor'tag and a new release'tag. In effect, this is a special cvs commit to the vendor'branch.

Example 7-2 shows a vendor-branch import to the wizzard project. Note the new release'tag. The project name and vendor'tag remain the same as in Example 7-1.

Example III-19. Importing a vendor branch

bash-2.05a$ cvs -d cvs:/var/lib/cvs import wizzard wizzard ver_2-0
C wizzard/TODOf" 5L, 270C written
C wizzard/README
C wizzard/Makefile
C wizzard/INSTALL
C wizzard/doc/design/Specification.rtf
C wizzard/doc/design/Requirements.doc
C wizzard/doc/design/Design.rtf
C wizzard/doc/design/Analysis.rtf
C wizzard/doc/design/AcceptanceTest.doc
17 conflicts created by this import.
Use the following command to help the merge:
     cvs -d cvs:/var/lib/cvs checkout -j<prev_rel_tag> -jver_2-0 wizzard

If a file is changed on the vendor'branch and the local file has not changed, CVS sets the trunk revision of the file to the new vendor-branch revision.

If both the vendor-branch copy and the local copy of a file have changes, CVS reports this as a conflict with the message N conflicts created by this import, where Nis the number of conflicts. CVS also provides a suggested variant of cvs checkout to help you resolve all the conflicts, using two -j command options to merge the vendor branch with the trunk.

CVS 1.11.5 tries to provide the exact cvs checkout command you can use to find the differences between the previous release and the current release of the vendor branch and merge those differences to the trunk. If your version doesn't do this, try the following command:

cvs checkout -j previous_release_tag -j current_release_tag 

You may also need to include the -d repository_path CVS option if you run this command from outside the sandbox, where CVS can't find the repository path in the sandbox administrative files.

Section 4.3.6 of Chapter 4 explains how the -j option works and how to resolve any conflicts that arise from this type of merge. Tag the vendor branch once these conflicts have been resolved, and use that tag as the previous_release_tag when you next merge changes from a vendor branch.


If the vendor branch contains binary files or files with keywords, use the keyword (-k)'and wrappers (-W) command options to cvs import. Keywords and wrappers are discussed in Chapter 3.

If you have more than one external source for a project, you can use the -b command option to run cvs import on an arbitrary branch number. This creates an additional vendor branch with the specified branch number. Create a separate vendor branch for each separate code source for your project, to help you keep track of which changes came from which vendor.

When selecting a branch to use as a new vendor branch for a new code source, determine that there is no preexisting branch with the number you choose. When updating an existing vendor branch (other than the default branch), use the -b option with the numeric ID of the branch, to specify the target branch for the current import.


Be careful when using -b, as CVS doesn't check whether the branch number corresponds to the vendor tag given in the import command. You can accidentally overwrite one vendor branch with another branch's files.

Alternatives to Importing

If your project does not contain files provided outside the project, it is unlikely you will need to use vendor branches. If this is the case, you can bypass cvs import. To do so, create a new project by adding a new subdirectory to the repository root directory, either by editing the repository directly or by checking out the repository root directory and then using cvs add in that sandbox.

If you do not need the vendor-branch capability of cvs import, the choice between cvs import and the combination of cvs checkout and cvs add is a matter of personal taste. However, I recommend that you use either of those methods — using CVS commands to create a new project — rather than editing the repository directly.

Creating a project with cvs add

To create a project with CVS commands other than cvs import, you need to check out a sandbox that contains only the repository root directory, create the new project root directory, and add that directory with cvs add.

To check out the repository root directory, use a period (.) as the project name. Use the -d name command option to give the sandbox a directory name. To avoid including subdirectories, use the -l command option to cvs checkout.

Example 7-3 shows how to create a project and an initial project sandbox by checking out the repository root directory and using cvs add.

Example III-20. Importing without cvs import

bash-2.05a$ cvs -d cvs:/var/lib/cvs checkout -l -d cvsroot .
cvs server: Updating cvsroot
bash-2.05a$ cd cvsroot
bash-2.05a$ mkdir wizzard
bash-2.05a$ cvs add wizzard
Directory /var/lib/cvs/wizzard added to the repository
bash-2.05a$ cd ~/cvs
bash-2.05a$ cvs -d cvs:/var/lib/cvs checkout wizzard     
cvs server: Updating wizzard

These are the steps for this method:

  1. Check out the entire top level-directory of the repository, renaming the sandbox root directory with a name such as cvsroot, as shown in Example 7-3.
  2. Change directory into cvsroot and create the new project root directory. In Example 7-3, the project directory is wizzard.
  3. If you have existing files or directories to import, copy them into your newly created directory.
  4. Use cvs add to add the new files and directories to the repository. If you add files as well as directories, you also need to use cvs commit. Example 7-3 shows directory addition only.

    As explained in Chapter 3, the cvs add command adds directories immediately, but files are added to the repository only after the next cvs commit.

  5. Change directories to a working directory and check out the new project to test whether the import worked successfully, as shown in Example 7-3.
  6. Use cvs release to remove the repository root sandbox you checked out in step 1. Remove any temporary files you created when preparing to import the project.

Creating a project by editing the repository

The second way to create a new project and bypass cvs import is to edit the repository directly. Editing the repository always involves the risk of making a minor typing error and affecting other projects (or your own), so I suggest you back up the repository before editing it.

To create a new project by editing the repository:

  1. Log in to the repository computer with any user that has write access to the repository root directory.
  2. Change directory into the repository root directory.
  3. Use mkdir project_root_name to create the project root directory.

The rest of this process is optional, and is needed only if you have existing files to add to the project:

  1. Log in to a workstation computer.
  2. Use cvs -d repository_path checkout project_root_name to check out a sandbox.
  3. Copy any existing project files and directories into the new sandbox.
  4. Use cvs add to add the new files and directories to the repository. If you add files as well as directories, you also need to use cvs commit.

Importing from Other Version Control Systems

You might want to import a project, including the project's history, from a different version control system into CVS.

If the other version control system can export files into RCS format, use the following steps to install the RCS files into CVS:

  1. Ensure that the latest revision in each RCS file isn't locked, the access list is empty, and the file-locking type is set to strict, using the tools available in your version control system if possible.

    If your version control system doesn't have tools to do these things and you have RCS installed, you can use rcs -elL filenameto set locking and access appropriately.

    If RCS isn't available, you can proceed to steps 2 and 3; then, once the files are in the repository, use cvs admin commands to set locking and access.

  2. Create the project's directory structure in CVS using cvs import or one of the alternative methods described earlier.
  3. Copy the RCS files directly into the appropriate directories in the repository.

If the other version control system can't make RCS files, write a script to check out each revision of each file from the other system and commit it into CVS, or create an RCS file with the RCS ci command and use ci to add each revision of the original file to the RCS file. If you create RCS files, you can install them to CVS as described earlier.

The contrib directory in the CVS source contains scripts that convert SCCS and PVCS files to files CVS can use. These scripts can be used as templates for scripts to convert files from other version control systems. Appendix B contains more information about the sccs2rcs and pvcs2rcs scripts.

Distributing Files

All project leads need to distribute completed work at various stages of a project. When working with some projects, such as content management of web sites, you need to distribute files frequently with small changes. With others, such as large programming projects, you need to distribute files less often and with larger changes.

checkout and update

One way to distribute files is to use the cvs checkout command to produce a set of files for distribution. Another way is to use the cvs update command on an existing set of files.

The checkout and update commands are designed to produce a sandbox suitable for editing the files being checked out or updated. The commands create administrative files in the sandbox that most project leads don't want in a public distribution, so you may need to remove the administrative files in the CVS subdirectory from each of the checked-out directories.

There is a benefit to using checkout and update to distribute files. When you use either command on an existing sandbox, CVS sends only the differences between the revisions currently in the sandbox and the revisions requested from the repository. This uses less bandwidth than the export command, which retrieves entire files.

Exporting Files

While cvs checkout creates a sandbox suitable for editing copies of a project's files, cvs export creates a release of the project's files that is suitable for publication. This command uses most of the same internal code as cvs checkout, but cvs export does not create any CVS subdirectories or CVS administrative files.

cvs export requires that you use a date or tag command option. The -D now or -r HEAD options export the latest revisions of all files in a project.

If you don't have any binary files in your project, you can export with -kv to set the keyword-substitution mode to values only, so that the string$keyword: value$ shows in each exported file as value. This can be an advantage when you are publishing a completed release of your project. For example, the string $Revision: 1.5 $ displays as 5.7 and the string $Author: jenn$ displays as jenn. Keywords are commonly used in "About this program" displays.

The syntax for cvs export is:

cvs [cvs-options] export [command-options] project_name

cvs export uses a subset of the options available with cvs checkout. It accepts the -D date, -r revision, and -r tagoptions to specify the revision to be exported. You can also use the -foption, which instructs CVS to use the HEAD revision if the specified revision is not available.

The -l and -R options specify local or recursive operation, and the -k mode option is used to set the keyword-expansion mode. Use the -n option to prevent CVS from running an export program specified in the modules scripting file.

The -d directory option provides a directory name for CVS to export the files to. Otherwise, CVS exports the files and subdirectories to a directory named for the module, or for the project root directory.

If you are exporting only one file from a subdirectory, CVS removes intermediate directories. Use-N with-d to prevent CVS from removing intermediate directories.

Example 7-4 shows an export of the wizzard project.

Example III-21. Using cvs export

bash-2.05a$ cvs -d cvs:/var/lib/cvs export -D now wizzard
cvs export: Updating wizzard
U wizzard/Changelog
U wizzard/INSTALL
U wizzard/Makefile
U wizzard/README
U wizzard/TODO
cvs export: Updating wizzard/doc
cvs export: Updating wizzard/doc/design
U wizzard/doc/design/AcceptanceTest.doc
U wizzard/doc/design/Analysis.rtf
U wizzard/doc/design/Requirements.doc
U wizzard/doc/design/Specification.rtf
cvs export: Updating wizzard/doc/plan
U wizzard/doc/plan/Schedule.rtf
cvs export: Updating wizzard/lib
cvs export: Updating wizzard/man
cvs export: Updating wizzard/src
U wizzard/src/config.h
U wizzard/src/handheld.c
U wizzard/src/server.c
U wizzard/src/wizzard.c

If you are using CVS to manage a web server, FTP site, or other publication system, you may want to ensure that the latest revisions of the project files are in your publication directory. You can do this by automatically exporting the files on a regular basis using cvs export, cvs checkout, or cvs update.

cvs export attempts to write the full revision of each file, and it will not export a file if an existing file of the same name is in the directory it is trying to write to. If you want to overwrite an existing copy of the project or if you want to transmit only changes, cvs checkout or cvs update may be more useful. See Section 7.2.1 of this chapter.


You may need to set up permissions, create symbolic links, or move files after they have been exported. A build tool such as make can be useful for this.

Example 7-5 shows a simple script that exports a project, tests whether the export worked, and then runs an installation script using the make utility. The script in the example could be called from cron or any other Unix or Linux scheduler.

Example III-22. Export cron script

cd /ftp/internal
/bin/rm -r wizzard
/usr/bin/cvs export -D now wizzard
if [ $? -eq 0 ]; then
      cd wizzard
      /usr/bin/make install
        /bin/cat "Export failed with return code $?" | /usr/bin/mail wizmanager -s \

You can also run a script similar to the one in Example 7-5 to export your project whenever a file has been changed, using the loginfoadministrative file or the -i option in the modules file. These files are explained in the following section.

Running Scripts

In the repository's CVSROOT directory, there are several scripting files that allow you to run scripts while a project is being committed, tagged, updated, or modified in other ways. The scripts called from scripting files are often used to interact with other programs or to enforce standards.

Example 7-6 shows a commitinfo scripting file that runs a layout-testing program called indent-tester on the wizzard project files. CVS calls scripts in commitinfo before files are committed.

Example III-23. Sample commitinfo file

^wizzard/src\(/\|$\) /var/lib/cvs/CVSROOT/indent-tester -gnu

When a project file is committed, CVS searches the commitinfo file for rules that match the project file's path; if CVS finds a rule, it runs the script given in that rule. The rule in Example 7-6 matches all files in the src directory (and its subdirectories) of the wizzard project and tells CVS to run the indent-tester script on those files. Later in this chapter, Example 7-8 shows the indent-testerscript that can be used to enforce indentation standards for a project.

CVS processes the scripting files separately for each directory that is affected by a command, and it calls the script in the scripting file once for each directory it matches. If you run cvs commit wizzard, CVS checks commitinfo for patterns that match wizzard and runs the script once for the files in the wizzard directory, then does the same for each subdirectory, checking for patterns that match wizzard/doc and processing its files, followed by wizzard/lib and wizzard/src.

Working with Scripting Files

The scripting files are all stored in your repository's CVSROOT directory. The purpose of each file is described in the section of this chapter named for that file. Most of these files use a common syntax.


Some files don't use the common syntax. In such cases, the syntax is described in the same section as the file.

The common syntax is rule-based, one line per rule, and each rule consists of a pattern to be matched and an action to be taken. When CVS reads a scripting file, it tries to match the pattern to project files. If the pattern matches, CVS runs the action. The action is usually a pathname to a script and parameters for that script. The common syntax is described in the next section.

To edit any of the scripting files, check out the file with the following command:

cvs -d repository_path checkout CVSROOT/filename

Edit the file with any text editor; then commit the file with cvs commit. CVS stores these files in the CVSROOT directory in plain-text and RCS formats and updates both types of files every time a file is committed.

You can also store the scripts you call from these files in your repository's CVSROOT directory or a subdirectory of CVSROOT. Add such scripts to the checkoutlist file in CVSROOT, so they are automatically exported to the CVSROOT directory when they're committed. The checkoutlist file is described in Chapter 6.


It is tempting to store scripts called by the scripting files in the project or module they are called for. Resist this temptation, as CVS then tries to call such a script when the script is itself being committed. Also be aware that if you store scripts in a project or module, they'll be stored on the repository machine in RCS format only; scripts can't be run from that format.

Do not attempt to interact with the user from scripts called from the scripting files. Some remote-access methods do not support such interaction at all, and other methods can be affected by having unexpected data go through their socket. Users other than the one involved in the interaction may also be left waiting on the first user until the interaction is complete.

If any script contains a CVS command, be aware that the command that calls the script will not finish until the script does. You may find that some directories have been locked by the original command, which can't finish and release them until the script is finished, but the script can't finish because the second command can't run until those same locks are released. This situation can be prevented by having your script call any CVS commands in the background or by simply calling the script in the background.

Scripts called from all files except the modules file are run on the computer that hosts the repository. Two of the modules scripts run on the sandbox computer; the others run on the repository computer.

If you do not provide a full path for a script, CVS searches the user's PATH environment variable. To prevent the wrong script from being called, I recommend using the full path in all scripting files.

Your repository administrator may limit your access to the scripting files for security reasons. Work with the repository administrator; you are asking for permission to run arbitrary scripts, so try to address her concerns.

Common Syntax

Most of the scripting files'in CVSROOT --including commitinfo, loginfo, editinfo, rcsinfo, and verifymsg — share a common syntax. The modules file does not use the common syntax.

Each line of a file using the common syntax should contain the following items:

                  name_pattern action

name_pattern should match a directory in the repository, and this pattern applies to all files in that directory. The pattern used is a regular expression, described in Chapter 11. The directory path that is tested against the pattern is the relative path, starting at the repository root directory.

action is a command-line template or the address of a script, plus any parameters required for that script or template. For most files, the script or the command must expect to read from the standard input (stdin). When CVS runs actions from some of the scripting files, it appends parameters to all actions run from those files. These parameters are often a list of the paths to the project files and metadata for copies of the project files that CVS has matched the rule to.


Lines in scripting files that start with the hash symbol (# ) are comments, which CVS ignores.

When CVS processes a scripting file, it searches the scripting file from top to bottom and runs the action on the first line with a directory name pattern that matches the directory that contains the project file it is trying to match. CVS runs the script on the first matching rule and ignores other rules that match. In addition, if there are any ALL rules, CVS runs the scripts associated with them.

There are two special patterns, ALL and DEFAULT. A script designated with the ALL pattern is used on files in any directory, even if another line anywhere in the file matches the directory pattern. A script designated with the DEFAULT pattern is used if no other patterns (excluding ALL) match.

The actions in scripting files can use any of the variables CVS recognizes and expands. Chapter 6 explains variable expansion in administrative and scripting files.

The modules File

The modules file is used to define CVS modules. While a project is defined by the project root directory and contains all files and subdirectories, a module can be an arbitrary set of directories and files. Once a module is defined, the files and directories it describes can be checked out into a sandbox by either the module name or the name of the repository directory (or directories) it represents.

The modules file can also specify scripts to run when CVS acts on a file within a module.

Each module definition needs to be on a line of its own. When adding directories to module definitions, use relative paths from the repository root directory.

Example 7-7 shows a modules file. The lines in the file are explained in the following sections on module definition.

Example III-24. CVSROOT/modules

# wizzard project. Project lead: jenn. Module type: regular.
wiz -s development wizzard
# singer project. Project lead: doppel. Module type: alias.
singer -a music/descant !chorus/tenor !chorus/bass chorus
# wizsinger project. Project lead: doppel. Module type: regular
wizsinger -i /var/lib/cvs/CVSROOT/export &wiz &singer
#wizmake project: retrieves just the Makefile from wizzard. 
wizmake wizzard Makefile
# wizhandheld project: retrieves just the handheld.c file.
wiztest wizzard/src handheld.c

Alias modules

An alias module can be used to group files and directories into a single module, regardless of where they are stored in the repository tree. The syntax for an alias module definition is:

                     module-name -a path [path...]

The path may be an existing module, a pathname to a file, or a directory path, and there may be any number of paths. If the path leads to a directory, the directory and all subdirectories and files are included in the module.

If you wish to exclude a subdirectory in an alias module, use an exclamation mark (!) in front of the directory name.

Alias modules cannot have options, nor can you specify scripts to run for them. If you need to use options or scripts, you can define an alias module as part of a regular module, or you can simply use a regular module instead.

Alias modules are easier to use than regular modules(described in the next section). You define alias modules with a space-separated list of all the items you want in the module, while regular modules have a more complicated syntax. You can also add multiple directories, files, and modules in an alias module; a regular module allows only one directory.

The singer project in Example 7-7 defines an alias module that includes all files and directories in the chorus directory, except those under the subdirectories tenor and bass. This project also includes the descant file from the music directory.

When you use an alias module's name in any CVS command, CVS treats the module name as if the list of items in the path were used instead. When you use an alias module in the checkout command, CVS creates a separate sandbox for each item in the path.

Regular modules

A regular module is used to define options for a project. It can also be used to define subsets of a project or to group modules together. If you have a particularly complex project, you may need to define your project with a combination of alias and regular modules.

Using alias modules in a complex definition allows you to add multiple directories with one alias, and the regular module allows you to define options to use when calling that module.

The syntax to define a regular module is:

                     module-name [options] [directory [filename|subdir...]] [&module...]

The directory should be a path relative to the repository root directory. If filenames or subdirectory names are included, they should refer to files or subdirectories in the directory. The module definition then applies only to those files or subdirectories. Note that you need a space between the directory and the files within the directory, and you can define only one directory within a regular module.

You can include an existing module in a regular module by prefixing the existing module name with an ampersand (&) and listing it in the regular module definition. The existing module can be an alias or a regular module. If it is not defined in the modules file, it must be a directory path relative to the repository's root directory. This rule is illustrated in the wizsinger module definition in Example 7-7, which includes two existing modules. The sequence of the module definitions doesn't matter; you can include a module in an earlier line than the module's own definition.

Module options and scripts

Most of the options for a regular module define scripts to run immediately after a CVS command on the files in the module. Here is the full list of options:

-d directory_name
Use directory_name rather than the module name as the name of the sandbox root directory.
-e script
Run the specified script whenever the module is exported. This script is given the module name as an argument.

-i script
Run the specified script whenever the module is committed. This script is given the full pathname of the relevant repository directory as an argument.

This option may be removed in CVS 1.11.6 and later, for security reasons.

-o script
Run the specified script whenever the module is checked out. This script is given the module name as an argument.

-s status
Provide a status for the module. This has no meaning internal to CVS, but when the command cvs checkout -s runs, the display output is sorted in ascending order by status. You can use the status string for anything; examples include the name of the person responsible for the module or whether the module is in active development.

CVS allocates an 11-character space for the status string when it is displayed, but the string is not truncated if it is longer than 11 characters.

-t script
Run the specified script whenever rtag is used on the module. This script does not run when tag is used. The taginfo file is a better way to run a script when files are tagged. This script is given two arguments: the module name and the name of the tag.

-u script
Run the specified script whenever the module is updated. This script is given the full pathname of the affected repository directory as an argument.

This option may be removed in CVS 1.11.6 and later, for security reasons.

The scripts designated in the modules file run after their respective processes have been completed.

If the repository is not on the same computer as the client, CVS stores the scripts for the -i and -uoptions in the Checkin.prog and Update.prog files in the CVS subdirectories in the sandbox. These scripts will run on the client computer. All other scripts called from the modules file run on the repository server computer.


The behavior of Checkin.prog and Update.prog will change in CVS 1.11.6 for security reasons. Check the documentation with your version of CVS for details.

The sandbox copies of the Checkin.prog and Update.prog files are not changed when the modules file or the repository computer's copies of the scripts are changed. If you change a module definition or the commit or update scripts for your project, you will need to have your users release and recreate their sandboxes.

The commitinfo File

The commitinfo file is processed before a file is committed and is usually used to enforce project standards for file format or content. It runs very early in the commit, during the tests to determine which files have changed.

commitinfo uses the syntax described in the Section 7.3.2 earlier in this chapter, with one rule per line and each rule consisting of the following elements:

                  name_pattern action

If a name_pattern matches the name and path of the directory or module parameter to cvs commit, CVS calls the action and appends as parameters the full path of the repository and the filenames of all files to be committed.

If you run cvs commit from a sandbox directory without specifying a project, CVS tries to match the repository path that corresponds to your current sandbox directory with a rule in commitinfo. If you run cvs commit with a file or directory parameter, CVS tries to match the repository path that corresponds to that parameter.

CVS makes a set of normal-format files from the data it receives from the CVS client and stores them in a temporary directory to allow the action to read a normal file rather than an RCS file. If CVS is running in client/server mode, the script or command in the action runs on the repository computer in a directory that is a copy of the sandbox directory being committed. If this script returns a nonzero value, the commit does not proceed. The script cannot change the file that is being checked; so, while you can use a layout-testing program, you cannot successfully use one that modifies the layout.

Example 7-8 shows an indentation tester that can be used with the commitinfo file in Example 7-6. Note that this tester assumes that all files it is called for can be tested validly with indent.

Example III-25. Indentation tester

while test "$1" != ""
  diff $FILENAME $TMPFILE 2>/dev/null >/dev/null
  if [ $? -ne 0 ]; then
    return 1
return 0

The loginfo File

The loginfo file is processed after files have been committed to the repository. loginfo uses the syntax described in Section 7.3.2 of this chapter, with one rule per line and each rule consisting of the following elements:


If a name_pattern matches the name and path of the directory parameter to cvs commit, CVS calls the action and passes it the log message from the commit on the standard input. CVS also passes the repository path being committed to, followed by an expanded format string. The action can consist of a script, a shell command, or a group of shell commands that will run as one command. If the repository is not on the same computer as the client, the script or command in the action runs on the repository computer.

The format string is stored in the loginfo file, and it controls the information that CVS passes to the script or command. The string consists of a % , followed by a space, a single variable, or a set of variables enclosed in braces ({ }). These are the variables:

Expands to the name of the current file being processed.
Expands to the file's revision number prior to the commit.
Expands to the file's revision number after the commit.

When CVS process the format string, the output is in the form:

"variable_expansion [variable_expansion...]"

The output includes a variable expansion for each file that was committed. The variable expansion is a comma-separated set of strings, such that %{sV} could be expanded to a string like main.c,1.3. Dollar signs ($), backticks (`), backslashes (\),'and quotation marks (") in the repository path or filename are escaped with backslashes.

If you run cvs commit from a sandbox directory, CVS tries to match the repository path that corresponds to your current sandbox directory with a rule in loginfo. If you run cvs commit with a file or directory parameter, CVS tries to match the repository path that corresponds to that parameter.

The loginfo file is usually used to control where (aside from the repository) the log information from cvs commit is sent. It can also be used to trigger actions, such as notifying all developers of changes, maintaining a file that logs a record of changes, or exporting the changed files to a file server.

Example 7-9 shows a loginfo file that emails the log message for every change to the user cvsadmin. This file also runs the changelog script that is stored in CVSROOT for every file that is committed to the wizzard project. The line for theteppic project demonstrates a way to call an export script in the background.

Example III-26. A loginfo file

^wizzard\(/\|$\) /var/lib/cvs/CVSROOT/changelog %{sv}
^teppic\(/\|$\) /var/lib/cvs/CVSROOT/export &
ALL mail -s "CVS commit" cvsadmin

The rcsinfo File

The rcsinfo file is processed before a file is committed. It uses the syntax described in Section 7.3.2 of this chapter. The action in each rule of an rcsinfo file should not be a path to a script; it should be a path to a template file that will be displayed when CVS asks the user for a log message during a cvs commitoperation.

If you run cvs commit from a sandbox directory, CVS tries to match the repository path that corresponds to your current sandbox directory with a rule in rcsinfo. If you run cvs commit with a file, directory, or module parameter, CVS tries to match the repository path that corresponds to that parameter. The DEFAULT and ALL rules described in Section 7.3.2 are honored.

If CVS finds a match, it displays the contents of the template file when it opens an editor to receive a log message. If there is a matching rule and an ALL rule in rcsinfo, CVS displays the contents of the file each rule refers to (that is, it displays the contents of two files). Subject to any editing you do when you commit, the contents of the template file (or files) are stored as part of the log message.


If you use -m message or -f file as options to cvs commit, the rcsinfo file is ignored.

If CVS is running in client/server mode, it stores a copy of the template file in the Template file in the CVS subdirectory of the sandbox when the sandbox is checked out. This file is not updated with other sandbox files, so if the rcsinfo file or the templates it refers to are changed, your users should release and recreate their sandboxes.

The taginfo File

The taginfo file is processed before a file is tagged using either the tag or the rtag commands. taginfo uses the syntax described in Section 7.3.2 of this chapter, with one rule per line and each rule consisting of the following elements:


If a name_pattern matches the name and path of the directory or module parameter to cvs tag or cvs rtag, CVS calls the script or command in the action and appends a parameter string. The parameter string has the following format:

                  file_revision_pair [file_revision_pair...]

The parameters are provided to enable the script you specify in the action to check that the tag name meets standards, interact with other programs, or log tag operations.

The file_revision_pair is a space-separated pair of filename and revision number, and there is a pair for each file to be tagged.

The operations CVS provides are add, mov, and del. The mov operation is provided when tag or rtag is called with the tag-moving option -F, and del is provided when tag or rtag are called with the tag-deletion option -d. The add operation is provided when a new tag is added.

If CVS is running in client/server mode, the script given in the action runs on the repository computer. If the script exits with a nonzero exit status, the tag or rtag operation does not proceed.

The taginfo file is often used to enforce tag-name standards in a project. Example 7-10 shows the first part of a parser that returns1 if a tag name doesn't fit the project's standards and 0 if it does.

Example III-27. Parsing tag names

if (!is_in(('prealpha','alpha','beta','stable'),$tagfields[1])) {
     return 1;
return 0;.

The verifymsg File

The verifymsg file is processed before a file is committed and after you enter the cvs commit log message, either in the editor that CVS opens or by using the -m message or -F fileoptions to cvs commit.

verifymsg uses the syntax described in Section 7.3.2 of this chapter, with one rule per line and each rule consisting of the following elements:


If a name_pattern matches the name and path of the directory or module parameter to cvs commit, CVS calls the action and passes it the path to a temporary file containing the cvs commitlog message, and a copy of the log message on standard input (stdin). If the repository is not on the same computer as the client, the script given in the action runs on the repository computer. If the script returns a nonzero value, the commit does not proceed.

The verifymsg file is usually used to enforce project standards for commit log messages, and the script often either checks or modifies the log message you enter when you commit files

The script can modify the log message, and the RereadLogAfterVerify option in the config file in the repository's CVSROOT directory determines whether the original or the modified log message is used. (If the log message is reread, CVS reads it from the temporary file.) See Chapter 6 for information on the config file.

The ALL keyword is not supported for use in the verifymsg file.

Interfacing with External Programs

CVS is rarely used alone. There are a number of project-management tools available, each used to perform a different set of tasks. The scripting files can be used to connect CVS to other project-management tools, such as bug trackers and build scripts.

Interfacing with Bug Trackers

Interfacing with a bug tracker is easiest if the tracker accepts input from standard input or via an email form. Use the rcsinfo file to have the cvs commit editor screen include the template your bug tracker requires, and use the verifymsg file to ensure that all fields are filled in appropriately. Then have a script in verifymsg or loginfo mail the log data to your bug tracker.

Example 7-11 and Example 7-12 show how to integrate with the Bugzilla bug-tracking tool, available at These examples assume that the Bugzilla email gateway has been enabled. The @resolution field in the message template allows you to change a project's status and is supported directly by Bugzilla.

Use the verifymsg script in Example 7-11 to separate out the bug ID so it can be used in the mail subject (which must have the format [Bug XXXX]). The rest of the log message can be sent as the mail body. The script uses the read command to get the bug ID from the first line of the log message, checks that the first line it reads uses the format it expects, and then mails the bug ID and the rest of the stdin input to the user bugzilla on the same computer. The script returns 1 if the format was wrong and 0 if it succeeds in mailing everything to Bugzilla.

Example III-28. Interfacing with Bugzilla, verifymsg

#! /bin/bash
read prompt bugid
if [ $prompt != '@bugid' ]; then
     return 1
     mail -s "[Bug $bugid]" bugzilla
return 0

Example 7-12 shows an rcsinfo template file that contains the @bugid string that Example 7-11 expects and the @resolution and @message strings that Bugzilla supports. You should enter the bug ID, resolution status, and a log message to the right of each of these prompts.

Example III-29. Interfacing with Bugzilla, message template


A more complete verifymsg script than the one shown in Example 7-11 would include a facility to strip comments out of the template file, and the template file would have comments explaining what to write beside each prompt. (CVS strips lines that start with the string CVS:, so you can use that mechanism instead of your own comment-stripping code.)

Example 7-13 shows the lines in the verifymsg and rcsinfo files that would call these scripts for the wizzard project, if the scripts were called bugzilla-mail and bugzilla-template.

Example III-30. Interfacing with Bugzilla, scripting files

# In verifymsg, call the script bugzilla-mail
^wizzard\(/\|$\) /var/lib/cvsroot/CVSROOT/scripts/bugzilla-mail
# In rcsinfo, call bugzilla-template
^wizzard\(/\|$\) /var/lib/cvsroot/CVSROOT/scripts/bugzilla-template

Interfacing with Build Scripts

A build for publication should be slightly different from a build for development. During development, your developers want to start with a recent sandbox, build from that sandbox, make their changes, and then test only those changes. However, a build for testing or publication should be taken from a known set of revisions, frequently the most up-to-date revisions.

Fortunately, both building for incremental testing and building for a major release can be done using the same build script. If the script is included in the project files and designed to be able to be run from a sandbox, the developers can run their test builds from their sandboxes and the project lead can run testing or publication builds from an exported copy of the project files. Note that it's good practice to tag the project's files just before a testing or publication build, so that the exact revisions can be easily retrieved later.

If your developers are sharing a central set of files and have only a partial set of the project's files in their sandboxes, you can have the build script set up symbolic links to the shared files from their sandboxes or call files from a central location.

If your developers are changing copies of a set of files but will eventually want to distribute your project's files across several unrelated directories, it is best to develop the project to build under a single root directory, then distribute the files across multiple directories as part of the installation script.

Example 7-14 is a script for the wizzard project that tags the head revisions of all files, then builds and installs the project (assuming the Makefile is properly configured). To create a build of this script for development, produce a smaller script that contains only the make and make install commands and run the miniscript from a developer's sandbox. To create a build of the project for testing or publication, run the full script given in Example 7-14 from any directory.

Example III-31. Building for testing or publication

/usr/bin/cvs -d cvs:/var/lib/cvs rtag -r HEAD $1 wizzard
if [ $? -eq 0 ]; then
     cd /tmp
     /usr/bin/cvs -d cvs:/var/lib/cvs export -r $1 wizzard
     if [ $? -eq 0 ]; then
          cd wizzard
          /usr/bin/make install
          echo "Export failed."
        echo "Tag failed."

Enforcing Standards

The different scripting files can be used in the following ways to enforce project standards:

Can be used to run a program through a layout checker, run a system configuration file through a syntax checker, or run a documentation file through a spelling and grammar checker. If the checker returns a nonzero exit status, the file is not committed and the commit fails for all files in that commit.
Can be used to ensure that log messages meet the project's standards and contain the correct fields. This file can also be used to generate fields and include them in the log message.
Can be used to enforce a standard format for tag names.


Project administrators should be aware of the CVS commands described in Chapter 3 and Chapter 5. You may also want to read Chapter 4 and Chapter 6.

In addition to the commands described in those chapters, there are two commands that are particularly useful to project administrators: cvs admin and cvs history. cvs admin allows you to use RCS-like commands on files stored in the repository. cvs history provides a record of the actions performed on a project's files and is similar to cvs log or cvs annotate.

The cvs admin Command

The cvs admin command is used to edit the RCS files directly in the repository. It is more accurately thought of as a set of commands than a single command, as it provides many of the commands that RCS would make available, though not all of these commands are usable or have an effect that matters to CVS. These commands are present mostly for historic reasons and backward compatibility with early versions of CVS.

If there is a system group called cvsadmin on the repository server, only users in that group can use the cvs admin command. If this group does not exist, any user can use the cvs admin commands on any repository files they have permission to change. Consider using the cvsadmin system group, as some of the cvs admin commands can prevent CVS from using the affected file or files.


There is a new option in the config file of CVS 1.12.1 and later. The UserAdminCommands option allows the system administrator to set cvs admin options that users who are not in the cvsadmin group can run.

The syntax for cvs admin is as follows:

cvs [cvs-options] admin command-options [filename...]

I strongly recommend always stating the filenames you wish cvs admin to act on, as some of the cvs admin commands can be difficult or impossible to recover from if you accidentally use them on the wrong file. If you do not specify a filename, cvs admin operates on all files in the current sandbox directory and moves recursively down its subdirectories.

Each of the RCS commands that cvs admin provides is represented by a command option to cvs admin. RCS commands that are obsolete or meaningless when used with CVS are not listed in this chapter. For many of the commands, there can be no space between an option and its argument. See Chapter 10 for the full list of cvs admin command options.

The most commonly used cvs admin option is the -k option, which sets the default keyword-substitution mode for a file. This option is explained in the Section 3.11 of Chapter 3. If you forget to set the -kb keyword-substitution mode of a binary file when you add it to the repository, you can use cvs admin -kb filename to correct the mistake.

If you use the rcslock script to reserve development of files, as described in Chapter 5, you use cvs admin -l filename and cvs admin -u filename to lock and unlock the file you are reserving.

The -o option is used in one of the methods of moving files, as described in Chapter 6. This option allows you to remove old revisions of a file, effectively collapsing the changes from those revisions into a single revision. It cannot be reversed once it is done, so be very careful and test it on a copy of the repository first. The full syntax of the -ooption is provided in Chapter 10.

The -m option allows you to replace a log message. The -s option is used to set the state of a file. The state is shown in cvs log output and in the results of the Logkeyword. The -toption is used to set a description of the file, which is also shown in cvs log output.


The -U option to cvs admin sets file locking to nonstrict, which prevents CVS from working with a file effectively. The-L option repairs this problem by setting the file locking to strict. The -U and -L options should not be used, but if someone accidentally uses -U while trying to use -u, you now know you can use -L to repair the damage.

The cvs history Command

The cvs history command reports on the history of actions performed on the files in a repository. This command is a variation of the cvs log command explained in Chapter 5. The cvs log command displays only commit actions; cvs history can display most types of actions.

The history file

cvs history uses the history file in a repository's CVSROOT directory as a database and displays only events that have occurred while the file is present and writable. The cvs init command creates the history file by default. Note that CVS will not report an error if the history file is removed.

The history file must be writable by all users. Since it never causes a script to be executed, this is not a security issue. The actions that are logged to the history file are controlled by the LogHistory setting in the configfile in the repository's CVSROOT directory.

cvs history output

The format of the output of the cvs history command varies, depending on the options chosen. The basic format of each line of the history output is:

                     user {revision|path|branch} {module|directory|tag|filename} 
[module|project-root-directory] access_type

The type is a single letter, representing one of the types given in the list at the end of this section. The timezone is +0000 (i.e., UTC) if not specified otherwise. The access type is remote or local; if it is local, it shows the directory the sandbox was in when the command recorded in the history file was run.

Example 7-15 shows an example of most of the output types for cvs history. Whitespace has been removed to prevent the output from wrapping.

Example III-32. cvs history command output

bash-2.05a$ cvs history -e -zUTC
O 2002-08-22 05:42 UTC jenn wizzard =wizzard= <remote>/*
O 2002-10-03 08:38 UTC jenn wizzard =wizmake= /tmp/*
O 2002-10-03 08:38 UTC jenn wizzard/src =wiztest= /tmp/*
M 2002-08-22 08:00 UTC jenn 1.8 1-introduction.sxw cvsbook =  = <remote>
A 2002-08-29 12:17 UTC jenn 1.1 Z-copiesto cvsbook =  = <remote>
W 2002-09-12 04:36 UTC jenn wizzard.h wizzard/src =  = <remote>/src
C 2002-09-12 05:32 UTC jenn 1.2 main.c wizzard/src =  = <remote>
G 2002-09-12 05:32 UTC jenn 1.4 wizzard.h wizzard/src  =  = <remote>
R 2002-09-12 06:07 UTC jenn 1.3 main.c wizzard/src =  = <remote>
T 2002-09-13 04:04 UTC jenn wizzard [pre_alpha_0-2:HEAD]
T 2002-09-13 04:04 UTC jenn wizzard [pre_alpha_0-2:2002.]
T 2002-09-13 07:12 UTC jenn wizzard [beta_0-1_branch:beta_0-1_branch_root]
E 2002-10-01 07:00 UTC jenn [2002.10.01] wizzard =wizzard= <remote>/*
F 2002-10-02 17:48 UTC jenn =wizzard= <remote>/*

Using the cvs history command

The syntax for the cvs history command is

cvs [cvs-options] history [command-options] [filenames...]

The options to cvs history modify how much of the history is shown for which users and modules. The -T, -c, and -o options display the records for tags, commits, and checkouts, respectively. The -e option displays all record types. The -z timezoneoption converts times and displays them in the specified time zone.

CVS does not allow you to combine the -T, -c, -o, -x, and -e options. If you wish to display multiple action types, use the -x option with a type letter.

The types shown in the history output are designated by letters, which can also be used with the-x option to specify the types of events you wish to display. Multiple types can be specified. These are the types:

Report on records of files added to the repository (a committed add).
Report on records showing files that would have been updated in a sandbox, but that needed to be merged and for which there were conflicts in the merge (compare with G and U).
Report on records of files being exported.
Report on records of files being released.
Report on records of a file being updated in a sandbox with a successful merge (compare withC and U).
Report on records of a file being modified (a successful commit of a sandbox revision).
Report on records of files being checked out.
Report on records of files being removed from the active part of the repository (a committed remove).
Report on records of files being tagged or rtagged.
Report on records of a file being updated in a sandbox with no merge required (compare with C and G).
Report on records of a file being deleted from a sandbox during an update because it is no longer active in the repository.

The full list of command options for cvs history is provided in Chapter 10.

Strategies and Practices

CVS is a tool that can help you manage a project, but you need to decide how to use CVS with your project to make it work effectively for you and your team. The following sections present some ideas to help you integrate CVS into your working practices.

Tag and Branch Strategies

Decide whether and how you will use tags and branches, and then decide on the format to use for tag and branch names. Branching philosophies, styles, and policies are explained in Section 4.4 section of Chapter 4.

I strongly recommend keeping almost-complete work separate from experimental work. In programming projects, I recommend keeping bug fixes separated from the main body of development by putting either bug corrections or development on a branch.

If you use branches, designate an owner for each branch. The owner should be responsible for seeing that the branch is used for its specified purpose, for maintaining a record of the changes made to that branch, and for managing merges to or from that branch.

I strongly recommend tagging at strategic points in development, at the root of each branch, and at each merge point. See Section 4.1.6 of Chapter 4 for more suggestions for tagging.

Automation Strategies

Automation can be helpful in ensuring that a project moves smoothly to completion. Generally, automation translates into consistency. When using CVS on a project, you may find the following automation strategies to be helpful:

  • Use the scripting files in CVSROOT to enforce project standards and assist project communication.
  • Automate builds to encourage projects to be tested frequently. This also ensures that the entire build process is recorded and performed correctly every time.
  • Integrate CVS to your change-tracking program, to minimize the effort developers have to put in to do both version control and change tracking.
  • Keep computer clocks synchronized with each other to ensure that CVS can read accurate last-modified times.

Project Structure and Sandboxes

Consider breaking a project into subprojects or using modules so that each developer needs to check out only their part of the project.

In small projects, each sandbox can contain the entire source tree. In larger projects, use symbolic links or other tools to allow the developers to share a central set of standard files. A developer's sandbox should contain only the files he needs to work on, and the build script in his sandbox should include commands to create the necessary symbolic links to the central files. This allows him to compile and test his own code without having to compile files he hasn't changed.

Consider having a central build sandbox that is automatically updated and built on a regular schedule. This sandbox can be used to test the integration of the whole project. If developers fail to commit all their necessary files, the central build usually fails, because the update is unable to retrieve the uncommitted files. This makes it obvious when someone has forgotten to commit a crucial file.

Each task should have its own sandbox. Each developer, tester, and editor should work from her own sandbox, and anyone who is working on multiple branches should work from a different sandbox for each branch. If there is a central build, it should be in its own sandbox. If there is a file server, it should be a separate sandbox.

Use cvs release to remove sandboxes. This helps prevent modified or new files from being forgotten. If the history file is in use, this command records the removal of a sandbox.

Practices for Development

Decide whether to use the simultaneous development model or one of the watching files or reserving files models of exclusive development. These models are explained in Chapter 5, which also explains how to use each model in CVS.

Consider using watches, notification emails sent via scripting files, or other tools to keep developers informed of what is happening during a project's development.

Develop strategies for updating and committing sandboxes. Sandboxes and the repository need to be kept synchronized to minimize conflicts and ensure that a project is fully backed-up. Deciding how frequently your users update and commit is a matter of judging the risk of breaking the build with incomplete work versus the benefits of storing data, even when it won't compile.

The most common work pattern for development projects is update-edit-test-commit . In this pattern, your developers update a sandbox first thing in the morning, work on their files, test their files, and commit at the end of the day. Some groups commit at lunchtime and update again after lunch, working on a shorter cycle.

The greatest risk with frequent commits occurs in environments where one person can break something for everyone, such as a programming environment. If a person's code doesn't compile and this broken code is propogated to the entire team, no one can compile until that code is either removed or fixed. For this reason, many programming teams have a rule against committing before your code compiles.

The rule about not committing until your code compiles works well in some situations, but if a person is doing long-term experimental code, the code might not be in the repository for days. If the sandboxes aren't backed up, that's several days of work at risk. In such cases, I recommend letting the person work on an experimental branch and commit to the branch.

Some teams prefer to restrict updates rather than restrict commits. These teams update stable code into their sandbox and work from that stable code base until they have completed a feature. Then they update from a new stable code base. The risk in this case is that the changes one person has made to the code will conflict with changes from a team member in a way that CVS cannot merge, and someone will have to merge those changes by hand. The benefit is that team members are working against a known code base. This system can be duplicated by using a branch to build the feature on, then merging the branch and the new feature to the trunk. Using the branch also provides a way to commit the partially completed feature without affecting other people's sandboxes, which helps protect against mistakes.

Personal tools