Essential CVS/CVS Administration/Repository Management

From WikiContent

< Essential CVS | CVS Administration
Revision as of 13:08, 7 March 2008 by Docbook2Wiki (Talk)
(diff) ←Older revision | Current revision (diff) | Newer revision→ (diff)
Jump to: navigation, search
Essential CVS

This chapter is about the repository and the projects stored in it. If you want a quick guide to creating an initial repository, see the instructions in Chapter 2.

Topics covered in this chapter include repository creation, configuration, security, backup, and recovery. This chapter also covers the structure of the repository and the sandbox, the file locking CVS uses internally, how to edit the repository manually, and the CVS administrative files and environment variables.

CVS uses the term CVSROOT in two different ways. As the environment variable, it refers to the root directory of the repository. However, inside the repository is a directory called CVSROOT. To avoid confusion, this chapter never refers to the root directory of the repository as CVSROOT, and, except when mentioning the environment variable, this chapter uses the term solely to refer to the CVSROOT subdirectory of the repository root directory.

Tip

If your repository is in /var/lib/cvs, then /var/lib/cvs is the repository root directory and /var/lib/cvs/CVSROOT is the repository's CVSROOT directory.

Contents

Creating a Repository

The repository root directory and the administrative files should be owned by a user made specifically for CVS; typically, the user is named cvs. This reduces the damage a malicious or careless user can do to the repository, as long as they don't have root access.

To create a repository, create the repository root directory on the computer that will act as the CVS server and ensure that the root directory is owned by the user who will ultimately own the repository. Execute the command cvs -d repository_root_directory init,where repository_root_directory is the name of your directory, to set up that directory as a CVS repository. The root directory given must be an absolute path, not a relative path. Example 2-5 in Chapter 2 shows repository creation.

The cvs init command sets up the directory as a CVS repository by creating the CVSROOT subdirectory and the initial files for that directory, with their default configurations.

For any project, ensure there is enough room for three times the expected final size of the project. If you intend to store binary files, multiply their expected size by at least five. Monitor the repository's partition; your users may have a use pattern that requires more or less space than this.

For most projects, the amount of RAM on the repository server is not an issue. CVS is usually modest in its memory requirements, and a server with 32MB of RAM can handle most repositories. The two major areas of memory use are large checkouts and large diffs.

For each checkout request from a client, CVS spawns two processes. The smaller process uses a negligible amount of memory, but the larger consumes at least two megabytes and can grow to just over the size of the files in the directory being checked out. Fortunately, the larger process can use swap space rather than physical memory. Allow enough memory for several of these processes to run simultaneously, and base your calculations on the project directory that occupies the most disk space.

The diff process can also use swap space and runs at each commit. For efficient processing, allow enough swap space or physical memory to store ten times the size of the largest file you expect to commit. If memory is a problem, allow five times the size of the largest file. The diff process is fairly fast, so there is little need to allow for simultaneous processes.

The repository is user data, and it should be on a partition that is backed up and that won't shut down the machine if it is filled. Repositories are often stored in /var/lib/cvsroot.

Ensure that the repository is on a machine that everyone who will need to access the repository can reach. The repository can be local to a user, or it can be accessed remotely across a network. Remote access methods are explained in Chapter 8.

Deleting a Repository

A CVS repository that is no longer needed can be removed as you would remove a normal directory tree. All the information CVS stores about the repository and the projects in it are stored within the repository tree, most of it in the CVSROOT subdirectory of that repository.

If you have edited the scripting files in the CVSROOT directory, they may refer to scripts stored elsewhere. Check the scripting files; if the referenced scripts are no longer needed, they can also be removed.

Once you have backed up any projects you may need to refer to later, remove the repository with rm -rf repository_root_directory.

Securing Your Projects

The security of your projects relies on the security of your CVS repository, the repository's computer and its environment, all the computers that contain sandboxes, your access method, your backup storage, and your developer's working environments. The security of access methods is covered in Chapter 8. General computer security is outside the scope of this book, but you should consider it when securing your repository.

Sandboxes are checked out of the repository with the username of the user who creates them or does the checking out. If the client's computer has a group that matches the group ownership of files in the repository, files in the sandbox are usually stored with that group ownership.

Permissions in a sandbox depend on the settings for new files in the sandbox computer's operating system, on whether the user has the CVSREAD environment variable set, and on whether the files are being watched with the cvs watch commands. If a file is imported or added as executable, it is set as executable in the repository and the executable setting is preserved in the sandboxes. The CVSREAD environment variable is described in Section 6.10, later in this chapter.

Repository security is more complex than sandbox security, and it is based on the filesystem security for the operating system that the repository is running under. The specific instructions in this section are based on traditional Unix and Linux filesystem permissions, but the principles can be used to secure a repository running under systems with more extensive security options, such as Access Control Lists (ACLs).

Repository Root Directory

Secure the repository root directory so that only users who are allowed to create new projects have write access. Users who will be using existing repository projects, even though they may be creating and writing to project files, should have read access to this directory.

Securing the repository root directory usually involves creating a group for the users with the right to import projects, setting the repository root directory read-write-execute for the repository's owner and that group, and setting it read-execute for all others.

You may want to set the Set Group ID (SGID) bit on the repository root directory to ensure that all projects are imported with a reasonably secure group. In many cases, people who can import projects can also be trusted with the CVSROOT subdirectory. Example 6-1 shows a small repository and the permissions and groups set for it. The user cvs is the owner of the repository, and the group cvs is the group for people who can create new projects. The user doppel created the wowow project for the people in the group sauce, and the user jenn created the wizzard project for those in the group wizzard.

Example 6-1. Repository permissions

/var/lib/cvs$ ls -la
drwxrwsr-x   39 cvs      cvs          4096 Sep 17 22:52 .
drwxrwsr-x   41 root     staff        4096 Sep 17 23:07 ..
drwxrwsr-x    4 cvs      cvs          4096 Sep 17 23:30 CVSROOT
drwxrwsr-x    4 doppel   sauce        4096 Aug 17 12:08 wowow
drwxrwsr-x    4 jenn     wizzard      4096 Sep 24 20:14 wizzard

CVSROOT Directory

The permissions on the CVSROOT directory in the repository should be considered carefully. A user who can modify the files in this directory may be able to cause CVS to run arbitrary commands on the repository computer. Only trusted users should have write access to this directory or most of the files in this directory.

All users who will have access to CVS should have read access to all the files in the CVSROOT directory.

Two files should be writeable (as well as readable) to all users who will be running CVS commands: the history file and the val-tags file. If the history file exists, most CVS commands attempt to append to it, and fail if they can't. Many commands attempt to modify val-tags. Neither of these files trigger scripts; both are purely informational.

Project Directories

You have only directory-level control over the security of project files. CVS sets the permissions of files stored in the repository and overwrites any read or write permissions you set when it next changes the files.

When a file is changed, CVS writes a temporary version of the file, then copies the temporary file over the original file. The resulting file is owned by the person who last changed it.

Group ownership of project files and directories controls project security. Ensure that each project is group-owned by a group with appropriate membership, and set the group permissions for the project files to the project's group. Also set the SGID bit, as described earlier in Section 6.3.1.

Users who change the files in a project repository must have read, write, and execute access to the project directories. Because files are overwritten when changed (during the commit process), a user changing a file needs write permission to the directory the file is in, but not to the file itself. Usually, you create a group with the appropriate access and add your project members to that group.

Users reading project files must have read and execute access to the project directories and write access to the location of the project's locks, usually the project's directories. The LockDir option in the config file in the repository's CVSROOT directory can change the location of the lock files if you want to avoid giving out write permissions to the repository's project directories.

Note

Read-only, anonymous access to a CVS repository can be done in two ways:

  • The recommended way is to use your operating system's security mechanisms. Create an account with no password and ensure that it can only run CVS. Give that account read access to the projects you want it to access, and give the account both read and write access to the directory tree listed in LockDir.
  • The other way is explained in Chapter 8. It uses the pserver access method with no password and relies on a permissions system internal to CVS.

To ensure that group ownership is set correctly, ensure that your project directories automatically create the correct group permissions for new files. In Unix or Linux, set each directory's SGID bit to ensure that any new files or directories created in the directory are created with the same group ID as the directory they are created in. Use the command chmod g+s directory to set the SGID bit. The directories in Example 6-1 have their SGID bits set.

If a file is executable when it is imported or added, CVS sets it executable in the repository. The executable bit in the repository copy of the file is preserved when the file is checked out to the sandbox. If a file should be executable but isn't, or shouldn't be but is, change the setting in the repository with chmod.

When you change the settings on a file in the repository, the file permissions of existing files in existing sandboxes are unaffected. To correct the sandbox permissions, delete the file from the sandbox and then retrieve a new copy with cvs update.

Committing a file with sandbox permissions that differ from the repository permissions does not affect the permissions in the repository version. Repository files acquire sandbox file permissions only during an add.

General Information on Security

If you need to set project file permissions differently from your usual file permissions, you can use the CVSUMASK environment variable, which specifies a umask setting for CVS to use. The CVS process on the repository server reads the calling user's CVSUMASK and, if this variable is present, uses the umask to set the permissions of any files or directories the CVS process creates. The format of CVSUMASKis the same as the Unix and Linux shell setting umask.

CVS normally uses the project's repository directories for lock files. Consider designating an alternate directory by using the LockDir setting in the config file in the repository's CVSROOT directory. This allows you to restrict users from writing to the project directories. If you use LockDir, remember to modify any scripts you are using to freeze the repository. Also, when you need to remove a lock manually, be sure to look in the LockDir directory. Changing the lock directory is rarely needed, but you should be aware that it is possible.

Tip

If you want to use setuid, setgid, or similar ownership-changing systems, be aware that CVS is not designed to prevent a determined user from escaping from CVS and gaining access to the shell.

Repository Structure

A CVS repository is composed of the special CVSROOT administrative directory and any project directories you create. All the CVS administrative files and configuration files are kept in CVSROOT.

The project directories contain the project's files and subdirectories. The project's files are stored in RCS format and have a ,vfile suffix.

Any project directory or subdirectory may contain Attic or CVS directories. An Attic subdirectory stores any file from the directory it's part of that doesn't exist on the trunk (the main development line for the file). The Attic directory is explained in Chapter 3. A CVS subdirectory stores metadata for the files in its directory.

The server also stores files in a temporary directory set by either the TMPDIR environment variable or the -T command-line option. These files are under a directory calledcvs-serverPID , where PID is the process ID of the server. If the server is shut down and unable to clean up after itself, the files may be left in place. They can be removed safely if there is no CVS process running with the relevant process ID.

CVS Subdirectory

The only file stored in the CVS subdirectories in a repository is fileattr, which lists the file attributes of the files in the parent directory. The file attributes are settings for the cvs watch commands. In later versions of CVS, the fileattrfile may be used for other attributes or the CVS subdirectory may be used for other files.

The format for fileattr is one line per attribute, each line containing information as follows:

                  entry-type 
                  filename (tab) attribute-name = attribute-value
 [;attribute-name = attribute-value...]

Attribute names starting with _ are reserved for CVS. Future versions of CVS may allow user-defined attributes. In the current version of CVS (version 1.11.5) and earlier, user-defined attributes are not allowed.

The F entry type designates that a line contains attributes for a file. The D entry type with no filename designates that the line contains default attributes for all files in the directory that includes the CVS subdirectory.

These are the supported attributes:

_watched
Indicates that the file is being watched and should be checked out read-only.

_watchers
Lists the users watching the file. The value is in the form:

                           watcher > type [, watcher > type...]

watcher is a username; type is edit, unedit, commit, or some combination of those keywords, separated by + symbols. The tedit, tunedit, and tcommit types refer to the temporary watches created by the cvs edit command.

_editors
Lists the users editing the file. The value is in the form:

                           editor > value [, editor > value...]

editor is a username and value is time+hostname+pathname, where time indicates when the cvs edit command was run and hostname and pathname identify the sandbox from which the command was run.


Example 6-2 shows a fileattr file.

Example 6-2. CVS/fileattr

Fconfig.h   _watchers=doppel>edit+unedit+commit;_watched=
Fhandheld.c _watchers=doppel>edit+unedit+commit;_watched=
Fmain.c     _watchers=doppel>edit+unedit+commit,jenn>
edit+unedit+commit+tedit+tunedit+tcommit;_watched=;_editors=jenn>Fri Sep 27 19:15:18 2002 
GMT+helit+/home/jenn/cvs/wizzard/src
Fserver.c   _watchers=doppel>edit+unedit+commit;_watched=
D           _watchers=doppel>edit+unedit+commit

Locks

CVS uses read and write locks to prevent processes from simultaneously writing to or reading from the same repository files. These locks are signalled by the presence of a file or directory with a specific name pattern in project directories.

CVS locks the repository one directory at a time, each lock locking a directory, its Attic and CVS subdirectories, and all files in that directory. A lock does not lock subdirectories, so to lock an entire directory tree each subdirectory must be locked individually.

Note

Because CVS locks the repository one directory at a time, it is possible to check out a sandbox that comprises part of another user's committed sandbox. This happens when user A's process locks the foo subdirectory and commits to it while user B's process locks the bar subdirectory and updates from it, then user A commits to the bar subdirectory while user B updates from foo, which now contains A's changes.

In actual practice, this scenario rarely causes problems.

These are the file and directory names that indicate CVS locks:

#cvs.lock
The presence of this directory in any repository directory indicates that a process holds the master lock in the current directory.
#cvs.rfl or #cvs.rfl.*
The presence of a file with this name or name pattern in a repository directory indicates that a process holds a read lock in the current directory.
#cvs.wfl or #cvs.wfl.*
The presence of a file with this name or name pattern in a repository directory indicates that a process holds a write lock in the current directory.

Holding a master lock prohibits other programs that honor such locks from creating new locks in the locked directory. Any program that honors the locks will not read from or write to a file unless it has locked the directory the file is in. Master locks are directories, because in most operating systems directory creation is done as a single operation and can't be interrupted by another process.

A read lock is a nonexclusive lock, which permits others to read files while the directory is locked but not to write to them. You create a master lock before you create a read lock, to ensure that no one else locks the directory while you are trying to lock it. You can release the master lock once you have your read lock, to allow others to read the files as well.

To obtain a read lock:

  1. Create the #cvs.lock directory in the directory you wish to lock. In most operating systems, directory creation is an atomic process. Check the response code for the directory-creation command; if it fails because the directory already exists, wait and try again later.
  2. Once you have the master lock, create #cvs.rfl or #cvs.rfl.extension in the #cvs.lockdirectory. extension is any data you wish to add and is often a process ID. Remove the #cvs.lock directory to release the master lock.
  3. When you are finished reading in the #cvs.lock directory, you must remove the #cvs.rfl file to release the read lock.

A write lock is an exclusive lock, which prohibits others from reading or writing the files while you hold the lock. CVS prohibits reading or writing by having processes that use a write lock also hold the master lock until after they release the write lock. The write-lock file exists primarily to provide the process ID, so that you can identify which process is holding the lock if you need to clear it.

To obtain a write lock:

  1. Obtain the master lock in the same way that you obtain a read lock.
  2. Check for the presence of a read lock by looking for a #cvs.rfl or #cvs.rfl.* file. If there is a read lock, release the master lock, wait, and try again.
  3. Once you have a master lock on a directory with no read lock, create a write lock by creating a #cvs.wfl or #cvs.wfl.extension file in the target directory, where the extension may be a process ID or other identifier. Retain the master lock.
  4. When you are finished writing to the repository, release the write lock first. Then release the master lock.

If you are trying to lock multiple directories simultaneously and you encounter an existing lock, you should release all the locks you have obtained before waiting and trying again. This helps avoid deadlocks.

A deadlock occurs when two processes attempt to lock multiple directories and each is holding one directory locked while attempting to lock a directory the other is holding. Such a deadlock prevents either process from continuing.

CVSROOT Files

The CVSROOT directory contains administrative files that contain information about the projects stored in CVS. The CVSROOT directory can be checked out to a sandbox, edited, and committed in the same way as any other directories or files managed by CVS.

As part of the process of committing an administrative file, CVS exports a clear-text copy into the CVSROOT directory. The CVSROOT directory contains both RCS-format repository copies of administrative files and clear-text copies of the latest revision of the files. The RCS-format files are named filename,v. While CVS is creating the clear-text copies, it prints the message cvs commit: Rebuilding admnistrative file database.

Some of the files in CVSROOT allow you to run user-created scripts during the execution of CVS commands. Therefore, it's important to restrict the people authorized to commit or edit files in the CVSROOT directory.

It's good practice to have a specific username to own the CVSROOT directory and the repository root directory, and to be the initial owner of the CVSROOT files. This restricts the amount of damage that can be done to these files by a malicious or careless user, unless they happen to have root permissions.

Create a group to have the group ownership of the CVSROOT directory and files, and include only trusted people in that group. If this group should be permitted to create new projects, and consists of all the people who can, it can also own the repository root directory. The CVSROOT directory and most of the files should be writable only by the repository's owner and the group, but they must be readable by all users who will be running CVS commands. The directory's SGID bit should be set, to ensure that new files are created with the same group ownership as the directory.

Two files in the CVSROOT directory should be writeable by all the users who will be using the CVS commands: the history and val-tags files. These files may have a different group ownership than the rest of the files in this directory.

It is possible to commit an administrative file that has settings that prevent CVS from commiting anything else. If this happens, you can edit the clear-text repository copy of the file directly to correct the problem.

Configuration Files

The files described in the next few subsections allow you to modify CVS's behavior. Three of these files are explained in more detail in chapters about the tasks the files are associated with, but the config file is fully explained here.

config

The config file contains CVS configuration options. Lines that start with a # are comments. Other lines are in the form keyword=value, one pair per line. Whitespace is significant, including carriage returns, tabs, and extra spaces.

These are the available configuration options:


LockDir=directory
This option is available only in CVS Versions 1.10.2 and later.

If this setting is present, CVS puts lock files in the nominated directory rather than in the repository. This allows you to set the repository directories read-only for people who should not be committing changes.

You need to create directory, but CVS will create all the necessary subdirectories itself.

Warning

Do not use LockDir if any of your users are running CVS 1.9 or earlier and accessing a local repository, as users will then be putting locks in two different places and not honoring each other's locks. CVS 1.10 doesn't use LockDir; it displays an error and does not work. Versions prior to 1.10 ignore LockDir silently.

LogHistory=value'
The text in value controls which actions are logged to the historyfile in the repository's CVSROOT directory. The valid values are any combination of the following letters:

A
Log when a file is added to the repository.
C
Log when a file would have been updated in a sandbox, but needed to be merged and there were conflicts in the merge.
E
Log when a file or files are exported.
F
Log when a file or files are released.
G
Log when a file is updated in a sandbox with a successful merge.
M
Log when a file is modified (a sandbox revision is added to the repository).
O
Log when a file or files are checked out.
R
Log when a file is removed from the repository.
T
Log when a file or files are tagged or rtagged.
U
Log when a file is updated in a sandbox with no merge required.
W
Log when a file is deleted from a sandbox during an update because it is no longer active in the repository.

RCSBIN=directory'
This option provides the directory in which CVS should look for the rcs program. It applies only to CVS Versions 1.9.12 to 1.9.18; it is obsolete and ignored in later versions.

RereadLogAfterVerify=value'
This option is useful only if the verifymsg file in the repository's CVSROOT directory is in use. It applies to CVS Versions 1.11.2 and later.

The log message saved during cvs commit might be changed if the verifymsg file is in use. This option controls whether the message is reread after the program listed in verifymsg file is run.

These are the available options:

always or yes
Reread the log message after the verifymsg file has been processed. This is the default case.
never or no
Do not reread the log message after the verifymsg file has been processed.
stat
Check whether the log message has been changed, using the filesystem's stat( )command. If the log message has changed, reread it. This option can take up to an extra second per directory to process.

SystemAuth=value'
This option is useful only if the client connects to CVS in pserver mode. It applies to CVS Versions 1.9.14 and later.

If value is yes, the server authenticates the connecting user with the passwd file in the repository's CVSROOT directory. If the user fails to authenticate there, the server authenticates the user against the main user database for the operating system.

If value is no, the server authenticates the user only against the passwd file.

The default value is yes. Consider setting the value to no if you run pserver, as the pserver access mode transmits passwords with minimal security. See Chapter 8 for more information.

TopLevelAdmin=value'
If value is yes, a CVS subdirectory is created in the current working directory when you check out a sandbox.

If value is no, the CVS subdirectories are created only in the actual sandbox directory tree. The default is no.

The yes setting is useful if you run CVS commands in the working directory above your sandboxes. The CVS subdirectory contains a Root file, so you don't need to specify the -d repository option to CVS commands. If you use other CVS subdirectory files (such as Template), they will also be stored in the CVS subdirectory.

TopLevelAdmin is valid in CVS 1.9.29 and later.

PreservePermissions=value'
The current version of CVS (CVS 1.11.5) does not support this option. In the versions that support it, this option causes CVS to store the sandbox permissions in the repository with other data for each revision and pass that data to the sandbox when a file revision is checked out. The valid values are yes and no.

Warning

The PreservePermissions code is not reliable. Setting it to yes is not recommended, even in CVS versions that support it.


Example 6-3 shows a configuration file (with all comments removed).

Example 6-3. CVSROOT/config

SystemAuth=no
LockDir=/var/lock/cvs
LogHistory=TMAR
RereadLogAfterVerify=stat

cvswrappers

The cvswrappers file contains a line-separated list of wrappers that control the merge method or keyword-substitution mode of files, based on a filename pattern. Wrappers are explained in Chapter 3.

There are two additional functions for wrappers. These are not available in CVS 1.11.5, so check the documentation for your version before relying on them. They are:

-f path_to_filter
Process the file through the filter program every time the file leaves the repository.
-t path_to_filter
Process the file through the filter program every time the file enters the repository.

modules

The modules file contains information about projects in the repository and can group arbitrary files or directories into a single module. Information in this file must be created by the repository or project administrator; CVS does not update this file when a new project is imported.

Once a module is defined, the project files and directories it defines can be checked out into a sandbox using either the module name or the name of the repository directory it represents. A module can represent a directory and all its files and subdirectories, a file, or any collection of such files or directories. Directories can also be excluded from a module explicitly.

Module definitions are useful for splitting a project into several virtual projects, especially when you need to have several projects that share common files. You can also use module definitions to merge projects into a single virtual project.

Warning

If you intend to use modules for project organization, be aware that modules are not versioned. If you change the structure of the project, you may not be able to retrieve old releases with the module names in effect at the time those releases were current.

For example, if you create an animal project with elephant and turtle subprojects, then later change the name of turtle to tortoise, there is no way to record that versions of the project prior to the change use turtle and versions after the change use tortoise.

The modules file can also specify programs to run when files in the module are committed, exported, updated, checked out, or tagged with rtag. These programs can be used to integrate CVS with bug trackers or other project-management or program-development tools. The developers of CVS recommend that you use the scripting files rather than the options in the modules file.

Tip

Use the files described in Section 6.5.2 in this chapter to define programs to run during the operation of certain commands.

Chapter 7 explains the modules file in detail.

notify

The notify file contains the commands to run when conditions exist for cvs watch to notify a user of a change to a watched file. Chapter 5 explains uses for the notify file and provides an example in Example 5-1.

The syntax of the notify file is a series of lines, each line appearing as follows:

                     filename-pattern command
                  

The filename pattern can be ALL or it can be any CVS standard pattern, as used in cvsignore and cvswrappers and explained in Chapter 11. The command can be any sh shell command, but it must contain a %s , which is replaced by the name of the user to notify. The rest of the notification information is provided to the command through standard input-- stdin, in Unix or Linux.

Tip

CVS does not notify you of your own changes to a file.

Scripting Files

The files described in this section control scripts that run at specific times when the repository is modified. They can be used to interface CVS to bug-management or change-tracking systems, integrated development environments, or other tools; to enforce compliance with a project policy; or to trigger processes such as automated export programs to keep an up-to-date copy of the project files on a file server.

These files usually are configured on a per-project basis, so Chapter 7 explains them in more detail. For most of the files, the syntax is a series of lines, each containing information in the following pattern:

                  name_pattern action
               

The name pattern defines which files the action is run for and may include the project's root directory. The action is used to specify programs, and the programs defined in these files are passed the results or parameters of CVS commands, either as parameters or via standard input.

commitinfo

The commitinfo file defines programs to run before a file is committed. Typical uses include determining whether a file meets your project's coding standards or whether a system configuration file has the correct syntax. If any of the programs exit with a nonzero exit status, the commit will not proceed.

editinfo

The editinfo file is obsolete and has been replaced effectively by verifymsg and rcsinfo.

In CVS versions that use editinfo, it enforces the use of a specific editor when entering log messages. If CVS is called from a remote client or if the -m or -F command options are used with cvs commit, the editinfo file is not used. If the editor exits with a nonzero exit status, the commit will not proceed.

loginfo

The loginfo file defines programs to run when a file has been committed successfully. The loginfo file is intended as a way to record log messages to specific places, such as a ChangeLog generation program, but is often used to trigger automated export programs.

rcsinfo

The rcsinfo file does not actually trigger any scripts, but it uses the same syntax as the scripting files. It defines forms to be displayed as the template for commit log messages.

taginfo

The taginfo file defines programs to run before a file is tagged. Typical uses include determining whether tag names meet your project's standards, and logging tags. If any of the the programs exit with a nonzero exit status, the tag will not proceed.

verifymsg

The verifymsg file defines programs to run after a log message for a commit has been entered but before the commit takes place. The programs are passed the log message and can modify it or parse it to ensure that all essential fields have been filled in. The verifymsg file usually is used in tandem with the rcsinfo file to manage log messages and sometimes to interact with bug-tracking programs. If any of the programs exit with a nonzero error status, the commit is aborted.

Informational Files

The files in this section contain information CVS refers to when processing commands. You can set the information for most of these files, but history and val-tagsshould be written to only by CVS.

checkoutlist

The checkoutlist file contains a list of user-defined files stored in the CVSROOT directory and exported into that directory when they're committed, in the same way that the standard administrative files are. This can be a useful way to store user-defined scripts for the other administrative files.

The file format is simply a list of the names of the files, one file per line. Paths are not needed; all files must belong in the CVSROOT directory. Example 6-4 shows a checkoutlist file.

Example 6-4. CVSROOT/checkoutlist

passwd
wiz_bugzilla

cvsignore

The cvsignore file contains a list of filenames or filename patterns indicating files that CVS should not attempt to store in the repository. These files are also ignored when CVS displays informational messages, such as during update, commit, or status commands. The syntax for this file is a space-separated or line-ending-separated list of filenames or name patterns. Example 6-5 shows a cvsignore file.

Example 6-5. CVSROOT/cvsignore

*~ ignoreme ChangeLog
test.prog testing* a?out

In any of the lists of filenames to be ignored, the special filename ! causes CVS to clear the ignore list. While creating its ignore list, if CVS encounters a ! in a list of patterns to be ignored, it clears the list it has created to that point and starts a new ignore list with the next filename pattern it encounters.

Tip

The ! makes sense when you understand how CVS builds up its complete ignore list. I describe that process soon.

The special filename * causes CVS to ignore everything. cvsignore uses the standard CVS pattern matching explained in Chapter 11

The cvsignore file is space-separated, so it is difficult to ignore filenames that include spaces. You can attempt to work around this problem using the pattern-match syntax foo?bar, but that not only matches the file foo bar, it also matches fooxbar and foombar. Unfortunately, there is no perfect solution for ignoring filenames that contain spaces.

CVS includes a default list of filenames and filename patterns that are either CVS special files or common files that users don't want to store, such as C object code files. The default list is coded into the CVS source code:

. .. core RCSLOG tags TAGS RCS SCCS .make.state
 .nse_depinfo #* .#* cvslog.* ,* CVS CVS.adm .del-* *.a *.olb *.o *.obj
 *.so *.Z *~ *.old *.elc *.ln *.bak *.BAK *.orig *.rej *.exe _$* *$

There are many places where you can specify files for CVS to ignore. Not only can you have a cvsignore file in your repository's CVSROOT directory, but all CVS users can also have their own .cvsignore (note the dot) files in their home directories or in subdirectories in a sandbox. Files to ignore can also be specified via environment variables and command-line options. CVS generates the list of files to ignore in the following sequence:

  1. Create the ignore list with the default filenames and filename patterns.
  2. Add the entries from cvsignore in the repository's CVSROOT directory.
  3. Add the entries from .cvsignore in the user's home directory. (The .cvsignore file is explained later in this chapter.)
  4. Add the entries from the user's CVSIGNORE environment variable.
  5. Add the entries from the -I command option.
  6. Add the entries from the .cvsignore file in the current sandbox directory. These entries apply only to the directory they are in, not to any subdirectories. Each sandbox directory or subdirectory, excluding the administrative CVS directories, can have a .cvsignore file. (The sandbox .cvsignore file is explained later in this chapter.)

The CVS command-line option -I ! causes CVS to process every file, except any files that are specified in.cvsignore in the sandbox (or, if importing, the import directory). That's because -I !resets the ignore list at step 5 in the previous list. This behavior is extremely useful when you're using cvs import on a directory that contains only files that you want to store, as it ensures that every file in the directory is added to the CVS repository even if it would otherwise be ignored. Later versions of CVS may alter the processing sequence so that -I !will clear sandbox .cvsignore lists too.

history

The history file contains the information displayed by the cvs history command. It must be writeable by all CVS users, and it is created by cvs init to be owner- and group-writeable. This file should not be edited manually; all changes should occur through CVS.

If you wish to turn history logging off, simply rename the history file.

passwd

The passwd file contains the usernames and passwords used for the pserver remote-access method. Chapter 8 explains this file.

This file usually is edited in place, not checked out like the other administrative files. If you wish to check it out, add it to the commitinfo file, but be aware of the security risks explained in Chapter 8.

readers

The readers file contains the usernames of people who have read-only access to the repository via the pserver remote-access method. (Also see the writers administrative file.) Chapter 8 explains this file.

users

The users file provides a list of email addresses for users whose mailboxes are not on the same machine as the CVS repository. This list is used by the command given in the notify file, and the email address provided for the relevant username becomes the input represented by the %s string.

The format of this file is a separate line for each user, each line consisting of the username and the email address to send notifications to:

                     user:email
                  

Chapter 5 explains the use of this file and provides an example in Example 5-2.

val-tags

The val-tags file contains a list of valid tag names, acting as an internal cache for CVS. It must be writeable by all CVS users, and it is created by cvs init to be owner- and group-writeable. This file should not be edited manually; all changes should occur through CVS.

writers

This file contains the usernames of people who have read-write access to the repository via the pserver remote-access method. If this file exists, any username not in this file is given read-only access. A username listed in both writers and readers is given read-only access. Chapter 8 explains the readers and writers files.

Variable Expansion

The administrative files in CVSROOT can use several types of variables, including internal, user-defined, environment, and shell variables.

The syntax to use when referencing CVS internal variables is ${VARIABLE}. If the character immediately following the variable is neither alphanumeric nor an underscore (_), you can use the alternative syntax $VARIABLE. These are the internal variables:

CVSROOT
The path to the repository root directory (not to the CVSROOT directory within the repository). This variable contains the path only; it does not contain any access method or host information.
CVSEDITOR or EDITOR or VISUAL
The editor CVS calls for commit or import commands. This is calculated after the -e CVS option or the client's environment variables have been read.
RCSBIN
The path to the rcs program. This variable applies only to CVS 1.9.18 or earlier.

USER
The username (on the server machine, if in client/server mode) of the user running CVS.

In the pserver access method, USER represents the third field of the appropriate line in the passwd file in the repository's CVSROOT directory; or, if there is no username there, USER is the name in the leftmost field.


CVS recognizes two shell variables within the CVS administrative files:

~/
The home directory of the user calling the CVS process.
~username
The home directory of the user identified as username.

CVS sets three environment variables in the environments of scripts run via the CVS administrative files:

CVS_USER
This variable is meaningful only with the pserver access method. It refers to the CVS-specific username provided in the leftmost field of the appropriate line in the passwd file in the repository's CVSROOT directory. If this username does not exist, the variable expands to an empty string.
LOGNAME or USER
The username of the user calling the CVS process. In the pserver access method, this is the third field of the line in the passwd file; or, if there is no username there, LOGNAME or USER is the same as the CVS_USER.

CVS permits user-defined variables that can be passed to administrative files from the client, allowing CVS users to pass information to the scripts and commands set up by project leads and repository administrators. In an administrative file, read such a variable with the syntax ${=VARIABLE}. In the command line, use the -s variable=value CVS option to pass the variable to CVS.

Example 6-6 shows how to call CVS while providing and defining the user variable ${=TESTDIR}. Use the variable ${=TESTDIR} in one of the administrative files under CVSROOT.

Example 6-6. User-defined variables

cvs -s TESTDIR=/home/jenn/tests commit

I suggest using the contents of ${=TESTDIR} to point to either a location for test files, or a source of test data. In an administrative file (possibly the loginfo file described in Chapter 7), I suggest calling a test script with the ${=TESTDIR} variable as a parameter to the script. The loginfo file is read after the commit has been processed, so use that script to run an automated test suite over a program, using the contents of the directory provided with ${=TESTDIR} as the data for that test suite. If you have two teams working on the same project but with different test data or standards, you can use user-defined variables to allow each team to provide their own information source.

Note

All strings that contain the $ symbol, other than the variables, are reserved for CVS internal use. There is no way to escape the $symbol.


Server Environment Variables

CVS reads some of the calling user's environment variables whenever you run a command for which the variable may affect the results. These variables, shown in the following list, are read and used by the process that runs on the repository's server. Note that these variables must be in the calling user's environment on the repository server, so different users may cause different behaviors.

Tip

In local-access mode, CVS uses both client and server environment variables.

CVS_SERVER_SLEEP
Delays the start of the client/server process by CVS_SERVER_SLEEP seconds to allow a debugger to be attached to it. Use this variable only when debugging the server in client/server mode.

CVSUMASK
Sets the default permissions of files in the repository. See Section 6.3 earlier in this chapter.

This variable may be added to the client code in a later version of CVS.

PATH
Locates any programs whose paths are not coded into the CVS program. It is also used for the programs called in the scripting files. The PATH variable is less important to CVS than it was when the rcs, diff, and patch programs CVS now uses were not included with CVS.

TMPDIR
Sets the temporary directory CVS stores data in. It defaults to /tmp. CVS creates temporary files with mkstemp (BSD 4.3) if possible. It determines what is available at compile time, and if it can't find mkstemp it tries tempnam (SVID 3), mktemp (BSD 4.3), or tmpnam (POSIX), in that order. If it uses tmpnam, it cannot use the TMPDIR environment variable, and files will be created in /tmp.

Currently, some parts of CVS use/tmp, regardless of the contents of TMPDIR. This is a bug and should be fixed in a later version.


Backing Up a Repository

A CVS repository can be backed up using the same backup tools and schedule that you use for ordinary text and binary files. You must be able to restore the repository with the same permissions and structure it had when it was backed up. You must restore the CVSROOT directory and its contents before the project directories are useful, but you can restore project root directories and their contents independently of each other.

If a file in a repository is being written to while a backup is in progress, it is possible that the file could be backed up in a state that might make it difficult for CVS to read it accurately when it is restored. For this reason, you should prevent processes from writing to the repository during a backup.

Freezing a Repository

Freezing a repository is CVS-speak for the act of preventing users from changing the repository. The most common reason to freeze a repository is to ensure that a backup operation copies all the repository files in a consistent state.

The simplest way to freeze a repository is to block clients from accessing the CVS server. There are many ways to do this, such as shutting down the CVS server program, disallowing logins to the server system, blocking the server with a firewall rule, setting the server to single-user mode, or pulling out the network cable. Rather than blocking access to the server completely, you can use CVS's locks to read-lock the repository.

To keep the server running and allow clients to continue to read the repository files while a backup or other critical work takes place, use a script that creates lock files like CVS's internal locks. CVS honors these locks as if they were its own. Lock the directories you want to back up, make your backup, and then unlock the directories. See Section 6.4.2 in this chapter for a full explanation of CVS locks.

Example 6-7 shows a script that locks an entire repository. It attempts to lock the repository and backs off if it can't lock the whole repository. The backing off is necessary to prevent deadlocks. Example 6-8 shows a script that unlocks a repository.

Example 6-7. Script to lock a CVS repository

#!/bin/sh
# Freeze - Lock a whole repository for backup.
   
KEYMAGIC=$$
TMPFILE=/tmp/freeze.$KEYMAGIC
bail_out (  ) {
        # Exit abruptly. Return a failure code and display
        # an error message.
        echo "$1"
        rm -f $TMPFILE
        exit 1
}
   
freeze_directory (  ) {
        echo "FREEZE: $1"
        # Obtain the master lock
        mkdir "$1/#cvslock"
        if [ $? != 0  ]
                then
                # Could not get master lock
                return 1
                fi
        # Create the read lock
        touch "$1/#cvs.rfl.$KEYMAGIC"
        # Record it in case of trouble
        echo $1 >> $TMPFILE
        rmdir "$1/#cvslock"
        return 0
}
   
thaw_repository (  ) {
        # If we encounter anyone else playing with the
        # CVS locks during this, then there's a small risk
        # of deadlock. In that event, we should undo everything
        # we've done to the repository, wait and try again.
        # This function removes all the locks we've produced during
        # the run so far.
        for dir in `cat $TMPFILE`
                do
                echo "** THAW ** $dir"
                mkdir "$dir/#cvslock"
                if [ $? ]
                        then
                        # Remove read lock
                        rm -f "$dir/#cvs.rfl.$KEYMAGIC"
                        # Remove masterlock
                        rmdir "$dir/#cvslock"
                        fi
                done
        return 0
}
   
freeze_repository (  ) {
        for dirname in `find $CVSROOT/$REPOSITORY -type d ! -iname CVS ! \
        -iname Attic ! -iname "#cvslock"`
                do
                freeze_directory $dirname
                if [ $? != 0 ]
                        then
                        # We couldn't get the master lock.
                        # Someone else must be working on the
                        # repository
                        thaw_repository
                        return 1
                        fi
                done
        return 0
}
   
if [ "$CVSROOT" =  = "" ]
        then
        echo "No CVSROOT specified in the environment"
        bail_out "Aborting"
        fi
   
if [ "$KEYROOT" =  = "" ]
        then
        KEYROOT="$CVSROOT"
        fi
   
if [ "$1" =  = "" ]
        then
        echo "No Repository specified."
        echo "Usage: $0 repository"
        bail_out "Aborting"
else
        REPOSITORY="$1"
        fi
   
# Double-check the validity of supplied paths
KEYFILE=$KEYROOT/.$REPOSITORY
test -d $CVSROOT || bail_out "Can't access $CVSROOT - is it a directory?"
touch $KEYFILE || bail_out "Can't access $KEYFILE - aborting"
   
TRIES=0
while   ! freeze_repository
        do
        let TRIES=$TRIES+1
        echo "Could not freeze. Repository in use. (Attempt $TRIES)"
        if [ $TRIES -gt 9 ]
                then
                bail_out "Giving up"
                fi
        echo " Sleeping 1 second."
        sleep 1
        rm -f $TMPFILE
        echo "Trying again.."
        done
echo "** Repository $REPOSITORY frozen"
echo "$KEYMAGIC" >> $KEYROOT/.$REPOSITORY
   
rm -f $TMPFILE
exit 0

Example 6-8. Script to unlock a CVS repository

#!/bin/sh
# Unfreeze - Unlock a whole repository.
   
bail_out (  ) {
        # Exit abruptly. Return a failure code and display
        # an error message.
        echo "$*"
        rm -f $TMPFILE
        exit 1
}
   
unfreeze_directory (  ) {
        echo "UNFREEZE: $1"
        mkdir "$1/#cvslock"
        if [ $? != 0  ]
                then
                # Could not get master lock
                return 1
                fi
        test -f "$1/#cvs.rfl.$KEYMAGIC" || echo "THAW: Expected to find a lock file: \
        # Proceed anyway.
        rm -f "$1/#cvs.rfl.$KEYMAGIC"
        rmdir "$1/#cvslock"
        return 0
}
   
unfreeze_repository (  ) {
        TMPFILE=/tmp/freeze.$KEYMAGIC
        for dirname in `find $CVSROOT/$REPOSITORY -type d ! -iname CVS ! \
        -iname Attic ! -iname "#cvslock"`
                do
                unfreeze_directory $dirname
                if [ $? != 0 ]
                        then
                        return 1
                        fi
                done
        return 0
}
   
if [ "$CVSROOT" =  = "" ]
        then
        echo "No CVSROOT specified in the environment"
        bail_out "Aborting"
        fi
   
if [ "$KEYROOT" =  = "" ]
        then
        KEYROOT="$CVSROOT"
        fi
   
if [ "$1" =  = "" ]
        then
        echo "No Repository specified."
        echo "Usage: $0 repository"
        bail_out "Aborting"
else
        REPOSITORY="$1"
        fi
   
# Double-check the validity of supplied paths
KEYFILE=$KEYROOT/.$REPOSITORY
test -d $CVSROOT || bail_out "Can't access $CVSROOT - is it a directory?"
test -f $KEYFILE || bail_out "No $KEYFILE appears to exist. Repository does \
not appear to be frozen"
TRIES=0
   
# Walk through each of the keys that the repository has been frozen with
# and unlock each one in turn. A single run of unfreeze thaws multiple
# runs of freeze.
for KEYMAGIC in `cat $KEYFILE`
        do
        unfreeze_repository 
        if [ "$?" = "1" ]
                then
                echo "** Unable to obtain master locks for all directories."
                let TRIES=$TRIES+1
                if [ "$TRIES" = "10" ]
                        then
                        bail_out "Too many attempts. Giving up."
                        fi
                sleep 1
                echo "** Trying again."
        else
              echo "** Repository $REPOSITORY thawed from freeze $KEYMAGIC"
                fi
                
        done
echo "** Unfreeze complete"
echo "** Repository $REPOSITORY thawed"
rm -f $KEYFILE
exit 0

If you need to freeze the repository entirely, preventing anyone from either reading or writing, modify the scripts in Example 6-7 and 6-8 to create and remove write locks. For example, you need to do this when attempting to restore a backup of the whole repository.

Restoring a Backup

A CVS repository can be restored using the same tools that you use to restore ordinary text and binary files. You must restore the CVSROOT directory and its contents to be able to use the project files reliably. You need not restore every project in the repository, but when you restore a project you should restore all its files and directories.

When you restore a repository, you must ensure that no one can write to it. You can do this by freezing the repository as described in Section 6.7.1, but if you use locks you must use write locks rather than the read locks described in that section. That's because the repository will be in an inconsistent state while the files are being restored; you don't want anyone to read from or write to the repository during the restore process.

After you have restored a CVS repository from a backup, it is safest to assume that any sandbox files are based on revisions that do not exist in the backup of the repository. This means that individual developers are likely to have changes in their sandboxes that need to be committed (or recommitted) to the repository in order to bring it up-to-date.

This is the simplest way to restore changes that have been preserved in sandboxes:

  1. Check out a new sandbox from the restored repository.
  2. Try to use commands such as cvs diff, cvs status, and cvs update on a sandbox from before the repository was restored, to determine which files have been changed and what the changes are. If this does not work, try using the Unix diff program to determine the differences between an old and the new sandbox.
  3. Copy the old sandbox files you want to commit or recommit into the new sandbox.
  4. Commit the changes as if they were ordinary changes.
  5. Make sure the log message indicates what happened.

Mirroring a Repository

Many project teams have public, read-only CVS repositories. If you want to do likewise, you can copy the repository, reserve the original repository for your project work, and use the copy (or mirror) repository as a public repository.

The copy can be on the same computer as the original or it can be on a different computer or even a different network. Having two or more copies of a repository allows you to put one copy on a trusted network for your developers and another on a more public part of your network for the general public.

Note

You can't mirror a CVS repository for making changes. CVS stores its data on a revision-by-revision basis and cannot resolve copies of a file that have different sets of changes with the same revision number.

This situation may change in the near future. With a combination of a program called CVSup and a recent patch that will likely be incorporated in new versions of CVS, you will be able to commit to branches on mirror repositories.

Mirror a repository by copying the repository files. To avoid getting part of one release and part of another due to the copy overlapping with a commit, freeze the repository with read locks before mirroring and unfreeze it afterward.

The Unix and Linux rsync program is a useful tool to use when mirroring. It allows you to recursively transfer only the changed files between any two directories and recovers properly if it's interrupted. It also has an optional CVS-exclusion mode, called with the --cvs-exclude option, which ignores the same files CVS ignores by default. Use the -e ssh option to rsync to transfer using the SSH protocol.

Editing a Repository

There are times when you need to restructure a project or when something has gone wrong in the repository. In an ideal world, restructures or repairs would all be done using the CVS commands explained in Chapter 3 or the cvs admin command explained in Chapter 7. Sometimes, however, those commands can't do what you're trying to achieve, and you need to modify the repository directly.

The main problem with editing the repository directly is that you may lose historic data. Removing files or directories affects retrieval of older releases of a project and may affect build scripts. Moving or renaming files or directories can affect older releases, build scripts, and instructions within your files.

Warning

Always freeze a repository with write locks before hand-editing it.

Always consider backing up and freezing the directories you're hand-editing. If you're just removing a stale lock, this isn't necessary. But you should backup and freeze directories if you're moving, removing, editing, or renaming content files. Try to get your users to commit and release their sandboxes before you hand-edit the repository. They can checkout new sandboxes afterwards.

If a user does not release an old sandbox and tries to act on a filename that the sandbox has records of, but the repository does not have the file, the user will receive strange errors. Correct these errors by releasing the user's sandbox copy of the file in question and then checking out the file again using the new filename.

If you are using the scripting files in the CVSROOT directory, you may need to edit them when a directory is moved or removed.

Moving Files and Directories

CVS does not have a built-in command for moving or renaming a file or directory. This doesn't mean that you can't move or rename files and directories; it just requires ingenuity.

Tip

To rename a file or directory, move it from the old name to the new name.

As shown in Chapter 3, the simplest way to move a file involves using cvs remove and cvs add on the sandbox copy of the file. This method is simple, retains the history of the file, and allows mistakes to be corrected easily. The revision numbering for the file starts over at 1.1, but if you find that bothersome, you can change the revision numbering with cvs commit -r revision filename. If this method for moving or renaming a file doesn't suit your needs, you can edit the repository.

There are two ways to move a file by editing the repository and two ways to move a directory. Directories can't be moved easily with CVS commands, as they can't be deleted without editing the repository. Chapter 3 explains how to get the effect of moving a directory without losing historic information.

Prerequisites to moving a file

To move a file by editing the repository directly, you need to move all the file's data. Most of this data is stored in the file itself, but if the file is being watched or is flagged for editing with cvs edit, there may be data about it in the fileattr file in the CVS subdirectory of the directory your file is stored in.

If there is data in fileattr about the file you want to move, the record for that file needs to be cleared before you move or remove the file. It is best to clear this record with CVS commands — by having users use cvs release or cvs unedit, and cvs watch remove — and then usingcvs watch offon the file. If you can't clear information for a file out of fileattr with CVS commands, edit fileattr directly and remove all lines with that file's filename. Then, copy the removed lines to the fileattr file in the new location.

Moving a file: Method 1

The first method for moving a file is simply to move the file:

  1. Have users cvs release their sandboxes.
  2. Freeze the repository directory that contains the file and the directory it will be moved to, to prevent people from reading or writing to them.
  3. Remove any information about the file from the fileattr file in the same directory. Save this information, if there is any, for step 6.
  4. Move the file with Unix's mv command (e.g., mv old,v new,v).
  5. Change any build scripts or other files that refer to the old name.
  6. Restore fileattr data in the new location.
  7. Unfreeze the repository and allow users to check out sandboxes.

This method for moving a file doesn't affect the file's revision numbering, and it allows the old directory to be removed if all the files are taken out of it in this way.

However, this method damages the historic record for the file being moved. When old revisions of the file or old releases of the project are checked out, the file will be retrieved with its new name. There will also be no record of when the name was changed and why.

Moving a file: Method 2

This method involves copying the file, then using CVS commands to move the old copy to the Attic as a dead revision, and finally removing tags from the new copy. Removing the tags ensures that retrieving old revisions by tag works as expected — retrieving the old filename, not the new one.

  1. Freeze at least part of the repository (for writing) in a way that allows you to access it as a client, possibly from a sandbox on the same machine as the repository. You need to freeze only the directory that contains the file you want to move and the directory it will be moved to. Because you need to use a sandbox, it may be easier to limit access to the server — effectively freezing the whole repository — than to use write locks on the affected directories.
  2. Make sure you have an active sandbox that contains the file you want to move.
  3. In the repository, copy the file using the Unix cp command (e.g., cp old,v new,v). You can now remove the lock on the old directory.
  4. Copy the relevant line of the fileattr file from the file's old location to its new location.
  5. In the sandbox, remove the old file with cvs remove old. Commit this change with a message that you've moved the file to the new filename.
  6. In the sandbox, retrieve the new file with cvs update new.
  7. Remove any nonbranch tags from the new file with cvs tag -d tagname new. Use cvs status -v new to get the tagnames. This works because CVS stores tags inside the repository copy of project files.
  8. Merge the file to other active branches, if necessary.
  9. Change any build scripts or other files that refer to the old name.
  10. Unfreeze the repository and allow users to resume work.

This method has the following advantages:

  • Checking out old revisions by tag works perfectly.
  • The log of changes is intact.
  • Revision numbers are intact.

This method has a few problems. The move is recorded only in the commit message in the old file, and retrieving old revisions by date retrieves old revisions with the new filename as well as the old one.

Another issue with this method involves branch tags. Removing the branch tags prevents old branch revisions from appearing under the new name, which is desirable but also means that the new file is not on the branch.

You can remove the old branch tags from the new file to prevent old revisions from being retrieved incorrectly, then add the new file to the branch with cvs add to put the file back onto the branch. But because of the way CVS stores branched files, this may corrupt the file if some of the information in the new file can't be retrieved properly. I recommend that you do not do this, as the data can be difficult to recover if you change your mind.

Tip

If you need to move a branched file, I recommend you use method 1 to move it, or omit the step of removing the tags if you use method 2. And if you do remove branch tags, make a backup first.

Example 6-9 and Example 6-10 show how to move a file with this method. Example 6-9 shows the command to copy the file in the repository, and Example 6-10 shows the commands used in the sandbox.

Example 6-9. Moving files with method 2, repository view

/var/lib/cvs/wizzard/src$ cp main.c,v wizzard.c,v
/var/lib/cvs/wizzard/src$

Example 6-10. Moving files with method 2, sandbox view

bash-2.05a$ cvs remove -f main.c
cvs server: scheduling `main.c' for removal
cvs server: use 'cvs commit' to remove this file permanently
bash-2.05a$ cvs update wizzard.c
U wizzard.c
bash-2.05a$ cvs status -v wizzard.c
=  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =
File: wizzard.c           Status: Up-to-date
   
   Working revision:    1.8
   Repository revision: 1.8    /var/lib/cvs/wizzard/src/wizzard.c,v
   Sticky Tag:          (none)
   Sticky Date:         (none)
   Sticky Options:      (none)
   
   Existing Tags:
   pre_beta_0-1         (revision: 1.8)
   
bash-2.05a$ cvs tag -d pre_beta_0-1 wizzard.c
D wizzard.c
bash-2.05a$ cvs commit -m "Moved main.c to wizzard.c"
cvs commit: Examining .
Removing main.c; 9L, 316C written
/var/lib/cvs/wizzard/src/main.c,v  <--  main.c
new revision: delete; previous revision: 1.8
done
bash-2.05a$ cvs commit -f -m "Moved main.c to wizzard.c" wizzard.c
Checking in wizzard.c;
/var/lib/cvs/wizzard/src/wizzard.c,v  <--  wizzard.c
new revision: 1.9; previous revision: 1.9
done

Moving a directory: Method 1

Moving a directory within the repository damages the historic record for the project. When old releases of the project are checked out, the directory and the files it contains are retrieved with the new name. There is no record of when the name was changed and why. To move a directory:

  1. Have users cvs release their sandboxes.
  2. Freeze the repository directory to be moved, any subdirectories, and the parent directory, to prevent people from reading from or writing to them.
  3. Edit any of the scripting files in the CVSROOT directory of the repository that refer to the directory you are moving. The most likely of these files is modules. CVS does not edit the scripting files itself, so you need to check only files that you or others with write access to the CVSROOT directory have changed.
  4. Move the directory with mv old new.
  5. Correct any build scripts or other files that refer to the old name.
  6. Unfreeze the repository and allow users to check out new sandboxes.

Moving a directory: Method 2

This alternate method of moving a directory preserves the old structure of the project, which can allow older build scripts to continue to work on old versions of the project. This method leaves the old directory in place, available for new files to be added to that directory. You may not always want this result.

  1. Follow steps 1-3 in method 1.
  2. Move the directory with cp old new.
  3. Correct any build scripts or other files that refer to the old name.
  4. Unfreeze the repository enough to allow you to create a single sandbox with permissions to the old directory.
  5. From that sandbox, remove all the files in the old directory with cvs remove.
  6. Unfreeze the repository and allow users to check out new sandboxes.

Deleting Files and Directories

Before deleting any files or directories manually, have your users cvs commit and cvs release their sandboxes. They will get odd errors if they try to commit or work with files and directories that no longer exist in the repository.

Deleting a file

When you delete a file with the cvs remove command, CVS sets its state to dead (see Section 6.8.4). If the revision you deleted was the head revision of the trunk, CVS stores the file in an Atticsubdirectory of its original directory.

To remove a file from the repository entirely:

  1. Have users cvs release their sandboxes.
  2. Freeze the affected repository directory, to prevent people from reading from or writing to it.
  3. Check whether the file is recorded in the fileattrfile and remove the reference if it is. (See Section 6.8.1 earlier in this chapter.)
  4. Remove the file with the Unix rm command.
  5. Unfreeze the repository and allow users to check out new sandboxes.

Deleting a directory

CVS does not provide any way to delete a directory with CVS commands, because removing a directory necessitates removing its Attic subdirectory. Even if the directory is empty of active files, if it has ever contained files it will have an Attic subdirectory, which needs to exist to allow old revisions of files or old releases of the project to be retrieved.

If you will not need to retrieve any files stored in a directory's Attic subdirectory, you can delete the directory in the repository with the following method:

  1. Have users cvs release their sandboxes.
  2. Freeze the repository directory that contains the directory to be removed, to prevent people from reading from or writing to it.
  3. Move any project files as shown earlier in Section 6.8.1. Use the cp method (method 2) if you want to retain old revisions; use either method if you don't.
  4. Delete any lock files, the Attic subdirectory, and the CVS subdirectory with rm or rmdir.
  5. Delete the directory with rm or rmdir.
  6. Unfreeze the repository and allow users to check out new sandboxes.

Deleting a Project

A project can be deleted by deleting its directory, as described in the previous section. However, a project directory is more likely to have entries in the scripting files in the CVSROOT directory. The most critical scripting file to check is modules, as this file defines module names that can be used as parameters to most CVS commands.

Ensure that all users have commited and released their project sandboxes before you delete the project. If they attempt to work on a project that has been removed, they will get interesting error messages.

Editing a Project's RCS Files

The project files in the repository are stored in Revision Control System (RCS) format. Applying RCS and SCCS (O'Reilly) by Don Bolinger and Tan Bronson explains RCS in detail. The RCS format is also explained in the CVS source-code document RCSFILES (in the docdirectory) and in the Unix and Linux manual page man 5 rcsfile.

If you think you need to edit the repository copy of a project file in order to solve a problem, always try to use the cvs admin commands to fix the problem first. These commands include most of the functions RCS provides for managing an RCS-format file. The cvs admin commands are explained in Chapter 7.

CVS edits an RCS file by editing a copy of the file, then replacing the existing RCS file with the edited copy. Lock the repository directory; then follow this same procedure if you are editing a file manually. You can edit an RCS file with any plain-text editor.

Example 6-11 shows the header of an RCS file.

Example 6-11. RCS file header

head  1.2;
access;
symbols
  beta_0-1_branch:1.1.0.2
  beta_0-1_branch_root:1.1
  pre_beta_0-1:1.1;
locks; strict;
comment @ * @;

The header contains metadata about the file, including the head (most recent) revision of the trunk, the revisions and names for each tag, the RCS lock status (always strict for CVS files), and a comment field bounded with @ symbols.

After the header, the RCS file contains revision metadata for each revision in the file. This metadata includes the revision number, date, author, state, any branches that use that revision as their base node, and the nextrevision. The next revision is the revision that is one revision older than the current revision on the same branch (or trunk) of the file. After all the revision metadata, the RCS file has a space to store a file description. The file description is bounded by @ symbols.

Example 6-12 shows RCS revision metadata. Note that the branch revision 1.1.2.1 is in the dead state, which means that the revision has been removed with cvs remove. The description of this file is empty, but the @ symbols that would bound a description are present on the line under desc.

Example 6-12. RCS revision metadata

1.2
date  2002.09.13.07.26.27;  author jenn;  state Exp;
branches;
next  1.1;
   
1.1
date  2002.09.12.08.56.41;  author jenn;  state Exp;
branches
  1.1.2.1;
next  ;
   
1.1.2.1
date  2002.09.13.07.42.06;  author jenn;  state dead;
branches;
next  ;
   
desc
@@

The rest of the RCS file contains the file content — the actual text of the file and the log messages. The head revision contains the bulk of the file content. Every other trunk revision contains only the differences between that revision and the revision that is one revision newer than it. These differences are stored in the same format used by the diff and patch programs.

Trunk revisions are stored in reverse order. To retrieve any trunk revision, you start with the contents of the head revision and recursively apply the patches stored with each revision to those contents. Work backward chronologically until you reach the desired revision. The next field in the revision metadata tells you which revision's patches should be applied in the next recursion.

Branch revisions are slightly different. If your desired revision is on a branch, you start with the most current revision of the trunk and work backward to the branch point, then work forward through branch revisions.

Revision 1.1 in Example 6-13 contains the code d12 3, which means delete 3 lines starting at line 12.

Each revision starts with the revision number, then the log message (preceded by log and bounded by @ symbols), then the revision text or diff (bounded by @ symbols). If an @ is required inside a section, it is escaped with another @. An email address looks like this:jenn@@nosuch.com.

Example 6-13. RCS file body

1.2
log
@Minor tweaks to the config file.
@
text
@/*
 * Wizzard.h
 * Sep 12 2002
 * Developer: Jenn Vesperman (jenn@@nosuch.com)
 *
 * Headers, macros and constants file for the Wizzard project.
 *
 */
   
#include "config.h"   /* using autoconf */
#include "options.h"  /* manual options that can't be done in autoconf */
   
#define TRUE 1
#define FALSE 0
@
   
1.1
log
@Moving src/wizzard.h to src/config.h
@
text
@d12 3
@

Clearing Locks

When a CVS client process is waiting on a lock in a repository, the client displays messages such as those shown in Example 6-14.

Example 6-14. Waiting for a lock

cvs server: [23:30:43] waiting for jenn's lock in /var/lib/cvs/wizzard/src
cvs server: [23:31:13] waiting for jenn's lock in /var/lib/cvs/wizzard/src
cvs server: [23:31:43] waiting for jenn's lock in /var/lib/cvs/wizzard/src

The process waits 30 seconds between tries. If the process is still waiting for a commit after an unusually long time, you may need to check whether the other user's CVS process has crashed and (if so) remove the lock manually. In CVS 1.11.3 and later, CVS provides the time in UTC. In earlier versions, the time is given in the server's time zone.

The simplest way to determine whether a process has crashed is to check with the user running the client program. If they're uploading large files over a slow link, wait a little longer.

If you can't get in touch with the user directly, you can check the status of the user's process on the repository server by using whatever commands are appropriate on your system (for example, ps -ef on many Unix systems). CVS puts the process ID in the name of the lock file. Check whether that process is still alive and functioning, and see if it is a CVS process. If the process has died or if some other program has taken the process ID, you can remove the lock safely by removing the lock file.

To remove a lock manually, check for a lock file or files in the directory given in the error message. There may be a lock file and a master directory belonging to the user's process. The lock file or files will be the #cvs.lock directory or files whose names start with #cvs.rfl or #cvs.wfl. Remove these files to remove the lock.

In Example 6-15, I have already determined that the process 20233 on the server named nest belongs to a crashed CVS client, so #cvs.wfl.nest.20233 is redundant. The master lock #cvs.lock is owned by the same user and also belongs to the crashed client. Example 6-15 shows how to remove the lock files.

Example 6-16 shows the CVS output to the client once the lock files are cleared.

Example 6-15. Clearing a lock, server view

jenn@nest:/var/lib/cvs/wizzard/src$ ls
#cvs.lock  #cvs.wfl.nest.20233   main.c,v   wizzard.h,v
jenn@nest:/var/lib/cvs/wizzard/src$ rm *cvs.wfl.nest.20233
jenn@nest:/var/lib/cvs/wizzard/src$ rmdir *cvs.lock
                  

Example 6-16. Clearing a lock, client view

cvs server: [23:33:13] obtained lock in /var/lib/cvs/wizzard/src
RCS file: /var/lib/cvs/wizzard/src/main.c,v
done
.
.
.
done
bash-2.05a$

Sandbox Structure

A CVS sandbox is composed of a sandbox root directory, the project files and directories, and a special CVS subdirectory in the root directory and in every project directory. The sandbox root directory usually has the same name as the module it was created for, or it takes the name of the project root directory in the repository.

Project files in a sandbox are in editable form, just as if they were exported from the repository for release. The CVS subdirectory in each project directory stores metadata for the files in the project directory it is part of. This metadata is contained in a number of files (and one directory). Each of the sandbox directories may also contain a .cvsignore file.

CVS Subdirectories

Each project directory in a sandbox contains a subdirectory named CVS. This CVS subdirectory contains the files that store the administrative data for the files in the project directory. The following list describes the files and directories you will most likely find in a CVS directory. Files that are rarely encountered or are temporary are not included in this list. The full list of files is available in Chapter 11.

Tip

If you need to edit any of these files manually, back the file up before you start.

Base
Stores the pre-editing revision of any files that are being edited with cvs edit. This is a directory, not a file.
Baserev
Contains the revision information for every file in the Base directory, in the format name/revision/. Later versions of CVS may add to this format.
Checkin.prog
Used if the modules file in CVSROOT has a -ioption for the module in this sandbox. Checkin.prog stores the program used when the module is committed. This file is obsolete in CVS 1.11.5 and later.

Entries
Contains a line for each file and directory in the relevant sandbox directory. Lines for files have the format:

                           /name/revision/timestamp[+conflict]/options/tagdate
                        

Lines for directories have the format:

                           D/name////
                        

There may be text between or after the empty slashes in the directory form, but in the current version of CVS (1.11.5) this text is ignored. The space is reserved for future expansion.

Entries.Log
Used to record planned changes to the Entries file, one change per line. Lines to be added to the Entries file start with A; lines to be removed start with R. There must be a space after the A or R, followed by the line to be added or removed from Entries in the same format as in that file.

Programs that read Entries should also check for Entries.Log. If this file exists, the programs should read Entries, apply the changes from Entries.Log, then rewrite Entries and remove Entries.Log.

Entries.Static
If this file exists, CVS did not receive a complete set of files from the repository and this sandbox directory is static; CVS will not create new files in the parent directory. This file can be cleared by using update -d to download a full set of files and subdirectories for the parent directory.
Notify
Contains any cvs watch notifications that have not yet been sent to the server.

Repository
Usually contains the path from the repository root to the repository directory that the relevant sandbox directory is a reflection of. This file may contain the full path, including the path to the root of the sandbox's repository.

If the current sandbox directory does not have a related repository directory, this file contains CVSROOT/Emptydir.

Root
Contains the path to the root of the sandbox's repository.

Tag
Used to store a sticky tag or date that applies to the whole parent directory, if one exists. This sticky tag or date is usually used to add the relevant tag or date to new files in the directory, especially when using branch tags.

If the first character in the Tag file is T, the Tag file designates that the parent directory has a sticky branch tag. N is a nonbranch tag, and Dis a date.

Template
In client/server mode, this file stores the login template specified in the rcsinfo file in the repository's CVSROOT directory.
Update.prog
Used if the modules file in CVSROOT has a -uoption for this sandbox's module. The file stores the program used when the module is updated. This file is obsolete in 1.11.5 and later.

Dot Files in Sandbox Directories

The .cvsignore file can be used in any sandbox subdirectory or sandbox root directory. It contains a list of files CVS should not process from the directory. This file uses the same format as cvsignore in the repository's CVSROOT directory, but its list applies only to files in the directory it is contained in.

Dot Files in User Home Directories

CVS reads several files and one directory in the calling user's home directory. These files and the directory are described in the following list. All but the .rhosts file are read and used by the process that runs on the client computer. .rhosts is used with rsh and resides in the user's server-side home directory. Note that these files and the directory must be in the calling user's directory, so different users may cause different behavior.

.cvsrc
Contains a list of CVS commands and the options the user wants as default options for those commands. See Chapter 3 for more details on command options.
.cvsignore
Contains a list of files CVS should not process. This file uses the same format as cvsignore in the repository's CVSROOT directory.
.cvswrappers
Contains a list of wrappers that affect how a file is stored, based on the filename. See Chapter 3 for more details.
.cvspass
Used in pserver remote-access mode. This file contains the user's password, stored in a simple form of encoding. Be aware that the file is human-readable and the password is easy to decrypt.
.rhosts
Used when connecting with rsh. This file should be in the user's home directory on the server machine, and it should contain the client's computer hostname and the username on the client machine. See Chapter 8.
.ssh
Used when connecting with SSH. This directory should be in the user's home directory on both client and server machines. See your SSH documentation for details.

Client Environment Variables

CVS reads the calling user's environment variables, described in the following list. The variables in the list are read and used by the process that runs on the client computer. Note that these variables must be in the calling user's environment, so different users may experience different behavior.

Tip

In local-access mode, CVS uses both client and server environment variables.

CVS_CLIENT_LOG
Used for debugging CVS in client/server mode. This variable should be set to a filename. If it is set, everything sent to the server is stored in the filename.in file and everything received by the client is stored in filename.out.
CVS_CLIENT_PORT
Used to set the port the client uses to connect to the CVS server in kserver, gserver, and pserver modes. By default, clients use port 2401 (gserver or pserver) or port 1999 (kserver) to connect to the server.
CVSIGNORE
A whitespace-separated list of filename patterns that should be ignored. See .cvsignorein the previous section.
CVSEDITOR or EDITOR or VISUAL
Used to set the editor CVS calls when it opens an editor for log messages. On Unix and Linux systems, this variable defaults to vi. Using CVSEDITOR to customize CVS is preferred, as the other variables may be used by other programs as well. CVSEDITOR is searched first, then EDITOR, and VISUAL last. If the -e editor CVS option is used, it overrides the environment variables.
CVS_PASSFILE
Used to change the file CVS uses to store and retrieve the password in pserver remote-access mode. The file defaults to .cvspass in the user's home directory.
CVSREAD
If this variable is set to 1, CVS tries to check sandboxes out in read-only mode. (CVS actually checks whether this variable is nonnull, so it works regardless of the setting. This behavior may change in the future.)
CVSROOT
May contain the full pathname of a CVS repository, as described in Chapter 3. When working in a sandbox, this variable is not needed. If working outside a sandbox, either this variable must be present or the -d repository_path command-line option must be used.
CVS_RSH
Used to set the program CVS calls to connect to a remote repository when using ext mode. The program defaults to rsh.
CVS_SERVER
If connecting to a CVS server using rsh, this variable is used to determine which program to start on the server side. In ext and server modes, the program defaults to cvs. In fork mode this defaults to the full path of the executing CVS client program.
CVSWRAPPERS
May contain one wrapper, as explained in Chapter 3.
HOME or HOMEPATH or HOMEDRIVE
Used to determine the location of the user's home directory, to enable CVS to locate its dot-files. On Unix, Linux, and related systems, only HOME is used. On Windows systems, HOMEDRIVE and HOMEPATH are used together. Some Windows operating systems (NT and later) set these variables automatically. If yours doesn't, HOMEDRIVE should be set in the format C:, and HOMEPATH should be set in the format\home\jenn.
RCSBIN
This environment variable became obsolete in CVS 1.9.20. It was used for the path to the external rcs program before rcs was added to the CVS distribution.

Exit Status

You may at some point want to write a script that includes one or more CVS commands, such as a script to automatically export files from a repository to a web server, or a script to automatically update a test directory and attempt to build a program. If you do, you will need to know whether a given CVS command succeeded.

CVS commands set an exit status when they complete processing. A successful command always returns the success status. A failing command prints an error message and returns the failure status.

The cvs diff command behaves differently from the other commands. It returns success if it finds no differences and failure if it finds differences or encounters an error. This behavior may change in later versions of CVS.

Testing the exit status depends on the operating system. In Unix and Linux, the sh shell variable $? is 0 if CVS returns success, and nonzero if it returns a failure. Example 6-17 is a script to test a CVS return value.

Example 6-17. Testing return values

cvs commit -m "Automated commit"
if [ $? -eq 0 ]; then
        echo "Commit successful."
else
        echo "Commit failed with return code $?"
fi
Personal tools