[cvsnt] postcommit

Misner, Ris rmisner at accelent.com
Thu Dec 11 15:30:00 GMT 2003


I apologize for the length of this message, but I decided on a whim to give a thorough answer to this question since I had to spend quite a while trying to get it working myself.

> -----Original Message-----
> 
> Hi, I have read several documents that say that you can use 
> the 'postcommit'
> file in the CVSroot to copy every file that is committed to a seperate
> location (for backup, whatever, etc.).  I have NO idea how to 
> do this and I
> would love it if you could offer me any help.


What you want to create is what I would call a Shadow Directory.  I have successfully set up some shadow directories as you describe by using a python script which I have copied below - you should copy the contents to a file and save it as 'ShadowDirectory.py'.  Comments in the top of the 'file' describe how to configure the postcommit file in your CVSROOT to cause the script to be run.  Note that you'll have to get and install python from www.python.org as well as ensure that .py files are set as an executable file extension in your OS configuration.  This script was developed for python 2.2.3 but 2.3 should also work.

My ShadowDirectory.py script works by updating a CVS sandbox that functions as the shadow directory.  It does not directly copy the file from the repository, but instead executes cvs.exe command lines to update, getting the clean copy and creating missing directories, etc.  You have to manually checkout the module you want to shadow using WinCVS or cvs.exe or your favorite cvs client interface in order to create the shadow directory in the first place.  You must also put any passwords in the CVSROOT used to create the checkout module because the shadow directory script will not be able to prompt anyone for a password to access CVS.  If this is some kind of security violation for you, then theoretically the script could be modified to acquire a password from a file, or simply have the password hard-coded into the script itself.

I feel that it is also worth noting that this script uses a module called AGCommon which defines several data elements and python functions specific to Alternity Games, a company I work for.  I cannot share that module.  It should be obvious what is missing that was provided in AGCommon, and any functionality or data elements should be easily reproduced in order to get the script to work for you.

> 
> For simplicity's sake I will define a couple things:
> 
> All paths I give will relate to my CVS server.
> I am running both the server and the "client" on Windows XP
> 
> My cvsroot is 'c:\cvs\cvsroot'
> My module is 'module1'
> My local copy of the modue is '\\computer\cvs\module1'
> My "backup" location is '\\backup\cvs\module1'


A word of warning: the use of UNC paths is going to cause you no end of trouble on this.  Despite rumours to the contrary, it IS possible to get it working at least most of the time if not all of the time, but the behavior is flaky, at least running in windows 2000.  For more about that, as well as postcommit in general, you should look at this: http://www.cvsnt.org/wiki/CvsChapter180.  Specifically, you will find the "net use" python functions which are provided in the missing AGCommon module and used in the ShadowDirectory.py script quoted below.

Hope this helps you and anyone else struggling to create shadow directories with CVS.

-Ris

> I want to commit file 'abc.txt' from my computer to the 
> server.  Then I want
> the server to automatically
> copy the file I just committed to my backup location.
> 
> Here is the sequence of events {this means an action or 
> command that takes
> place}:
> 
> \\computer\cvs\module1\abc.txt   {cvs commit}  -->  
> c:\cvs\cvsroot\abc.txt
> {copy}  -->  \\backup\cvs\module1\abc.txt
> 
> 
> Thanks a lot! (My e-mail is jfcube at hotmail.com)  God-Bless!  
> -Denver Root


---------- begin contents of ShadowDirectory.py ----------------
"""ShadowDirectory.py

This is a script intended to be executed by the CVS server on-commit of a file
to cause that file to be updated in a shadow directory.

A shadow directory is defined as a CVS sandbox accessible from the local
machine.  The shadow directory contains all read-only files which are kept
up-to-date at all times.  Every time any change is committed to the applicable
directories of the CVS repository, the CVS admin files call this script which
then copy those files to the shadow directory using cvs updates in the shadow
directory's CVS sandbox.

To set up this script, edit the CVSROOT/postcommit file.close
Consult your favorite CVS administration documentation for details
Or see this website for some info on how to set this up:
http://www.cvsnt.org/wiki/CvsChapter180

Basically the entry in the postcommit file (per line) is in the following format:
^Dir_Under_Repository/* $CVSROOT/CVSROOT/ShadowDirectory.py Shadow_Dir_Path $CVSROOT/Dir_Under_Repository

Where parameters in the above postcommit line are as follows:
Dir_Under_Repository = repository directory of which to create a shadow directory
Shadow_Dir_Path = path to the directory where the shadow is to be created

The command line parameters passed by the CVSNT server are as follows:
ShadowDirectory.py Shadow_Dir_Path $CVSROOT/Dir_Under_Repository Dir_Containing_Commit

The parameter meanings here are:
Shadow_Dir_Path = path to the directory where the shadow is to be created
Dir_Under_Repository = repository directory of which to create a shadow directory
Dir_Containing_Commit = path relative to CVSROOT containing the file that was committed
	This is likely to be a subdirectory of Dir_Under_Repository

Example postcommit entry:
^Projects/Website/* $CVSROOT/CVSROOT/ShadowDirectory.py C:\InetPub\Web $CVSROOT/Projects/Website

Following any user's commit of a change to the following file:
L:/LocalSandboxOfUser/Projects/Website/Subdir/File1.html

Matching example command line parameters in automatic call to this script:
c:/Repository/CVSROOT/ShadowDirectory.py C:/InetPub/Web c:/Repository/Projects/Website Projects/Website/Subdir

Which results in this script performing a CVS update on the shadowed directory:
C:/InetPub/Web/Subdir

Please note: this script is designed to never fail such that python.exe
	returns a non-zero exit code.  This is highly necessary to ensure that
	CVS commits will succeed even if the shadow directory features are failing.
	The worst that will happen is that the shadow directory will be out of date
	and log messages will record the applicable errors.

THIS SCRIPT CANNOT BE IMPORTED AS A MODULE!  DO NOT TRY IT!
it is designed to be a stand-alone application style script ONLY!
"""

# STANDARD IMPORTS
import sys
import os
import time
import AGCommon
from AGCommon import Print

# imported constants
SEPARATOR = AGCommon.SEPARATOR
UNCPATHSTART = AGCommon.UNCPATHSTART

INITIAL_WORKING_DIR = os.getcwd()

###############################################################################
# STATIC CONFIGURATION

# log info
LOGFILEPATH = os.path.normpath("D:/CVSShadow/ShadowDirectory.log")
MAXLOGSIZELINES = 1000 # max number of lines in the log file

###############################################################################
# LOG FILE

def OnExit(failuremessage=None):
	"""OneExit(failuremessage=None):
	returns none
	exits the script always with a zero exit code to allow commit to succeed
	logs failure message if it is not None
	logs success message otherwise
	"""
	# return to the original working directory before closing network shares
	os.chdir(INITIAL_WORKING_DIR)
	# unmap all network shares
	AGCommon.ReleaseAllNetAccess()
	# determine an exit code and termination message
	exitcode= 1 # failure
	if not failuremessage:
		failuremessage = "Script terminated successfully."
		exitcode = 0 # success
	# report termination message
	Print(failuremessage, 1) # always echo the termination message
	AGCommon.ClosePrintLog()
	# exit the script
	sys.exit(exitcode)
	return None # superfluous following sys.exit, but makes for good sanity

###############################################################################
# COMMAND-LINE PARAMETERS

def ProcessCommandLine():
	"""ProcessCommandLine():
	processes the command line expected for this script

	The command line parameters passed by the CVSNT server are as follows:
	ShadowDirectory.py Shadow_Dir_Path $CVSROOT/Dir_Under_Repository Dir_Containing_Commit

	This function returns the sandbox directory in which to perform an update
	"""
	# get command-line parameters
	# Log the parameters
	Print("Command Line Parameters (sys.argv):")
	Print("\n".join(sys.argv))
	Print("\n") # separating blank line

	ShadowRoot = os.path.normpath(sys.argv[1])

	# NOTE: due to several oddities, we must update the entire shadow root
	#	Oddity #1: if a new directory is added, we must update to create it
	#	Oddity #2: if several files in different directories are simultaneously committed
	#				we only get one notification for the last one in the list
	Print("Shadow root: %s" % ShadowRoot)
	return ShadowRoot


###############################################################################
# SHADOW DIRECTORY FUNCTIONS

def UpdateShadow(SandboxDirectory):
	"""UpdateShadow(SandboxDirectory):
	calls a cvs update command on the shadowed sandbox directory
	logs results
	returns None
	"""
	# if the sandbox directory is on a network share, then we need to map access to it
	if SandboxDirectory.startswith(UNCPATHSTART):
		SandboxDirectory = AGCommon.MapNetAccess(SandboxDirectory)
		if not SandboxDirectory:
			OnExit("ERROR: failed to acquire access to network share")
	
	# set the working directory to the shadow directory
	try:
		os.chdir(SandboxDirectory)
		Print("Changed to directory: " + SandboxDirectory)
	except OSError:
		Print("WARNING: Creating new directory: " + SandboxDirectory)
		try:
			os.makedirs(SandboxDirectory)
			os.chdir(SandboxDirectory)
		except Exception, e:
			OnExit("Failed to create/access shadow directory '%s' because of error: %s" % (SandboxDirectory, str(e)))
		Print("Changed to directory: " + SandboxDirectory)
		
	# tell the update to use these options:
	#   checkout files read-only (-r)
	#   quiet output (-q)
	#   use encryption (localhost should not require encryption)
	#   get the clean copy (-C)
	#   prune empty directories (-P)
	#	-d      Build directories, like checkout does.
	#   for debugging the script, add -n before "update" to avoid changing the disk
	sCommand = "cvs.exe -q -r update -C -d"
	(error_code, outputlines) = AGCommon.DoSysCall(sCommand)
	if error_code:
		OnExit("ERROR: CVS failed with error code: %d" % error_code)
	else:
		# echo the CVS output
		Print("Updating the following files:", 1)
		for line in outputlines:
			if (line[0] in ["U","P","M","C"]) or line.startswith("cvs server:"):
				Print(line[:-1], 1)
		Print("SUCCESS: the cvs.exe command terminated successfully")
	return None


###############################################################################
# MAIN


def Main():
	"""Main() performs the main work of this script.
	See doc string on this module for details.
	"""
	# figure out where we need to run an update
	SandboxDirectory = ProcessCommandLine()
	# run the update on that directory
	UpdateShadow(SandboxDirectory)
	# perform final cleanup
	AGCommon.CleanupPrintLog(MAXLOGSIZELINES)
	OnExit() # without failure message, this logs "script terminated successfully."
	return None


if __name__ == "__main__":
	# report through the CVS output that this script is being executed
	print "Running shadow directory script..."
	# We really need to have the log file open and writable to be able to run
	# the rest of the script and have any sanity for tracking what the script does
	# So if this fails, then we need to report a stdout error message and die
	if not AGCommon.InitPrintLog(LOGFILEPATH, 1):
		OnExit("ERROR: failed to initialize log.  Script cannot continue")
	Main()
---------- end contents of ShadowDirectory.py ------------------



More information about the cvsnt mailing list