#! /usr/bin/python # -*- Python -*- # Copyright (c) 2002, 2003 Barry Warsaw, Fred Drake, and contributors # Copyright (c) 2005 Behdad Esfahbod # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the names of the authors nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Automatic checkout This script is used to keep an up-to-date checkout of your CVS repository. This is useful for example when you have your website in CVS. This script is run from a CVS loginfo file (see $CVSROOT/CVSROOT/loginfo). To set this up, create a loginfo entry that looks something like this: mymodule /path/to/this/script path-to-checkout-root %%{sVv} %%{p} This version of the script supports loginfo formats of CVS versions both before and after 1.12. It doesn't support the import operation, but adding directories will trigger automatic check out. Usage: %(PROGRAM)s path-to-checkout-root %%{s} Where options is: --cvsroot= Use as the environment variable CVSROOT. Otherwise this variable must exist in the environment. --help -h Print this text. %%{sVv} %%p CVS loginfo expansion. When invoked by CVS, these will be the the directory the checkin is being made in, relative to $CVSROOT, and the list of files that are changing. The real expansion is a bit different depending the version of CVS server you are running. """ import os import os.path import sys import string import time import getopt PROGRAM = sys.argv[0] CVSCMD = 'cvs -Q 2>&1' def usage(code, msg=''): print __doc__ % globals() if msg: print msg sys.exit(code) def run_cvs(dir, args): # cannot wait for child process or that will cause parent to retain cvs # lock for too long. Urg! if not os.fork(): # in the child # give up the lock you cvs thang! time.sleep(5) os.chdir (dir) cvscmd = CVSCMD + ' "' + '" "'.join(args) + '"' os.system (cvscmd) sys.exit(0) # nobody checks the return status # scan args for options def main(): # What follows -- is the specification containing the files that were # modified. The first component containing the directory the checkin # is being made in, relative to $CVSROOT, followed by the list of files # that are changing. args = sys.argv[1:] try: opts, args = getopt.getopt(args, 'h', ['cvsroot=']) except getopt.error, msg: usage(1, msg) # parse the options for opt, arg in opts: if opt in ('-h', '--help'): usage(0) elif opt == '--cvsroot': os.environ['CVSROOT'] = arg checkoutdir, specs = args[0], args[1:] if len(specs) <= 2: # Old syntax (before CVS 1.12) # In this syntax, the first parameter contains an string of form: # module file1 file2 ... if len(specs) > 1: garbage = specs[1] else: garbage = '' specs = specs[0] specs += ' ' index = specs.index(' ') module, specs = specs[:index], specs[index+1:] # Sanity check, are we right? if garbage not in ['', '%p', '%1p', module]: print "Parameters after `--' smell bad. I assumed old style loginfo parameters," print "but seems like I was wrong. Not sure what I'm doing anymore..." # Parse file1,oldrev,newrev file2,oldrev,newrev into filespecs now import re pat = '(.*?),([^, ]*?),([^, ]*?) ' filespecs = re.compile(pat).findall(specs) else: # New syntax (after CVS 1.12) # In this syntax, parameters are of form: # file1 oldrev newrev file2 oldrev newrev ... module filespecs = [] while len(specs) >= 3: filespecs.append(specs[:3]) specs[:3] = [] module = string.join(specs) print "Updating the checkout in %s" % checkoutdir # now module contains the path of the updated directory relative to # CVSROOT. checkoutdir = checkoutdir.rstrip ("/") if module == ".": module = "" # We need to know which module is it checked out in checkoutdir, call it # rel try: repofile = file ("%s/CVS/Repository" % checkoutdir) rel = (repofile.readlines())[0].rstrip() if rel == ".": rel = "" repofile.close() except IOError: print "error: is %s really a CVS checkout?" % checkoutdir print "error: make a manual checkout first" sys.exit(1) # err if module doesn't start with rel if module[0:len(rel)] != rel: print "error: checked out version too specific in: %s" % checkoutdir print "error: this probably is a bug in your loginfo file, ignoring" sys.exit(0) relmod = module[len(rel):] if len (filespecs) == 0: # probably new directory added # nothing to do really sys.exit (1); # what to update in the target directory? all, or only updated files? # if we update all, then we should not pass -d, but -l. but then # need to switch to -d if the directory does not exist (see next comments) #toupdate = ['.'] toupdate = [filename for filename, oldrev, newrev in filespecs] # if the updated directory has been empty before, it doesn't exists # in the checkout. in that case we go up until we find a directory that # exists, and update the child from there. while (relmod and not os.path.isdir ("%s/%s" % (checkoutdir, relmod))): toupdate = [os.path.basename (relmod)] relmod = os.path.dirname (relmod).rstrip ('/') module = os.path.dirname (module).rstrip ('/') # make sure we have a checked out version of the target directory try: targetdir = "%s/%s" % (checkoutdir, relmod) repofile = file ("%s/CVS/Repository" % targetdir) targetmodule = (repofile.readlines())[0].rstrip() if targetmodule == ".": targetmodule = "" repofile.close() except IOError: print "error: is %s really a CVS checkout?" % targetdir print "error: make a manual checkout first" sys.exit(1) if targetmodule != module: print "error: is %s really a CVS checkout of %s?" % \ (targetdir, module) print "error: something's wrong, perhaps the loginfo syntax error" sys.exit(1) # run it finally args = ["update", "-d"] args.extend (toupdate) run_cvs (targetdir, args) if __name__ == '__main__': main() sys.exit(0)