Previous section   Next section

6.7 Backing Up a Repository

A CVS repository can be backed up using the same backup tools and schedule that you use for ordinary text and binary files. You must be able to restore the repository with the same permissions and structure it had when it was backed up. You must restore the CVSROOT directory and its contents before the project directories are useful, but you can restore project root directories and their contents independently of each other.

If a file in a repository is being written to while a backup is in progress, it is possible that the file could be backed up in a state that might make it difficult for CVS to read it accurately when it is restored. For this reason, you should prevent processes from writing to the repository during a backup.

6.7.1 Freezing a Repository

Freezing a repository is CVS-speak for the act of preventing users from changing the repository. The most common reason to freeze a repository is to ensure that a backup operation copies all the repository files in a consistent state.

The simplest way to freeze a repository is to block clients from accessing the CVS server. There are many ways to do this, such as shutting down the CVS server program, disallowing logins to the server system, blocking the server with a firewall rule, setting the server to single-user mode, or pulling out the network cable. Rather than blocking access to the server completely, you can use CVS's locks to read-lock the repository.

To keep the server running and allow clients to continue to read the repository files while a backup or other critical work takes place, use a script that creates lock files like CVS's internal locks. CVS honors these locks as if they were its own. Lock the directories you want to back up, make your backup, and then unlock the directories. See Section 6.4.2 in this chapter for a full explanation of CVS locks.

Example 6-7 shows a script that locks an entire repository. It attempts to lock the repository and backs off if it can't lock the whole repository. The backing off is necessary to prevent deadlocks. Example 6-8 shows a script that unlocks a repository.

Example 6-7. Script to lock a CVS repository
#!/bin/sh
# Freeze - Lock a whole repository for backup.
   
KEYMAGIC=$$
TMPFILE=/tmp/freeze.$KEYMAGIC
bail_out (  ) {
        # Exit abruptly. Return a failure code and display
        # an error message.
        echo "$1"
        rm -f $TMPFILE
        exit 1
}
   
freeze_directory (  ) {
        echo "FREEZE: $1"
        # Obtain the master lock
        mkdir "$1/#cvslock"
        if [ $? != 0  ]
                then
                # Could not get master lock
                return 1
                fi
        # Create the read lock
        touch "$1/#cvs.rfl.$KEYMAGIC"
        # Record it in case of trouble
        echo $1 >> $TMPFILE
        rmdir "$1/#cvslock"
        return 0
}
   
thaw_repository (  ) {
        # If we encounter anyone else playing with the
        # CVS locks during this, then there's a small risk
        # of deadlock. In that event, we should undo everything
        # we've done to the repository, wait and try again.
        # This function removes all the locks we've produced during
        # the run so far.
        for dir in `cat $TMPFILE`
                do
                echo "** THAW ** $dir"
                mkdir "$dir/#cvslock"
                if [ $? ]
                        then
                        # Remove read lock
                        rm -f "$dir/#cvs.rfl.$KEYMAGIC"
                        # Remove masterlock
                        rmdir "$dir/#cvslock"
                        fi
                done
        return 0
}
   
freeze_repository (  ) {
        for dirname in `find $CVSROOT/$REPOSITORY -type d ! -iname CVS ! \
        -iname Attic ! -iname "#cvslock"`
                do
                freeze_directory $dirname
                if [ $? != 0 ]
                        then
                        # We couldn't get the master lock.
                        # Someone else must be working on the
                        # repository
                        thaw_repository
                        return 1
                        fi
                done
        return 0
}
   
if [ "$CVSROOT" =  = "" ]
        then
        echo "No CVSROOT specified in the environment"
        bail_out "Aborting"
        fi
   
if [ "$KEYROOT" =  = "" ]
        then
        KEYROOT="$CVSROOT"
        fi
   
if [ "$1" =  = "" ]
        then
        echo "No Repository specified."
        echo "Usage: $0 repository"
        bail_out "Aborting"
else
        REPOSITORY="$1"
        fi
   
# Double-check the validity of supplied paths
KEYFILE=$KEYROOT/.$REPOSITORY
test -d $CVSROOT || bail_out "Can't access $CVSROOT - is it a directory?"
touch $KEYFILE || bail_out "Can't access $KEYFILE - aborting"
   
TRIES=0
while   ! freeze_repository
        do
        let TRIES=$TRIES+1
        echo "Could not freeze. Repository in use. (Attempt $TRIES)"
        if [ $TRIES -gt 9 ]
                then
                bail_out "Giving up"
                fi
        echo " Sleeping 1 second."
        sleep 1
        rm -f $TMPFILE
        echo "Trying again.."
        done
echo "** Repository $REPOSITORY frozen"
echo "$KEYMAGIC" >> $KEYROOT/.$REPOSITORY
   
rm -f $TMPFILE
exit 0
Example 6-8. Script to unlock a CVS repository
#!/bin/sh
# Unfreeze - Unlock a whole repository.
   
bail_out (  ) {
        # Exit abruptly. Return a failure code and display
        # an error message.
        echo "$*"
        rm -f $TMPFILE
        exit 1
}
   
unfreeze_directory (  ) {
        echo "UNFREEZE: $1"
        mkdir "$1/#cvslock"
        if [ $? != 0  ]
                then
                # Could not get master lock
                return 1
                fi
        test -f "$1/#cvs.rfl.$KEYMAGIC" || echo "THAW: Expected to find a lock file: \
        # Proceed anyway.
        rm -f "$1/#cvs.rfl.$KEYMAGIC"
        rmdir "$1/#cvslock"
        return 0
}
   
unfreeze_repository (  ) {
        TMPFILE=/tmp/freeze.$KEYMAGIC
        for dirname in `find $CVSROOT/$REPOSITORY -type d ! -iname CVS ! \
        -iname Attic ! -iname "#cvslock"`
                do
                unfreeze_directory $dirname
                if [ $? != 0 ]
                        then
                        return 1
                        fi
                done
        return 0
}
   
if [ "$CVSROOT" =  = "" ]
        then
        echo "No CVSROOT specified in the environment"
        bail_out "Aborting"
        fi
   
if [ "$KEYROOT" =  = "" ]
        then
        KEYROOT="$CVSROOT"
        fi
   
if [ "$1" =  = "" ]
        then
        echo "No Repository specified."
        echo "Usage: $0 repository"
        bail_out "Aborting"
else
        REPOSITORY="$1"
        fi
   
# Double-check the validity of supplied paths
KEYFILE=$KEYROOT/.$REPOSITORY
test -d $CVSROOT || bail_out "Can't access $CVSROOT - is it a directory?"
test -f $KEYFILE || bail_out "No $KEYFILE appears to exist. Repository does \
not appear to be frozen"
TRIES=0
   
# Walk through each of the keys that the repository has been frozen with
# and unlock each one in turn. A single run of unfreeze thaws multiple
# runs of freeze.
for KEYMAGIC in `cat $KEYFILE`
        do
        unfreeze_repository 
        if [ "$?" = "1" ]
                then
                echo "** Unable to obtain master locks for all directories."
                let TRIES=$TRIES+1
                if [ "$TRIES" = "10" ]
                        then
                        bail_out "Too many attempts. Giving up."
                        fi
                sleep 1
                echo "** Trying again."
        else
              echo "** Repository $REPOSITORY thawed from freeze $KEYMAGIC"
                fi
                
        done
echo "** Unfreeze complete"
echo "** Repository $REPOSITORY thawed"
rm -f $KEYFILE
exit 0

If you need to freeze the repository entirely, preventing anyone from either reading or writing, modify the scripts in Example 6-7 and 6-8 to create and remove write locks. For example, you need to do this when attempting to restore a backup of the whole repository.

6.7.2 Restoring a Backup

A CVS repository can be restored using the same tools that you use to restore ordinary text and binary files. You must restore the CVSROOT directory and its contents to be able to use the project files reliably. You need not restore every project in the repository, but when you restore a project you should restore all its files and directories.

When you restore a repository, you must ensure that no one can write to it. You can do this by freezing the repository as described in Section 6.7.1, but if you use locks you must use write locks rather than the read locks described in that section. That's because the repository will be in an inconsistent state while the files are being restored; you don't want anyone to read from or write to the repository during the restore process.

After you have restored a CVS repository from a backup, it is safest to assume that any sandbox files are based on revisions that do not exist in the backup of the repository. This means that individual developers are likely to have changes in their sandboxes that need to be committed (or recommitted) to the repository in order to bring it up-to-date.

This is the simplest way to restore changes that have been preserved in sandboxes:

  1. Check out a new sandbox from the restored repository.

  2. Try to use commands such as cvs diff, cvs status, and cvs update on a sandbox from before the repository was restored, to determine which files have been changed and what the changes are. If this does not work, try using the Unix diff program to determine the differences between an old and the new sandbox.

  3. Copy the old sandbox files you want to commit or recommit into the new sandbox.

  4. Commit the changes as if they were ordinary changes.

  5. Make sure the log message indicates what happened.

6.7.3 Mirroring a Repository

Many project teams have public, read-only CVS repositories. If you want to do likewise, you can copy the repository, reserve the original repository for your project work, and use the copy (or mirror) repository as a public repository.

The copy can be on the same computer as the original or it can be on a different computer or even a different network. Having two or more copies of a repository allows you to put one copy on a trusted network for your developers and another on a more public part of your network for the general public.

You can't mirror a CVS repository for making changes. CVS stores its data on a revision-by-revision basis and cannot resolve copies of a file that have different sets of changes with the same revision number.

This situation may change in the near future. With a combination of a program called CVSup and a recent patch that will likely be incorporated in new versions of CVS, you will be able to commit to branches on mirror repositories.

Mirror a repository by copying the repository files. To avoid getting part of one release and part of another due to the copy overlapping with a commit, freeze the repository with read locks before mirroring and unfreeze it afterward.

The Unix and Linux rsync program is a useful tool to use when mirroring. It allows you to recursively transfer only the changed files between any two directories and recovers properly if it's interrupted. It also has an optional CVS-exclusion mode, called with the --cvs-exclude option, which ignores the same files CVS ignores by default. Use the -e ssh option to rsync to transfer using the SSH protocol.


  Previous section   Next section
Top