# Part of the A-A-P recipe executive: CVS access # Copyright (C) 2002 Stichting NLnet Labs # Permission to copy and use this file is specified in the file COPYING. # If this file is missing you can find it here: http://www.a-a-p.org/COPYING # # Functions to get files out of a CVS repository and put them back. # See interface.txt for an explanation of what each function does. # import string import os import os.path from Error import * from Message import * from Util import * def cvs_command(server, url_dict, nodelist, action): """Handle CVS command "action". Return non-zero when it worked.""" # Since CVS doesn't do locking, quite a few commands can be simplified: if action == "refresh": action = "checkout" # "refresh" is exactly the same as "checkout" elif action in [ "checkin", "publish" ]: action = "commit" # "checkin" and "publish" are the same as "commit" elif action == "unlock": return 1 # unlocking is a no-op # TODO: Should group nodes in the same directory together and do them all # at once. res = 1 for node in nodelist: if cvs_command_node(server, url_dict, node, action) == 0: res = 0 return res def cvs_command_node(server, url_dict, node, action): """Handle CVS command for one node.""" if not server: # Obtain the previously used CVSROOT from CVS/Root. # There are several of these files that contain the same info, just use # the one in the current directory. try: f = open("CVS/Root") except StandardError, e: msg_warning(_('Cannot open for obtaining CVSROOT: "CVS/Root"') + str(e)) else: try: server = f.readline() f.close() except StandardError, e: msg_warning(_('Cannot read for obtaining CVSROOT: "CVS/Root"') + str(e)) server = '' # in case something was read else: if server[-1] == '\n': server = server[:-1] if server: serverarg = "-d" + server else: serverarg = '' msg_info(_('CVS %s for node "%s"') % (action, node.short_name())) # A "checkout" only works reliably when in the top directory of the # module. # "add" must be done in the current directory of the file. # Change to the directory where "path" + "node.name" is valid. # Use node.recipe_dir and take off one part for each part in "path". # Try to obtain the path from the CVS/Repository file. if os.path.isdir(node.absname): node_dir = node.absname else: node_dir = os.path.dirname(node.absname) if action == "checkout": cvspath = '' if url_dict.has_key("path"): # Use the specified "path" attribute. cvspath = url_dict["path"] dir_for_path = node.recipe_dir else: dir_for_path = node_dir fname = os.path.join(dir_for_path, "CVS/Repository") try: f = open(fname) except StandardError, e: msg_warning((_('Cannot open for obtaining path in module: "%s"') % fname) + str(e)) return 0 try: cvspath = f.readline() f.close() except StandardError, e: msg_warning((_('Cannot read for obtaining path in module: "%s"') % fname) + str(e)) return 0 if cvspath[-1] == '\n': cvspath = cvspath[:-1] dir = dir_for_path path = cvspath while path: if os.path.basename(dir) != os.path.basename(path): msg_warning(_('mismatch between path in cvs:// and tail of recipe directory: "%s" and "%s"') % (cvspath, dir_for_path)) ndir = os.path.dirname(dir) if ndir == dir: msg_error(_('path in cvs:// is longer than recipe directory: "%s" and "%s"') % (cvspath, dir_for_path)) dir = ndir npath = os.path.dirname(path) if npath == path: # just in case: avoid getting stuck break path = npath else: dir = node_dir cwd = os.getcwd() if cwd == dir: cwd = '' # we're already there, avoid a chdir() else: try: os.chdir(dir) except StandardError, e: msg_warning((_('Could not change to directory "%s"') % dir) + str(e)) return 0 # Use the specified "logentry" attribute or generate a message. if url_dict.has_key("logentry"): logentry = url_dict["logentry"] else: logentry = "Done by A-A-P" node_name = node.short_name() tmpname = '' if action == "remove" and os.path.exists(node_name): # CVS refuses to remove a file that still exists, temporarily rename # it. Careful: must always move it back when an exception is thrown! assert_aap_dir() tmpname = os.path.join("aap", node_name) try: os.rename(node_name, tmpname) except: tmpname = '' # TODO: quoting and escaping special characters try: res = exec_cvs_cmd(serverarg, action, logentry, node_name) # For a remove we must commit it now, otherwise the local file will be # deleted when doing it later. To be consistent, also do it for "add". if not res and action in [ "remove", "add" ]: res = exec_cvs_cmd(serverarg, "commit", logentry, node_name) finally: if tmpname: try: os.rename(tmpname, node_name) except StandardError, e: msg_error((_('Could not move file "%s" back to "%s"') % (tmpname, node_name)) + str(e)) if cwd: try: os.chdir(cwd) except StandardError, e: msg_error((_('Could not go back to directory "%s"') % cwd) + str(e)) if res != 0: return 0 # TODO: how to check if it really worked? return 1 def exec_cvs_cmd(serverarg, action, logentry, node_name): """Execute the CVS command for "action". Handle failure.""" if action == "commit": # If the file was never added to the repository we need to add it. # Since most files will exist in the repository, trying to commit and # handling the error is the best method. tmpfile = tempfname() try: cmd = ("cvs %s commit -m '%s' %s 2>&1 | tee %s" % (serverarg, logentry, node_name, tmpfile)) msg_system(cmd) res = os.system(cmd) except: res = 1 # Read the output of the command, also when it failed. text = '' try: f = open(tmpfile) text = f.read() f.close() except StandardError, e: msg_warning(_('Reading output of "cvs commit" failed: ') + str(e)) res = 1 if text: msg_log(text, msgt_result) # If the file was never in the repository CVS says "nothing known # about". If it was there before "use `cvs add' to create an # entry". if not res and (string.find(text, "nothing known about") >= 0 or string.find(text, "cvs add") >= 0): res = 1 # always remove the tempfile, even when system() failed. try: os.remove(tmpfile) except: pass if not res: return 0 try: msg_info(_("File does not appear to exist in repository, adding it")) logged_system("cvs %s add %s" % (serverarg, node_name)) except StandardError, e: msg_warning(_('Adding file failed: ') + str(e)) if action == "commit": return logged_system("cvs %s commit -m '%s' %s" % (serverarg, logentry, node_name)) return logged_system("cvs %s %s %s" % (serverarg, action, node_name)) def cvs_list(name, commit_item, dirname, recursive): """Obtain a list of items in CVS for directory "dirname". Recursively entry directories if "recursive" is non-zero. "name" is not used, we don't access the server.""" # We could use "cvs status" to obtain the actual entries in the repository, # but that is slow and the output is verbose and hard to parse. # Instead read the "CVS/Entries" file. A disadvantage is that we might # list a file that is actually already removed from the repository if # another user removed it. fname = os.path.join(dirname, "CVS/Entries") try: f = open(fname) except StandardError, e: raise UserError, (_('Cannot open "%s": ') % fname) + str(e) try: lines = f.readlines() f.close() except StandardError, e: raise UserError, (_('Cannot read "%s": ') % fname) + str(e) # The format of the lines is: # D/dirname//// # /itemname/vers/foo// # We only need to extract "dirname" or "itemname". res = [] for line in lines: s = string.find(line, "/") if s < 0: continue s = s + 1 e = string.find(line, "/", s) if e < 0: continue item = os.path.join(dirname, line[s:e]) if line[0] == 'D' and recursive: res.extend(cvs_list(name, commit_item, item, 1)) else: res.append(item) return res # vim: set sw=4 sts=4 tw=79 fo+=l: .