diff options
Diffstat (limited to 'bzrlib/tests/test_patches_data/mod')
-rw-r--r-- | bzrlib/tests/test_patches_data/mod | 2681 |
1 files changed, 2681 insertions, 0 deletions
diff --git a/bzrlib/tests/test_patches_data/mod b/bzrlib/tests/test_patches_data/mod new file mode 100644 index 0000000..103fe99 --- /dev/null +++ b/bzrlib/tests/test_patches_data/mod @@ -0,0 +1,2681 @@ +# Copyright (C) 2004 Aaron Bentley +# <aaron.bentley@utoronto.ca> +# +# This program 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. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +import sys +import arch +import arch.util +import arch.arch + +import pylon.errors +from pylon.errors import * +from pylon import errors +from pylon import util +from pylon import arch_core +from pylon import arch_compound +from pylon import ancillary +from pylon import misc +from pylon import paths + +import abacmds +import cmdutil +import shutil +import os +import options +import time +import cmd +import readline +import re +import string +import terminal +import email +import smtplib +import textwrap + +__docformat__ = "restructuredtext" +__doc__ = "Implementation of user (sub) commands" +commands = {} + +def find_command(cmd): + """ + Return an instance of a command type. Return None if the type isn't + registered. + + :param cmd: the name of the command to look for + :type cmd: the type of the command + """ + if commands.has_key(cmd): + return commands[cmd]() + else: + return None + +class BaseCommand: + def __call__(self, cmdline): + try: + self.do_command(cmdline.split()) + except cmdutil.GetHelp, e: + self.help() + except Exception, e: + print e + + def get_completer(index): + return None + + def complete(self, args, text): + """ + Returns a list of possible completions for the given text. + + :param args: The complete list of arguments + :type args: List of str + :param text: text to complete (may be shorter than args[-1]) + :type text: str + :rtype: list of str + """ + matches = [] + candidates = None + + if len(args) > 0: + realtext = args[-1] + else: + realtext = "" + + try: + parser=self.get_parser() + if realtext.startswith('-'): + candidates = parser.iter_options() + else: + (options, parsed_args) = parser.parse_args(args) + + if len (parsed_args) > 0: + candidates = self.get_completer(parsed_args[-1], len(parsed_args) -1) + else: + candidates = self.get_completer("", 0) + except: + pass + if candidates is None: + return + for candidate in candidates: + candidate = str(candidate) + if candidate.startswith(realtext): + matches.append(candidate[len(realtext)- len(text):]) + return matches + + +class Help(BaseCommand): + """ + Lists commands, prints help messages. + """ + def __init__(self): + self.description="Prints help mesages" + self.parser = None + + def do_command(self, cmdargs): + """ + Prints a help message. + """ + options, args = self.get_parser().parse_args(cmdargs) + if len(args) > 1: + raise cmdutil.GetHelp + + if options.native or options.suggestions or options.external: + native = options.native + suggestions = options.suggestions + external = options.external + else: + native = True + suggestions = False + external = True + + if len(args) == 0: + self.list_commands(native, suggestions, external) + return + elif len(args) == 1: + command_help(args[0]) + return + + def help(self): + self.get_parser().print_help() + print """ +If no command is specified, commands are listed. If a command is +specified, help for that command is listed. + """ + + def get_parser(self): + """ + Returns the options parser to use for the "revision" command. + + :rtype: cmdutil.CmdOptionParser + """ + if self.parser is not None: + return self.parser + parser=cmdutil.CmdOptionParser("fai help [command]") + parser.add_option("-n", "--native", action="store_true", + dest="native", help="Show native commands") + parser.add_option("-e", "--external", action="store_true", + dest="external", help="Show external commands") + parser.add_option("-s", "--suggest", action="store_true", + dest="suggestions", help="Show suggestions") + self.parser = parser + return parser + + def list_commands(self, native=True, suggest=False, external=True): + """ + Lists supported commands. + + :param native: list native, python-based commands + :type native: bool + :param external: list external aba-style commands + :type external: bool + """ + if native: + print "Native Fai commands" + keys=commands.keys() + keys.sort() + for k in keys: + space="" + for i in range(28-len(k)): + space+=" " + print space+k+" : "+commands[k]().description + print + if suggest: + print "Unavailable commands and suggested alternatives" + key_list = suggestions.keys() + key_list.sort() + for key in key_list: + print "%28s : %s" % (key, suggestions[key]) + print + if external: + fake_aba = abacmds.AbaCmds() + if (fake_aba.abadir == ""): + return + print "External commands" + fake_aba.list_commands() + print + if not suggest: + print "Use help --suggest to list alternatives to tla and aba"\ + " commands." + if options.tla_fallthrough and (native or external): + print "Fai also supports tla commands." + +def command_help(cmd): + """ + Prints help for a command. + + :param cmd: The name of the command to print help for + :type cmd: str + """ + fake_aba = abacmds.AbaCmds() + cmdobj = find_command(cmd) + if cmdobj != None: + cmdobj.help() + elif suggestions.has_key(cmd): + print "Not available\n" + suggestions[cmd] + else: + abacmd = fake_aba.is_command(cmd) + if abacmd: + abacmd.help() + else: + print "No help is available for \""+cmd+"\". Maybe try \"tla "+cmd+" -H\"?" + + + +class Changes(BaseCommand): + """ + the "changes" command: lists differences between trees/revisions: + """ + + def __init__(self): + self.description="Lists what files have changed in the project tree" + + def get_completer(self, arg, index): + if index > 1: + return None + try: + tree = arch.tree_root() + except: + tree = None + return cmdutil.iter_revision_completions(arg, tree) + + def parse_commandline(self, cmdline): + """ + Parse commandline arguments. Raises cmdutil.GetHelp if help is needed. + + :param cmdline: A list of arguments to parse + :rtype: (options, Revision, Revision/WorkingTree) + """ + parser=self.get_parser() + (options, args) = parser.parse_args(cmdline) + if len(args) > 2: + raise cmdutil.GetHelp + + tree=arch.tree_root() + if len(args) == 0: + a_spec = ancillary.comp_revision(tree) + else: + a_spec = cmdutil.determine_revision_tree(tree, args[0]) + cmdutil.ensure_archive_registered(a_spec.archive) + if len(args) == 2: + b_spec = cmdutil.determine_revision_tree(tree, args[1]) + cmdutil.ensure_archive_registered(b_spec.archive) + else: + b_spec=tree + return options, a_spec, b_spec + + def do_command(self, cmdargs): + """ + Master function that perfoms the "changes" command. + """ + try: + options, a_spec, b_spec = self.parse_commandline(cmdargs); + except cmdutil.CantDetermineRevision, e: + print e + return + except arch.errors.TreeRootError, e: + print e + return + if options.changeset: + changeset=options.changeset + tmpdir = None + else: + tmpdir=util.tmpdir() + changeset=tmpdir+"/changeset" + try: + delta=arch.iter_delta(a_spec, b_spec, changeset) + try: + for line in delta: + if cmdutil.chattermatch(line, "changeset:"): + pass + else: + cmdutil.colorize(line, options.suppress_chatter) + except arch.util.ExecProblem, e: + if e.proc.error and e.proc.error.startswith( + "missing explicit id for file"): + raise MissingID(e) + else: + raise + status=delta.status + if status > 1: + return + if (options.perform_diff): + chan = arch_compound.ChangesetMunger(changeset) + chan.read_indices() + if options.diffopts is not None: + if isinstance(b_spec, arch.Revision): + b_dir = b_spec.library_find() + else: + b_dir = b_spec + a_dir = a_spec.library_find() + diffopts = options.diffopts.split() + cmdutil.show_custom_diffs(chan, diffopts, a_dir, b_dir) + else: + cmdutil.show_diffs(delta.changeset) + finally: + if tmpdir and (os.access(tmpdir, os.X_OK)): + shutil.rmtree(tmpdir) + + def get_parser(self): + """ + Returns the options parser to use for the "changes" command. + + :rtype: cmdutil.CmdOptionParser + """ + parser=cmdutil.CmdOptionParser("fai changes [options] [revision]" + " [revision]") + parser.add_option("-d", "--diff", action="store_true", + dest="perform_diff", default=False, + help="Show diffs in summary") + parser.add_option("-c", "--changeset", dest="changeset", + help="Store a changeset in the given directory", + metavar="DIRECTORY") + parser.add_option("-s", "--silent", action="store_true", + dest="suppress_chatter", default=False, + help="Suppress chatter messages") + parser.add_option("--diffopts", dest="diffopts", + help="Use the specified diff options", + metavar="OPTIONS") + + return parser + + def help(self, parser=None): + """ + Prints a help message. + + :param parser: If supplied, the parser to use for generating help. If \ + not supplied, it is retrieved. + :type parser: cmdutil.CmdOptionParser + """ + if parser is None: + parser=self.get_parser() + parser.print_help() + print """ +Performs source-tree comparisons + +If no revision is specified, the current project tree is compared to the +last-committed revision. If one revision is specified, the current project +tree is compared to that revision. If two revisions are specified, they are +compared to each other. + """ + help_tree_spec() + return + + +class ApplyChanges(BaseCommand): + """ + Apply differences between two revisions to a tree + """ + + def __init__(self): + self.description="Applies changes to a project tree" + + def get_completer(self, arg, index): + if index > 1: + return None + try: + tree = arch.tree_root() + except: + tree = None + return cmdutil.iter_revision_completions(arg, tree) + + def parse_commandline(self, cmdline, tree): + """ + Parse commandline arguments. Raises cmdutil.GetHelp if help is needed. + + :param cmdline: A list of arguments to parse + :rtype: (options, Revision, Revision/WorkingTree) + """ + parser=self.get_parser() + (options, args) = parser.parse_args(cmdline) + if len(args) != 2: + raise cmdutil.GetHelp + + a_spec = cmdutil.determine_revision_tree(tree, args[0]) + cmdutil.ensure_archive_registered(a_spec.archive) + b_spec = cmdutil.determine_revision_tree(tree, args[1]) + cmdutil.ensure_archive_registered(b_spec.archive) + return options, a_spec, b_spec + + def do_command(self, cmdargs): + """ + Master function that performs "apply-changes". + """ + try: + tree = arch.tree_root() + options, a_spec, b_spec = self.parse_commandline(cmdargs, tree); + except cmdutil.CantDetermineRevision, e: + print e + return + except arch.errors.TreeRootError, e: + print e + return + delta=cmdutil.apply_delta(a_spec, b_spec, tree) + for line in cmdutil.iter_apply_delta_filter(delta): + cmdutil.colorize(line, options.suppress_chatter) + + def get_parser(self): + """ + Returns the options parser to use for the "apply-changes" command. + + :rtype: cmdutil.CmdOptionParser + """ + parser=cmdutil.CmdOptionParser("fai apply-changes [options] revision" + " revision") + parser.add_option("-d", "--diff", action="store_true", + dest="perform_diff", default=False, + help="Show diffs in summary") + parser.add_option("-c", "--changeset", dest="changeset", + help="Store a changeset in the given directory", + metavar="DIRECTORY") + parser.add_option("-s", "--silent", action="store_true", + dest="suppress_chatter", default=False, + help="Suppress chatter messages") + return parser + + def help(self, parser=None): + """ + Prints a help message. + + :param parser: If supplied, the parser to use for generating help. If \ + not supplied, it is retrieved. + :type parser: cmdutil.CmdOptionParser + """ + if parser is None: + parser=self.get_parser() + parser.print_help() + print """ +Applies changes to a project tree + +Compares two revisions and applies the difference between them to the current +tree. + """ + help_tree_spec() + return + +class Update(BaseCommand): + """ + Updates a project tree to a given revision, preserving un-committed hanges. + """ + + def __init__(self): + self.description="Apply the latest changes to the current directory" + + def get_completer(self, arg, index): + if index > 0: + return None + try: + tree = arch.tree_root() + except: + tree = None + return cmdutil.iter_revision_completions(arg, tree) + + def parse_commandline(self, cmdline, tree): + """ + Parse commandline arguments. Raises cmdutil.GetHelp if help is needed. + + :param cmdline: A list of arguments to parse + :rtype: (options, Revision, Revision/WorkingTree) + """ + parser=self.get_parser() + (options, args) = parser.parse_args(cmdline) + if len(args) > 2: + raise cmdutil.GetHelp + + spec=None + if len(args)>0: + spec=args[0] + revision=cmdutil.determine_revision_arch(tree, spec) + cmdutil.ensure_archive_registered(revision.archive) + + mirror_source = cmdutil.get_mirror_source(revision.archive) + if mirror_source != None: + if cmdutil.prompt("Mirror update"): + cmd=cmdutil.mirror_archive(mirror_source, + revision.archive, arch.NameParser(revision).get_package_version()) + for line in arch.chatter_classifier(cmd): + cmdutil.colorize(line, options.suppress_chatter) + + revision=cmdutil.determine_revision_arch(tree, spec) + + return options, revision + + def do_command(self, cmdargs): + """ + Master function that perfoms the "update" command. + """ + tree=arch.tree_root() + try: + options, to_revision = self.parse_commandline(cmdargs, tree); + except cmdutil.CantDetermineRevision, e: + print e + return + except arch.errors.TreeRootError, e: + print e + return + from_revision = arch_compound.tree_latest(tree) + if from_revision==to_revision: + print "Tree is already up to date with:\n"+str(to_revision)+"." + return + cmdutil.ensure_archive_registered(from_revision.archive) + cmd=cmdutil.apply_delta(from_revision, to_revision, tree, + options.patch_forward) + for line in cmdutil.iter_apply_delta_filter(cmd): + cmdutil.colorize(line) + if to_revision.version != tree.tree_version: + if cmdutil.prompt("Update version"): + tree.tree_version = to_revision.version + + def get_parser(self): + """ + Returns the options parser to use for the "update" command. + + :rtype: cmdutil.CmdOptionParser + """ + parser=cmdutil.CmdOptionParser("fai update [options]" + " [revision/version]") + parser.add_option("-f", "--forward", action="store_true", + dest="patch_forward", default=False, + help="pass the --forward option to 'patch'") + parser.add_option("-s", "--silent", action="store_true", + dest="suppress_chatter", default=False, + help="Suppress chatter messages") + return parser + + def help(self, parser=None): + """ + Prints a help message. + + :param parser: If supplied, the parser to use for generating help. If \ + not supplied, it is retrieved. + :type parser: cmdutil.CmdOptionParser + """ + if parser is None: + parser=self.get_parser() + parser.print_help() + print """ +Updates a working tree to the current archive revision + +If a revision or version is specified, that is used instead + """ + help_tree_spec() + return + + +class Commit(BaseCommand): + """ + Create a revision based on the changes in the current tree. + """ + + def __init__(self): + self.description="Write local changes to the archive" + + def get_completer(self, arg, index): + if arg is None: + arg = "" + return iter_modified_file_completions(arch.tree_root(), arg) +# return iter_source_file_completions(arch.tree_root(), arg) + + def parse_commandline(self, cmdline, tree): + """ + Parse commandline arguments. Raise cmtutil.GetHelp if help is needed. + + :param cmdline: A list of arguments to parse + :rtype: (options, Revision, Revision/WorkingTree) + """ + parser=self.get_parser() + (options, args) = parser.parse_args(cmdline) + + if len(args) == 0: + args = None + if options.version is None: + return options, tree.tree_version, args + + revision=cmdutil.determine_revision_arch(tree, options.version) + return options, revision.get_version(), args + + def do_command(self, cmdargs): + """ + Master function that perfoms the "commit" command. + """ + tree=arch.tree_root() + options, version, files = self.parse_commandline(cmdargs, tree) + ancestor = None + if options.__dict__.has_key("base") and options.base: + base = cmdutil.determine_revision_tree(tree, options.base) + ancestor = base + else: + base = ancillary.submit_revision(tree) + ancestor = base + if ancestor is None: + ancestor = arch_compound.tree_latest(tree, version) + + writeversion=version + archive=version.archive + source=cmdutil.get_mirror_source(archive) + allow_old=False + writethrough="implicit" + + if source!=None: + if writethrough=="explicit" and \ + cmdutil.prompt("Writethrough"): + writeversion=arch.Version(str(source)+"/"+str(version.get_nonarch())) + elif writethrough=="none": + raise CommitToMirror(archive) + + elif archive.is_mirror: + raise CommitToMirror(archive) + + try: + last_revision=tree.iter_logs(version, True).next().revision + except StopIteration, e: + last_revision = None + if ancestor is None: + if cmdutil.prompt("Import from commit"): + return do_import(version) + else: + raise NoVersionLogs(version) + try: + arch_last_revision = version.iter_revisions(True).next() + except StopIteration, e: + arch_last_revision = None + + if last_revision != arch_last_revision: + print "Tree is not up to date with %s" % str(version) + if not cmdutil.prompt("Out of date"): + raise OutOfDate + else: + allow_old=True + + try: + if not cmdutil.has_changed(ancestor): + if not cmdutil.prompt("Empty commit"): + raise EmptyCommit + except arch.util.ExecProblem, e: + if e.proc.error and e.proc.error.startswith( + "missing explicit id for file"): + raise MissingID(e) + else: + raise + log = tree.log_message(create=False, version=version) + if log is None: + try: + if cmdutil.prompt("Create log"): + edit_log(tree, version) + + except cmdutil.NoEditorSpecified, e: + raise CommandFailed(e) + log = tree.log_message(create=False, version=version) + if log is None: + raise NoLogMessage + if log["Summary"] is None or len(log["Summary"].strip()) == 0: + if not cmdutil.prompt("Omit log summary"): + raise errors.NoLogSummary + try: + for line in tree.iter_commit(version, seal=options.seal_version, + base=base, out_of_date_ok=allow_old, file_list=files): + cmdutil.colorize(line, options.suppress_chatter) + + except arch.util.ExecProblem, e: + if e.proc.error and e.proc.error.startswith( + "These files violate naming conventions:"): + raise LintFailure(e.proc.error) + else: + raise + + def get_parser(self): + """ + Returns the options parser to use for the "commit" command. + + :rtype: cmdutil.CmdOptionParser + """ + + parser=cmdutil.CmdOptionParser("fai commit [options] [file1]" + " [file2...]") + parser.add_option("--seal", action="store_true", + dest="seal_version", default=False, + help="seal this version") + parser.add_option("-v", "--version", dest="version", + help="Use the specified version", + metavar="VERSION") + parser.add_option("-s", "--silent", action="store_true", + dest="suppress_chatter", default=False, + help="Suppress chatter messages") + if cmdutil.supports_switch("commit", "--base"): + parser.add_option("--base", dest="base", help="", + metavar="REVISION") + return parser + + def help(self, parser=None): + """ + Prints a help message. + + :param parser: If supplied, the parser to use for generating help. If \ + not supplied, it is retrieved. + :type parser: cmdutil.CmdOptionParser + """ + if parser is None: + parser=self.get_parser() + parser.print_help() + print """ +Updates a working tree to the current archive revision + +If a version is specified, that is used instead + """ +# help_tree_spec() + return + + + +class CatLog(BaseCommand): + """ + Print the log of a given file (from current tree) + """ + def __init__(self): + self.description="Prints the patch log for a revision" + + def get_completer(self, arg, index): + if index > 0: + return None + try: + tree = arch.tree_root() + except: + tree = None + return cmdutil.iter_revision_completions(arg, tree) + + def do_command(self, cmdargs): + """ + Master function that perfoms the "cat-log" command. + """ + parser=self.get_parser() + (options, args) = parser.parse_args(cmdargs) + try: + tree = arch.tree_root() + except arch.errors.TreeRootError, e: + tree = None + spec=None + if len(args) > 0: + spec=args[0] + if len(args) > 1: + raise cmdutil.GetHelp() + try: + if tree: + revision = cmdutil.determine_revision_tree(tree, spec) + else: + revision = cmdutil.determine_revision_arch(tree, spec) + except cmdutil.CantDetermineRevision, e: + raise CommandFailedWrapper(e) + log = None + + use_tree = (options.source == "tree" or \ + (options.source == "any" and tree)) + use_arch = (options.source == "archive" or options.source == "any") + + log = None + if use_tree: + for log in tree.iter_logs(revision.get_version()): + if log.revision == revision: + break + else: + log = None + if log is None and use_arch: + cmdutil.ensure_revision_exists(revision) + log = arch.Patchlog(revision) + if log is not None: + for item in log.items(): + print "%s: %s" % item + print log.description + + def get_parser(self): + """ + Returns the options parser to use for the "cat-log" command. + + :rtype: cmdutil.CmdOptionParser + """ + parser=cmdutil.CmdOptionParser("fai cat-log [revision]") + parser.add_option("--archive", action="store_const", dest="source", + const="archive", default="any", + help="Always get the log from the archive") + parser.add_option("--tree", action="store_const", dest="source", + const="tree", help="Always get the log from the tree") + return parser + + def help(self, parser=None): + """ + Prints a help message. + + :param parser: If supplied, the parser to use for generating help. If \ + not supplied, it is retrieved. + :type parser: cmdutil.CmdOptionParser + """ + if parser==None: + parser=self.get_parser() + parser.print_help() + print """ +Prints the log for the specified revision + """ + help_tree_spec() + return + +class Revert(BaseCommand): + """ Reverts a tree (or aspects of it) to a revision + """ + def __init__(self): + self.description="Reverts a tree (or aspects of it) to a revision " + + def get_completer(self, arg, index): + if index > 0: + return None + try: + tree = arch.tree_root() + except: + tree = None + return iter_modified_file_completions(tree, arg) + + def do_command(self, cmdargs): + """ + Master function that perfoms the "revert" command. + """ + parser=self.get_parser() + (options, args) = parser.parse_args(cmdargs) + try: + tree = arch.tree_root() + except arch.errors.TreeRootError, e: + raise CommandFailed(e) + spec=None + if options.revision is not None: + spec=options.revision + try: + if spec is not None: + revision = cmdutil.determine_revision_tree(tree, spec) + else: + revision = ancillary.comp_revision(tree) + except cmdutil.CantDetermineRevision, e: + raise CommandFailedWrapper(e) + munger = None + + if options.file_contents or options.file_perms or options.deletions\ + or options.additions or options.renames or options.hunk_prompt: + munger = arch_compound.MungeOpts() + munger.set_hunk_prompt(cmdutil.colorize, cmdutil.user_hunk_confirm, + options.hunk_prompt) + + if len(args) > 0 or options.logs or options.pattern_files or \ + options.control: + if munger is None: + munger = cmdutil.arch_compound.MungeOpts(True) + munger.all_types(True) + if len(args) > 0: + t_cwd = arch_compound.tree_cwd(tree) + for name in args: + if len(t_cwd) > 0: + t_cwd += "/" + name = "./" + t_cwd + name + munger.add_keep_file(name); + + if options.file_perms: + munger.file_perms = True + if options.file_contents: + munger.file_contents = True + if options.deletions: + munger.deletions = True + if options.additions: + munger.additions = True + if options.renames: + munger.renames = True + if options.logs: + munger.add_keep_pattern('^\./\{arch\}/[^=].*') + if options.control: + munger.add_keep_pattern("/\.arch-ids|^\./\{arch\}|"\ + "/\.arch-inventory$") + if options.pattern_files: + munger.add_keep_pattern(options.pattern_files) + + for line in arch_compound.revert(tree, revision, munger, + not options.no_output): + cmdutil.colorize(line) + + + def get_parser(self): + """ + Returns the options parser to use for the "cat-log" command. + + :rtype: cmdutil.CmdOptionParser + """ + parser=cmdutil.CmdOptionParser("fai revert [options] [FILE...]") + parser.add_option("", "--contents", action="store_true", + dest="file_contents", + help="Revert file content changes") + parser.add_option("", "--permissions", action="store_true", + dest="file_perms", + help="Revert file permissions changes") + parser.add_option("", "--deletions", action="store_true", + dest="deletions", + help="Restore deleted files") + parser.add_option("", "--additions", action="store_true", + dest="additions", + help="Remove added files") + parser.add_option("", "--renames", action="store_true", + dest="renames", + help="Revert file names") + parser.add_option("--hunks", action="store_true", + dest="hunk_prompt", default=False, + help="Prompt which hunks to revert") + parser.add_option("--pattern-files", dest="pattern_files", + help="Revert files that match this pattern", + metavar="REGEX") + parser.add_option("--logs", action="store_true", + dest="logs", default=False, + help="Revert only logs") + parser.add_option("--control-files", action="store_true", + dest="control", default=False, + help="Revert logs and other control files") + parser.add_option("-n", "--no-output", action="store_true", + dest="no_output", + help="Don't keep an undo changeset") + parser.add_option("--revision", dest="revision", + help="Revert to the specified revision", + metavar="REVISION") + return parser + + def help(self, parser=None): + """ + Prints a help message. + + :param parser: If supplied, the parser to use for generating help. If \ + not supplied, it is retrieved. + :type parser: cmdutil.CmdOptionParser + """ + if parser==None: + parser=self.get_parser() + parser.print_help() + print """ +Reverts changes in the current working tree. If no flags are specified, all +types of changes are reverted. Otherwise, only selected types of changes are +reverted. + +If a revision is specified on the commandline, differences between the current +tree and that revision are reverted. If a version is specified, the current +tree is used to determine the revision. + +If files are specified, only those files listed will have any changes applied. +To specify a renamed file, you can use either the old or new name. (or both!) + +Unless "-n" is specified, reversions can be undone with "redo". + """ + return + +class Revision(BaseCommand): + """ + Print a revision name based on a revision specifier + """ + def __init__(self): + self.description="Prints the name of a revision" + + def get_completer(self, arg, index): + if index > 0: + return None + try: + tree = arch.tree_root() + except: + tree = None + return cmdutil.iter_revision_completions(arg, tree) + + def do_command(self, cmdargs): + """ + Master function that perfoms the "revision" command. + """ + parser=self.get_parser() + (options, args) = parser.parse_args(cmdargs) + + try: + tree = arch.tree_root() + except arch.errors.TreeRootError: + tree = None + + spec=None + if len(args) > 0: + spec=args[0] + if len(args) > 1: + raise cmdutil.GetHelp + try: + if tree: + revision = cmdutil.determine_revision_tree(tree, spec) + else: + revision = cmdutil.determine_revision_arch(tree, spec) + except cmdutil.CantDetermineRevision, e: + print str(e) + return + print options.display(revision) + + def get_parser(self): + """ + Returns the options parser to use for the "revision" command. + + :rtype: cmdutil.CmdOptionParser + """ + parser=cmdutil.CmdOptionParser("fai revision [revision]") + parser.add_option("", "--location", action="store_const", + const=paths.determine_path, dest="display", + help="Show location instead of name", default=str) + parser.add_option("--import", action="store_const", + const=paths.determine_import_path, dest="display", + help="Show location of import file") + parser.add_option("--log", action="store_const", + const=paths.determine_log_path, dest="display", + help="Show location of log file") + parser.add_option("--patch", action="store_const", + dest="display", const=paths.determine_patch_path, + help="Show location of patchfile") + parser.add_option("--continuation", action="store_const", + const=paths.determine_continuation_path, + dest="display", + help="Show location of continuation file") + parser.add_option("--cacherev", action="store_const", + const=paths.determine_cacherev_path, dest="display", + help="Show location of cacherev file") + return parser + + def help(self, parser=None): + """ + Prints a help message. + + :param parser: If supplied, the parser to use for generating help. If \ + not supplied, it is retrieved. + :type parser: cmdutil.CmdOptionParser + """ + if parser==None: + parser=self.get_parser() + parser.print_help() + print """ +Expands aliases and prints the name of the specified revision. Instead of +the name, several options can be used to print locations. If more than one is +specified, the last one is used. + """ + help_tree_spec() + return + +class Revisions(BaseCommand): + """ + Print a revision name based on a revision specifier + """ + def __init__(self): + self.description="Lists revisions" + self.cl_revisions = [] + + def do_command(self, cmdargs): + """ + Master function that perfoms the "revision" command. + """ + (options, args) = self.get_parser().parse_args(cmdargs) + if len(args) > 1: + raise cmdutil.GetHelp + try: + self.tree = arch.tree_root() + except arch.errors.TreeRootError: + self.tree = None + if options.type == "default": + options.type = "archive" + try: + iter = cmdutil.revision_iterator(self.tree, options.type, args, + options.reverse, options.modified, + options.shallow) + except cmdutil.CantDetermineRevision, e: + raise CommandFailedWrapper(e) + except cmdutil.CantDetermineVersion, e: + raise CommandFailedWrapper(e) + if options.skip is not None: + iter = cmdutil.iter_skip(iter, int(options.skip)) + + try: + for revision in iter: + log = None + if isinstance(revision, arch.Patchlog): + log = revision + revision=revision.revision + out = options.display(revision) + if out is not None: + print out + if log is None and (options.summary or options.creator or + options.date or options.merges): + log = revision.patchlog + if options.creator: + print " %s" % log.creator + if options.date: + print " %s" % time.strftime('%Y-%m-%d %H:%M:%S %Z', log.date) + if options.summary: + print " %s" % log.summary + if options.merges: + showed_title = False + for revision in log.merged_patches: + if not showed_title: + print " Merged:" + showed_title = True + print " %s" % revision + if len(self.cl_revisions) > 0: + print pylon.changelog_for_merge(self.cl_revisions) + except pylon.errors.TreeRootNone: + raise CommandFailedWrapper( + Exception("This option can only be used in a project tree.")) + + def changelog_append(self, revision): + if isinstance(revision, arch.Revision): + revision=arch.Patchlog(revision) + self.cl_revisions.append(revision) + + def get_parser(self): + """ + Returns the options parser to use for the "revision" command. + + :rtype: cmdutil.CmdOptionParser + """ + parser=cmdutil.CmdOptionParser("fai revisions [version/revision]") + select = cmdutil.OptionGroup(parser, "Selection options", + "Control which revisions are listed. These options" + " are mutually exclusive. If more than one is" + " specified, the last is used.") + + cmdutil.add_revision_iter_options(select) + parser.add_option("", "--skip", dest="skip", + help="Skip revisions. Positive numbers skip from " + "beginning, negative skip from end.", + metavar="NUMBER") + + parser.add_option_group(select) + + format = cmdutil.OptionGroup(parser, "Revision format options", + "These control the appearance of listed revisions") + format.add_option("", "--location", action="store_const", + const=paths.determine_path, dest="display", + help="Show location instead of name", default=str) + format.add_option("--import", action="store_const", + const=paths.determine_import_path, dest="display", + help="Show location of import file") + format.add_option("--log", action="store_const", + const=paths.determine_log_path, dest="display", + help="Show location of log file") + format.add_option("--patch", action="store_const", + dest="display", const=paths.determine_patch_path, + help="Show location of patchfile") + format.add_option("--continuation", action="store_const", + const=paths.determine_continuation_path, + dest="display", + help="Show location of continuation file") + format.add_option("--cacherev", action="store_const", + const=paths.determine_cacherev_path, dest="display", + help="Show location of cacherev file") + format.add_option("--changelog", action="store_const", + const=self.changelog_append, dest="display", + help="Show location of cacherev file") + parser.add_option_group(format) + display = cmdutil.OptionGroup(parser, "Display format options", + "These control the display of data") + display.add_option("-r", "--reverse", action="store_true", + dest="reverse", help="Sort from newest to oldest") + display.add_option("-s", "--summary", action="store_true", + dest="summary", help="Show patchlog summary") + display.add_option("-D", "--date", action="store_true", + dest="date", help="Show patchlog date") + display.add_option("-c", "--creator", action="store_true", + dest="creator", help="Show the id that committed the" + " revision") + display.add_option("-m", "--merges", action="store_true", + dest="merges", help="Show the revisions that were" + " merged") + parser.add_option_group(display) + return parser + def help(self, parser=None): + """Attempt to explain the revisions command + + :param parser: If supplied, used to determine options + """ + if parser==None: + parser=self.get_parser() + parser.print_help() + print """List revisions. + """ + help_tree_spec() + + +class Get(BaseCommand): + """ + Retrieve a revision from the archive + """ + def __init__(self): + self.description="Retrieve a revision from the archive" + self.parser=self.get_parser() + + + def get_completer(self, arg, index): + if index > 0: + return None + try: + tree = arch.tree_root() + except: + tree = None + return cmdutil.iter_revision_completions(arg, tree) + + + def do_command(self, cmdargs): + """ + Master function that perfoms the "get" command. + """ + (options, args) = self.parser.parse_args(cmdargs) + if len(args) < 1: + return self.help() + try: + tree = arch.tree_root() + except arch.errors.TreeRootError: + tree = None + + arch_loc = None + try: + revision, arch_loc = paths.full_path_decode(args[0]) + except Exception, e: + revision = cmdutil.determine_revision_arch(tree, args[0], + check_existence=False, allow_package=True) + if len(args) > 1: + directory = args[1] + else: + directory = str(revision.nonarch) + if os.path.exists(directory): + raise DirectoryExists(directory) + cmdutil.ensure_archive_registered(revision.archive, arch_loc) + try: + cmdutil.ensure_revision_exists(revision) + except cmdutil.NoSuchRevision, e: + raise CommandFailedWrapper(e) + + link = cmdutil.prompt ("get link") + for line in cmdutil.iter_get(revision, directory, link, + options.no_pristine, + options.no_greedy_add): + cmdutil.colorize(line) + + def get_parser(self): + """ + Returns the options parser to use for the "get" command. + + :rtype: cmdutil.CmdOptionParser + """ + parser=cmdutil.CmdOptionParser("fai get revision [dir]") + parser.add_option("--no-pristine", action="store_true", + dest="no_pristine", + help="Do not make pristine copy for reference") + parser.add_option("--no-greedy-add", action="store_true", + dest="no_greedy_add", + help="Never add to greedy libraries") + + return parser + + def help(self, parser=None): + """ + Prints a help message. + + :param parser: If supplied, the parser to use for generating help. If \ + not supplied, it is retrieved. + :type parser: cmdutil.CmdOptionParser + """ + if parser==None: + parser=self.get_parser() + parser.print_help() + print """ +Expands aliases and constructs a project tree for a revision. If the optional +"dir" argument is provided, the project tree will be stored in this directory. + """ + help_tree_spec() + return + +class PromptCmd(cmd.Cmd): + def __init__(self): + cmd.Cmd.__init__(self) + self.prompt = "Fai> " + try: + self.tree = arch.tree_root() + except: + self.tree = None + self.set_title() + self.set_prompt() + self.fake_aba = abacmds.AbaCmds() + self.identchars += '-' + self.history_file = os.path.expanduser("~/.fai-history") + readline.set_completer_delims(string.whitespace) + if os.access(self.history_file, os.R_OK) and \ + os.path.isfile(self.history_file): + readline.read_history_file(self.history_file) + self.cwd = os.getcwd() + + def write_history(self): + readline.write_history_file(self.history_file) + + def do_quit(self, args): + self.write_history() + sys.exit(0) + + def do_exit(self, args): + self.do_quit(args) + + def do_EOF(self, args): + print + self.do_quit(args) + + def postcmd(self, line, bar): + self.set_title() + self.set_prompt() + + def set_prompt(self): + if self.tree is not None: + try: + prompt = pylon.alias_or_version(self.tree.tree_version, + self.tree, + full=False) + if prompt is not None: + prompt = " " + prompt + except: + prompt = "" + else: + prompt = "" + self.prompt = "Fai%s> " % prompt + + def set_title(self, command=None): + try: + version = pylon.alias_or_version(self.tree.tree_version, self.tree, + full=False) + except: + version = "[no version]" + if command is None: + command = "" + sys.stdout.write(terminal.term_title("Fai %s %s" % (command, version))) + + def do_cd(self, line): + if line == "": + line = "~" + line = os.path.expanduser(line) + if os.path.isabs(line): + newcwd = line + else: + newcwd = self.cwd+'/'+line + newcwd = os.path.normpath(newcwd) + try: + os.chdir(newcwd) + self.cwd = newcwd + except Exception, e: + print e + try: + self.tree = arch.tree_root() + except: + self.tree = None + + def do_help(self, line): + Help()(line) + + def default(self, line): + args = line.split() + if find_command(args[0]): + try: + find_command(args[0]).do_command(args[1:]) + except cmdutil.BadCommandOption, e: + print e + except cmdutil.GetHelp, e: + find_command(args[0]).help() + except CommandFailed, e: + print e + except arch.errors.ArchiveNotRegistered, e: + print e + except KeyboardInterrupt, e: + print "Interrupted" + except arch.util.ExecProblem, e: + print e.proc.error.rstrip('\n') + except cmdutil.CantDetermineVersion, e: + print e + except cmdutil.CantDetermineRevision, e: + print e + except Exception, e: + print "Unhandled error:\n%s" % errors.exception_str(e) + + elif suggestions.has_key(args[0]): + print suggestions[args[0]] + + elif self.fake_aba.is_command(args[0]): + tree = None + try: + tree = arch.tree_root() + except arch.errors.TreeRootError: + pass + cmd = self.fake_aba.is_command(args[0]) + try: + cmd.run(cmdutil.expand_prefix_alias(args[1:], tree)) + except KeyboardInterrupt, e: + print "Interrupted" + + elif options.tla_fallthrough and args[0] != "rm" and \ + cmdutil.is_tla_command(args[0]): + try: + tree = None + try: + tree = arch.tree_root() + except arch.errors.TreeRootError: + pass + args = cmdutil.expand_prefix_alias(args, tree) + arch.util.exec_safe('tla', args, stderr=sys.stderr, + expected=(0, 1)) + except arch.util.ExecProblem, e: + pass + except KeyboardInterrupt, e: + print "Interrupted" + else: + try: + try: + tree = arch.tree_root() + except arch.errors.TreeRootError: + tree = None + args=line.split() + os.system(" ".join(cmdutil.expand_prefix_alias(args, tree))) + except KeyboardInterrupt, e: + print "Interrupted" + + def completenames(self, text, line, begidx, endidx): + completions = [] + iter = iter_command_names(self.fake_aba) + try: + if len(line) > 0: + arg = line.split()[-1] + else: + arg = "" + iter = cmdutil.iter_munged_completions(iter, arg, text) + except Exception, e: + print e + return list(iter) + + def completedefault(self, text, line, begidx, endidx): + """Perform completion for native commands. + + :param text: The text to complete + :type text: str + :param line: The entire line to complete + :type line: str + :param begidx: The start of the text in the line + :type begidx: int + :param endidx: The end of the text in the line + :type endidx: int + """ + try: + (cmd, args, foo) = self.parseline(line) + command_obj=find_command(cmd) + if command_obj is not None: + return command_obj.complete(args.split(), text) + elif not self.fake_aba.is_command(cmd) and \ + cmdutil.is_tla_command(cmd): + iter = cmdutil.iter_supported_switches(cmd) + if len(args) > 0: + arg = args.split()[-1] + else: + arg = "" + if arg.startswith("-"): + return list(cmdutil.iter_munged_completions(iter, arg, + text)) + else: + return list(cmdutil.iter_munged_completions( + cmdutil.iter_file_completions(arg), arg, text)) + + + elif cmd == "cd": + if len(args) > 0: + arg = args.split()[-1] + else: + arg = "" + iter = cmdutil.iter_dir_completions(arg) + iter = cmdutil.iter_munged_completions(iter, arg, text) + return list(iter) + elif len(args)>0: + arg = args.split()[-1] + iter = cmdutil.iter_file_completions(arg) + return list(cmdutil.iter_munged_completions(iter, arg, text)) + else: + return self.completenames(text, line, begidx, endidx) + except Exception, e: + print e + + +def iter_command_names(fake_aba): + for entry in cmdutil.iter_combine([commands.iterkeys(), + fake_aba.get_commands(), + cmdutil.iter_tla_commands(False)]): + if not suggestions.has_key(str(entry)): + yield entry + + +def iter_source_file_completions(tree, arg): + treepath = arch_compound.tree_cwd(tree) + if len(treepath) > 0: + dirs = [treepath] + else: + dirs = None + for file in tree.iter_inventory(dirs, source=True, both=True): + file = file_completion_match(file, treepath, arg) + if file is not None: + yield file + + +def iter_untagged(tree, dirs): + for file in arch_core.iter_inventory_filter(tree, dirs, tagged=False, + categories=arch_core.non_root, + control_files=True): + yield file.name + + +def iter_untagged_completions(tree, arg): + """Generate an iterator for all visible untagged files that match arg. + + :param tree: The tree to look for untagged files in + :type tree: `arch.WorkingTree` + :param arg: The argument to match + :type arg: str + :return: An iterator of all matching untagged files + :rtype: iterator of str + """ + treepath = arch_compound.tree_cwd(tree) + if len(treepath) > 0: + dirs = [treepath] + else: + dirs = None + + for file in iter_untagged(tree, dirs): + file = file_completion_match(file, treepath, arg) + if file is not None: + yield file + + +def file_completion_match(file, treepath, arg): + """Determines whether a file within an arch tree matches the argument. + + :param file: The rooted filename + :type file: str + :param treepath: The path to the cwd within the tree + :type treepath: str + :param arg: The prefix to match + :return: The completion name, or None if not a match + :rtype: str + """ + if not file.startswith(treepath): + return None + if treepath != "": + file = file[len(treepath)+1:] + + if not file.startswith(arg): + return None + if os.path.isdir(file): + file += '/' + return file + +def iter_modified_file_completions(tree, arg): + """Returns a list of modified files that match the specified prefix. + + :param tree: The current tree + :type tree: `arch.WorkingTree` + :param arg: The prefix to match + :type arg: str + """ + treepath = arch_compound.tree_cwd(tree) + tmpdir = util.tmpdir() + changeset = tmpdir+"/changeset" + completions = [] + revision = cmdutil.determine_revision_tree(tree) + for line in arch.iter_delta(revision, tree, changeset): + if isinstance(line, arch.FileModification): + file = file_completion_match(line.name[1:], treepath, arg) + if file is not None: + completions.append(file) + shutil.rmtree(tmpdir) + return completions + +class Shell(BaseCommand): + def __init__(self): + self.description = "Runs Fai as a shell" + + def do_command(self, cmdargs): + if len(cmdargs)!=0: + raise cmdutil.GetHelp + prompt = PromptCmd() + try: + prompt.cmdloop() + finally: + prompt.write_history() + +class AddID(BaseCommand): + """ + Adds an inventory id for the given file + """ + def __init__(self): + self.description="Add an inventory id for a given file" + + def get_completer(self, arg, index): + tree = arch.tree_root() + return iter_untagged_completions(tree, arg) + + def do_command(self, cmdargs): + """ + Master function that perfoms the "revision" command. + """ + parser=self.get_parser() + (options, args) = parser.parse_args(cmdargs) + + try: + tree = arch.tree_root() + except arch.errors.TreeRootError, e: + raise pylon.errors.CommandFailedWrapper(e) + + + if (len(args) == 0) == (options.untagged == False): + raise cmdutil.GetHelp + + #if options.id and len(args) != 1: + # print "If --id is specified, only one file can be named." + # return + + method = tree.tagging_method + + if options.id_type == "tagline": + if method != "tagline": + if not cmdutil.prompt("Tagline in other tree"): + if method == "explicit" or method == "implicit": + options.id_type == method + else: + print "add-id not supported for \"%s\" tagging method"\ + % method + return + + elif options.id_type == "implicit": + if method != "implicit": + if not cmdutil.prompt("Implicit in other tree"): + if method == "explicit" or method == "tagline": + options.id_type == method + else: + print "add-id not supported for \"%s\" tagging method"\ + % method + return + elif options.id_type == "explicit": + if method != "tagline" and method != explicit: + if not prompt("Explicit in other tree"): + print "add-id not supported for \"%s\" tagging method" % \ + method + return + + if options.id_type == "auto": + if method != "tagline" and method != "explicit" \ + and method !="implicit": + print "add-id not supported for \"%s\" tagging method" % method + return + else: + options.id_type = method + if options.untagged: + args = None + self.add_ids(tree, options.id_type, args) + + def add_ids(self, tree, id_type, files=()): + """Add inventory ids to files. + + :param tree: the tree the files are in + :type tree: `arch.WorkingTree` + :param id_type: the type of id to add: "explicit" or "tagline" + :type id_type: str + :param files: The list of files to add. If None do all untagged. + :type files: tuple of str + """ + + untagged = (files is None) + if untagged: + files = list(iter_untagged(tree, None)) + previous_files = [] + while len(files) > 0: + previous_files.extend(files) + if id_type == "explicit": + cmdutil.add_id(files) + elif id_type == "tagline" or id_type == "implicit": + for file in files: + try: + implicit = (id_type == "implicit") + cmdutil.add_tagline_or_explicit_id(file, False, + implicit) + except cmdutil.AlreadyTagged: + print "\"%s\" already has a tagline." % file + except cmdutil.NoCommentSyntax: + pass + #do inventory after tagging until no untagged files are encountered + if untagged: + files = [] + for file in iter_untagged(tree, None): + if not file in previous_files: + files.append(file) + + else: + break + + def get_parser(self): + """ + Returns the options parser to use for the "revision" command. + + :rtype: cmdutil.CmdOptionParser + """ + parser=cmdutil.CmdOptionParser("fai add-id file1 [file2] [file3]...") +# ddaa suggests removing this to promote GUIDs. Let's see who squalks. +# parser.add_option("-i", "--id", dest="id", +# help="Specify id for a single file", default=None) + parser.add_option("--tltl", action="store_true", + dest="lord_style", help="Use Tom Lord's style of id.") + parser.add_option("--explicit", action="store_const", + const="explicit", dest="id_type", + help="Use an explicit id", default="auto") + parser.add_option("--tagline", action="store_const", + const="tagline", dest="id_type", + help="Use a tagline id") + parser.add_option("--implicit", action="store_const", + const="implicit", dest="id_type", + help="Use an implicit id (deprecated)") + parser.add_option("--untagged", action="store_true", + dest="untagged", default=False, + help="tag all untagged files") + return parser + + def help(self, parser=None): + """ + Prints a help message. + + :param parser: If supplied, the parser to use for generating help. If \ + not supplied, it is retrieved. + :type parser: cmdutil.CmdOptionParser + """ + if parser==None: + parser=self.get_parser() + parser.print_help() + print """ +Adds an inventory to the specified file(s) and directories. If --untagged is +specified, adds inventory to all untagged files and directories. + """ + return + + +class Merge(BaseCommand): + """ + Merges changes from other versions into the current tree + """ + def __init__(self): + self.description="Merges changes from other versions" + try: + self.tree = arch.tree_root() + except: + self.tree = None + + + def get_completer(self, arg, index): + if self.tree is None: + raise arch.errors.TreeRootError + return cmdutil.merge_completions(self.tree, arg, index) + + def do_command(self, cmdargs): + """ + Master function that perfoms the "merge" command. + """ + parser=self.get_parser() + (options, args) = parser.parse_args(cmdargs) + if options.diff3: + action="star-merge" + else: + action = options.action + + if self.tree is None: + raise arch.errors.TreeRootError(os.getcwd()) + if cmdutil.has_changed(ancillary.comp_revision(self.tree)): + raise UncommittedChanges(self.tree) + + if len(args) > 0: + revisions = [] + for arg in args: + revisions.append(cmdutil.determine_revision_arch(self.tree, + arg)) + source = "from commandline" + else: + revisions = ancillary.iter_partner_revisions(self.tree, + self.tree.tree_version) + source = "from partner version" + revisions = misc.rewind_iterator(revisions) + try: + revisions.next() + revisions.rewind() + except StopIteration, e: + revision = cmdutil.tag_cur(self.tree) + if revision is None: + raise CantDetermineRevision("", "No version specified, no " + "partner-versions, and no tag" + " source") + revisions = [revision] + source = "from tag source" + for revision in revisions: + cmdutil.ensure_archive_registered(revision.archive) + cmdutil.colorize(arch.Chatter("* Merging %s [%s]" % + (revision, source))) + if action=="native-merge" or action=="update": + if self.native_merge(revision, action) == 0: + continue + elif action=="star-merge": + try: + self.star_merge(revision, options.diff3) + except errors.MergeProblem, e: + break + if cmdutil.has_changed(self.tree.tree_version): + break + + def star_merge(self, revision, diff3): + """Perform a star-merge on the current tree. + + :param revision: The revision to use for the merge + :type revision: `arch.Revision` + :param diff3: If true, do a diff3 merge + :type diff3: bool + """ + try: + for line in self.tree.iter_star_merge(revision, diff3=diff3): + cmdutil.colorize(line) + except arch.util.ExecProblem, e: + if e.proc.status is not None and e.proc.status == 1: + if e.proc.error: + print e.proc.error + raise MergeProblem + else: + raise + + def native_merge(self, other_revision, action): + """Perform a native-merge on the current tree. + + :param other_revision: The revision to use for the merge + :type other_revision: `arch.Revision` + :return: 0 if the merge was skipped, 1 if it was applied + """ + other_tree = arch_compound.find_or_make_local_revision(other_revision) + try: + if action == "native-merge": + ancestor = arch_compound.merge_ancestor2(self.tree, other_tree, + other_revision) + elif action == "update": + ancestor = arch_compound.tree_latest(self.tree, + other_revision.version) + except CantDetermineRevision, e: + raise CommandFailedWrapper(e) + cmdutil.colorize(arch.Chatter("* Found common ancestor %s" % ancestor)) + if (ancestor == other_revision): + cmdutil.colorize(arch.Chatter("* Skipping redundant merge" + % ancestor)) + return 0 + delta = cmdutil.apply_delta(ancestor, other_tree, self.tree) + for line in cmdutil.iter_apply_delta_filter(delta): + cmdutil.colorize(line) + return 1 + + + + def get_parser(self): + """ + Returns the options parser to use for the "merge" command. + + :rtype: cmdutil.CmdOptionParser + """ + parser=cmdutil.CmdOptionParser("fai merge [VERSION]") + parser.add_option("-s", "--star-merge", action="store_const", + dest="action", help="Use star-merge", + const="star-merge", default="native-merge") + parser.add_option("--update", action="store_const", + dest="action", help="Use update picker", + const="update") + parser.add_option("--diff3", action="store_true", + dest="diff3", + help="Use diff3 for merge (implies star-merge)") + return parser + + def help(self, parser=None): + """ + Prints a help message. + + :param parser: If supplied, the parser to use for generating help. If \ + not supplied, it is retrieved. + :type parser: cmdutil.CmdOptionParser + """ + if parser==None: + parser=self.get_parser() + parser.print_help() + print """ +Performs a merge operation using the specified version. + """ + return + +class ELog(BaseCommand): + """ + Produces a raw patchlog and invokes the user's editor + """ + def __init__(self): + self.description="Edit a patchlog to commit" + try: + self.tree = arch.tree_root() + except: + self.tree = None + + + def do_command(self, cmdargs): + """ + Master function that perfoms the "elog" command. + """ + parser=self.get_parser() + (options, args) = parser.parse_args(cmdargs) + if self.tree is None: + raise arch.errors.TreeRootError + + try: + edit_log(self.tree, self.tree.tree_version) + except pylon.errors.NoEditorSpecified, e: + raise pylon.errors.CommandFailedWrapper(e) + + def get_parser(self): + """ + Returns the options parser to use for the "merge" command. + + :rtype: cmdutil.CmdOptionParser + """ + parser=cmdutil.CmdOptionParser("fai elog") + return parser + + + def help(self, parser=None): + """ + Invokes $EDITOR to produce a log for committing. + + :param parser: If supplied, the parser to use for generating help. If \ + not supplied, it is retrieved. + :type parser: cmdutil.CmdOptionParser + """ + if parser==None: + parser=self.get_parser() + parser.print_help() + print """ +Invokes $EDITOR to produce a log for committing. + """ + return + +def edit_log(tree, version): + """Makes and edits the log for a tree. Does all kinds of fancy things + like log templates and merge summaries and log-for-merge + + :param tree: The tree to edit the log for + :type tree: `arch.WorkingTree` + """ + #ensure we have an editor before preparing the log + cmdutil.find_editor() + log = tree.log_message(create=False, version=version) + log_is_new = False + if log is None or cmdutil.prompt("Overwrite log"): + if log is not None: + os.remove(log.name) + log = tree.log_message(create=True, version=version) + log_is_new = True + tmplog = log.name + template = pylon.log_template_path(tree) + if template: + shutil.copyfile(template, tmplog) + comp_version = ancillary.comp_revision(tree).version + new_merges = cmdutil.iter_new_merges(tree, comp_version) + new_merges = cmdutil.direct_merges(new_merges) + log["Summary"] = pylon.merge_summary(new_merges, + version) + if len(new_merges) > 0: + if cmdutil.prompt("Log for merge"): + if cmdutil.prompt("changelog for merge"): + mergestuff = "Patches applied:\n" + mergestuff += pylon.changelog_for_merge(new_merges) + else: + mergestuff = cmdutil.log_for_merge(tree, comp_version) + log.description += mergestuff + log.save() + try: + cmdutil.invoke_editor(log.name) + except: + if log_is_new: + os.remove(log.name) + raise + + +class MirrorArchive(BaseCommand): + """ + Updates a mirror from an archive + """ + def __init__(self): + self.description="Update a mirror from an archive" + + def do_command(self, cmdargs): + """ + Master function that perfoms the "revision" command. + """ + + parser=self.get_parser() + (options, args) = parser.parse_args(cmdargs) + if len(args) > 1: + raise GetHelp + try: + tree = arch.tree_root() + except: + tree = None + + if len(args) == 0: + if tree is not None: + name = tree.tree_version() + else: + name = cmdutil.expand_alias(args[0], tree) + name = arch.NameParser(name) + + to_arch = name.get_archive() + from_arch = cmdutil.get_mirror_source(arch.Archive(to_arch)) + limit = name.get_nonarch() + + iter = arch_core.mirror_archive(from_arch,to_arch, limit) + for line in arch.chatter_classifier(iter): + cmdutil.colorize(line) + + def get_parser(self): + """ + Returns the options parser to use for the "revision" command. + + :rtype: cmdutil.CmdOptionParser + """ + parser=cmdutil.CmdOptionParser("fai mirror-archive ARCHIVE") + return parser + + def help(self, parser=None): + """ + Prints a help message. + + :param parser: If supplied, the parser to use for generating help. If \ + not supplied, it is retrieved. + :type parser: cmdutil.CmdOptionParser + """ + if parser==None: + parser=self.get_parser() + parser.print_help() + print """ +Updates a mirror from an archive. If a branch, package, or version is +supplied, only changes under it are mirrored. + """ + return + +def help_tree_spec(): + print """Specifying revisions (default: tree) +Revisions may be specified by alias, revision, version or patchlevel. +Revisions or versions may be fully qualified. Unqualified revisions, versions, +or patchlevels use the archive of the current project tree. Versions will +use the latest patchlevel in the tree. Patchlevels will use the current tree- +version. + +Use "alias" to list available (user and automatic) aliases.""" + +auto_alias = [ +"acur", +"The latest revision in the archive of the tree-version. You can specify \ +a different version like so: acur:foo--bar--0 (aliases can be used)", +"tcur", +"""(tree current) The latest revision in the tree of the tree-version. \ +You can specify a different version like so: tcur:foo--bar--0 (aliases can be \ +used).""", +"tprev" , +"""(tree previous) The previous revision in the tree of the tree-version. To \ +specify an older revision, use a number, e.g. "tprev:4" """, +"tanc" , +"""(tree ancestor) The ancestor revision of the tree To specify an older \ +revision, use a number, e.g. "tanc:4".""", +"tdate" , +"""(tree date) The latest revision from a given date, e.g. "tdate:July 6".""", +"tmod" , +""" (tree modified) The latest revision to modify a given file, e.g. \ +"tmod:engine.cpp" or "tmod:engine.cpp:16".""", +"ttag" , +"""(tree tag) The revision that was tagged into the current tree revision, \ +according to the tree""", +"tagcur", +"""(tag current) The latest revision of the version that the current tree \ +was tagged from.""", +"mergeanc" , +"""The common ancestor of the current tree and the specified revision. \ +Defaults to the first partner-version's latest revision or to tagcur.""", +] + + +def is_auto_alias(name): + """Determine whether a name is an auto alias name + + :param name: the name to check + :type name: str + :return: True if the name is an auto alias, false if not + :rtype: bool + """ + return name in [f for (f, v) in pylon.util.iter_pairs(auto_alias)] + + +def display_def(iter, wrap = 80): + """Display a list of definitions + + :param iter: iter of name, definition pairs + :type iter: iter of (str, str) + :param wrap: The width for text wrapping + :type wrap: int + """ + vals = list(iter) + maxlen = 0 + for (key, value) in vals: + if len(key) > maxlen: + maxlen = len(key) + for (key, value) in vals: + tw=textwrap.TextWrapper(width=wrap, + initial_indent=key.rjust(maxlen)+" : ", + subsequent_indent="".rjust(maxlen+3)) + print tw.fill(value) + + +def help_aliases(tree): + print """Auto-generated aliases""" + display_def(pylon.util.iter_pairs(auto_alias)) + print "User aliases" + display_def(ancillary.iter_all_alias(tree)) + +class Inventory(BaseCommand): + """List the status of files in the tree""" + def __init__(self): + self.description=self.__doc__ + + def do_command(self, cmdargs): + """ + Master function that perfoms the "revision" command. + """ + + parser=self.get_parser() + (options, args) = parser.parse_args(cmdargs) + tree = arch.tree_root() + categories = [] + + if (options.source): + categories.append(arch_core.SourceFile) + if (options.precious): + categories.append(arch_core.PreciousFile) + if (options.backup): + categories.append(arch_core.BackupFile) + if (options.junk): + categories.append(arch_core.JunkFile) + + if len(categories) == 1: + show_leading = False + else: + show_leading = True + + if len(categories) == 0: + categories = None + + if options.untagged: + categories = arch_core.non_root + show_leading = False + tagged = False + else: + tagged = None + + for file in arch_core.iter_inventory_filter(tree, None, + control_files=options.control_files, + categories = categories, tagged=tagged): + print arch_core.file_line(file, + category = show_leading, + untagged = show_leading, + id = options.ids) + + def get_parser(self): + """ + Returns the options parser to use for the "revision" command. + + :rtype: cmdutil.CmdOptionParser + """ + parser=cmdutil.CmdOptionParser("fai inventory [options]") + parser.add_option("--ids", action="store_true", dest="ids", + help="Show file ids") + parser.add_option("--control", action="store_true", + dest="control_files", help="include control files") + parser.add_option("--source", action="store_true", dest="source", + help="List source files") + parser.add_option("--backup", action="store_true", dest="backup", + help="List backup files") + parser.add_option("--precious", action="store_true", dest="precious", + help="List precious files") + parser.add_option("--junk", action="store_true", dest="junk", + help="List junk files") + parser.add_option("--unrecognized", action="store_true", + dest="unrecognized", help="List unrecognized files") + parser.add_option("--untagged", action="store_true", + dest="untagged", help="List only untagged files") + return parser + + def help(self, parser=None): + """ + Prints a help message. + + :param parser: If supplied, the parser to use for generating help. If \ + not supplied, it is retrieved. + :type parser: cmdutil.CmdOptionParser + """ + if parser==None: + parser=self.get_parser() + parser.print_help() + print """ +Lists the status of files in the archive: +S source +P precious +B backup +J junk +U unrecognized +T tree root +? untagged-source +Leading letter are not displayed if only one kind of file is shown + """ + return + + +class Alias(BaseCommand): + """List or adjust aliases""" + def __init__(self): + self.description=self.__doc__ + + def get_completer(self, arg, index): + if index > 2: + return () + try: + self.tree = arch.tree_root() + except: + self.tree = None + + if index == 0: + return [part[0]+" " for part in ancillary.iter_all_alias(self.tree)] + elif index == 1: + return cmdutil.iter_revision_completions(arg, self.tree) + + + def do_command(self, cmdargs): + """ + Master function that perfoms the "revision" command. + """ + + parser=self.get_parser() + (options, args) = parser.parse_args(cmdargs) + try: + self.tree = arch.tree_root() + except: + self.tree = None + + + try: + options.action(args, options) + except cmdutil.ForbiddenAliasSyntax, e: + raise CommandFailedWrapper(e) + + def no_prefix(self, alias): + if alias.startswith("^"): + alias = alias[1:] + return alias + + def arg_dispatch(self, args, options): + """Add, modify, or list aliases, depending on number of arguments + + :param args: The list of commandline arguments + :type args: list of str + :param options: The commandline options + """ + if len(args) == 0: + help_aliases(self.tree) + return + else: + alias = self.no_prefix(args[0]) + if len(args) == 1: + self.print_alias(alias) + elif (len(args)) == 2: + self.add(alias, args[1], options) + else: + raise cmdutil.GetHelp + + def print_alias(self, alias): + answer = None + if is_auto_alias(alias): + raise pylon.errors.IsAutoAlias(alias, "\"%s\" is an auto alias." + " Use \"revision\" to expand auto aliases." % alias) + for pair in ancillary.iter_all_alias(self.tree): + if pair[0] == alias: + answer = pair[1] + if answer is not None: + print answer + else: + print "The alias %s is not assigned." % alias + + def add(self, alias, expansion, options): + """Add or modify aliases + + :param alias: The alias name to create/modify + :type alias: str + :param expansion: The expansion to assign to the alias name + :type expansion: str + :param options: The commandline options + """ + if is_auto_alias(alias): + raise IsAutoAlias(alias) + newlist = "" + written = False + new_line = "%s=%s\n" % (alias, cmdutil.expand_alias(expansion, + self.tree)) + ancillary.check_alias(new_line.rstrip("\n"), [alias, expansion]) + + for pair in self.get_iterator(options): + if pair[0] != alias: + newlist+="%s=%s\n" % (pair[0], pair[1]) + elif not written: + newlist+=new_line + written = True + if not written: + newlist+=new_line + self.write_aliases(newlist, options) + + def delete(self, args, options): + """Delete the specified alias + + :param args: The list of arguments + :type args: list of str + :param options: The commandline options + """ + deleted = False + if len(args) != 1: + raise cmdutil.GetHelp + alias = self.no_prefix(args[0]) + if is_auto_alias(alias): + raise IsAutoAlias(alias) + newlist = "" + for pair in self.get_iterator(options): + if pair[0] != alias: + newlist+="%s=%s\n" % (pair[0], pair[1]) + else: + deleted = True + if not deleted: + raise errors.NoSuchAlias(alias) + self.write_aliases(newlist, options) + + def get_alias_file(self, options): + """Return the name of the alias file to use + + :param options: The commandline options + """ + if options.tree: + if self.tree is None: + self.tree == arch.tree_root() + return str(self.tree)+"/{arch}/+aliases" + else: + return "~/.aba/aliases" + + def get_iterator(self, options): + """Return the alias iterator to use + + :param options: The commandline options + """ + return ancillary.iter_alias(self.get_alias_file(options)) + + def write_aliases(self, newlist, options): + """Safely rewrite the alias file + :param newlist: The new list of aliases + :type newlist: str + :param options: The commandline options + """ + filename = os.path.expanduser(self.get_alias_file(options)) + file = util.NewFileVersion(filename) + file.write(newlist) + file.commit() + + + def get_parser(self): + """ + Returns the options parser to use for the "alias" command. + + :rtype: cmdutil.CmdOptionParser + """ + parser=cmdutil.CmdOptionParser("fai alias [ALIAS] [NAME]") + parser.add_option("-d", "--delete", action="store_const", dest="action", + const=self.delete, default=self.arg_dispatch, + help="Delete an alias") + parser.add_option("--tree", action="store_true", dest="tree", + help="Create a per-tree alias", default=False) + return parser + + def help(self, parser=None): + """ + Prints a help message. + + :param parser: If supplied, the parser to use for generating help. If \ + not supplied, it is retrieved. + :type parser: cmdutil.CmdOptionParser + """ + if parser==None: + parser=self.get_parser() + parser.print_help() + print """ +Lists current aliases or modifies the list of aliases. + +If no arguments are supplied, aliases will be listed. If two arguments are +supplied, the specified alias will be created or modified. If -d or --delete +is supplied, the specified alias will be deleted. + +You can create aliases that refer to any fully-qualified part of the +Arch namespace, e.g. +archive, +archive/category, +archive/category--branch, +archive/category--branch--version (my favourite) +archive/category--branch--version--patchlevel + +Aliases can be used automatically by native commands. To use them +with external or tla commands, prefix them with ^ (you can do this +with native commands, too). +""" + + +class RequestMerge(BaseCommand): + """Submit a merge request to Bug Goo""" + def __init__(self): + self.description=self.__doc__ + + def do_command(self, cmdargs): + """Submit a merge request + + :param cmdargs: The commandline arguments + :type cmdargs: list of str + """ + parser = self.get_parser() + (options, args) = parser.parse_args(cmdargs) + try: + cmdutil.find_editor() + except pylon.errors.NoEditorSpecified, e: + raise pylon.errors.CommandFailedWrapper(e) + try: + self.tree=arch.tree_root() + except: + self.tree=None + base, revisions = self.revision_specs(args) + message = self.make_headers(base, revisions) + message += self.make_summary(revisions) + path = self.edit_message(message) + message = self.tidy_message(path) + if cmdutil.prompt("Send merge"): + self.send_message(message) + print "Merge request sent" + + def make_headers(self, base, revisions): + """Produce email and Bug Goo header strings + + :param base: The base revision to apply merges to + :type base: `arch.Revision` + :param revisions: The revisions to replay into the base + :type revisions: list of `arch.Patchlog` + :return: The headers + :rtype: str + """ + headers = "To: gnu-arch-users@gnu.org\n" + headers += "From: %s\n" % options.fromaddr + if len(revisions) == 1: + headers += "Subject: [MERGE REQUEST] %s\n" % revisions[0].summary + else: + headers += "Subject: [MERGE REQUEST]\n" + headers += "\n" + headers += "Base-Revision: %s\n" % base + for revision in revisions: + headers += "Revision: %s\n" % revision.revision + headers += "Bug: \n\n" + return headers + + def make_summary(self, logs): + """Generate a summary of merges + + :param logs: the patchlogs that were directly added by the merges + :type logs: list of `arch.Patchlog` + :return: the summary + :rtype: str + """ + summary = "" + for log in logs: + summary+=str(log.revision)+"\n" + summary+=log.summary+"\n" + if log.description.strip(): + summary+=log.description.strip('\n')+"\n\n" + return summary + + def revision_specs(self, args): + """Determine the base and merge revisions from tree and arguments. + + :param args: The parsed arguments + :type args: list of str + :return: The base revision and merge revisions + :rtype: `arch.Revision`, list of `arch.Patchlog` + """ + if len(args) > 0: + target_revision = cmdutil.determine_revision_arch(self.tree, + args[0]) + else: + target_revision = arch_compound.tree_latest(self.tree) + if len(args) > 1: + merges = [ arch.Patchlog(cmdutil.determine_revision_arch( + self.tree, f)) for f in args[1:] ] + else: + if self.tree is None: + raise CantDetermineRevision("", "Not in a project tree") + merge_iter = cmdutil.iter_new_merges(self.tree, + target_revision.version, + False) + merges = [f for f in cmdutil.direct_merges(merge_iter)] + return (target_revision, merges) + + def edit_message(self, message): + """Edit an email message in the user's standard editor + + :param message: The message to edit + :type message: str + :return: the path of the edited message + :rtype: str + """ + if self.tree is None: + path = os.get_cwd() + else: + path = self.tree + path += "/,merge-request" + file = open(path, 'w') + file.write(message) + file.flush() + cmdutil.invoke_editor(path) + return path + + def tidy_message(self, path): + """Validate and clean up message. + + :param path: The path to the message to clean up + :type path: str + :return: The parsed message + :rtype: `email.Message` + """ + mail = email.message_from_file(open(path)) + if mail["Subject"].strip() == "[MERGE REQUEST]": + raise BlandSubject + + request = email.message_from_string(mail.get_payload()) + if request.has_key("Bug"): + if request["Bug"].strip()=="": + del request["Bug"] + mail.set_payload(request.as_string()) + return mail + + def send_message(self, message): + """Send a message, using its headers to address it. + + :param message: The message to send + :type message: `email.Message`""" + server = smtplib.SMTP("localhost") + server.sendmail(message['From'], message['To'], message.as_string()) + server.quit() + + def help(self, parser=None): + """Print a usage message + + :param parser: The options parser to use + :type parser: `cmdutil.CmdOptionParser` + """ + if parser is None: + parser = self.get_parser() + parser.print_help() + print """ +Sends a merge request formatted for Bug Goo. Intended use: get the tree +you'd like to merge into. Apply the merges you want. Invoke request-merge. +The merge request will open in your $EDITOR. + +When no TARGET is specified, it uses the current tree revision. When +no MERGE is specified, it uses the direct merges (as in "revisions +--direct-merges"). But you can specify just the TARGET, or all the MERGE +revisions. +""" + + def get_parser(self): + """Produce a commandline parser for this command. + + :rtype: `cmdutil.CmdOptionParser` + """ + parser=cmdutil.CmdOptionParser("request-merge [TARGET] [MERGE1...]") + return parser + +commands = { +'changes' : Changes, +'help' : Help, +'update': Update, +'apply-changes':ApplyChanges, +'cat-log': CatLog, +'commit': Commit, +'revision': Revision, +'revisions': Revisions, +'get': Get, +'revert': Revert, +'shell': Shell, +'add-id': AddID, +'merge': Merge, +'elog': ELog, +'mirror-archive': MirrorArchive, +'ninventory': Inventory, +'alias' : Alias, +'request-merge': RequestMerge, +} + +def my_import(mod_name): + module = __import__(mod_name) + components = mod_name.split('.') + for comp in components[1:]: + module = getattr(module, comp) + return module + +def plugin(mod_name): + module = my_import(mod_name) + module.add_command(commands) + +for file in os.listdir(sys.path[0]+"/command"): + if len(file) > 3 and file[-3:] == ".py" and file != "__init__.py": + plugin("command."+file[:-3]) + +suggestions = { +'apply-delta' : "Try \"apply-changes\".", +'delta' : "To compare two revisions, use \"changes\".", +'diff-rev' : "To compare two revisions, use \"changes\".", +'undo' : "To undo local changes, use \"revert\".", +'undelete' : "To undo only deletions, use \"revert --deletions\"", +'missing-from' : "Try \"revisions --missing-from\".", +'missing' : "Try \"revisions --missing\".", +'missing-merge' : "Try \"revisions --partner-missing\".", +'new-merges' : "Try \"revisions --new-merges\".", +'cachedrevs' : "Try \"revisions --cacherevs\". (no 'd')", +'logs' : "Try \"revisions --logs\"", +'tree-source' : "Use the \"^ttag\" alias (\"revision ^ttag\")", +'latest-revision' : "Use the \"^acur\" alias (\"revision ^acur\")", +'change-version' : "Try \"update REVISION\"", +'tree-revision' : "Use the \"^tcur\" alias (\"revision ^tcur\")", +'rev-depends' : "Use revisions --dependencies", +'auto-get' : "Plain get will do archive lookups", +'tagline' : "Use add-id. It uses taglines in tagline trees", +'emlog' : "Use elog. It automatically adds log-for-merge text, if any", +'library-revisions' : "Use revisions --library", +'file-revert' : "Use revert FILE", +'join-branch' : "Use replay --logs-only" +} +# arch-tag: 19d5739d-3708-486c-93ba-deecc3027fc7 |