#!/usr/bin/python2.3 -ut
# Copyright 2003-2005 Iustin Pop
#
# This file is part of cfvers.
#
# cfvers is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# cfvers is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with cfvers; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# $Id: cfv 219 2005-10-30 10:13:57Z iusty $
import tempfile
import sets
import re
import fnmatch
from mx import DateTime
import cfvers
import cfvers.cmd
from cfvers.main import Result, Item, Entry, Revision
from optparse import OptionParser, OptionGroup
import os, os.path
import sys
import errno
import types
import time
import getpass
try:
import readline
except ImportError:
pass
# Return codes for store, add commands
RET_INVALID = 1 << 0
RET_STORED = 1 << 1
RET_NOTCHANGED = 1 << 2
RET_ERRORS = 1 << 3
RET_DELETED = 1 << 4
class CLI(object):
def __init__(self):
commands = {
'find': (self.cmd_findentries, "", 'Finds entries by their properties'),
'log': (self.cmd_log, "[files...]", 'Show changelog of items'),
'store': (self.cmd_store, "[files...]", 'Store new versions of items'),
'retrieve': (self.cmd_retrieve, "[files...]", 'Retrieves items from the repository'),
'cat': (self.cmd_cat, "<filename>", 'Shows the contents of an item'),
'diff': (self.cmd_diff, "[files...]", 'Shows differences between versions'),
'stat': (self.cmd_stat, "[files...]", 'Show metadata of an item'),
'export': (self.cmd_export, "", 'Exports the repository to an external format'),
'add': (self.cmd_add, "<file>...", 'Adds new items to the repository'),
'addnew': (self.cmd_addnew, "", 'Adds new items in the tracke directories'),
'list': (self.cmd_list, "", 'List the items registered in the repository'),
'register': (self.cmd_register, "<name> <command>", "Register a virtual item in the repository")
}
self.ts_begin = time.time()
self.curr_cmd = "<command>"
self.usage="usage: %prog [global options] command [command options and arguments]\nwhere command is one of\n"
l = commands.keys()
l.sort()
for i in l:
self.usage += " %-8s %-16s %s\n" % (i, commands[i][1], commands[i][2])
self.usage += "\nGlobal options:"
self.commands = commands
return
def get_scp(self, earg, find=False):
"""Returns a sub-command parser"""
op = OptionParser(version=cfvers.cmd.CLIScript.get_version(),
usage="%%prog [global options] %s [options] " \
"%s\nFor global options, see %%prog --help" \
% (self.curr_cmd, earg))
if find:
ogg = OptionGroup(op, "Global filters", description="global selection options")
# Global toggles
ogg.add_option("--all-areas", dest="allareas",
help="search on all areas",
default=False, action="store_true",
)
ogg.add_option("--all-entries", dest="allentries",
help="return all matching entries for a file, not only latest",
default=False, action="store_true",
)
ogr = OptionGroup(op, "Revision filters", description="revision selection options")
# Revision options
ogr.add_option("--logmsg", dest="find_logmsg",
help="revision log message matches regexp PATTERN",
default=None, metavar="PATTERN")
ogr.add_option("--revno", dest="find_revno", action="append",
help="revision number is OP N", metavar="OP N",
default=None, nargs=2)
oge = OptionGroup(op, "Entry filters", description="entry selection options")
# Entry options
oge.add_option("--empty", dest="find_empty",
help="file is empty and is either a regular file or a directory",
default=False, action="store_true")
oge.add_option("--gid", dest="find_gid", action="append",
help="file's numeric group ID is OP n", nargs=2,
default=None, type="string", metavar="OP N")
oge.add_option("--group", dest="find_group", action="append",
help="file's group name is OP gname", nargs=2,
default=None, type="string", metavar="OP gname")
oge.add_option("--uid", dest="find_uid", action="append",
help="file's numeric user ID is OP n", nargs=2,
default=None, type="string", metavar="OP N")
oge.add_option("--user", dest="find_user", action="append",
help="file's user name is OP uname", nargs=2,
default=None, type="string", metavar="OP uname")
oge.add_option("--regex", dest="find_regex", action="append",
help="file name matches regular expression PATTERN; this is a match on the whole path, not a search",
default=None, type="string", metavar="PATTERN")
oge.add_option("--iregex", dest="find_iregex", action="append",
help="like -regex, but the match is case insensitive",
default=None, type="string", metavar="PATTERN")
oge.add_option("--size", dest="find_size", action="append",
help="file's size is OP n", nargs=2,
default=None, type="string", metavar="OP N")
oge.add_option("--type", dest="find_type", action="append",
help="file's type is/is not n", nargs=2,
default=None, type="string", metavar="OP N")
oge.add_option("--nouser", dest="find_nouser",
help="no user corresponded to file's numeric user ID at store time",
default=False, action="store_true")
oge.add_option("--nogroup", dest="find_nogroup",
help="no group corresponded to file's numeric group ID at store time",
default=False, action="store_true")
oge.add_option("--name", dest="find_name", action="append",
help="base of file name (the path with the leading directories removed) matches shell pattern GLOB",
default=None, type="string", metavar="GLOB")
oge.add_option("--iname", dest="find_iname", action="append",
help="like -name, but the match is case insensitive",
default=None, type="string", metavar="GLOB")
oge.add_option("--path", dest="find_path", action="append",
help="file name matches shell pattern GLOB",
default=None, type="string", metavar="GLOB")
oge.add_option("--ipath", dest="find_ipath", action="append",
help="like -path, but the match is case insensitive",
default=None, type="string", metavar="GLOB")
oge.add_option("--lname", dest="find_lname", action="append",
help="file is a symbolic link whose contents match shell pattern GLOB",
default=None, type="string", metavar="GLOB")
oge.add_option("--ilname", dest="find_ilname", action="append",
help="like -lname, but the match is case insensitive",
default=None, type="string", metavar="GLOB")
oge.add_option("--links", dest="find_links", action="append",
help="file has OP n links", nargs=2,
default=None, type="string", metavar="OP n")
oge.add_option("--inum", dest="find_inum", action="append",
help="file has inode number OP n", nargs=2,
default=None, type="string", metavar="OP n")
oge.add_option("--perm", dest="find_perm", action="append",
help="file's permission bits check", nargs=2,
default=None, type="string", metavar="OP n")
op.add_option_group(ogg)
op.add_option_group(ogr)
op.add_option_group(oge)
return op
def prompt_func(self, key):
prompt = "Please enter the %s: " % key
if "password" in key:
value = getpass.getpass(prompt)
else:
value = raw_input(prompt)
return value
def main(self, argv):
op = OptionParser(version=cfvers.cmd.CLIScript.get_version(),
usage=self.usage)
op.disable_interspersed_args()
op.add_option("-a", "--area", dest="area",
help="the area to work on",
type="string", default=None,
metavar="AREA")
op.add_option("--local", dest="server_type",
action="store_const", const="local",
help="connect locally (not through the server)")
op.add_option("--remote", dest="server_type",
action="store_const", const="remote",
help="connect through the server")
ogl = OptionGroup(op, "Local options")
ogl.add_option("--rtype", dest="repo_meth",
type="choice", choices=('postgresql', 'sqlite'),
help="repository type")
ogl.add_option("--rdata", dest="repo_data",
type="string",
help="repository connect information")
op.add_option_group(ogl)
ogr = OptionGroup(op, "Remote options")
ogr.add_option("-s", "--server", dest="host",
help="the host to connect to",
type="string", default=None,
metavar="HOSTNAME")
ogr.add_option("-p", "--port", dest="port",
help="the port on the server",
type="int", default=None,
metavar="PORT")
ogr.add_option("-u", "--username", dest="username",
help="the username for authentication to the server",
type="string", default=None,
metavar="USERNAME")
op.add_option_group(ogr)
(options, args) = op.parse_args(argv)
if len(args) == 0:
op.print_help()
return
command = args.pop(0)
if not command in self.commands:
cfound = []
for fcom in self.commands:
if fcom.startswith(command):
cfound.append(fcom)
if len(cfound) > 1:
print >> sys.stderr, "Ambigous command '%s' (%s)" % (command, ",".join(cfound))
op.print_help()
return
elif len(cfound) == 0:
print >>sys.stderr, "Unknown command %s" % command
op.print_help()
return
command = cfound[0]
try:
self.cmdi = cfvers.cmd.Commands(options, self.prompt_func)
except cfvers.ConfigException, e:
print >>sys.stderr, "Error: your configuration is invalid.\nError message: %s." % e
return
except cfvers.RepositoryException, e:
print >>sys.stderr, "Error: repository opening failed.\nError message: %s." % e
return
except cfvers.CommException, e:
print >>sys.stderr, "Error: communication error.\nError message: %s" % (" ".join(e.args),)
return
except KeyboardInterrupt:
print "Abort."
return
except Exception, e:
print >>sys.stderr,"Error while initializing: %s" % e
raise
return
self.curr_cmd = command
try:
func = self.commands[command][0]
retcode = func(options, args)
finally:
self.cmdi.close()
return retcode
def cmd_list(self, options, args):
"""Lists the items in the repository"""
op = self.get_scp("")
op.add_option("--name", dest="name",
help="name matches shell-glob NAME",
metavar="NAME", type="string",
default=None)
op.add_option("--regexp", dest="regexp",
help="name matches regular expression REGEXP (not anchored)",
metavar="REGEXP", type="string",
default=None)
dformat = "%(id)s %(flag)c %(name)s %(command)s"
op.add_option("-F", "--format", dest="format",
help='the listing format, please see the manual; ' \
'default is "%s"' % dformat,
default=dformat,
metavar="FORMAT")
(cmdopts, cmdargs) = op.parse_args(args)
if cmdopts.regexp is not None:
cmdopts.regexp = re.compile(cmdopts.regexp)
for i in self.cmdi.portal.getItems(self.cmdi.area.name):
if cmdopts.regexp is not None and \
not cmdopts.regexp.search(i.name):
continue
if cmdopts.name is not None and \
not fnmatch.fnmatch(i.name, cmdopts.name):
continue
mydict = {
'name': "%s" % i.name,
'id': "%6d" % i.id,
'ctime': i.ctime,
'command': "",
}
if i.command is not None:
mydict['command'] = '=`%s`' % i.command
if i.flags & Item.STORE_VIRTUAL:
mydict['flag'] = 'V'
elif i.flags & Item.STORE_CONTENTS:
mydict['flag'] = 'F'
elif i.flags & Item.STORE_CHECKSUM:
mydict['flag'] = 'C'
elif i.flags & Item.STORE_METADATA:
mydict['flag'] = 'M'
elif i.flags > 0:
mydict['flag'] = '?'
else:
mydict['flag'] = 'N'
try:
print cmdopts.format % mydict
except (KeyError, TypeError, ValueError), e:
print >>sys.stderr, "Invalid format. Error details: %s" % e
break
return
def cmd_findentries(self, options, args):
"""Searches in the repository"""
op = self.get_scp("[files...]", find=True)
op.add_option("-d", "--detail", dest="detail",
help="shows detailed information",
default=None, action="store_true",
)
op.add_option("-l", "--long", dest="long",
help="shows 'ls -ld' like details",
default=None, action="store_true",
)
(cmdopts, cmdargs) = op.parse_args(args)
cmdopts.do_payload = False
cmdopts.match_and = True
cmdopts.area = self.cmdi.area.name
try:
entries = self.cmdi.portal.getEntries(cmdopts)
except cfvers.main.ParsingException, e:
print "Invalid search options: error `%s'" % str(e)
return
for e in entries:
if cmdopts.detail:
#print "-" * 25
print "File %s" % e.filename
i = self.cmdi.portal.getItemByID(e.item)
print "Registered at: %s" % (i.ctime.localtime().strftime("%F %T"))
revs = self.cmdi.portal.getRevNumbers(i.id)
rlist = Revision.format_crevnos(Revision.collapse_revnos(revs))
print "Available revisions: %s" % rlist
print
elif cmdopts.long:
if e.mtime is not None:
m = DateTime.TimestampFromTicks(e.mtime).strftime("%Y-%m-%d %T")
else:
m = 'N/A'
print "%s %5d %-8s %-8s %8d %s %s" % \
(e.mode2str(), e.revno,
e.uname or e.uid, e.gname or e.gid,
e.size,
m, e.filename)
else:
print e.filename
return
def cmd_add(self, options, args):
"""Register some items in the repository"""
op = self.get_scp("<file>...")
op.add_option("-c", "--commiter", dest="commiter",
help="commiter information",
type="string", default=None)
op.add_option("-m", "--logmsg", dest="logmsg",
help="log message",
type="string", default=None,
metavar="MESSAGE")
op.add_option("-N", "--no-recurse", dest="norecurse",
help="disable recursion on directories",
action="store_true", default=False)
op.add_option("-q", "--quiet", dest="quiet",
help="only display errors",
action="store_true", default=False)
op.add_option("-v", "--verbose", dest="verbose",
help="display additional information",
action="store_true", default=False)
op.add_option("-i", "--inputfile", dest="inputfile",
help="File which contains additional items to store",
default=None)
op.add_option("--store", dest="storage",
choices=('none', 'metadata', 'checksum', 'full'),
help="Level of information stored: none (to " \
"prevent an item from being stored at all), " \
"metadata (only metadata), checksum (metadata "\
"and checksum), full (including contents)",
default="full")
(cmdoptions, cmdargs) = op.parse_args(args)
if cmdoptions.storage == "none":
cmdoptions.flags = 0
elif cmdoptions.storage == "metadata":
cmdoptions.flags = Item.STORE_METADATA
elif cmdoptions.storage == "checksum":
cmdoptions.flags = Item.STORE_METADATA | Item.STORE_CHECKSUM
elif cmdoptions.storage == "full":
cmdoptions.flags = Item.STORE_METADATA | \
Item.STORE_CHECKSUM | \
Item.STORE_CONTENTS
else:
raise ValueError("Invalid value for storage type")
if cmdoptions.logmsg is None:
if cmdoptions.inputfile == "-":
print >> sys.stderr, "Log message not specified and input list was specified on stdin!"
print >> sys.stderr, "Please specify log message using -m"
print >> sys.stderr, "Commit aborted."
return RET_INVALID
elif sys.stdin.isatty() and sys.stdout.isatty():
nt = self.get_text("")
if len(nt) == 0:
print >> sys.stderr, "Commit aborted."
return RET_INVALID
cmdoptions.logmsg = nt
else:
print >> sys.stderr, "Log message not specified and stdin/stout are not ttys."
print >> sys.stderr, "Commit aborted."
return RET_INVALID
if cmdoptions.inputfile is not None:
try:
if cmdoptions.inputfile == "-":
f = sys.stdin
else:
f = file(cmdoptions.inputfile)
cmdargs += map(lambda line: line.rstrip('\n'), f.readlines())
f.close()
except EnvironmentError, e:
print >> sys.stderr, "Can't read input file: %s" % e
return RET_INVALID
if len(cmdargs) == 0:
print >> sys.stderr, "No items given."
print >> sys.stderr, "Commit aborted."
return RET_INVALID
errors, store_done, newrev = self.cmdi.add(files=cmdargs, options=cmdoptions)
self.ts_end = time.time()
totals = {}
totals[Result.ADDED_OK] = 0
totals[Result.ADDED_EXISTING] = 0
totals[Result.STORED_IOERROR] = 0
totals[Result.ADDED_INVALIDNAME] = 0
for res in errors:
totals[res.code] += 1
if not cmdoptions.quiet:
if store_done:
print "Status: Added, revision %d" % newrev
else:
print "Status: Commit not done"
print "Time begin: %s" % time.strftime("%Y-%m-%d %T %Z", time.localtime(self.ts_begin))
print "Time end: %s" % time.strftime("%Y-%m-%d %T %Z", time.localtime(self.ts_end))
l = totals.keys(); l.sort()
for code in l:
print "Total %s: %d" % (Result._codeinfo[code].lower(), totals[code])
for res in errors:
if res.critical:
print >> sys.stderr, str(res)
else:
if cmdoptions.verbose:
print >> sys.stdout, str(res)
retcode = 0
if totals[Result.ADDED_INVALIDNAME] > 0:
retcode |= RET_INVALID
if totals[Result.ADDED_EXISTING] > 0:
retcode |= RET_NOTCHANGED
if totals[Result.ADDED_OK] > 0:
retcode |= RET_STORED
return retcode
def cmd_addnew(self, options, args):
"""Register new items in the directories existing in the repository"""
op = self.get_scp("")
op.add_option("-c", "--commiter", dest="commiter",
help="commiter information",
type="string", default=None)
op.add_option("-m", "--logmsg", dest="logmsg",
help="log message",
type="string", default=None,
metavar="MESSAGE")
op.add_option("-N", "--no-recurse", dest="norecurse",
help="disable recursion on directories",
action="store_true", default=False)
op.add_option("-q", "--quiet", dest="quiet",
help="only display errors",
action="store_true", default=False)
op.add_option("-v", "--verbose", dest="verbose",
help="display additional information",
action="store_true", default=False)
(cmdoptions, cmdargs) = op.parse_args(args)
if cmdoptions.logmsg is None:
if cmdoptions.inputfile == "-":
print >> sys.stderr, "Log message not specified and input list was specified on stdin!"
print >> sys.stderr, "Please specify log message using -m"
print >> sys.stderr, "Commit aborted."
return RET_INVALID
elif sys.stdin.isatty() and sys.stdout.isatty():
nt = self.get_text("")
if len(nt) == 0:
print >> sys.stderr, "Commit aborted."
return RET_INVALID
cmdoptions.logmsg = nt
else:
print >> sys.stderr, "Log message not specified and stdin/stout are not ttys."
print >> sys.stderr, "Commit aborted."
return RET_INVALID
errors, store_done, newrev = self.cmdi.addfromdirs(options=cmdoptions)
self.ts_end = time.time()
totals = {}
totals[Result.ADDED_OK] = 0
totals[Result.STORED_IOERROR] = 0
totals[Result.ADDED_EACCES] = 0
for res in errors:
totals[res.code] += 1
if not cmdoptions.quiet:
if store_done:
print "Status: Added, revision %d" % newrev
else:
print "Status: Commit not done"
print "Time begin: %s" % time.strftime("%Y-%m-%d %T %Z", time.localtime(self.ts_begin))
print "Time end: %s" % time.strftime("%Y-%m-%d %T %Z", time.localtime(self.ts_end))
l = totals.keys(); l.sort()
for code in l:
print "Total %s: %d" % (Result._codeinfo[code].lower(), totals[code])
for res in errors:
if res.critical:
print >> sys.stderr, str(res)
else:
if cmdoptions.verbose:
print >> sys.stdout, str(res)
retcode = 0
if totals[Result.ADDED_OK] > 0:
retcode |= RET_STORED
return retcode
def cmd_register(self, options, args):
"""Register a virtual item in the repository"""
op = self.get_scp("<name> <command line>...")
op.add_option("-c", "--commiter", dest="commiter",
help="commiter information",
type="string", default=None)
op.add_option("-m", "--logmsg", dest="logmsg",
help="log message",
type="string", default=None,
metavar="MESSAGE")
op.add_option("-q", "--quiet", dest="quiet",
help="only display errors",
action="store_true", default=False)
(cmdoptions, cmdargs) = op.parse_args(args)
if cmdoptions.logmsg is None:
if sys.stdin.isatty() and sys.stdout.isatty():
nt = self.get_text("")
if len(nt) == 0:
print >> sys.stderr, "Commit aborted."
return RET_INVALID
cmdoptions.logmsg = nt
else:
print >> sys.stderr, "Log message not specified and stdin/stout are not ttys."
print >> sys.stderr, "Commit aborted."
return RET_INVALID
if len(cmdargs) < 2:
print >> sys.stderr, "No name and command line given."
print >> sys.stderr, "Commit aborted."
return RET_INVALID
name = cmdargs.pop(0)
if not name.startswith("/"):
print >> sys.stderr, "Error: name must be an absolute path (i.e. it must begin with /)"
return RET_INVALID
result, store_done, newrev = self.cmdi.register(name, cmdargs, cmdoptions)
if result.code == Result.ADDED_OK:
msg = "Item registered"
retcode = RET_STORED
elif result.code == Result.ADDED_EXISTING:
msg = "Item skipped (already registered)"
retcode = RET_NOTCHANGED
else:
msg = "Unhandled result code: %s" % \
Result._codeinfo[result.code].lower()
retcode = RET_INVALID
if not cmdoptions.quiet:
if store_done:
print "Status: Added, revision %d" % newrev
else:
print "Status: Commit not done"
print msg
return retcode
def cmd_store(self, options, args):
"""Store some items in the repository"""
op = self.get_scp("[files...]")
op.add_option("-c", "--commiter", dest="commiter",
help="commiter information",
type="string", default=None)
op.add_option("-m", "--logmsg", dest="logmsg",
help="log message",
type="string", default=None,
metavar="MESSAGE")
op.add_option("-N", "--no-recurse", dest="norecurse",
help="disable recursion on directories",
action="store_true", default=False)
op.add_option("-q", "--quiet", dest="quiet",
help="only display errors",
action="store_true", default=False)
op.add_option("-v", "--verbose", dest="verbose",
help="display additional information",
action="store_true", default=False)
op.add_option("-i", "--inputfile", dest="inputfile",
help="File which contains additional items to store",
default=None)
(storeoptions, storeargs) = op.parse_args(args)
if storeoptions.logmsg is None:
if storeoptions.inputfile == "-":
print >> sys.stderr, "Log message not specified and input list was specified on stdin!"
print >> sys.stderr, "Please specify log message using -m"
print >> sys.stderr, "Commit aborted."
return RET_INVALID
elif sys.stdin.isatty() and sys.stdout.isatty():
nt = self.get_text("")
if len(nt) == 0:
print >> sys.stderr, "Commit aborted."
return RET_INVALID
storeoptions.logmsg = nt
else:
print >> sys.stderr, "Log message not specified and stdin/stout are not ttys."
print >> sys.stderr, "Commit aborted."
return RET_INVALID
if storeoptions.inputfile is not None:
try:
if storeoptions.inputfile == "-":
f = sys.stdin
else:
f = file(storeoptions.inputfile)
storeargs += map(lambda line: line.rstrip('\n'), f.readlines())
f.close()
except EnvironmentError, e:
print >> sys.stderr, "Can't read input file: %s" % e
return RET_INVALID
errors, store_done, newrev = self.cmdi.store(files=storeargs, options=storeoptions)
self.ts_end = time.time()
totals = {}
totals[Result.STORED_DELETED] = 0
totals[Result.STORED_IOERROR] = 0
totals[Result.STORED_NOTCHANGED] = 0
totals[Result.STORED_NOTREG] = 0
totals[Result.STORED_OK] = 0
totals[Result.STORED_TOSKIP] = 0
for res in errors:
totals[res.code] += 1
if not storeoptions.quiet:
if store_done:
print "Status: Stored revision %d" % newrev
else:
print "Status: Commit not done"
print "Time begin: %s" % time.strftime("%Y-%m-%d %T %Z", time.localtime(self.ts_begin))
print "Time end: %s" % time.strftime("%Y-%m-%d %T %Z", time.localtime(self.ts_end))
l = totals.keys(); l.sort()
for code in l:
print "Total %s: %d" % (Result._codeinfo[code].lower(), totals[code])
for res in errors:
if res.critical:
print >> sys.stderr, str(res)
else:
if storeoptions.verbose:
print >> sys.stdout, str(res)
retcode = 0
if totals[Result.STORED_IOERROR] > 0:
retcode |= RET_ERRORS
if totals[Result.STORED_NOTCHANGED] > 0:
retcode |= RET_NOTCHANGED
if totals[Result.STORED_OK] > 0:
retcode |= RET_STORED
if totals[Result.STORED_DELETED] > 0:
retcode |= RET_DELETED
return retcode
def cmd_retrieve(self, options, args):
"""Retrieve some items from the repository"""
op = self.get_scp("[files...]")
op.add_option("-d", "--destdir", dest="destdir",
help="destination directory",
type="string", default=None,
metavar="PATH")
op.add_option("-N", "--no-recurse", dest="norecurse",
help="disable recursion on directories restored",
action="store_true", default=False)
op.add_option("-q", "--quiet", dest="quiet",
help="only display errors",
action="store_true", default=False)
op.add_option("-r", "--revno", dest="revno",
help="revision number to be retrieved",
metavar="REVNO", type="int")
op.add_option("-s", "--skip-dirs", dest="use_dirs",
help="together with -d, extract files directly under PATH, ignoring their stored paths",
default=1, action="store_false")
op.add_option("-v", "--verbose", dest="verbose",
help="display additional information",
action="store_true", default=False)
(retroptions, retrargs) = op.parse_args(args)
results = self.cmdi.retrieve(files=retrargs, options=retroptions)
totals = {}
if not retroptions.quiet:
for res in results:
totals[res.code] = totals.setdefault(res.code, 0) + 1
l = totals.keys()
l.sort()
for code in l:
print "Total %s: %d" % (Result._codeinfo[code].lower(), totals[code])
for res in results:
if res.critical:
print >> sys.stderr, str(res)
else:
if retroptions.verbose:
print str(res)
return
def cmd_diff(self, options, args):
"""Show the difference between versions for selected items"""
op = self.get_scp("filename...")
op.add_option("-r", "--revno", dest="rev",
help="revision number(s) to be compared",
metavar="REVNO[:REVNO]",
default=None
)
op.add_option("-l", "--list", dest="list",
help="just list filenames which are different",
action="store_true", default=False,
)
op.add_option("-c", "--check", dest="checks",
help="check this attribute (multiple options allowed); overrides --no-check",
action="append", default=None,
type="choice", choices=cfvers.Entry.DIFFABLE_ATTRS,
metavar="ATTR",
)
op.add_option("-n", "--no-check", dest="nochecks",
help="don't check this attribute (multiple options allowed)",
action="append", default=None,
type="choice", choices=cfvers.Entry.DIFFABLE_ATTRS,
metavar="ATTR",
)
(diffoptions, diffargs) = op.parse_args(args)
if diffoptions.checks is None:
diffoptions.checks = list(cfvers.Entry.DIFFABLE_ATTRS)
if diffoptions.nochecks is not None:
for i in diffoptions.nochecks:
if i in diffoptions.checks:
diffoptions.checks.remove(i)
results = self.cmdi.diff(options=diffoptions, files=diffargs)
if diffoptions.list:
for name, status, err, data, e1, e2 in results:
if not status or len(data) == 0:
print name
else:
for areao, nameo, arean, namen, status, err, data, e1, e2 in results:
if status and len(data) == 0:
continue
if (nameo == namen or namen is None) and (areao == arean or arean is None):
print "===== %s" % nameo,
else:
if areao == arean:
print "===== %s - %s" % (nameo, namen),
else:
print "===== %s:%s - %s:%s" % (areao, nameo, arean, namen),
if e1 is not None and e2 is not None:
if e1.revno == e2.revno:
print "(rev %s)" % e1.revno,
else:
print "(rev %s -> %s)" % (e1.revno, e2.revno or 'current'),
if not status:
print ": can't compute diff, error: %s" % err
continue
print
if e1 is not None and e2 is not None and e1.status != e2.status:
print "File has gone from status `%s' to `%s'" % \
(Entry.STATUS_MAP[e1.status],
Entry.STATUS_MAP[e2.status])
continue
for row in data:
if len(row) == 1:
print row[0]
elif len(row) == 2:
print "%s:\n%s\n" % (row[0], row[1])
elif len(row) == 3:
kind, old, new = row
if type(old) == list and type(new) == list:
print "%s:" % kind
for name in old:
print "- %s" % name
for name in new:
print "+ %s" % name
print
else:
print "%s:\n- %s\n+ %s\n" % (kind, old, new)
return
def cmd_log(self, options, args):
"""Show the editing history for selected items"""
op = self.get_scp("[files...]")
op.add_option("-r", "--revno", dest="rev",
help="revision number(s) to be listed",
metavar="REVNO[:REVNO]",
default=None
)
op.add_option("-l", "--list", dest="list",
help="only list number and date",
default=False, action="store_true",
)
(cmdopts, cmdargs) = op.parse_args(args)
arearevs = self.cmdi.log()
if len(cmdargs) > 0:
itemrevs = sets.Set()
for itname in cmdargs:
item = self.cmdi.portal.getItemByName(self.cmdi.area.name, itname)
if item is None:
raise ValueError, "Item %s not found in repository!" % itname
itemrevs |= sets.Set(self.cmdi.portal.getRevNumbers(item.id))
else:
itemrevs = sets.Set([x.revno for x in arearevs])
if cmdopts.rev is not None:
r1, r2 = cfvers.cmd.CLIScript.parserev(cmdopts.rev)
if r1 is not None and r2 is not None:
givenrevs = sets.Set(range(r1, r2+1))
elif r1 is None and r2 is not None:
givenrevs = sets.Set([r2])
elif r1 is not None and r2 is None:
givenrevs = sets.Set([r1])
selrevs = itemrevs & givenrevs
else:
selrevs = itemrevs
if cmdopts.list:
for ar in arearevs:
if ar.revno in selrevs:
print "%4d %12s %12s %s %s" % \
(ar.revno, ar.server, ar.commiter,
ar.ctime.localtime().strftime("%Y-%m-%d %T"), ar.ctime.tz)
else:
for ar in arearevs:
if ar.revno in selrevs:
print "-" * 80
print "Revision number: %d" % ar.revno
print "Source server: %s" % ar.server
print "Date entered: %s %s" % (ar.ctime.localtime().strftime("%F %T"), ar.ctime.tz)
print "Commiter info: %s" % ar.commiter
print "Commiter uid/gid: %d/%d" % (ar.uid, ar.gid)
print "Modified items:"
for itemid, itemname, entrystatus in \
self.cmdi.portal.getRevisionItems(ar.area, ar.revno):
if entrystatus == cfvers.Entry.STATUS_ADDED:
char = "A"
elif entrystatus == cfvers.Entry.STATUS_DELETED:
char = "D"
elif entrystatus == cfvers.Entry.STATUS_MODIFIED:
char = "M"
else:
char = "?"
print " %c %s" % (char, itemname)
print "Log message:"
print ar.logmsg
if len(selrevs) > 0:
print "-" * 80
return
def cmd_cat(self, options, args):
"""Displays the contents of selected items"""
op = self.get_scp("filename")
op.add_option("-r", "--revno", dest="rev",
help="revision number to be shown",
metavar="REVNO")
(catoptions, catargs) = op.parse_args(args)
if len(catargs) == 0:
print >>sys.stderr, "Missing filename!"
sys.exit(1)
if len(catargs) > 1:
print >>sys.stderr, "Only one filename allowed!"
sys.exit(1)
try:
data = self.cmdi.show(catargs[0], rev=catoptions.rev)
sys.stdout.write(data)
except cfvers.OperationError, e:
print >>sys.stderr, "Error: %s" % e
return
def cmd_stat(self, options, args):
op = self.get_scp("[files...]")
op.add_option("-r", "--revno", dest="rev",
help="revision number to be shown",
metavar="REVNO")
(cmdopts, cmdargs) = op.parse_args(args)
results = self.cmdi.stat(cmdopts, cmdargs)
for name, err, data in results:
if err is not None:
print "cannot stat `%s': %s" % (name, err)
continue
sys.stdout.write(data)
return
def cmd_export(self, options, args):
op = self.get_scp("", find=True)
op.add_option("-F", "--format", dest="format",
help="the export format, one of sha1sum or tar [sha1sum]",
metavar="FORMAT", default="sha1sum",
type="choice", choices=("tar", "sha1sum"))
op.add_option("-o", "--output", dest="output",
help="the name of the destination file [STDOUT]",
metavar="FILENAME", default="-")
(cmdopts, cmdargs) = op.parse_args(args)
if len(cmdargs) > 0:
print >>sys.stderr, "The export command takes no arguments, please see the help"
return
if cmdopts.format not in ("tar", "sha1sum"):
print >>sys.stderr, "Unknown export format '%s'!" % cmdopts.format
sys.exit(1)
if cmdopts.output == "-":
output = sys.stdout
close = False
else:
output = file(cmdopts.output, "w")
close = True
try:
self.cmdi.export(cmdopts, output)
finally:
if close:
output.close()
return
def get_text(self, extratext):
"""Method to get a text from the user"""
fd, fname = tempfile.mkstemp()
fo = os.fdopen(fd, "w+")
guardian = "---- This line and the rest below won't be included ----"
fo.write("\n")
fo.write(guardian)
fo.write("\n")
fo.write(extratext)
fo.flush()
editor=os.getenv("EDITOR")
if editor is None:
editor="vi"
try:
os.system("%s %s" % (editor,fname))
fo.seek(0, 0)
ndata = fo.read()
lin = ndata.split("\n")
realtext = []
for i in lin:
if i != guardian:
realtext.append(i)
else:
break
if len(realtext) > 0 and realtext[-1] == "\n":
realtext.pop()
return "\n".join(realtext)
finally:
fo.close()
os.unlink(fname)
return ""
if __name__ == "__main__":
os.stat_float_times(False)
cli = CLI()
try:
retcode = cli.main(sys.argv[1:])
except IOError, e:
if e.errno == errno.EPIPE:
print >> sys.stderr, "Broken pipe."
sys.exit(1)
raise
except KeyboardInterrupt:
print >> sys.stderr, "Abort."
sys.exit(1)
if retcode is not None:
sys.exit(retcode)
syntax highlighted by Code2HTML, v. 0.9.1