Questions are invited at any time.
You should note that CVS will occasionally drop you into a full-screen editor. By default, this is vi. If you use pico, then you need to let CVS know that that is your editor of choice.
CVS follows the standard convention of using the
environment variable to specify a full-screen editor. If you want to
use pico, append the following line to your
file in your home directory:
VISUAL=pico export VISUAL
and the corresponding line to your
setenv VISUAL pico
then restart your shell (logging in again will do this).
I've tried to keep this as concise and cook-booky as possible. I indicate command-line sessions using blocks of text as follows, with the > standing in for your prompt. Stuff that you type is indicated in bold. Responses are shown (where they're interesting).
> echo "hello world" hello world
I'll not labour the point further in the notes, but remember that CVS is just one tool available to you as a developer. Successful use of any versioning system in a team setting relies on decent communication channels. All CVS operation should take place in parallel with development mailing list traffic.
As you recall, CVS might really be said to version file contents.
A CVS module consists of a hierarchy of directories. Directories
themselves are unversioned; when you
cvs add a
directory, it essentially appears "throughout time" in the CVS
Each file, however, has a recorded history. When it is added, and every time a modification is checked in, a change record is associated with the file. The information saved includes the date and time of the change, details of what exactly has changed (a diff or patch as it is known), and possibly a commit message supplied by the user.
Every file, then, has its own version number (which has nothing to do with the version of the software product the file is a part of). Even when changes to a set of files are committed together, there is no logical connection between versions of files comprising a change set. In fact, CVS has no real notion of a change set. CVS users are used to this; normally the lack of real change sets goes unnoticed.
CVS gives users the ability to view the state of the repository by checking out a working copy. The most common use of CVS involves keeping one's working copy completely up-to-date with the latest checked-in versions of all files (the so-called HEAD).
Alternatively, if we give CVS a date and time, we can move our working copy backwards in time, viewing an older point in the module's history.
> cvs up -D'Dec 14 20:00:00 GMT 2003' cvs server: Updating . cvs server: Updating build cvs server: Updating htdocs cvs server: Updating htdocs/jan
The working copy remembers that it holds a non-HEAD view of the
repository, as can be seen using the
> cvs status README =================================================================== File: README Status: Up-to-date Working revision: 1.2 Repository revision: 1.2 /internal/cvs/cvsrep/example/README,v Sticky Tag: (none) Sticky Date: 2003.12.14.20.00.00 Sticky Options: (none)
We can bring the working copy back up-to-date with the HEAD as follows:
> cvs up -A cvs server: Updating . cvs server: Updating build cvs server: Updating htdocs cvs server: Updating htdocs/jan > cvs status README =================================================================== File: README Status: Up-to-date Working revision: 1.2 Repository revision: 1.2 /internal/cvs/cvsrep/example/README,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none)
To understand CVS file versions, one should remember that the notion that a working copy is at a particular point or version is simply a convenient fiction. Actually all files are separately versioned. For conceptual convenience, we adopt the simpler view.
Rather than try to remember significant dates associated with a project's development, we can instead assign logical names to those points. This is done by tagging. By tagging the files (or a subset of the files) in a working copy, we associate the tag name with the corresponding version of each file.
> cvs tag -cR mytag cvs server: Tagging . T README cvs server: Tagging build T build/build.sh cvs server: Tagging htdocs T htdocs/index.html cvs server: Tagging htdocs/jan T htdocs/jan/index.html
The tagging operation itself does not alter the working copy- that still tracks the HEAD:
> cvs status README =================================================================== File: README Status: Up-to-date Working revision: 1.2 Repository revision: 1.2 /internal/cvs/cvsrep/example/README,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none)
We can, however, ask CVS to produce us a working copy checked out
to a particular tag. Rather than face a potential confusion of sticky
tags in a single working copy, we utilise a little of the cheap,
readily-available disk space to check out a second working copy.
Technically, it should be possible to do this with a single command:
cvs co -r mytag example - however, there's a bug present
in many versions of
cvs which can be worked around in two
steps as shown below.
> cd ../.. > mkdir mytag > cd mytag > cvs co example cvs server: Updating example U example/README cvs server: Updating example/build U example/build/build.sh cvs server: Updating example/htdocs U example/htdocs/index.html cvs server: Updating example/htdocs/jan U example/htdocs/jan/index.html > cvs up -r mytag example cvs server: Updating example cvs server: Updating example/build cvs server: Updating example/htdocs cvs server: Updating example/htdocs/jan > cd example > cvs status README =================================================================== File: README Status: Up-to-date Working revision: 1.2 Repository revision: 1.2 /internal/cvs/cvsrep/example/README,v Sticky Tag: mytag (revision: 1.2) Sticky Date: (none) Sticky Options: (none)
One might, for example, put down a tag to capture the state of play at the point you roll a release of your project.
Tags don't have to stay in one place once laid down. It's possible
to move a tag to a different version of a file, or to remove it
completely from a file. If you update a working copy checked out
to a tag, CVS will recover the appropriate versions of any files.
cvs tag -H for help on this.
As seen above, it's perfectly possible to have multiple working copies checked out of CVS at the same time. For development projects where you're juggling several things at once, this is not only possible, it's actually pretty prudent, so I'll suggest a typical way to do this that you may want to follow.
Under a parent directory, create a
Into this, check out a working copy that tracks the HEAD.
Normally, you will do most work in this working copy.
Parallel to the
head/ directory, you can create directories
named after significant tags (for example, the tags that capture
your previous releases and maybe
Thus far we've pictured all the versions of the various files of a project stretching backwards through time to the start of the project. Considering the project as a whole rather than as individual files, it has a single linear history. We may have marked points on that history using tags for easy recovery, but at any point in time there's only one corresponding version of each file in the project.
CVS permits a more complex arrangement than this, however. It lets us fork or branch the development of a project. Instead of a single line of history on the HEAD, we have HEAD and another branch. From the point of the split, changes to each branch are tracked separately. We can use CVS to collect up changes from one branch and apply them to another.
CVS isn't limited to two branches. Additional branches can be forked
off the mainline (HEAD) - or, in fact, off subsidiary branches.
Care is needed, however:
although it is technically possible to recover a complex map
of branches using
cvs, it's easy to get lost.
(Sometimes a map on a whiteboard can help!)
Before getting intoxicated with the ability to create branches,
however, it's worthwhile to figure out why you want them.
There are at least two scenarios where branches may be appropriate. In the first case, a branch may have quite a long lifetime. In the latter, a branch comes into existence briefly and is abandoned once it has served its purpose.
The obvious use of a long-term branch is to track software releases. The division point where a new branch is forked can be thought of like a tag. Every time a software project goes through a release cycle, a branch is created to track that release.
This works just like using tags to track software releases, except that we can continue to commit changes to a branch. Normally most development work will continue on the HEAD; however, bugfixes can also be applied to the release branch (or branches) too. We can then re-roll a maintenance release of the software rather than requiring a whole-version update.
CVS can assist in applying a patch (in this case, a bugfix) from one branch to another, as we'll see, and the results are surprisingly useful. However, it is not a panacea: patches may not apply cleanly without some human intervention, and the usual pre-release testing cycle should be completed.
As an alternative motivation, consider the use of CVS amongst a team of developers. As more people contribute to a module, the level of discipline required to keep the wheels rolling smoothly goes up.
As an absolute minimum requirement, the rule that your commits should not break the build is often adopted. That is, after a set of commits, a working copy resulting from checking out a clean copy of the repo should build without error. Build here may be extended to include passing a series of unit or regression tests. It is possible to get the CVS repo to collaborate in this regard; that is, to reject commits that fail some automated process. However, such draconian measures are probably not warranted in many situations - in addition, the lack of atomic change sets in CVS means that the guarantees that such a system makes are not always as strong as you might think.
As the constraints on a team-friendly commit go up, the granularity of commits will correspondingly tend to come down. Developers tend to keep more local, transient (and most importantly, unsaved!) state in their working copies. They cannot share this work in progress until it is in a state where it can be checked in.
In such situations, a short-lived development branch may be appropriate. The developer creates a branch to hold their work on a particular task. Most development continues on the HEAD; however, the developer can make fine-grained commits to his branch with impunity. He is free to check in changes to that branch that cause build instabilities; these will be localised to that branch.
If two or more developers are interested in developing the same piece of functionality, they can both check out working copies of the branch to collaborate in (although again, the level of discipline required may go up), or simply to track the progress of the developer in question.
Once the development on the branch is complete, one of the developers has the task of merging the work into the HEAD branch. This involves merging the accumulated changes from the side-branch to the a working copy on the main branch, resolving any conflicts, ensuring that the resulting copy meets commit constraints (ie, fixing any last-minute problems), and committing the code.
We'll look at the mechanics of this now.
CVS is organised into modules. Each cvs repository consists of one or more modules. You can think of a module as a file hierarchy with a history.
In day-to-day operation, you do your work in a local copy of a module called your working copy. Getting an initial copy of a module is called checking out the module.
In order to check out a module, you need to tell CVS where the repository is located and the name of the module. The CVS repository location is specified in your CVSROOT environment variable (which you configured as a part of the prep for this session).
We'll be using the module from the previous session for this work. You can check it out as follows:
> mkdir -p workspace/head > cd workspace/head > cvs co example cvs server: Updating example U example/README cvs server: Updating example/build U example/build/build.sh cvs server: Updating example/htdocs U example/htdocs/index.html cvs server: Updating example/htdocs/jan U example/htdocs/jan/index.html
Now is the time to yell if this doesn't work.
You should now have a small directory hierarchy starting with
example in your current directory. You can
into the directory and have a look around.
You'll notice that as well as normal files and subdirectories,
each directory has a subdirectory called
The CVS directory contains a number of text files that cvs uses
to do its book-keeping. You will not normally need to edit these
For this step, everyone is going to create their own branch. This is simply so that everyone can see the mechanics of branch operation. In "real world use" branch operation is generally not so profligate.
It is important to adopt a naming convention for branches. It's
less important exactly what that convention is, as long
as all the developers on your team can stick to it. For today,
you'll create a branch called
(or whatever your name is).
> pwd /home/john/workspace/head/example > cvs tag -cRb B_JOHN cvs server: Tagging . T README cvs server: Tagging build T build/build.sh cvs server: Tagging htdocs T htdocs/index.html cvs server: Tagging htdocs/jan T htdocs/jan/index.html
-c option to
cvs tag ensures that there
are no pending changes in your working copy before doing the tag.
You pretty much always want to use this option, so you may want to
add the following line to your
Now, create room for a working copy checked out to this branch. Do this outside any CVS-controlled directories.
> cd ../.. > pwd /home/john/workspace > mkdir B_JOHN > cd B_JOHN > cvs co example cvs server: Updating example U example/README cvs server: Updating example/build U example/build/build.sh cvs server: Updating example/htdocs U example/htdocs/index.html cvs server: Updating example/htdocs/jan U example/htdocs/jan/index.html > cvs up -r B_JOHN example cvs server: Updating example cvs server: Updating example/build cvs server: Updating example/htdocs cvs server: Updating example/htdocs/jan > cd example
The process to check out a copy of the branch named after the person sitting next to you is just the same.
> cd ../.. > pwd /home/john/workspace > mkdir B_JANE > cd B_JANE > cvs co example cvs server: Updating example U example/README cvs server: Updating example/build U example/build/build.sh cvs server: Updating example/htdocs U example/htdocs/index.html cvs server: Updating example/htdocs/jan U example/htdocs/jan/index.html > cvs up -r B_JANE example cvs server: Updating example cvs server: Updating example/build cvs server: Updating example/htdocs cvs server: Updating example/htdocs/jan > cd example
This is all very well so far, but currently the HEAD and all child branches are identical. Time to make some changes.
Before making any changes, however, you need to understand how the branch naming works. Just as HEAD is the conventional name that tracks the latest copy of all files on the main line, B_JOHN will track the latest copy of all files on that branch. When we come to merging our changes into the main tree, we'll need to be able to refer to the start of the branch too. We do that by laying down a tag on all the files in the branch before we make any changes. This tag we'll call T_START_JOHN. This is such a common operation that you should really think of it as part and parcel of creating a branch.
> cd ../../B_JOHN/example > cvs tag -cR T_START_JOHN cvs server: Tagging . T README cvs server: Tagging build T build/build.sh cvs server: Tagging htdocs T htdocs/index.html cvs server: Tagging htdocs/jan T htdocs/jan/index.html
Change into the
example/htdocs/ directory in your
branch. Create a new directory named after you (if there's already
one there from the previous session, create
Add the directory to CVS.
> cd htdocs > mkdir john > cvs add john Directory /usr/local/cvsroot/example/htdocs/john added to the repository --> Using per-directory sticky tag `B_JOHN'
The response from CVS is it letting us know that any files created
in the new subdirectory will automatically come into existence on
Now, create an
index.html file in your new directory.
You can use the one in
example/htdocs/jan/ as the
basis for this. Make some changes to it and check it in.
Remember to update before a checkin. For larger commits, this gives you
the opportunity to view and resolve any merge problems before
you commit. This is just a good habit to get into with CVS.
> cp jan/index.html john/index.html > vi john/index.html ... > cvs add john/index.html cvs server: scheduling file `john/index.html' for addition on branch `B_JOHN' cvs server: use 'cvs commit' to add this file permanently > cvs up john cvs server: Updating john A john/index.html > cvs ci -m "All about me" john cvs commit: Examining john RCS file: /usr/local/cvsroot/example/htdocs/john/Attic/index.html,v done Checking in john/index.html; /usr/local/cvsroot/example/htdocs/john/Attic/index.html,v <-- index.html new revision: 220.127.116.11; previous revision: 1.1 done
We can finish our work on this branch by modifying the
example/htdocs/index.html file to include a pointer
to our new directory.
> pwd /home/john/workspace/B_JOHN/example/htdocs > vi index.html [add the following line to the list of pages] <li> <a href="john/">john's pages</a> <li> > cvs up index.html M index.html > cvs ci -m "Added pointer to my page" index.html Checking in index.html; /usr/local/cvsroot/example/htdocs/index.html,v <-- index.html new revision: 18.104.22.168; previous revision: 1.1 done
Now everyone can update their working copies and have a look at the various changes that have been made to each one. Currently the HEAD branch is just like we left it.
> cd ../../.. > pwd /home/john/workspace > cvs up -d head/example B_JOHN/example B_JANE/example cvs server: Updating head/example cvs server: Updating head/example/build cvs server: Updating head/example/htdocs cvs server: Updating head/example/htdocs/jan cvs server: Updating head/example/htdocs/john cvs server: Updating head/example/htdocs/jane cvs server: Updating B_JOHN/example cvs server: Updating B_JOHN/example/build cvs server: Updating B_JOHN/example/htdocs cvs server: Updating B_JOHN/example/htdocs/jan cvs server: Updating B_JOHN/example/htdocs/john cvs server: Updating B_JOHN/example/htdocs/jane cvs server: Updating B_JANE/example cvs server: Updating B_JANE/example/build cvs server: Updating B_JANE/example/htdocs cvs server: Updating B_JANE/example/htdocs/jan cvs server: Updating B_JANE/example/htdocs/john cvs server: Updating B_JANE/example/htdocs/jane U B_JANE/example/htdocs/jane/index.html
Ok, so things aren't quite like we left them. Remember that CVS doesn't
really version directories; once they exist, they exist throughout time and
across all branches. By giving the
-d option, we asked CVS to pull
down from the repo any directories that we don't currently have in our working
copies. It has consequently created the directories that have been added on
The closest we can get to avoiding this is to get CVS to automatically prune
empty directories from a working copy. You can do this automatically by adding
the following line to your
Directory handling is one of the less satisfactory aspects of CVS operation. Generally this somewhat odd behaviour is worth bearing in mind - for example, if you want to create an empty directory in a module that can be used by build scripts to compile code into, you may need to get your build script to ensure that the directory exists before attempting to target it.
When a branch is finished with, we can simply abandon it and continue our development on another branch (or HEAD). The abandoned branch remains in our repository (and is available for perusal or recovery at a later date).
No special action is required to retire a branch; simply stop committing to it. You can remove the working copy corresponding to the branch from your workspace (but check that any pending commits have been made before you do so).
We'll take a quick breather and detour through the CVSWeb installation http://cvs.ilrt.org/cvsweb/ to look at those branches.
Now that we've successfully made our changes on our branch (you might try them out using the build script), we'd like to merge those changes back into the main development line.
We'll go around the room now and do this in turn.
The way we perform a merge is as follows. We select a start point and an end point. We can do this by specifying dates, explicit versions, or more conveniently, by giving the names of tags that mark the points we're interested in. For each file, CVS slurps up the changes between the start point and the end point and applies them to the working copy.
That explains why we laid down a tag before we started working in our branches - what about the end point? Well, the name of the branch itself marks the latest checked-in copy of all files on that branch.
Managing a merge is straightforward if you go about it in a systematic way. To begin with, get yourself a clean, up-to-date working copy of the target branch (HEAD in this case).
> cd head/example > cvs up ... output elided ...
Now, we apply the changes that we've made to our working branch. We do this by specifying the tags that name the beginning and end of our changes.
> cvs up -j T_START_JOHN -j B_JOHN cvs server: Updating . cvs server: Updating build cvs server: Updating htdocs RCS file: /usr/local/cvsroot/example/htdocs/index.html,v retrieving revision 1.1 retrieving revision 22.214.171.124 Merging differences between 1.1 and 126.96.36.199 into index.html cvs server: Updating htdocs/jan cvs server: Updating htdocs/john U htdocs/john/index.html
We can confirm that the first merge has completed successfully by
cvs up to look for merge problems; we might also
use the build script to deploy directly from the working copy
in order to check the changes we're about to commit.
> cvs up cvs server: Updating . cvs server: Updating build cvs server: Updating htdocs M htdocs/index.html cvs server: Updating htdocs/jan cvs server: Updating htdocs/john A htdocs/john/index.html
Having seen that everything looks ok, we put back our merge.
> cvs ci -m "Merge from B_JOHN" cvs commit: Examining . cvs commit: Examining build cvs commit: Examining htdocs cvs commit: Examining htdocs/jan cvs commit: Examining htdocs/john Checking in htdocs/index.html; /usr/local/cvsroot/example/htdocs/index.html,v <-- index.html new revision: 1.2; previous revision: 1.1 done Checking in htdocs/john/index.html; /usr/local/cvsroot/example/htdocs/john/index.html,v <-- index.html new revision: 1.2; previous revision: 1.1 done
We'll continue around the room. As we go, we may see conflicts
arising in the
... update as before ... > cvs up -j T_START_JANE -j B_JANE ... cvs server: Updating htdocs RCS file: /internal/cvs/cvsrep/example/htdocs/index.html,v retrieving revision 1.1 retrieving revision 188.8.131.52 Merging differences between 1.1 and 184.108.40.206 into index.html rcsmerge: warning: conflicts during merge ... > cvs up cvs server: Updating . cvs server: Updating build cvs server: Updating htdocs C htdocs/index.html cvs server: Updating htdocs/jan cvs server: Updating htdocs/john cvs server: Updating htdocs/jane A htdocs/jane/index.html
C indicates a conflict arising during the merge.
We edit the file to remove the conflict, just as with any other merge.
cvs up reports that no conflicts exist, we can commit as
There is one final common problem that can occur when working with a branch. We may decide, rather than abandoning a branch, to carry on additional development work. Here is an example:
> cd ../../B_JOHN/example > pwd /home/john/workspace/B_JOHN/example > vi htdocs/index.html ... > cvs diff -u htdocs/index.html Index: htdocs/index.html =================================================================== RCS file: /usr/local/cvsroot/example/htdocs/index.html,v retrieving revision 220.127.116.11 diff -u -u -r18.104.22.168 index.html --- htdocs/index.html 15 Dec 2003 16:03:05 -0000 22.214.171.124 +++ htdocs/index.html 15 Dec 2003 16:05:18 -0000 @@ -10,7 +10,7 @@ <ul> <li> <a href="jan/">jan's pages</a> </li> -<li> <a href="john/">john's pages</a> </li> +<li> <a href="john/">john's other pages</a> </li> </ul> </body> > cvs up cvs server: Updating . cvs server: Updating build cvs server: Updating htdocs M htdocs/index.html cvs server: Updating htdocs/jan cvs server: Updating htdocs/john > cvs ci -m "Fixed text pointing at my index page" cvs commit: Examining . cvs commit: Examining build cvs commit: Examining htdocs cvs commit: Examining htdocs/jan cvs commit: Examining htdocs/john Checking in htdocs/index.html; /usr/local/cvsroot/example/htdocs/index.html,v <-- index.html new revision: 126.96.36.199; previous revision: 188.8.131.52 done
Some time later, we may come to merge our updates into the main branch. Here's how not to do it:
> cd ../../head/example > cvs up cvs server: Updating . cvs server: Updating build cvs server: Updating htdocs cvs server: Updating htdocs/jan cvs server: Updating htdocs/john > cvs up -j T_START_JOHN -j B_JOHN cvs server: Updating . cvs server: Updating build cvs server: Updating htdocs RCS file: /usr/local/cvsroot/example/htdocs/index.html,v retrieving revision 1.1 retrieving revision 184.108.40.206 Merging differences between 1.1 and 220.127.116.11 into index.html rcsmerge: warning: conflicts during merge cvs server: Updating htdocs/jan cvs server: Updating htdocs/john cvs server: file htdocs/john/index.html exists, but has been added in revision B_JOHN
Urk! What went wrong?
> cat htdocs/index.html <html> <head> <title> An introduction to CVS </title> </head> <body> <h1> These documents are under source-code control </h1> <p> People on the course were: jan </p> <ul> <li> <a href="jan/">jan's pages</a> </li> <<<<<<< index.html <li> <a href="john/">john's pages</a> </li> ======= <li> <a href="john/">john's other pages</a> </li> >>>>>>> 18.104.22.168 </ul> </body> </html>
The problem is that CVS has already merged some of these changes. We originally asked it to apply the changes between T_START_JOHN and B_JOHN. After we made some additional changes, we again asked CVS to apply changes between the original start tag, T_START_JOHN and our latest B_JOHN. What we'd really have liked was for CVS to remember that we'd already done one merge, and only apply the latest changes. Unfortunately, CVS isn't that smart, so we have to be smart for it.
The way to avoid the problem is this: once we have successfully merged changes from a branch into the HEAD, we lay down another tag on the branch to act as a "we got this far" marker. It's possible to use a single tag for this purpose, or to create a new tag every time we perform a merge.
... successful merge and ci to head ... > cd ../../B_JOHN/example > cvs tag -cR T_JOHN_MERGE_20031215 cvs server: Tagging . T README cvs server: Tagging build T build/build.sh cvs server: Tagging htdocs T htdocs/index.html cvs server: Tagging htdocs/jan T htdocs/jan/index.html cvs server: Tagging htdocs/john T htdocs/john/index.html
Now we can perform a second merge from this branch by specifying the endpoints
cvs up -j T_JOHN_MERGE_20031215 -j B_JOHN
in this case. To get this to work with a minimum of fuss you need to lay down
the tag before doing any other work on the branch! Consequently, it is probably
worthwhile getting into the habit of tagging a branch after a successful merge,
even if you do not immediately plan to continue work on that branch.
Finally, you might be asking yourself how you can keep track of the various tags and branches in use. It's a good question. You can make your task easier by adopting a sensible naming strategy. From the command line, you can call up a list of the tags on files as follows:
> cvs log -t README ... output elided ... symbolic names: T_JOHN_MERGE_20031215: 22.214.171.124 T_START_JOHN: 1.1 B_JAN: 126.96.36.199 B_JOHN: 188.8.131.52 mytag: 1.1 T_INITIAL: 1.1 ... output elided ...
You'll need to specify a filename because CVS doesn't keep this kind of information about directories.
A good naming strategy for tags and branches is pretty much vital, as we've seen. Additionally, it's worth thinking a bit before branching development. There is additional work involved in managing merges and a bit of bookkeeping - so sometimes it may be better to do all development work on the HEAD. Sometimes, however, you simply can't do without a branch.
When that's the case, particularly for short-lived branches, it's useful to plan out your work before you begin. It helps if you can clarify what work you will be doing on the branch (rather than letting all development that you do slop over into it) and to figure out what your milestones are; also, to try to identify the point where your work on the branch will be complete.
Although it's not always possible to look into the future with 100% accuracy, a little foresight can be worthwhile. Drop a line to your technical development list outlining what you'll be doing the branch for. Include the tag names you'll be doing. Then stick to the plan.
The email doesn't need to be hugely expansive, but my own preference is to go into sufficient detail that others will be able to follow the work. If nothing else, this encourages the airing of ideas on the -dev list. Here's one I did earlier, with the names changed to protect the guilty:
From: Jan Grant <Jan.Grant _at_ bristol.ac.uk> Subject: UP plan Here's the plan. First, if anyone has any pending checkings, please get let me know and get the repo into a buildable state; I need to tag and branch - which I'll do at 11.30 this morning (it should take no more than 10 minutes). This is easier to do if I'm the only one with my hands in the repo at the time. I'll email once before I do the tag and branch, then once when it's complete. I propose a three-step integration of the new code: 1. I'm going to factor out interface and implementation for the UP package. That is, the existing UP code will go into an implementation sub-package and all that will be left in the main package are minimal interfaces to provide the required functionality. This work is considered complete (milestone 1) when: - everything builds - existing test cases operate as before - the system passes integration testing (in this case, that means I'll be asking Susan to make sure that the portal looks like it works while exercising the bits of xsearch that need the UP) Once that work is done, I'll merge it to the main branch as a first step and tag the branch at that stage. 2. Bring the updated code across as an implementation of the same existing UP interface. Basically, that means I'll hammer out convenience methods until it compiles against the interfaces it claims to do. This work is complete (milestone 2) when: - the new profile implementation compiles as an implementation of the existing UP interface; - all the appropriate test cases pass (the Command stuff is hidden in the new implementation) - new test cases (as appropriate) also work, including the "testTickleBug" case. 3. Integrate the new code (milestone 3). This means: - update the main configuration files to use the new implementation - update the build process to add new targets to install the new schema in the database - test by hand again, including persistence layer and the alerting code. When this is complete I'll merge it to the main branch and stop working on the branch. Tags will be as follows: the branch is BRANCH_NEW_UP on the branch, post branch: TAG_POST_BRANCH_NEW_UP Milestone 1, on the branch: TAG_NEW_UP_M1 Main code, after merge1: TAG_POST_MERGE_NEW_UP_M1 Milestone 2, on the branch: TAG_NEW_UP_M2 Milestone 3, on the branch: TAG_NEW_UP_M3 Main code, after merge of M2 and M3: TAG_POST_MERGE_NEW_UP_M3
There are a few kinds of things that generally go wrong: you commit code to the wrong branch; you get into a mess with a merge; you perform a double-merge.
Committing code to the wrong branch is easy to do. You back it out just like you would any other CVS change; although obviously it's better to not make the commit in the first place. Things that can help are:
cvs diffprior to committing;
Sometimes a merge just goes plain wrong. On those occasions it helps to take a deep breath and start again, merging pieces of code a few at a time and testing as you go.
If you begin with a pristine working copy of the target branch, then there is less room for grief. That is, ensure that you have no pending changes in your target working copy (or even check out a fresh working copy to manage the merge in). That way, there is no loss of uncommitted changes if you decide to just remove the working copy and try again.
Double-merges can be a bit of a pain to deal with. The best approach is to form the habit of always tagging the source branch once you have committed a successful merge. If you are planning on bidirectional merges (from a branch to mainline and from mainline to branch - as you might if you are managing a maintenance release) then don't be afraid to tag everything at every step of the way.
Pick a naming strategy for your tags and branches and stick to it.
As we've seen,
cvs up is agnostic about what file versions
it uses to create the patch it applies. Providing you're careful to avoid
double merges, you can take patches between branches pretty much ad hoc.
It's therefore useful to consider how you're going to manage branches as you plan out your project's use of CVS. There are a number of common patterns detailed in the CVS book (http://cvsbook.red-bean.com/) which you may be interested in looking into more closely.
The two common scenarios I have already outlined, however, are generally sufficient for the majority of projects: you can go a long way with a simple branching strategy and a clear head!
Well done. You've just earned another gold star - you need feel no shame when conversing with other developers should the topic of conversation come around to source-code control. Hold your head up high!