#!/usr/bin/env python # Written by Ross Cohen # See LICENSE.txt for license information. # Setup import path in case we can't find codeville in standard places try: import psyco assert psyco.__version__ >= 0x010300f0 psyco.full() except: pass try: import Codeville.db except ImportError: import sys, os.path from os.path import dirname, abspath, join pypath = "lib/python%d.%d/site-packages" % \ (sys.version_info[0], sys.version_info[1]) base = dirname(dirname(abspath(sys.argv[0]))) sys.path[0:0] = [ join(base, pypath) ] # first place to look import binascii import Codeville from Codeville.db import db from Codeville.db import VersionMismatchException from Codeville.db import check_format_version, check_rebuild_version from Codeville.client import cli_init, PathError from Codeville.client import add, delete, rename, revert, edit from Codeville.client import history, status, diff, describe, annotate from Codeville.client import update, commit, set_password, rebuild from Codeville.client import create_repo, remove_repo, list_repos from Codeville.client import find_co, server_to_tuple, Checkout from Codeville.client import cli_is_ancestor, cli_print_mini_dag, cli_print_dag from Codeville.client import cli_construct, cli_heads, cli_last_modified from Codeville.client import term_encoding, text_encoding from Codeville.history import long_id, short_id from Codeville.history import ChangeNotKnown, ChangeNotUnique, ChangeBadRepository from getopt import gnu_getopt, GetoptError import os from os import path from random import seed import signal from sys import argv, exit from time import time, strptime, mktime from traceback import print_exc def run_init(co, opts, args): return cli_init(args) def run_add(co, opts, args): return add(co, args) def run_remove(co, opts, args): return delete(co, args) def run_rename(co, opts, args): return rename(co, args[0], args[1]) def run_revert(co, opts, args): unmod_flag = 0 for (opt, arg) in opts: if opt == '-a': unmod_flag = 1 return revert(co, args, unmod_flag) def run_edit(co, opts, args): by_id = False for (opt, arg) in opts: if opt == '-i': by_id = True return edit(co, args, by_id=by_id) def run_history(co, opts, args): head, limit, skip = None, -1, 0 by_id = False v = 0 for (opt, arg) in opts: if opt == '-h': head = long_id(co, arg) elif opt == '-i': by_id = True elif opt == '-c': try: limit = int(arg) except ValueError: print 'Invalid count: ' + arg return 1 elif opt == '-s': try: skip = int(arg) except ValueError: print 'Invalid skip: ' + arg return 1 if skip < 0: print 'Invalid skip: ' + arg return 1 elif opt == '-v': v = 1 return history(co, head, limit, skip, v, by_id, args) def run_status(co, opts, args): verbose = 0 for (opt, arg) in opts: if opt == '-v': verbose = 1 return status(co, args, verbose) def run_heads(co, opts, args): return cli_heads(co) def run_last_mod(co, opts, args): uhead = None by_id = False for (opt, arg) in opts: if opt == '-h': uhead = arg elif opt == '-i': by_id = True return cli_last_modified(co, args[0], uhead, by_id) def run_diff(co, opts, args): print_new = False rev = [co.repo, 'local'] revnum = 0 for (opt, arg) in opts: if opt == '-r': if revnum == 2: print '-r cannot be used more than twice' return 1 rev[revnum] = arg revnum += 1 elif opt == '-N': print_new = True return diff(co, rev, args, print_new) def run_update(co, opts, args): merge = True for (opt, arg) in opts: if opt == '-d': merge = False if co.repo is None: print 'error - no server specified' return 1 remote = server_to_tuple(co.repo) if remote is None or remote[2] is None: print 'error - Invalid server: ' + co.repo return 1 return update(co, remote, merge=merge) def run_commit(co, opts, args): backup, tstamp, comment, noserver = False, None, None, 0 for (opt, arg) in opts: if opt == '-b': backup = True elif opt == '-D': tstamp = mktime(strptime(arg, '%Y/%m/%d %H:%M:%S %Z')) elif opt == '-m': comment = arg ucomment = comment.decode(term_encoding) comment = ucomment.encode('utf8') elif opt == '-M': try: fd = open(arg, "r") comment = fd.read() fd.close() except IOError, err: print 'error - Cannot read "%s": %s' % (arg, err[1]) return 1 try: ucomment = comment.decode(text_encoding) comment = ucomment.encode('utf8') except UnicodeDecodeError: print "error - Invalid %s characters in comment" % \ (text_encoding,) return 1 elif opt == '-n': noserver = 1 if noserver or co.repo is None: co.repo = None remote = None else: remote = server_to_tuple(co.repo) if remote is None or remote[2] is None: print 'error - Invalid server: ' + co.repo return 1 return commit(co, remote, comment, tstamp=tstamp, backup=backup, files=args) def run_construct(co, opts, args): return cli_construct(co, args[0]) def run_rebuild(co, opts, args): uheads = [] for (opt, arg) in opts: if opt == '-h': uheads.append(arg) return rebuild(co, uheads) def run_is_ancestor(co, opts, args): return cli_is_ancestor(co, args[0], args[1]) def run_print_dag(co, opts, args): return cli_print_dag(co, args) def run_print_mini_dag(co, opts, args): uheads = [] by_id = False for (opt, arg) in opts: if opt == '-h': uheads.append(arg) if opt == '-i': by_id = True return cli_print_mini_dag(co, args[0], uheads, by_id) def run_create(co, opts, args): remote = server_to_tuple(args[0]) if remote is None or remote[2] is None: print 'Invalid server: ' + args[0] return 1 return create_repo(co, remote) def run_destroy(co, opts, args): remote = server_to_tuple(args[0]) if remote is None or remote[2] is None: print 'Invalid server: ' + args[0] return 1 return remove_repo(co, remote) def run_list(co, opts, args): return list_repos(co) def run_set(co, opts, args): txn = co.txn_begin() co.varsdb.put(args[0], args[1], txn=txn) co.txn_commit(txn) print 'Variable set' return 0 def run_unset(co, opts, args): txn = co.txn_begin() try: co.varsdb.delete(args[0], txn=txn) except db.DBNotFoundError: print 'Variable was not set' txn.abort() return 1 co.txn_commit(txn) print 'Variable unset' return 0 def run_show_vars(co, opts, args): for item in co.varsdb.items(): print "%s:\t%s" % item return 0 def run_describe(co, opts, args): dodiff, short, xml = False, False, False for (opt, arg) in opts: if opt == '-d': dodiff = True elif opt == '-s': short = True elif opt == '-x': xml = True if dodiff and xml: print 'error - Only 1 of -x, -d is allowed' return 1 return describe(co, args[0], short, xml, dodiff, args[1:]) def run_password(co, opts, args): return set_password(co) def run_annotate(co, opts, args): rev = 'local' for (opt, arg) in opts: if opt == '-r': rev = arg return annotate(co, rev, args) options = { "unmodified": ('a', 0, "Only files which were not modified"), "backup": ('b', 0, "Backup existing changesets, don't create a new one"), "count": ('c', 1, " elements to display"), "date": ('D', 1, " indicating changeset creation time"), "diff": ('d', 0, "Display a diff"), "dont-merge": ('d', 0, "Don't merge changes into the workspace"), "head": ('h', 1, "Use as the head"), "id": ('i', 0, "Treat name as a file identifier"), "message": ('m', 1, "Use as the commit message"), "message-file": ('M', 1, "Get commit message from "), "no-network": ('n', 0, "Don't perform any network activity"), "new-files": ('N', 0, "Show diffs for new and deleted files"), "path": ('p', 1, " to client"), "revision": ('r', 1, " or "), "repository": ('R', 1, " for this operation"), "skip": ('s', 1, " number of elements"), "user": ('u', 1, " as whom to perform this operation"), "verbose": ('v', 0, ""), "version": ('V', 0, "Print version information"), "xml": ('x', 0, "Output XML") } # the format is (function, min args, max args, long opts) commands = { 'init': (run_init, 0, 0, []), 'add': (run_add, 1, None, []), 'rename': (run_rename, 2, 2, []), 'remove': (run_remove, 1, None, []), 'revert': (run_revert, 1, None, ["unmodified"]), 'edit': (run_edit, 1, None, ["id"]), 'annotate': (run_annotate, 0, None, ["revision"]), 'diff': (run_diff, 0, None, ["revision", "new-files"]), 'describe': (run_describe, 1, None, ["diff", "xml"]), 'history': (run_history, 0, None, ["head", "id", "count", "skip", "verbose"]), 'status': (run_status, 0, None, ["verbose"]), 'heads': (run_heads, 0, 0, []), 'last-modified': (run_last_mod, 1, 1, ["head", "id"]), 'update': (run_update, 0, 0, ["dont-merge"]), 'commit': (run_commit, 0, None, ["backup", "date", "message", "message-file", "no-network"]), 'construct': (run_construct, 1, 1, []), 'rebuild': (run_rebuild, 0, 0, ["head"]), 'is_ancestor': (run_is_ancestor, 2, 2, []), 'print_mini_dag': (run_print_mini_dag, 1, 1, ["head", "id"]), 'print_dag': (run_print_dag, 0, None, []), 'create': (run_create, 1, 1, []), 'destroy': (run_destroy, 1, 1, []), 'list-repos': (run_list, 0, 0, []), 'set': (run_set, 2, 2, []), 'unset': (run_unset, 1, 1, []), 'show-vars': (run_show_vars, 0, 0, []), 'password': (run_password, 0, 0, []) } def run_help(): print 'Valid commands are:\n\t', coms = commands.keys() coms.sort() print '\n\t'.join(coms) def opt_help(opts): for lopt in opts: sopt = options[lopt][0] desc = options[lopt][2] print " -%s, --%s\t%s" % (sopt, lopt, desc) return def subcommand_help(cmd): print "Global options:" opt_help(["path", "repository", "user", "version"]) if cmd is None: return print "\nOptions for '%s':" % (cmd,) opt_help(commands[cmd][3]) return def aggregate_opts(commands): fopts, lfopts = [], [] for key, value in options.items(): if value[1] == 1: fopts.append(value[0] + ':') lfopts.append(key + '=') else: fopts.append(value[0]) lfopts.append(key) return ''.join(fopts), lfopts def convert_to_short_opts(optlist): new_optlist = [] for opt, arg in optlist: if options.has_key(opt[2:]): opt = '-' + options[opt[2:]][0] new_optlist.append((opt, arg)) return new_optlist def check_subcommand_opts(cmd, opts): known_opts = [] command = commands[cmd] for lo in command[3]: known_opts.append('--' + lo) if options.has_key(lo): known_opts.append('-' + options[lo][0]) for opt, arg in opts: if opt not in known_opts: raise GetoptError, "option %s not recognized" % (opt,) return def run(args): seed(time() + os.getpid()) local, ulocal, repo, user = None, None, None, None retval = 1 # get rid of nuisance traceback if os.name != 'nt': signal.signal(signal.SIGPIPE, signal.SIG_DFL) all_opts, all_lopts = aggregate_opts(commands) try: optlist, args = gnu_getopt(args, all_opts, all_lopts) except GetoptError, msg: print "error - %s\n" % (msg.args[0],) # maybe we can find something that looks like a subcommand cmd = None for arg in args: if arg not in commands: continue cmd = arg break subcommand_help(cmd) return 1 # parse the top level options optlist = convert_to_short_opts(optlist) optlist_unhandled = [] for (opt, arg) in optlist: if opt == '-p': ulocal = os.path.abspath(arg) elif opt == '-R': repo = arg elif opt == '-u': user = arg elif opt == '-V': print "cdv, version %s" % (Codeville.version,) return 0 else: optlist_unhandled.append((opt, arg)) optlist = optlist_unhandled # pull the subcommand if len(args) == 0: run_help() return 1 user_cname = args.pop(0) cname_list = [] for cname in commands.keys(): if cname.startswith(user_cname): cname_list.append(cname) if len(cname_list) > 1: print 'Command \'%s\' is not specific enough.' % (user_cname,) print 'Matching commands:\n\t', print '\n\t'.join(cname_list) return 1 elif len(cname_list) == 1: cname = cname_list[0] command = commands[cname] else: print '\'%s\' does not match any commands.' % (user_cname,) run_help() return 1 # check the options passed in against the subcommand try: check_subcommand_opts(cname, optlist) except GetoptError, msg: print 'error - %s\n' % (msg.args[0],) subcommand_help(cname) return 1 # check the proper number of arguments were passed if len(args) < command[2]: print 'Too few arguments to "%s" (requires %s).' % \ (cname, command[2]) return 1 elif command[3] is not None and len(args) > command[3]: print 'Too many arguments to "%s" (max %d).' % \ (cname, command[3]) return 1 # find the metadata directory if ulocal is None: ulocal = os.getcwd() try: local = find_co(ulocal) except PathError, msg: if cname != 'init': print msg return 1 if cname == 'init': if local != None: print 'error - found existing repository at %s' % (local,) return 1 return command[0](None, optlist, [ulocal]) metadata_dir = path.join(local, '.cdv') try: check_format_version(metadata_dir) except VersionMismatchException, versions: print "error - expected version %d does not match repository version %d, you probably need to run cdvupgrade." % versions.args return 1 if cname != 'rebuild': try: check_rebuild_version(metadata_dir) except VersionMismatchException, versions: print "error - auxiliary format %d does not match repository format %d, you need to run 'cdv rebuild'" % versions.args return 1 # run the user's command co = None try: # open the database co = Checkout(local) if repo is None: co.repo = co.varsdb.get('repository') else: co.repo = repo if user is not None: co.user = user retval = command[0](co, optlist, args) except KeyboardInterrupt: print 'Aborting...' except ChangeNotKnown, point: print 'error - %s is not a valid changeset' % (point,) except ChangeNotUnique, (point, changes): changes = [short_id(co, binascii.unhexlify(change)) for change in changes] print 'error - %s matches more than one changeset:\n\t%s' % \ (point, '\n\t'.join(changes)) except ChangeBadRepository, repo: print 'error - %s is not a known repository' % (repo,) except: print_exc() if co is not None: # clean up and shut down if co.txn is not None: co.txn_abort(co.txn) co.close() return retval if __name__ == '__main__': if 0: import hotshot, hotshot.stats prof = hotshot.Profile("cdv.prof") retval = prof.runcall(run, argv[1:]) prof.close() stats = hotshot.stats.load("cdv.prof") stats.strip_dirs() stats.sort_stats('time', 'calls') stats.print_stats() else: retval = run(argv[1:]) #retval = run(argv[1:]) exit(retval)