No conflicts? Great! You can blow away the distribution and
update your development area:
cd ..
rm -rf acs
cd /web/myservice/
cvs update -d
Note that the -d option to cvs update will create any new
directories that were added to ACS. Without this option the update
command will only operate on directories that already exist in your
working copy.
Vendor branches and import conflicts
A CVS import into an active project rarely goes that smoothly.
Before we go into the mechanics of finishing the upgrade, it's
important to understand how CVS stores each import within your project
repository using vendor branches.
Let's say that over time we've upgraded the project three times. In the simplest case of a file that we never touched, the revision tree looks like this:
% cvs -d /cvsweb import myservice ArsDigita acs-3-1-0
% cvs -d /cvsweb import myservice ArsDigita acs-3-1-1
% cvs -d /cvsweb import myservice ArsDigita acs-3-1-2
file
|
| ArsDigita (branch 1.1.1)
| /
1.1 ------------- 1.1.1.1 acs-3-1-0
|
|
1.1.1.2 acs-3-1-1
|
|
1.1.1.3 acs-3-1-2
Each import adds a new revision to the vendor branch, and a subsequent update or checkout will pull that latest revision out to your working copy.
What if you and the vendor both changed the file?
% cvs -d /cvsweb import myservice ArsDigita acs-3-1-1
...
2 conflicts created by this import.
Use the following command to help the merge:
cvs checkout -jArsDigita:yesterday -jArsDigita myservice
file
|
| ArsDigita (branch 1.1.1)
| /
1.1 ------------- 1.1.1.1 acs-3-1-0
| |
| |
1.2 1.1.1.2 acs-3-1-1
Because you and the vendor both changed the file, we have an
import conflict, which really just means that we need to take
an extra step to merge the vendor's changes (1.1.1.2) with ours (1.2).
CVS tells you exactly what to do:
% cd ~/develop
% cvs checkout -j ArsDigita:yesterday -j ArsDigita myservice
file
|
| ArsDigita (branch 1.1.1)
| /
1.1 ------------- 1.1.1.1 acs-3-1-0
| |
| |
1.2 1.1.1.2 acs-3-1-1
. .
. .
1.3 . . . . . merge (and resolve conflicts)
Note what's happening here. We are checking out a fresh copy of
the project and doing a simultaneous merge. The patch to be applied is
computed by selecting only the changes that have occured along the
ArsDigita branch in the past 24 hours, which will of course
include only the code that we just imported.
Now you have to go through the normal process of conflict
resolution if you had overlapping changes. Otherwise CVS will
combine your changes with the vendor's changes to produce the fully
updated file. Don't forget to commit the results of the merge and
update your development area.
% cd ~/develop/myservice
(resolve conflicts)
% cvs commit -m "merged with acs-3-1-1, conflicts resolved"
% cd /web/myservice-dev
% cvs update
Managing Releases for a Client Project
Our final topic concerns managing the code between your three
servers (development, staging and production). Say you are running a
client project on which the site has launched but your programming
team is still developing new features. For example, every week you
release a new set of features onto the production site, plus you fix
some bugs, plus you start up a development project that won't be
released for some time. How do you keep track of what set of changes
need to migrate from one working copy to another?
This is a problem with many possible solutions but not one "best"
solution. You'll have to make the appropriate choice based on the
complexity of the site and the amount of time you're willing to invest
in software management. Here are three choices for solving the
problem, differentiated by the number of code branches used.
One Branch
The basic idea is that we have only one branch of development and
we use CVS tags to mark the revision that should migrate to the
production server. The development tree looks like this:
trunk
|
|
1.35
|
|
1.36 ... myservice-production
|
|
1.37
|
|
1.38
We use a special tag (myservice-production) to mark files
for migrating to the production server. Note that all work is done on
the development site. Any programmer can release a file for
production using:
cvs tag -F myservice-production [files]
or remove a file from production using:
cvs tag -D myservice-production [files]
After that development continues but files would only be re-tagged
when a new set of features is ready for release to the production
server. The only command ever executed on the production server is
cvs update:
cd /web/myservice
cvs update -d -r myservice-production
The arguments to the update are -d to create new directories
if necessary and -r to update all of the local copies to the
revision tagged as myservice-production, and remove files that
no longer have that tag.
Note that files on the production server will not be editable.
When you ask CVS to update a file using a regular tag like
myservice-production, it sets a sticky option for the
file. This is to prevent you from trying to commit changes to any
file that is not based on the latest revision. The command update
-A will reset all sticky options and replace your local copy with
the latest trunk revision.
The practical implication of sticky tags is the following. If a
problem is discovered it must be fixed on the development server,
re-tagged, and then the production server must be updated again to
bring over the changes. However, this process is easily automated.
You can have a cron job on the production box that updates the site
every 15 minutes, or every hour, or every day - whatever schedule fits
your management goals.
For better record keeping, or in case you need to suddenly revert
to a previous working image of the production server, you can record
particular snapshots of the tree using additional tags and the cvs
rtag command:
cd /web/myservice
cvs -d /cvsweb rtag -r myservice-production myservice-1-0-5 myservice
This applies the tag myservice-1-0-5 to all revisions that
are currently tagged as myservice-production. Note that
rtag does not require a working copy. It goes straight to the
repository and adds the myservice-1-0-5 to every revision that
is currently tagged myservice-production.
The production tag will change over time, but the release tags
will record fixed snapshots of your project. Eventually your
development tree will look like this:
trunk
|
|
1.35
|
|
1.36 ... myservice-1-0-3
|
|
1.37 ... myservice-1-0-4
|
|
1.38 ... myservice-1-0-5, myservice-production
In case you need to suddenly revert the production site to a
previous snaphot, it's a simple matter of executing:
cvs update -r myservice-1-0-3
If you set things up like this you will not need a staging server,
although you could easily introduce one, e.g. for testing prior to
updating the production server. This is essentially the way we run www.arsdigita.com, with all changes
taking place on the development server, the tagging performed by a
shell script called arsdigita-publish.sh
, and a cron job
updating the production site every 15 minutes.
To summarize the one-branch scenario:
- Advantages
- Simplicity
- Files can be automatically migrated to production so that
management of the site is distributed over all
developers
- Disadvantages
- Difficult to fix bugs on the production site, espcially
if newer revisions of the file have been checked in from
the development site
- Clashes with content management tools that have been
integrated with CVS because no commits are allowed from
the production site
Two Branches (Development and Production)
If you want a little more flexibility and better separation between
your development work and your production site, you can introduce a
separate code branch for staging/production.
You're shooting for something like this:
/web/myservice-dev/
|
Development server (working copy of your project trunk)
|
/web/myservice-staging/
|
Staging (working copy of the production branch)
|
/web/myservice/
|
Production (read-only copy of the production branch)
|
You want to keep active development on your development server,
testing and bug fixes on your staging server, and no edits (cvs update
only) on your production server. Conceptually the development tree
looks like this:
file
|
| myservice-production (branch 1.1.2)
| /
1.1 -------------- 1.1.2.1
| |
| |
1.2 1.1.2.2 ... myservice-1-0-1
| |
| |
1.3 1.1.2.3
| |
| |
1.4 1.1.2.4 ... myservice-1-0-2
Unlike the one-branch scenario, the production branch now allows
normal development, e.g. you can edit and commit changes from the
staging server just like any other working copy. These changes will
be recorded on the myservice-production branch. This also
enables content management tools that have been integrated with CVS to
record changes in the production branch. And you can still record
particular release snapshots (myservice-1-0-2) on the
production branch.
The only additional complexity is that you will need to merge
changes between your production and development branches. The command
to add a file to the production branch for the first time is the same as
above, with the addition of a -b branch option:
cvs tag -b myservice-production [files]
The production and staging servers are updated as before:
cd /web/myservice-staging
cvs update -d -r myservice-production
cd /web/myservice
cvs update -d -r myservice-production
with the subtle distinction that myservice-production is now
a branch tag (refers to a family of revisions rooted to a common
ancestor on the trunk) rather than a regular tag (refers to a single
revision in the repository).
To migrate changes from your development site to the production
site, you will need to explicitly merge them. For example, let's say
you've been working on custom-module and now want to migrate
the changes to your production branch. The commands are:
% cd /web/myservice-staging/www/custom-module/
% cvs update -kk -j HEAD
% cvs commit -m "merged with dev"
trunk myservice-production (branch 1.35.2)
| |
| |
| |
1.35 1.35.2.1
| |
| |
1.36 1.35.2.2
. |
. |
. . . . . . 1.35.2.3
This uses the reserved tag HEAD, which always refers to the
latest revision on the trunk, to merge all files in the directory
/www/custom-module/
. Once everything is tested on
the staging server, a single cvs update on the production
server will activate the changes.
One advantage to this approach is that you can quickly fix problems
on the staging server and commit the changes to the production
branch. If this happens you will want to merge the changes into your
development copy so that you don't have to duplicate the fixes (and
generate spurious conflicts later). This is also done with a merge,
but in the opposite direction. As a general policy you will want to
tag your production branch and do this at regular intervals, each time
merging the changes between previous production "releases".
% cd /web/myservice-dev/www/custom-module/
% cvs update -kk -j myservice-1-0-2 -j myservice-1-0-3
% cvs commit -m "merged with myservice-1-0-3"
trunk myservice-production (branch 1.35.2)
| |
| |
| |
1.35 1.35.2.1 ... myservice-1-0-2
| |
| |
1.36 1.35.2.2 ... myservice-1-0-3
| .
| .
1.37 . . . . .
To summarize the two-branch scenario:
- Advantages
- Separation of development from production
- Problems on the production branch can be fixed quickly
regardless of activity on the development branch
- Compatible with content-management tools that have been
integrated with CVS
- Disadvantages
- Requires merging code between the production and
development branches
Multiple Branches
Our final option to use a separate branch for every
production release of the site. The complication over using two
branches is that you will create another release branch each time you
want to launch a new set of features. In this case the release cycle
involves merging the current release branch with your development
copy, creating a new release branch, moving your staging server onto
the new branch for testing, and finally updating the production server
when testing is complete.
Over time your revision history tree will have multiple branches
coming out of the trunk and then merging back in as you go through the
process of successive releases:
file
|
| myservice-1-0 (branch 1.35.2)
| /
1.35 ------------- 1.35.2.1
| |
| |
1.36 1.35.2.2
. .
. .
1.37 . . . . .
|
|
1.38
| myservice-1-1 (branch 1.39.2)
| /
1.39 ------------- 1.39.2.1
| |
| |
1.40 1.39.2.2
. .
. .
1.41 . . . . .
Although the basic steps are described above, it might be helpful to
list them explicitly in the context of a full release cycle. They are:
- Make sure all work in your development area has been committed by
looking at the output of:
% cd /web/myservice-dev
% cvs -n update
- Merge the current production release (myservice-1-0) with
the development copy:
% cd /web/myservice-dev/
% cvs update -j myservice-1-0
% cvs commit -m "merged with myservice-1-0"
- Create a branch for the new release:
% cd /web/myservice-dev
% cvs tag -b myservice-1-1
- Switch your staging server over to the new release:
% cd /web/myservice-staging
% cvs update -d -r myservice-1-1
Run any necessary datamodel upgrades, test the new code on the
staging server, fix any problems that arise, and commit all changes.
- Update your production server to activate the changes:
% cd /web/myservice
% cvs update -d -r myservice-1-1
This is essentially the way we handle new releases of the ArsDigita
Community System, except for the final steps of packaging up the
distribution.
To summarize the multiple-branches scenario:
Concluding Remarks
Version control is a critical aspect of software development, and
doing it correctly will not only save you from potential coding
disasters but also help reinforce good software engineering. It
enourages developers to document what they do, fosters communiction
among your team, and helps you (as a project leader) track the
activity taking place from day to day. At ArsDigita we've
standardized on CVS as our version-control system. Although CVS may
seem complex at first, the process of managing a Web development
project is straightforward once you learn a few basic principles. The
benefits far outweigh the small amount of overhead necessary to use
CVS effectivly.
Here are some general guidelines to avoid trouble:
- All of the files required for your site to operate should be
under version control
- Never copy files between working copies (use cvs update to migrate
changes)
- Avoid forced or anonymous commits (CVS is not a backup utility)
- Commit changes frequently but keep the repository in a runnable
state
References
asj-editors@arsdigita.com
Related Links
- WinCVS GUI- This is a great front-end to CVS. It doesn't require any knowledge of the command line, but allows you to use it if you wish. (contributed by Phillip Thurmond)