Account Links: Cart | Register | Log In

Skip to content


 
An Introduction C to Development on Linux

Using the Concurrent Versions System for Source Management

Now our little hello world program is a much more complex development project with multiple sources and a makefile to put it all together consistently. As it gains popularity, it is sure to attract developers from around the world who want to work on it. It is now time to start looking into version control.
[What is version control, and why do I need it?]7

The standard version control system for Linux is called cvs, which stands for Concurrent Versions System. The man page for cvs is about 1500 lines long and contains a remarkable amount of information. As the man page states however, there are only about 5 commands you will need to know for basic operation. Of course, here you will be setting cvs up for our project so there will be a few more commands to know, but it still isn't all that difficult. However, if you make a mistake in cvs you could destroy months of work. It is certainly recommended that you learn as much about cvs as you can. The man page is a good place to start and www.gnu.org also has extensive documentation on the subject.

Creating the cvs source repository (where the safe copies of your source will live) is easy.

To start with, you will need to set your CVSROOT environment variable so that when you call cvs, it will know where to look for your source repository. It is possible to avoid this and simply tell cvs where to look when you invoke it, but this value usually stays the same for a given development system. It saves a lot of time and effort to just set it in your .bashrc file. To do this, add the following lines to you .bashrc file:

CVSROOT=/usr/local/cvsroot
export CVSROOT

This will tell all cvs commands that the source repository will live in /usr/local/cvsroot - obviously you could change this, but it is recommended that you make the last directory 'cvsroot' wherever you put the repository.

Remember, you will need to run 'source ~/.bashrc' to get this environment variable set in your current shell (or logout and log back in).

Now that this is set, we can call the 'cvs init' command to create the repository:

[nthomas@daisy hello]$ source ~/.bashrc
[nthomas@daisy src]$ cvs init
cvs [init aborted]: cannot make directory /usr/local/cvsroot: No such file or directory

This example shows that you will need write permissions on the directory where you want to put the cvsroot. Either su to a user who can write to that directory (root, presumably) or put the repository in a place you can write to (somewhere in your home directory). Putting the tree in a central location facilitates widespread accesses better. Try to avoid puting it in your home directory. It is also important to put it somewhere it isn't likely to get destroyed. A home directory isn't necessarily the safest place in that regard.

[nthomas@daisy src]$ su -
Password:
[root@daisy /root]# cvs init
cvs init: No CVSROOT specified! Please use the `-d' option
cvs [init aborted]: or set the CVSROOT environment variable.

We didn't alter the .bashrc for root, so cvs doesn't know where CVSROOT should be. We add the -d argument along with the directory where CVSROOT should go:

[root@daisy /root]# cvs -d /usr/local/cvsroot init

Now when we look in /usr/local/cvsroot we can see that CVSROOT has been created there. This is the administration file for cvs where you can set a number of variables. However, all of the source trees will live as subdirectories off of /usr/local/cvsroot.

It is the idea of cvs that you will never need to concern yourself with looking in the /usr/local/cvsroot/ subdirectories to deal with the source trees manually. However, there are a couple of cases where this can be important. The most common one is to set the permissions properly so that a given Unix group can gain access to that tree.

Anyone who is going to even read the files must have write permissions for the directory as it keeps track of all cvs accesses and must write a logfile in that directory.

What we will do is simply tell cvs where our working directory is for the miniwall program we're working on. It will copy all files automatically and set up the history information on their initial checkin. However, it will also assign the source tree a revision number, obsoleting our rudimentary numbering scheme for our files. Additionally we have multiple versions of the various files in our working directory which we don't want included in cvs. So, let's create a subdirectory, cvshello (or whatever you want). Copy the needed files into it and drop any numbering information we've assigned to them.

[nthomas@daisy hello]$ mkdir cvshello
[nthomas@daisy hello]$ cp *.c cvshello/
[nthomas@daisy hello]$ cp *.h cvshello/
[nthomas@daisy hello]$ cp makefile cvshello/
[nthomas@daisy hello]$ cd cvshello
[nthomas@daisy cvshello]$ ls
hw1.c  hw2.c  hw3.c  hw3.h  makefile  ui1.c  ui1.h
[nthomas@daisy cvshello]$ rm hw1.c
[nthomas@daisy cvshello]$ rm hw2.c
[nthomas@daisy cvshello]$ ls
hw3.c  hw3.h  makefile  ui1.c  ui1.h
[nthomas@daisy cvshello]$ mv hw3.c hw.c
[nthomas@daisy cvshello]$ mv hw3.h hw.h
[nthomas@daisy cvshello]$ mv ui1.h ui.h
[nthomas@daisy cvshello]$ mv ui1.c ui.c

Now we need to change the .c files to reflect the change in name of the .h files they include as well as the makefile to relfect all of the name changes.

Be sure to attempt to build miniwall when you are done.

Assuming it builds and runs cleanly, you will want to run 'make clean' and delete the miniwall executable as well. Now wwe are ready to su back to root and create import the sources to the repository.

[root@daisy cvshello]# cvs -d /usr/local/cvsroot import -m "Imported sources" miniwall RedHat start
N miniwall/hw.c
N miniwall/hw.h
N miniwall/ui.h
N miniwall/ui.c
N miniwall/makefile

No conflicts created by this import

You can see that we still had to use the '-d /usr/local/cvsroot' argument. Then we told it to import, followed by '-m "Imported sources", which is simply the message we want the history files of the files we're importing to show. If we didn't specify this like this it would take us into an editor automatically to type a message in. Then comes the name of the source tree, the name of the vendor, and the release tag (should be 'start' for this case).

To avoid making things too complicted, we're just going to set the permissions to allow any user to have read/write access for CVSROOT and miniwall directory in /usr/local/, as well as the history file in /usr/local/cvsroot/CVSROOT - this would be a remarkably bad idea in practice.

Next, we'll exit back out to our normal user and checkout the source code. When you check out the source tree it will create a directory by the name of 'miniwall' and put all of the sources into it. Once this is done, you really should remove your orignal working directory so that you don't get confused about having two sets of files around.

[nthomas@daisy src]$ cvs checkout miniwall
cvs checkout: Updating miniwall
U miniwall/hw.c
U miniwall/hw.h
U miniwall/makefile
U miniwall/ui.c
U miniwall/ui.h

Now CVS is aware that these files are checked out. Anyone else who checks out these files will be made aware of this fact if they concurrently check out the tree and then try to submit changes after we have already done so. For example, we can log in as root and check the tree out as well:

[root@daisy /root]# cvs -d /usr/local/cvsroot checkout miniwall
cvs checkout: Updating miniwall
U miniwall/hw.c
U miniwall/hw.h
U miniwall/makefile
U miniwall/ui.c
U miniwall/ui.h

Now, say root decides to add 'miniwall' to the makefile clean target. Once the change is made, it can be committed with a 'cvs commit' command:

[root@daisy miniwall]# cvs -d /usr/local/cvsroot commit -m "Improved clean target" makefile
Checking in makefile;
/usr/local/cvsroot/miniwall/makefile,v <-- makefile
new revision: 1.2; previous revision: 1.1
done

Again, you see the -d argument and the -m followed by a message, and finally, the name of the file to commit.

Performing this operation has produced the first change in our version number. You can see from the output that we are now working on revision 1.2 instead of 1.1 as it was at the start.

Everything went smoothly here, even though nthomas has the tree checked out, because no changes have been checked in. Now, nthomas will make a trivial change to the makefile and try to check it back in. This change will be to add '-f' to the 'rm' command in the clean target:

[nthomas@daisy miniwall]$ cvs commit -m "Improved clean target" makefile cvs commit: Up-to-date check failed for `makefile' cvs [commit aborted]: correct above errors first!

This fails the 'Up-to-date' check, because the tree that nthomas has is now older than the tree in the repository. It should be updated with the newer repository version, then have the changes made to it and committed.

There are a number of mechanisms provided by cvs for notifying a user when a file is checked out and for doing reserved checkouts. These are not the default behavior though. For now, we will focus on more basic operations.

cvs does provide an 'update' command that deals with concurrent checkouts and revisions well by merging the new source in the cvs tree with your altered source. If you have not altered sources in your working directory this will behave just like a 'checkout' and simply put the latest cvs copies in your working directory. Here, it will help us fix our makefile discrepencies:

[nthomas@daisy miniwall]$ cvs update makefile
RCS file: /usr/local/cvsroot/miniwall/makefile,v
retrieving revision 1.1.1.1
retrieving revision 1.2
Merging differences between 1.1.1.1 and 1.2 into makefile
rcsmerge: warning: conflicts during merge
cvs update: conflicts found in makefile
C makefile

It tried to do a merge, but found conflicts. This means that when it tried to merge, it found that some of the sections modified in the latest cvs tree (committed by root) were the same sections where changes had been made in working directory. It can tell this by seeing that nthomas' working directory was built from the 1.1 source and that it had changed from that, but that it was also different from the 1.2 source.

Since it couldn't merge the two sources, it copied the source in question to a backup file in the working directory name .#makefile.1.1.1.1 (which you can see with the 'ls -a' command).

The file 'makefile' will now include everything it did before, plus output the differences generated by the merge:

miniwall : ui.o hw.o
        gcc -o miniwall ui.o hw.o

ui.o : ui.c
        gcc -c -w ui.c

hw.o : hw.c
        gcc -c -w hw.c

.PHONY : clean

clean :
<<<<<<< makefile
        rm -f *.o
=======
        rm *.o miniwall
>>>>>>> 1.2

The section beginning with '<<<<<<< makefile' is what was in my working directory. The section after the '=======' characters shows what's in the new cvs tree. It is left up to us what to do about this. Both of these changes could be nice, so we'll just edit the file to show

rm -f *.o miniwall

and commit it. Be sure to test it before you commit it!

[nthomas@daisy miniwall]$ cvs commit makefile
cChecking in makefile;
/usr/local/cvsroot/miniwall/makefile,v <-- makefile
new revision: 1.3; previous revision: 1.2
done

You can see that I didn't use the -m "message" option, so I had to enter the message in a text editor when it was brought up. Upon exiting the editor (which is where it says 'done'), it finished the cvs commit.

Once you have done your commit, it is recommended that you release your working directory (this can be an issue with some cvs configurations). To do this, use the 'release' command:

[nthomas@daisy src]$ cvs release miniwall
You have [0] altered files in this repository.
Are you sure you want to release directory `miniwall'

Believe it or not, those are all of the basic things you will need to know to set up and use a simple version control system. cvs is a very full featured system. rHhowever, to take advantage of what it has to offer you need to do quite a bit more research into it. One of the things you will likely need to know if you're doing an open-source project with cvs is how to do remote access. This is not terribly complicated and is covered in depth in the www.gnu.org documentation on cvs.

7 - What is version control, and why do I need it?

Version control systems are designed to let multiple users access a source repository and keep track of what changes have been made to the sources throughout the development cycle. This means that the sources can be reverted to any state that they were in throughout the development cycle at any time (say, back to before a given bug was introduced). This is accomplished by creating history files and keeping track of all of the transactions made with regard to the files in the source repository.

back home next
Using make to Simplify the Build Process   Final Thoughts and Resources

 

[an error occurred while processing this directive]