# 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: