1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453
|
# Part of the A-A-P recipe executive: CVS access
# Copyright (C) 2002-2003 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(recdict, 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 == "fetch":
action = "checkout" # "fetch" 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 [] # unlocking is a no-op
# All CVS commands require an argument to specify where the server is.
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_extra(recdict,
_('Cannot open for obtaining CVSROOT: "CVS/Root"') + str(e))
else:
try:
server = f.readline()
f.close()
except StandardError, e:
msg_warning(recdict,
_('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 = ''
#
# Loop over the list of nodes and handle each separately. This is required
# to be able to do something useful with an error message.
# For a "tag" command all nodes with the same tag are done at once to speed
# it up.
#
failed = []
if action == "tag":
# Loop over todolist, taking out nodes with identical tags, until it's
# empty.
todolist = nodelist[:]
while todolist:
tag = ''
thislist = []
for node in todolist[:]:
# Use the specified "tag" attribute for tagging.
if not node.attributes.has_key("tag"):
msg_error(recdict, _('tag attribute missing for "%s"')
% node.short_name())
failed.extend(todolist)
failed.extend(thislist)
return failed
if not tag or node.attributes["tag"] == tag:
thislist.append(node)
todolist.remove(node)
tag = node.attributes["tag"]
f = cvs_tag(recdict, serverarg, tag, thislist)
if f:
failed.extend(f)
else:
for node in nodelist:
if not cvs_command_node(recdict, serverarg, url_dict, node, action):
failed.append(node)
return failed
def cvs_prepare(recdict):
"""
Prepare for using the cvs command. Returns the program name.
"""
# Install cvs when needed.
from DoInstall import assert_pkg
assert_pkg([], recdict, "cvs")
# Create ~/.cvspass if it doesn't exist. An empty file should be
# sufficient for anonymous logins.
if os.environ.get("HOME"):
fn = os.path.expanduser("~/.cvspass")
if not os.path.exists(fn):
from Commands import touch_file
touch_file(fn, 0644)
n = get_var_val(0, recdict, "_no", "CVSCMD")
if n:
return n
# Use $CVS if defined, otherwise use "cvs".
return get_progname(recdict, "CVS", "cvs", "")
def cvs_tag(recdict, serverarg, tag, nodelist):
"""Handle CVS tag command for a list of nodes.
Return list of nodes that failed."""
msg_info(recdict, _('CVS tag for nodes %s')
% str(map(lambda x: x.short_name(), nodelist)))
# Prepare for using CVS and get the command name.
cvscmd = cvs_prepare(recdict)
names = ''
for node in nodelist:
names = names + '"' + node.short_name() + '" '
# TODO: check which of the nodes actually failed
if logged_system(recdict,
'"%s" %s tag "%s" %s' % (cvscmd, serverarg, tag, names)) == 0:
return []
return nodelist
def cvs_get_repository(recdict, dir):
"""Get the first line of the CVS/Repository file in directory "dir"."""
cvspath = ''
fname = os.path.join(dir, "CVS/Repository")
try:
f = open(fname)
except StandardError, e:
# Only give this error when the directory exists, when it doesn't it's
# probably the first time the files are checked out.
if os.path.exists(os.path.join(dir, "CVS")):
msg_warning(recdict,
(_('Cannot open for obtaining path in module: "%s"')
% fname) + str(e))
else:
try:
cvspath = f.readline()
f.close()
except StandardError, e:
msg_warning(recdict,
(_('Cannot read for obtaining path in module: "%s"')
% fname) + str(e))
else:
if cvspath[-1] == '\n':
cvspath = cvspath[:-1]
return cvspath
def cvs_command_node(recdict, serverarg, url_dict, node, action):
"""Handle CVS command "action" for one node.
Return non-zero when it worked."""
msg_info(recdict, _('CVS %s for node "%s"') % (action, node.short_name()))
# Count the number of directories in the node name.
n = node.short_name()
dirlevels = 0
while n:
prev = n
n = os.path.dirname(n)
if n == prev:
break
dirlevels = dirlevels + 1
# 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.
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:
# Try to obtain the path from the CVS/Repository file.
if os.path.isdir(os.path.join(node.absname, "CVS")):
dir_for_path = node.absname
else:
dir_for_path = os.path.dirname(node.absname)
cvspath = cvs_get_repository(recdict, dir_for_path)
# Use node.recipe_dir and take off one part for each part in "path".
dir = fname_fold(dir_for_path)
path = fname_fold(cvspath)
while path:
if os.path.basename(dir) != os.path.basename(path):
# This might happen when a module has an alias.
msg_note(recdict, _('mismatch between path in cvs:// and tail of recipe directory: "%s" and "%s"') % (cvspath, dir_for_path))
break
ndir = os.path.dirname(dir)
if ndir == dir:
# This is probably an error somewhere...
msg_error(recdict, _('path in cvs:// is longer than recipe directory: "%s" and "%s"') % (cvspath, dir_for_path))
break
dir = ndir
npath = os.path.dirname(path)
if npath == path: # just in case: avoid getting stuck
break
path = npath
if not path:
break
# Check that the CVS repository mentioned here is correct. Bail
# out here when it isn't, happens when a full path was used in
# CVS/Repository (CVS sometimes does that for unknown reasons).
# Also happens when a module "foo" is an alias for "bar/foo".
# CVS/Repository then contains "bar/foo" but we must checkout
# "foo", because the "bar" directory doesn't exist.
p = cvs_get_repository(recdict, dir)
if not p:
break
# Extra check for wrong assumptions about CVS/Repository contents.
if fname_fold(os.path.basename(p)) != os.path.basename(path):
msg_note(recdict, _('mismatch between contents of CVS/Repository at different levels: "%s" and "%s"') % (dir, path))
break
else:
dir = os.path.dirname(node.absname)
# Use the specified "logentry" attribute for a log message.
# Only used for "commit" (also for add and remove).
if url_dict.has_key("logentry"):
logentry = url_dict["logentry"]
elif node.attributes.has_key("logentry"):
logentry = node.attributes["logentry"]
else:
logentry = get_var_val_int(recdict, "LOGENTRY")
# Changing directory, don't return until going back!
cwd = os.getcwd()
if fname_equal(cwd, dir):
cwd = '' # we're already there, avoid a chdir()
else:
try:
os.chdir(dir)
except StandardError, e:
msg_warning(recdict,
(_('Could not change to directory "%s"') % dir) + str(e))
return 0
msg_log(recdict, 'Cvs command in "%s"' % dir)
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(recdict)
tmpname = in_aap_dir(node_name)
try:
os.rename(node_name, tmpname)
except:
tmpname = ''
try:
# If the node has a "binary" attribute, give CVS the "-kb" argument for
# an "add" action (also for commit with auto-add).
if node.attributes.get("binary"):
addbinarg = "-kb"
else:
addbinarg = ""
ok = exec_cvs_cmd(recdict, serverarg, action, addbinarg,
logentry, node_name, dirlevels = dirlevels)
# 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 ok and action in [ "remove", "add" ]:
ok = exec_cvs_cmd(recdict, serverarg, "commit", "", logentry,
node_name, dirlevels = dirlevels, auto_add = 0)
finally:
if tmpname:
try:
os.rename(tmpname, node_name)
except StandardError, e:
msg_error(recdict, (_('Could not move file "%s" back to "%s"')
% (tmpname, node_name)) + str(e))
if cwd:
try:
os.chdir(cwd)
except StandardError, e:
msg_error(recdict, (_('Could not go back to directory "%s"')
% cwd) + str(e))
# TODO: how to check if it really worked?
return ok
def exec_cvs_cmd(recdict, serverarg, action, addbinarg, logentry, node_name,
dirlevels = 1, auto_add = 1):
"""Execute the CVS command for "action". Handle failure.
For "commit" may create directories up to "dirlevels" upwards.
When "auto_add" is non-zero and committing fails, try to add the file
first.
Return non-zero when it worked."""
# Prepare for using CVS and get the command name.
cvscmd = cvs_prepare(recdict)
if logentry:
logarg = '-m "%s"' % logentry
else:
logarg = ''
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.
# Repeat this when the directory needs to be added.
did_add_dir = 0
while 1:
# TODO: escaping special characters
cmd = ('"%s" %s commit %s "%s"'
% (cvscmd, serverarg, logarg, node_name))
ok, text = redir_system_int(recdict, cmd)
if text:
msg_log(recdict, text)
# If the directory for the file doesn't exist, CVS says "there
# is no version here". Need to create the directory first.
# This is only done for the number of levels that were included
# in the node name for the original directory.
# Errors are ignored, the commit will fail later.
if ((string.find(text, "no version here") >= 0
or string.find(text, "not open CVS/Entries") >= 0)
and not did_add_dir
and auto_add):
msg_info(recdict, _("Directory does not appear to exist in repository, adding it"))
commit_dir(recdict, node_name, serverarg, dirlevels, cvscmd)
did_add_dir = 1
continue
# 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 ok and (string.find(text, "nothing known about") >= 0
or string.find(text, "cvs add") >= 0):
ok = 0
break
if ok or not auto_add:
return ok
try:
msg_info(recdict,
_("File does not appear to exist in repository, adding it"))
logged_system(recdict, '"%s" %s add %s %s'
% (cvscmd, serverarg, addbinarg, node_name))
except StandardError, e:
msg_warning(recdict, _('Adding file failed: ') + str(e))
# TODO: escaping special characters
return logged_system(recdict, '"%s" %s commit %s "%s"'
% (cvscmd, serverarg, logarg, node_name)) == 0
# TODO: escaping special characters
if action != "add":
addbinarg = ""
return logged_system(recdict, '"%s" %s %s %s "%s"'
% (cvscmd, serverarg, action, addbinarg, node_name)) == 0
def commit_dir(recdict, node_name, serverarg, dirlevels, cvscmd):
"""Commit to create the current directory. If its parent is not in CVS
either go up further."""
cwd = os.getcwd()
try:
os.chdir("..")
dirname = os.path.dirname(node_name)
if not dirname:
dirname = os.path.basename(cwd)
if dirlevels > 0 and not os.path.isdir("CVS"):
commit_dir(recdict, dirname, serverarg, dirlevels - 1, cvscmd)
logged_system(recdict, '"%s" %s add "%s"'
% (cvscmd, serverarg, dirname))
except:
pass
os.chdir(cwd)
def cvs_list(recdict, 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:
msg_error(recdict, (_('Cannot open "%s": ') % fname) + str(e))
return []
try:
lines = f.readlines()
f.close()
except StandardError, e:
msg_error(recdict, (_('Cannot read "%s": ') % fname) + str(e))
return []
# 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(recdict, name, commit_item, item, 1))
else:
res.append(item)
return res
# vim: set sw=4 et sts=4 tw=79 fo+=l:
|