The previous section explained CVS concepts in general terms. In this section we explain how to do various useful things.
How do I make a new repository?
Set the CVSROOT environment variable to point to the top directory of the new repository.
% setenv CVSROOT ~/csc369/cvsrootNow do cvs init:
% cvs initThis creates a new empty repository.
How do I make a new project in a repository?
There are two ways to add code to a CVS repository. One is to use cvs add to add files and directories one at a time. The other is to use cvs import to do a bulk import of a whole existing source tree. The next section describes using cvs import; cvs add is described further below.
How do I import existing code into a repository?
First, make sure the CVSROOT environment variable is set to the right CVS repository, the one you want to import into. (Always double-check this. It's embarrassing to import into the wrong repository.)
% echo $CVSROOTUnpack the source tree you're going to import in a temporary directory. Then, go into the top level of the source tree you wish to import:
% mkdir ~/tmp % cd ~/tmp % tar -xvzf ~/somewhere/os161-1.05.tar.gz % cd os161-1.05Now run cvs import. You need to provide three things: the place to import into, a symbolic name (which will become a tag, see above) that identifies the stuff you're importing or where it came from, and another symbolic name (also a tag) that identifies the particular version of the stuff you're importing.
To specify the place to import into, you provide a relative path within the repository where you want the contents of the current directory to appear. For CS161, you want the OS/161 distribution to appear in a directory called src at the top level of the repository, so you would specify src.
The first tag is a branch tag. It will name a branch (see above) on which the imported version will exist. The second tag names a specific version on this branch, which is known as a vendor branch.
The idea is that you can, later, import a new version of the same source tree using the same branch tag and a new version tag, and CVS will help you merge the changes with your own work. This is intended to allow people to maintain their own personal modifications to other people's programs; it can be useful for other purposes as well. It is an almost painless use of branches.
In addition to the required argument, you can specify an optional log message with the -m option. If you don't do this, CVS will run an editor for you to enter the message.
% cvs import -m "Import OS/161 distribution" src cs161-staff os161-baseWhen you run cvs import, CVS will print one line for every file it processes, with a key letter before it. "N" means the file is new. "U" means CVS has updated an existing file in the repository. "C" means that cvs has attempted to update a file, but a conflict exists and a merge is required. (The "C" and "U" results cannot happen on an initial import when there are no files already in the repository; they apply only to second and subsequent imports on a vendor branch.)
You do not need to watch these lines for "C" as they whiz by. If CVS finds conflicts, it reports them when it finishes. It will then print a message showing you a cvs checkout command to do to perform the merge. At this point, you must perform this checkout in a temporary directory, resolve any merge conflicts, and commit the results. If you do not, when you next update your main working area, some files and not others may reflect changes from the new import.
Adding files and directories to a repository
You can add files or directories to a repository using cvs add. When adding files or directories, it is important to get their permissions correct before adding them, so check permissions and use the chmod, chown, and chgrp commands to set the permissions on the files/directories the way you want them.
To add a directory into an existing repository, create the directory in the appropriate place in a checked out tree and then ask CVS to add the directory:
% cvs add dirTo add files to an existing directory, create the new files in the appropriate directory in a checked out CVS tree, and then ask CVS to add them to the repository:
% cvs add newfileUnlike adding directories, these files will not be added to the repository until you use CVS to commit your changes.
How do I check out a working tree?
Use the cvs checkout command with the name of the project (the top-level directory in the CVS repository):
% cvs checkout srcThis will create a directory called src that holds a working tree. Be sure to do this in a good place. Do not do it in the CVS repository-this creates a huge mess.
If you want to check out a particular version, or branch, you can use the -r option with a tag that names the version or branch. (While you can give file version numbers as well, doing so does not produce useful results.) You can also specify a date using the -D option, to get a snapshot of the project as of a particular date. Note that when you do this, CVS remembers what you did, and may as a result not update or let you commit changes. Use the -A option to make it go back to the normal behavior.
How do I update my working tree?
Use cvs update. You can update whole directory trees or individual files. It's your responsibility, if you don't update everything at once, to make sure the resulting working tree you have is self-consistent.
One should (almost) always use the -d and -P options with cvs update when updating directories or whole trees. The -d option causes CVS to add new directories that are not in your working tree; without it, these directories never appear. The -P option causes it to remove completely empty directories from your working tree. This is important, because there is no way to really remove directories from the repository (see below).
The -q option before the update can be used to make CVS not print the cvs update: Updating foo messages, which can improve legibility with large trees.
% cvs -q update -dP srcor
% cvs update src/kern/vm/vmstuff.cIf you don't specify what to update, CVS updates the current directory (and any subdirectories).
Like with cvs checkout you can specify particular versions or dates with -r or -D, and make CVS forget about these using -A.
When you update, CVS prints one line for each file it processes, with a letter in front of it reflecting the file's status. These letters are:
CVS can be made to shut up about unknown files whose presence is routine by adding them to a .cvsignore file, which can then be committed to the source tree. There are a number of examples in the OS/161 source.
There is no good way to retrieve the status summary without also doing an update. (There is a cvs status command, but its output is much less concise.)
How do I commit my changes?
Use cvs commit. You can commit directories or individual files. You can use the -m option to supply a commit message on the command line; if you don't, CVS will invoke the editor for each directory into which it commits files. Like with most commands, if you do not specify anything to commit explicitly, CVS commits all changes in the current directory and all subdirectories.
% cvs commit foo.cor
% cvs commit src/kernRemember that changes that have not been committed-including adding and removing files- will not be seen by other developers.
How do I add files?
Use cvs add and supply the filenames. The files will then show up with the "A" code on subsequent updates until they are committed. Remember, they will not be seen by other developers until they are committed.
Important: before committing new files, always chmod them so their permissions allow access by everyone you're working with. CVS does not cross-check the permissions of files in the repository; it replicates the permissions of the files when they're first committed. If those permissions are not correct, the file may appear in the repository with permissions that make it inaccessible to some or all other developers, causing them to become upset until you chmod the repository file properly. (See below.) This behavior is a long-standing bug in CVS.
How do I remove files?
When you wish to remove a file from the tree, delete it, then use cvs remove to make CVS take it away too. Commit the file (or its directory) after removing it.
It's a good idea to compile the project after removing but before committing, just to make sure you aren't breaking things.
Files that have been deleted are still kept around by CVS; while they'll be removed from people's working trees by default, you can still look at them, and you can bring them back again later using cvs add.
How do I rename files?
Carefully. Unfortunately, there isn't any completely satisfactory way to do this.
You can rename the file in your working directory, add the new name, and remove the old name; the problem with this is that you can't get at the previous change history of the file or make diffs of the file across the rename without knowing the old name and fiddling around.
You can rename the CVS file in the repository. This preserves the previous change history of the file, but breaks checkouts of old versions, because the file will appear under a name not expected by makefiles, scripts, etc.
Or, you can copy the CVS file in the repository from the old name to the new name, being sure to get the permissions right, and then use cvs remove in a working tree to get rid of the old name. This also preserves the previous change history, and does not break checkouts of old versions, but does cause the latter to acquire spurious extra copies of files.
There isn't any entirely satisfactory solution. (This is a design flaw in CVS.)
How do I add a directory?
Create the directory in your working area, and run cvs add on it.
Unlike files, directories get added instantly in the repository. Since they cannot be removed, do this only with caution.
Always check (and correct) the permissions of the directory within the repository after adding it.
How do I remove a directory?
In short, you don't. There isn't any way to do this in CVS. The design of CVS is flawed in certain ways, and this is the most obvious (and aggravating) consequence.
The best you can do is remove all the files from it and use the -P option with cvs update, which causes empty directories to disappear from your working tree.
You cannot remove the directory in the repository, because CVS keeps the deleted files there.
How do I make a merge less painful?
CVS's merge algorithm is entirely textual and not all that smart; when nothing else is visibly the same, it tends to use blank lines or lines with just braces as "anchors". Since these often aren't properly the same braces or blank lines, the results are usually bad and extremely tedious to straighten out.
If you anticipate making large changes that may cause unpleasant merges, one trick is to insert comments with unique code numbers at key points in the file beforehand, something like this:
int foo(void) {
// marker foo.c 55
if (bar()) {
stuff();
}
// marker foo.c 56
// marker foo.c 57
if (baz()) {
otherstuff();
}
// marker foo.c 58
}
Before you change anything else, commit the comments, and have everyone
concerned about the upcoming merge update to get the comments. (This may
require merging, but, with some caution, it shouldn't be painful.) Then
when you edit, treat the comments as markers that identify blocks of code.
Commit the new hacked-up version with the markers intact, perhaps like
this:
int foo(void) {
// marker foo.c 55
if (newbar()) {
newstuff();
}
// marker foo.c 56
if (totallynew()) {
morenewstuff();
}
// marker foo.c 57
if (baz()) {
otherstuff();
}
// marker foo.c 58
}
As long as you don't change the marker lines, CVS will generally be able
to match them up while merging. This will not prevent merge conflicts,
but it will usually keep the conflict blocks from becoming severely misaligned.
Once everyone has merged, you can delete the markers.
How do I set file permissions in the CVS repository?
CVS does not always set permissions correctly by itself, and so at various times (which this document attempts to point out) one may need to go into the CVS repository itself and chmod, chown, or chgrp files.
The CVS files (the ones that end in .,v) are never writable. They should be readable (and executable if the file they represent should be executable) by the people who use the repository. Generally this means they should either be mode 444 or mode 555, or mode 440 or 550 and belong to the right group, or, for an entirely private repository, mode 400 or 500.
Directories should be readable, writable, and executable by everyone who uses the repository. (Read-only access to the repository tree is not useful, as CVS has to create lock files even when it is only reading the actual data.) On file systems with System V group semantics, it is generally useful to set the set-group-id bit. This causes new files in the directory to belong to the right group, and new subdirectories to inherit the right permissions. On file systems with BSD group semantics, new files will always belong to the right group, but the permissions of new directories will usually need to be set manually.
Certain files in $CVSROOT/CVSROOT need to be writable by everyone who uses the repository. These are: history, val-tags, and any global commit log files you might have.
Also note that in order for CVS to be able to access the repository at all, every directory above the repository in the directory tree must also be executable.
How do I create a tag?
Use cvs tag with the name of the tag. You can tag specific files or subdirectories by specifying them after the tag name; by default the current directory and all subdirectories are tagged. Generally one tags the entire project at once.
% cvs tag asst2-end srcThe versions tagged will be the version in the repository that your working tree is based on. (As always, uncommitted changes will not be processed.)
You can specify specific versions to be tagged (including via other tags) using the -r and -D options. You can also tag specific versions without reference to your working tree using the similar cvs rtag command. Consult the CVS documentation for more information.
How do I move an existing tag?
Use the -F option to tag:
% cvs tag -F asst2-end src/kern/vm/vm.cWithout this, CVS will complain if you try to apply a tag that already exists.
How do I export a release?
Use cvs export. This is like cvs checkout, except instead of creating a "subscription" to the CVS repository, it extracts a snapshot, with no CVS control/management files.
Always do cvs export in a temporary directory. Doing it over your working directory makes a mess.
You always must supply a -r or -D option with cvs export, to specify the explicit version you want to export. While you can use -Dnow to get the latest version, it is recommended that when exporting an actual release that one always use a tag to name that release.
How do I get rid of an extra working tree?
You can just delete it, after of course checking to make sure it doesn't have any changes or other things in it that you want to keep.
To do this the "right" way, use cvs release on the tree first, or cvs release -d to have CVS also delete it. This "unsubscribes" from the CVS repository.
All cvs release actually does is record in the history file that the cvs checkout that created the tree is no longer active. This is only useful if you want to audit the history file or try to keep track of how many working trees are outstanding. These things are occasionally worthwhile, so it's better to get in the habit of using cvs release.
How do I set up commit messages to be mailed out?
The way CVS processes commit messages, besides storing them in the CVS files themselves (which always happens) is controlled by a file in $CVSROOT/CVSROOT called loginfo.
To edit it, you need to check out a working tree for CVSROOT, which you do like this:
% cd /some/temporary/area % cvs checkout CVSROOT % cd CVSROOTEdit the loginfo file. By default it contains a large comment explaining how the file works. To mail out commit messages, you might use a line like this:
ALL (echo ""; echo $USER; date; cat) | /usr/bin/mailx -s "CS161 CVS commit" your@address partner@addressThe program /usr/bin/mailx should work on ICE; elsewhere you might need to use /usr/bin/mail or /usr/bin/Mail or something else, depending on the configuration of the system hosting the CVS repository.
It is possible to write scripts to batch up the commit messages and send out a digest every few minutes. Unfortunately, to do so requires the use of the cron program to send the digests, which, to our knowledge, is not permitted on ICE or FAS. If you would like an example script, please ask the course staff.
When done editing, commit loginfo. That commit will not mail out, but the next one will. You may want to make a test commit somewhere, in case it doesn't work.
When everything runs properly, cvs release or delete your working copy of CVSROOT.
How do I make diffs?
Use cvs diff. Specify the files or directory trees you wish to compare; if you do not specify anything, by default the current directory and all subdirectories are diffed.
By default your working tree is diffed against the version in the repository to which it was last updated. You can diff against a specific repository version by providing a -r or -D option (as described above under checkout). You can diff two specific versions by providing two such options.
If you want to see the latest commits that you haven't updated yet in your working directory, use -rHEAD as one of the arguments.
You can also provide most of the normal diff format options. The most commonly used format is the "-u" format. The "-w" option causes diff to ignore changes in spacing and is thus also useful. The "-N" option includes the contents of new files in the diffs, instead of just a note that such files are new. See the diff man page for more information. When preparing diffs for CS161, please use the "-uNw" options.
How do I find out where a particular line of code appeared?
The cvs annotate command prints each line of the file with a prefix containing the version number in which the line appeared, the date of that version, and the username of the person who committed it. You can use the -r and -D options to retrieve the file as it existed in any previous version.
This can be used in conjunction with cvs diff to track down the history of individual lines of code, as long as they haven't moved around very much.
How do I back out a bad commit?
It's late at night and you foolishly/accidentally commit some immensely stupid change that breaks everything. (We've all been there; if you haven't yet, you will eventually.)
All is not lost. Part of the role of CVS is to keep track of old versions; you can extract the old version and re-commit it, or you can tell CVS to unmerge the change, like this, supposing that the dud commit made version 1.3 of foo.c and the working version was 1.2:
% cvs update -j1.3 -j1.2 foo.cThis tells cvs to merge ("join") the diff from version 1.3 to 1.2, which you'll note is the reverse direction from time (since you want to back the change out), into the current version. If this causes merge conflicts, you should resolve them in the usual way, then commit the modified file.
It does not matter if version 1.3 is not the latest version.
Note that you can do this on whole trees if the versions you want are named by tags.
How do I move my working directory?
In general, you can just rename the entire tree with mv. The CVS control files in the working directory are position-independent.
What if I move the repository?
In this case, however, because each working directory contains references back to the repository, you need to fiddle.
In general, it's easiest to cvs release all working directories prior to moving the repository, and check out new ones afterwards. If this is impractical, or impossible, you can recover by editing the files Repository and Root in the CVS subdirectory of every directory in the working tree to reflect the new location. Needless to say this is a nuisance, even if the edits are scripted. It's best avoided.
How do I use remote CVS?
CVS actually supports two different remote access methods, one that runs over a remote-shell channel like rsh or ssh, and one that uses a CVS-specific remote access protocol.
With the advent of ssh, the first method is generally preferable, and can be set up like this:
% setenv CVSROOT user@machine:/path/to/CVS/repository % setenv CVS_RSH sshwhere "machine" is the system hosting the CVS repository, and "user" is your username on that machine. If that's the same as on the local machine, it can be left off.
With these settings, cvs operates remotely. Remote CVS is completely transparent, except that ssh will ask you for your password all the time. To use this setup effectively, you will want to set up ssh to allow passwordless logins. See the ssh documentation for more information.