CVS - Concurrent Versions System


First I'd like you to read lecture notes on Make, and then we'll move on to CVS. CVS stands for Concurrent Versions System. It's a pretty neat tool that, like RCS, allows you to keep track of your software through version numbers. With CVS you can keep track of revisions that you have made to our software. We'll use CVS such that our files will be grouped together as modules and locked away in a separate area called the repository. The repository is where our files reside along with the modification history.

A typical CVS session involves checking out a copy of a project, making changes, and then committing those changes to the repository so that others can see the modifications.

Different modifications of a project are kept track of by CVS in the form of revisions. This means that if you want to back up to an older version of your software you can do so without too much grief.

Different versions of a project are called the revision numbers of that project. They typically look like 1.1, 1.2. Nothing too magical. CVS also allows us to branch off of our revision tree and develop older revisions without affecting newer ones. We won't be going over that.

Preparation

Pick a place where you will keep your repository. Once you have done so then you need to set the CVSROOT environmental variable. You can use the printenv command to see which variables you currently have set. I also set a variable named CVS_RSH to ssh which makes CVS use ssh rather than rsh. I encourage you to do this as well.

So --- pick a place where you want your repository to be located. For me this was /home/cs300/cvsStuff. I'll add the following to the end of my .cshrc file:
	setenv CVSROOT /home/cs300/CVSstuff
        setenv CVS_RSH "ssh"
and then either log out or
	source ~/.cshrc
Now activate CVS by issuing cvs init at the command line.

 
UNIX% cvs init  
You should be able to cd to your repository now. It will be a blank directory except for a directory named CVSROOT which contains various administrative files:
UNIX% printenv CVSROOT
/home/cs300/CVSstuff
UNIX% cvs init
UNIX% cd $CVSROOT
cs300/CVSstuff% ls
CVSROOT/  


The CVSROOT directory is where CVS will do its bookkeeping and is not the same as the CVSROOT environmental variable. Don't mess with this directory.

Adding A Project

I need some files to work with. In /home/cs300/pub I am going to create a project1 directory and create a text file to put in there:
cs300/pub/project1% cat > textfile
Knight Rider was a funny movie, but somehow I liked Herby better as a car!!

cs300/pub/project1% ls
textfile
cs300/pub/project1% pwd
/home/cs300/pub/project1 
Now maybe you think otherwise!!  Say you want to discuss this important issue and I decide the best thing to do would be to put  the directory into the repository.

How to do that ??? - By using the magic term : cvs import

cs300/pub/project1% cvs import -m "new project" proj1 v1 r1
N proj1/textfile

No conflicts created by this import 
I have created a new module in our repository named proj1. The -m switch is for a log message that is required by CVS. If you don't put one on the command line CVS will drop you into whatever editor you have specified with your EDITOR environmental variable. This is most annoying. I always go ahead and specify a message using -m. Don't worry about the last two arguments. I don't think CVS does either.

You can verify that the files have been placed in the repository by changing to the repository directory and poking around (i.e. cd $CVSROOT). The directory structure of the repository will reflect the directory structure of whatever module you imported.

The file names will have ,v's appended to them. These are the history files that contain the information that CVS can use to recreate various versions of the original files. The v files are created read-only and you shouldn't mess with their permissions. They are owned by whomever checked them in last.

Now I will take a leap of faith and assume my repository has the stuff it needs to recreate my files. I'll wipe out my project1 directory and check a working copy of the module out from the repository. Remember that when I imported the module to my repository I gave it the name proj1:
cs300/pub/project1% cd ..;rm project1
cs300/pub% cvs checkout proj1
cvs checkout: Updating proj1
U proj1/textfile    
We are working from a copy from our repository. The directory proj1 looks the same as the old one with the addition of a CVS directory for the version control system. You can examine these:
cs300/pub/proj1% cd CVS
cs300/pub/proj1/CVS% ls
Entries      Repository   Root
cs300/pub/proj1/CVS% cat Repository
proj1
cs300/pub/proj1/CVS% cat Root
/home/cs300/CVSstuff
cs300/pub/proj1/CVS% cat Entries
/textfile/1.1.1.1/Tue Aug 29 05:27:30 2000//
D  
FYI: I would caution you not to edit them unless you know what you are doing. I have found, however, that a lot of times one learns the most from making an absolute mess out of things.

Okay lets do some more to our files. Let's edit the textfile, add a directory , and add a new text file.
cs300/pub/proj1% cat >> textfile
A shadowy flight into the dangerous world of a man who does not exist!
cs300/pub/proj1% cp -r /home/cs300/www-home/prg/cq .
cs300/pub/proj1% ls
cq/        CVS/       textfile 
cs300/pub/proj1% touch newfile 
Now at this point CVS has no idea what I have done. Until I make these changes known to the repository they aren't official. They'll just be present in my local copy. We have to make CVS aware of these changes. First lets take care of the change to textfile. :
cs300/pub/proj1% cvs commit -m "Added line two!!"
cvs commit -m "Added line two touch newfile"
cvs commit: Examining .
Checking in textfile;
/home/cs300/CVSstuff/proj1/textfile,v  <--  textfile
new revision: 1.2; previous revision: 1.1
done  
cs300/pub/proj1% cvs add newfile
cvs add: scheduling file `newfile' for addition
cvs add: use 'cvs commit' to add this file permanently 
cs300/pub/proj1% cvs add cq
Directory /home/cs300/CVSstuff/proj1/cq added to the repository
 
cs300/pub/proj1% ls cq
bad.c*       cq.h*        histo.c*      int*         makefile*
cq.c*        CVS/         index.html*  int.c*       qcat.c* 
Notice that CVS added a directory named CVS within the cq directory. CVS will place a CVS directory with the administrative files in each directory within a project. Go ahead and commit again:
cs300/pub/proj1% cvs commit -m "added a file and a directory"
cvs commit: Examining .
cvs commit: Examining cq
RCS file: /home/cs300/CVSstuff/proj1/newfile,v
done
Checking in newfile;
/home/cs300/CVSstuff/proj1/newfile,v  <--  newfile
initial revision: 1.1
done 
Now erase the directory and check out the module again. This will be our working copy of the project (or module, I'll use both terms) that is stored in the project.
cs300/pub% rm proj1
cs300/pub% cvs checkout proj1
cvs checkout: Updating proj1
U proj1/newfile
U proj1/textfile
cvs checkout: Updating proj1/cq
cs300/pub% ls proj1
cq/        CVS/       newfile    textfile
cs300/pub% cat proj1/testfile 
cs300/pub% cat proj1/textfile
Knight Rider
A shadowy flight into the dangerous world of a man who does not exist
We have a problem however. Examine the contents of the cq directory:
cs300/proj1% ls
cq/        CVS/       newfile    textfile
cs300/proj1% ls cq
CVS/
So where did the files go?

To be quite honest CVS can be irritating in the way that it makes you deal with directories at time. It turns out that if we want to add a directory that has files in it and we want those files to be part of our repository we must do a cvs add on each of the files as well. Let's fix it and make sure that all of the files are known to the repository. My steps will be: The output:
cs300/proj1% cvs remove cq
cvs remove: Removing cq
cs300/proj1% rm cq
cs300/proj1% !cp
cp -r /home/cs300/www-home/prg/cq .
cs300/proj1% cvs add cq
Directory /home/cs300/CVSstuff/proj1/cq added to the repository
cs300/proj1/cq% foreach file (`ls`)
foreach? cvs add $file
foreach? end
cvs add: scheduling file `calculate.c' for addition
cvs add: use 'cvs commit' to add this file permanently
cvs add: scheduling file `day-number.c' for addition
cvs add: use 'cvs commit' to add this file permanently
cvs add: scheduling file `find_max.c' for addition
cvs add: use 'cvs commit' to add this file permanently
cvs add: cannot add special file `CVS'; skipping
cvs add: scheduling file `histo.c' for addition
cvs add: use 'cvs commit' to add this file permanently
cs300/proj1/cq% cd ..;cvs commit -m "Added the directory AND the files I hope"
Now let's delete our working copy and check out another one and see if the directory contains all of the files it needs to:
cs300/proj1% cd ..
cs300% rm proj1
cs300% cvs checkout !:1
cvs checkout proj1
cvs checkout: Updating proj1
U proj1/newfile
U proj1/textfile
cvs checkout: Updating proj1/cq
U proj1/cq/calculate.c
U proj1/cq/day-number.c
U proj1/cq/find_max.c
U proj1/cq/histo.c
Now to verify: 
cs300% cd proj1
cs300/proj1% ls cq
CVS/ calculate.c* day-number.c* find_max.c* histo.c*
So everything is there, and the only main difference seems to be the CVS directory inside the cq directory. Some of you may wonder what the line cvs checkout !:1 does. The csh has a history function that remembers previous commands and allows you to repeat previous commands or portions of previous commands. History manipulation is a very powerful tool, and hopefully we'll go over it in more detail in a later class. I just wanted to throw it in there so you could have something to mull over.

Diff and Conflicts

Suppose you want to see the difference between your files and the current version in the repository. First, make a change in histo.c in your working directory and then use cvs diff:
cs300/proj1/cq% cvs diff histo.c
Index: histo.c
===================================================================
RCS file: /home/cs300/CVSstuff/proj1/cq/histo.c,v
retrieving revision 1.1
diff -r1.1 histo.c
37a38
> /* adding a comment at the end of histo.c */
As you can imagine the output from cvs diff can get pretty huge, which is why I modified 1 file and gave cvs diff instructions to only search for that one file (it defaults to ., as most other CVS commands do). You'll periodically want to do a cvs update to keep yourself in sync with the repository. This command will update your copies of source files from changes that other developers have made to the source in the repository, but doing a cvs update won't wipe out your changes:
cs300/proj1/cq% cvs update .
cvs update: Updating .
M histo.c
cs300/proj1/cq% tail -1 histo.c
/* adding a comment at the end of histo.c */
cs300/proj1/cq% cvs diff histo.c
Index: histo.c
===================================================================
RCS file: /home/cs300/CVSstuff/proj1/cq/histo.c,v
retrieving revision 1.1
diff -r1.1 histo.c
37a38
> /* adding a comment at the end of histo.c */
So your working copy still has the modified line in it. Now suppose someone else had checked out this same file before you had committed your change i.e. without the extra line at the end of histo.c:

On a separate window:

cs300% mkdir test
cs300/test% cvs checkout proj1
cvs checkout: Updating proj1
U proj1/newfile
U proj1/textfile
cvs checkout: Updating proj1/cq
U proj1/cq/histo.c
U proj1/cq/calculate.c
U proj1/cq/day-number.c
U proj1/cq/find_max.c
cs300/test% cd proj1/cq
cs300/test/proj1/cq% cat >> histo.c
/* Adding a comment and preparing to cause a conflict !! */
cs300/test/proj1/cq% cvs commit -m "conflict!!"
cvs commit -m "conflict!"
cvs commit: Examining .
Checking in histo.c;
/home/cs300/CVSstuff/proj1/cq/histo.c,v  <--  histo.c
new revision: 1.2; previous revision: 1.1
done
Okay now in my original window I decide that it is time to commit:
cs300/proj1/cq% cvs commit histo.c
cvs commit: file `histo.c' had a conflict and has not been modified
cvs [commit aborted]: correct above errors first!
I'll need to update to check and see what has been goofed around in my program. At this point CVS will try to merge the conflicts. Remember that the two lines were entered in the same location so when CVS did a line-by-line check it detected the errors:
cs300/proj1/cq% cvs update histo.c
RCS file: /home/cs300/CVSstuff/proj1/cq/histo.c,v
retrieving revision 1.1
retrieving revision 1.2
Merging differences between 1.1 and 1.2 into histo.c
rcsmerge: warning: conflicts during merge
cvs update: conflicts found in histo.c
C histo.c

Now I already know that the conflict exists toward the end of the file so I can just examine the end of histo.c:
cs300/proj1/cq% tail -7 histo.c

}
<<<<<<< histo.c
/* adding a comment at the end of histo.c */
=======
/* Adding a comment and preparing to cause a conflict !! */
>>>>>>> 1.2
The conflicts are denoted by the <<<<<<< and >>>>>>> markers. The second set of markers denotes the version that exists in the repository and the revision number while the line that is in my working copy of histo.c appears first. The way to resolve such conflicts is to edit the files, take out the conflicts, and re-commit. When working with a group of people on a project all of those involved will want to periodically cvs update and cvs commit to keep conflicts to a minimum. CVS will not allow you to check in a file for which an unresolved conflict exists.

File info

You can use the cvs status command to find out what sorts of operations have been performed on a file and checked out copies. This command can tell me when I need to update my working copy. For example let us check the status of histo.c in the window with the merged version that has yet to be fixed:
cs300/proj1/cq% cvs status histo.c
===================================================================
File: histo.c            Status: File had conflicts on merge

   Working revision:    1.2     Result of merge
   Repository revision: 1.2     /home/cs300/CVSstuff/proj1/cq/histo.c,v
   Sticky Tag:          (none)
   Sticky Date:         (none)
   Sticky Options:      (none)
CVS tells us that the file histo.c is the result of a merge and that there were conflicts. Now let's fix it by removing the delimiters and the comments that we don't want, commit our copy, and check the status:
cs300/proj1/cq% tail -2 histo.c
}
/* All comments removed */
cs300/proj1/cq% cvs commit -m "went ahead and removed both of the comments"
cvs commit: Examining .
Checking in histo.c;
/home/cs300/CVSstuff/proj1/cq/histo.c,v  <--  histo.c
new revision: 1.3; previous revision: 1.2
done
cs300/proj1/cq% cvs status histo.c
===================================================================
File: histo.c    
       Status: Up-to-date

   Working revision:    1.3     Wed Sep 13 14:19:09 2000
   Repository revision: 1.3     /home/cs300/CVSstuff/proj1/cq/histo.c,v
   Sticky Tag:          (none)
   Sticky Date:         (none)
   Sticky Options:      (none)

Removing Files

Periodically you will want to remove files and directories. IMHO you should do so in such a manner that removes the files and directories in the current version but lets you also have the option of retrieving them from earlier revisions to your module.

To remove a file you will: Here's an example:
cs300/proj1/cq% pwd
/home/cs300/proj1/cq
cs300/proj1/cq% cvs remove index.html
cvs remove: file `index.html' still in working directory
cvs remove: 1 file exists; remove it first
cs300/proj1/cq% rm index.html
cs300/proj1/cq% cvs remove index.html
cvs remove: scheduling `index.html' for removal
cvs remove: use 'cvs commit' to remove this file permanently
cs300/proj1/cq% cvs commit -m "removed cq/index.html"
cvs commit: Examining .
cvs commit: Committing .
Removing index.html;
/home/cs300/CVSstuff/proj1/cq/index.html,v  <--  index.html
new revision: delete; previous revision: 1.1
done
Notice in my example that first I tried to (incorrectly) cvs remove before I actually removed the file using rm and got an error message.

If I had changed my mind before executing cvs commit I could had added the file back by just executing cvs add:
cs300/proj1/cq% ls
CVS/ calculate.c* day-number.c* find_max.c* histo.c* 
cs300/proj1/cq% rm histo.c
cs300/proj1/cq% cvs remove histo.c
cvs remove: scheduling `histo.c' for removal
cvs remove: use 'cvs commit' to remove this file permanently
cs300/proj1/cq% cvs add histo.c
U histo.c
cvs add: histo.c, version 1.3, resurrected
cs300/proj1/cq% ls
CVS/ calculate.c* day-number.c* find_max.c* histo.c*

The file was resurrected. If you had caught the mistake before executing cvs remove then you could have just used cvs update:

cs300/proj1/cq% rm 
histo.c
cs300/proj1/cq% cvs update
cvs update: Updating .
cvs update: warning: histo.c was lost
U histo.c
cs300/proj1/cq% ls
CVS/ calculate.c* day-number.c* find_max.c* histo.c*

Removing Directories

When you remove a directory you'll want the directory to not exist in the current version but be available in old releases. Remove all of the files in your version
cs300/proj1/cq% rm *.c *.h int* makefile
mv: cannot access int.c
cs300/proj1/cq% ls
cs300/proj1/cq% cvs remove
cvs remove: Removing .
cvs remove: use 'cvs commit' to remove these files permanently
cs300/proj1/cq% cvs commit -m "removed the files in the cq directory"
cvs commit: Examining .
cvs commit: Committing .
...
Now just use cvs update with the -d (for directory) and -P (for "prune") options:
cs300/proj1/cq% cd ..
cs300/proj1% ls
CVS/       cq/        newfile    textfile
cs300/proj1% cvs update -dP
cvs update: Updating .
cvs update: Updating cq
cs300/proj1% ls
CVS/       newfile    textfile

Renaming Files

The best way to rename a file is to: Directories are similar. I'll copy over a new directory and show you how one can rename it in CVS:
cs300/proj1% mkdir pinger
cs300/proj1% cd pinger
cs300/proj1% cp /jade/homes/cs300/pub/pinger/* .
cs300/proj1% cvs add pinger
Directory /home/cs300/CVSstuff/proj1/pinger added to the repository
Now I used my foreach statement from above to add all of the files into the repository and did a cvs commit. Now we want to rename the directory pinger to something else. Just mv the files over to a new directory, do a cvs update -dP to clear the old directory out of your working copy, , and add the files in the new directory using cvs add and cvs commit. Working with directories and CVS can get convoluted very quickly, but thats just how things are.

Revisions

One of the benefits of CVS is revision control. When you're working on programs for some of your classes you'd probably like to be able to back up to previous revisions of your work. If you want to retrieve a specific version of a certain file you can simply use cvs update with the -r option. Suppose I have a module newmod consisting of an index.html file and add a line to it:
cs300/newmod% ls
CVS/         index.html*
cs300/newmod% cat >> index.html
<howdy></howdy>
cs300/newmod% cvs commit -m " modified the index file "
cvs commit: Examining .
cvs commit: Committing .
Checking in index.html;
/home/cs300/CVSstuff/newmod/index.html,v  <--  index.html
new revision: 1.2; previous revision: 1.1
done
Now suppose that I wanted to get a look at the old copy:
cs300/newmod% rm index.html
cs300/newmod% cvs update -r 1.1 index.html
cs300/newmod% tail -1 index.html
</HTML>
So perhaps this wasn't a very useful example, but apply this to programming projects. If you're trying to do a program in incremental steps and you goof up the current version then if you use CVS you can easily back up to a previous version. Now if I want to go back to the most recent copy:
cs300/newmod% cvs update -A
cvs update: Updating .
U index.html
cs300/newmod% tail -1 index.html
<howdy></howdy>
When I checked out the file with the lower revision it got checked out with something called a "sticky tag" that basically set the current version of the index.html file to 1.1 as far as my working copy was concerned even though the repository had a more recent copy (1.2). The cvs update -A was required to get back into the repository and check out the most recent version. If I hadn't used it CVS would have continually checked out the old revision instead of the new one when I tried to update.

Of course you may find that you decide that you want to use the files from an earlier revision as the current revision i.e. you want to back up your repository to an earlier source.
cs300/newmod% cvs status index.html
===================================================================
File: index.html        Status: Up-to-date

   Working revision:    1.2     Wed Sep 13 17:48:03 2000
   Repository revision: 1.2     /home/cs300/CVSstuff/newmod/index.html,v
   Sticky Tag:          (none)
   Sticky Date:         (none)
   Sticky Options:      (none)
cs300/newmod% cvs update -j 1.2 -j 1.1 index.html
cvs update: warning: index.html was lost
U index.html
RCS file: /home/cs300/CVSstuff/newmod/index.html,v
retrieving revision 1.2
retrieving revision 1.1
Merging differences between 1.2 and 1.1 into index.html
Now check to see that the revision made in 1.2 is gone:
cs300/newmod% tail -1 index.html
</HTML>
And notice that the status has set the revision as 1.2:
cs300/newmod% cvs status index.html
===================================================================
File: index.html        Status: Up-to-date

   Working revision:    1.2     Wed Sep 13 17:55:22 2000
   Repository revision: 1.2     /home/cs300/CVSstuff/newmod/index.html,v
   Sticky Tag:          (none)
   Sticky Date:         (none)
   Sticky Options:      (none)
There is also a way of "tagging" an entire directory with a "release number" using cvs tag version_number to tag the files in a directory and cvs checkout -r version_number module to retrieve the files. I never do this, and thus won't spend any more time on it.

Keyword Expansion

CVS can detect strings known as keyword strings in text files and replace them with various bits of information. Just surround the keyword with $ signs and CVS will do the rest. There are many of these, and a few of them are actually useful. I'll place the following ones at the top of the index.html file from the last example:
$Id$
$Date$
$Author$
$Revision$
Which are the following: After committing the changes and checking out again I have:
cs300/newmod% head -10 index.html
$Id: index.html,v 1.3 2000/09/13 18:10:12 cs300 Exp $
$Date: 2000/09/13 18:10:12 $
$Author: cs300 $
$Revision: 1.3 $
Revision 1.3  2000/09/13 18:10:12  cs300
sdf

Logs

You can use the cvs log command to find out what has been done to a file. This can be very very long.
cs300/newmod% cvs log
cvs log: Logging .

RCS file: /home/cs300/CVSstuff/newmod/index.html,v
Working file: index.html
head: 1.3
branch:
locks: strict
access list:
symbolic names:
        version_whatever: 1.2
        v1: 1.1.1.1
        r1: 1.1.1
keyword substitution: kv
total revisions: 4;     selected revisions: 4
description:
----------------------------
revision 1.3
date: 2000/09/13 18:10:12;  author: cs300;  state: Exp;  lines: +5 -0
sdf
----------------------------
revision 1.2
date: 2000/09/13 17:42:35;  author: cs300;  state: Exp;  lines: +1 -0
 modified the index file
----------------------------
revision 1.1
date: 2000/09/13 17:40:52;  author: cs300;  state: Exp;
branches:  1.1.1;
Initial revision
----------------------------
revision 1.1.1.1
date: 2000/09/13 17:40:52;  author: cs300;  state: Exp;  lines: +0 -0
creating a new module
=============================================================================
This can be handy if you are thinking about backing up a revision or two and are interested in checking out things such as old log messages or checking when the files were modified.

Remote Access

The only thing that we haven't really went over yet in our CVS overview is remote access. I'll gloss over this, mostly because I just want you to know that it can be done rather than worrying about all of the mechanics.

CVS will allow you to access files from a repository on a remote site. In your case this would be one that isn't on the cs network. If the guy on the other side is willing to authenticate you CVS will allow you to rsh to the remote site and grab the files. When I do this I set two environmental variables: For example, if I wanted to access a repository located in /home/cs300/repos on my machine larry.cas.utk.edu using the account name of joeuser, I would use:
setenv CVSROOT ext:joeuser@larry.cas.utk.edu:/home/cs300/repos
The other side has to be set up to accept connections. Two of the modifications require root access because you have to muck around with inetd.conf and /etc/services. I definitely won't be going into all of that, but I did want to make you aware that CVS has this capability.