Restructure subcommands parser creation
Restructure creation of subcommands to pass a full subparser object to the command class to allow more complex parser creation to facilitate adding argument groups and mutually exclusive groups. Change-Id: Ic6f7e3834cf8bd29b87d5d6cafdb8a9f041f4cf6
This commit is contained in:
		
				
					committed by
					
						
						Darragh Bailey
					
				
			
			
				
	
			
			
			
						parent
						
							7e9436f243
						
					
				
				
					commit
					ca9eefdf12
				
			@@ -16,9 +16,9 @@
 | 
			
		||||
# limitations under the License.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
import abc
 | 
			
		||||
import argparse
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AppendReplaceAction(argparse._AppendAction):
 | 
			
		||||
@@ -38,10 +38,37 @@ class AppendReplaceAction(argparse._AppendAction):
 | 
			
		||||
                                                  option_string)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_subcommands(subparsers):
 | 
			
		||||
class GitUpstreamCommand(object):
 | 
			
		||||
    """Base command class
 | 
			
		||||
 | 
			
		||||
    To create commands simply subclass and implement the necessary abstract
 | 
			
		||||
    methods.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    __metaclass__ = abc.ABCMeta
 | 
			
		||||
 | 
			
		||||
    def __init__(self, parser):
 | 
			
		||||
        self.parser = parser
 | 
			
		||||
 | 
			
		||||
    def validate(self, args):
 | 
			
		||||
        """Verify the arguments passed for this command"""
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    @abc.abstractmethod
 | 
			
		||||
    def run(self, args):
 | 
			
		||||
        """Execute this command"""
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_subcommands(parser):
 | 
			
		||||
 | 
			
		||||
    subparsers = parser.add_subparsers(title="commands", metavar='<command>',
 | 
			
		||||
                                       dest='subcommand')
 | 
			
		||||
 | 
			
		||||
    subcommands = _find_actions(subparsers, os.path.dirname(__file__))
 | 
			
		||||
 | 
			
		||||
    parser.set_defaults(subcommands=subcommands)
 | 
			
		||||
 | 
			
		||||
    return subcommands
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -51,23 +78,18 @@ def _find_actions(subparsers, module_path):
 | 
			
		||||
    for mod in (p[:-len('.py')] for p in os.listdir(module_path) if
 | 
			
		||||
                p.endswith('.py')):
 | 
			
		||||
        __import__(__name__ + '.' + mod)
 | 
			
		||||
        module = sys.modules[__name__ + '.' + mod]
 | 
			
		||||
        for attr in (a for a in dir(module) if a.startswith('do_')):
 | 
			
		||||
            command = attr[3:].replace('_', '-')
 | 
			
		||||
            func = getattr(module, attr)
 | 
			
		||||
            desc = func.__doc__ or ''
 | 
			
		||||
            help = desc.strip().split('\n')[0]
 | 
			
		||||
            args = getattr(func, 'arguments', [])
 | 
			
		||||
 | 
			
		||||
            subparser = subparsers.add_parser(
 | 
			
		||||
                command,
 | 
			
		||||
                help=help,
 | 
			
		||||
                description=desc)
 | 
			
		||||
            subparser.register('action', 'append_replace', AppendReplaceAction)
 | 
			
		||||
    for cmd_class in GitUpstreamCommand.__subclasses__():
 | 
			
		||||
        command = cmd_class.name
 | 
			
		||||
        desc = cmd_class.__doc__ or None
 | 
			
		||||
        help = desc.strip().split('\n')[0]
 | 
			
		||||
 | 
			
		||||
            for (args, kwargs) in args:
 | 
			
		||||
                subparser.add_argument(*args, **kwargs)
 | 
			
		||||
            subparser.set_defaults(func=func)
 | 
			
		||||
            subcommands[command] = subparser
 | 
			
		||||
        subparser = subparsers.add_parser(
 | 
			
		||||
            command,
 | 
			
		||||
            help=help,
 | 
			
		||||
            description=desc)
 | 
			
		||||
        subparser.register('action', 'append_replace', AppendReplaceAction)
 | 
			
		||||
        subparser.set_defaults(cmd=cmd_class(subparser))
 | 
			
		||||
        subcommands[command] = subparser
 | 
			
		||||
 | 
			
		||||
    return subcommands
 | 
			
		||||
 
 | 
			
		||||
@@ -15,16 +15,14 @@
 | 
			
		||||
# limitations under the License.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
import inspect
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
from git import BadObject
 | 
			
		||||
 | 
			
		||||
from git_upstream.commands import GitUpstreamCommand
 | 
			
		||||
from git_upstream.errors import GitUpstreamError
 | 
			
		||||
from git_upstream.lib.utils import GitMixin
 | 
			
		||||
from git_upstream import log
 | 
			
		||||
from git_upstream.log import LogDedentMixin
 | 
			
		||||
from git_upstream import subcommand
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    from git import BadName
 | 
			
		||||
@@ -50,6 +48,7 @@ class Drop(LogDedentMixin, GitMixin):
 | 
			
		||||
    Dropped: Walter White <heisenberg@hp.com>
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    DROP_HEADER = 'Dropped:'
 | 
			
		||||
    NOTE_REF = 'refs/notes/upstream-merge'
 | 
			
		||||
 | 
			
		||||
@@ -124,25 +123,31 @@ class Drop(LogDedentMixin, GitMixin):
 | 
			
		||||
                self.commit)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@subcommand.arg('commit', metavar='<commit>', nargs=None,
 | 
			
		||||
                help='Commit to be marked as dropped')
 | 
			
		||||
@subcommand.arg('-a', '--author', metavar='<author>',
 | 
			
		||||
                dest='author',
 | 
			
		||||
                default=None,
 | 
			
		||||
                help='Git author for the mark')
 | 
			
		||||
def do_drop(args):
 | 
			
		||||
    """
 | 
			
		||||
    Mark a commit as dropped.
 | 
			
		||||
class DropCommand(LogDedentMixin, GitUpstreamCommand):
 | 
			
		||||
    """Mark a commit as dropped.
 | 
			
		||||
 | 
			
		||||
    Marked commits will be skipped during the upstream rebasing process.
 | 
			
		||||
    See also the "git upstream import" command.
 | 
			
		||||
    """
 | 
			
		||||
    name = "drop"
 | 
			
		||||
 | 
			
		||||
    logger = log.get_logger('%s.%s' % (__name__,
 | 
			
		||||
                                       inspect.stack()[0][0].f_code.co_name))
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        # make sure to correctly initialize inherited objects before performing
 | 
			
		||||
        # any computation
 | 
			
		||||
        super(DropCommand, self).__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    drop = Drop(git_object=args.commit, author=args.author)
 | 
			
		||||
        self.parser.add_argument(
 | 
			
		||||
            'commit', metavar='<commit>', nargs=None,
 | 
			
		||||
            help='Commit to be marked as dropped')
 | 
			
		||||
        self.parser.add_argument(
 | 
			
		||||
            '-a', '--author', metavar='<author>', dest='author', default=None,
 | 
			
		||||
            help='Git author for the mark')
 | 
			
		||||
 | 
			
		||||
    if drop.mark():
 | 
			
		||||
        logger.notice("Drop mark created successfully")
 | 
			
		||||
    def run(self, args):
 | 
			
		||||
 | 
			
		||||
        drop = Drop(git_object=args.commit, author=args.author)
 | 
			
		||||
 | 
			
		||||
        if drop.mark():
 | 
			
		||||
            self.log.notice("Drop mark created successfully")
 | 
			
		||||
 | 
			
		||||
# vim:sw=4:sts=4:ts=4:et:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										40
									
								
								git_upstream/commands/help.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								git_upstream/commands/help.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
#
 | 
			
		||||
# Copyright (c) 2012-2015 Hewlett-Packard Development Company, L.P.
 | 
			
		||||
#
 | 
			
		||||
# Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
# you may not use this file except in compliance with the License.
 | 
			
		||||
# You may obtain a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
# Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
# distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 | 
			
		||||
# implied.
 | 
			
		||||
# See the License for the specific language governing permissions and
 | 
			
		||||
# limitations under the License.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
from git_upstream.commands import GitUpstreamCommand
 | 
			
		||||
from git_upstream.log import LogDedentMixin
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HelpCommand(LogDedentMixin, GitUpstreamCommand):
 | 
			
		||||
    """Display help about this program or one of its commands."""
 | 
			
		||||
    name = "help"
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super(HelpCommand, self).__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
        self.parser.add_argument('command', metavar='<command>', nargs='?',
 | 
			
		||||
                                 help="command to display help about")
 | 
			
		||||
 | 
			
		||||
    def run(self, args, parent_parser=None):
 | 
			
		||||
        if getattr(args, 'command', None):
 | 
			
		||||
            if args.command in args.subcommands:
 | 
			
		||||
                args.subcommands[args.command].print_help()
 | 
			
		||||
            else:
 | 
			
		||||
                self.parser.error("'%s' is not a valid subcommand" %
 | 
			
		||||
                                  args.command)
 | 
			
		||||
        else:
 | 
			
		||||
            parent_parser.print_help()
 | 
			
		||||
@@ -18,10 +18,10 @@
 | 
			
		||||
from abc import ABCMeta
 | 
			
		||||
from abc import abstractmethod
 | 
			
		||||
from collections import Sequence
 | 
			
		||||
import inspect
 | 
			
		||||
 | 
			
		||||
from git import GitCommandError
 | 
			
		||||
 | 
			
		||||
from git_upstream.commands import GitUpstreamCommand
 | 
			
		||||
from git_upstream.errors import GitUpstreamError
 | 
			
		||||
from git_upstream.lib.rebaseeditor import RebaseEditor
 | 
			
		||||
from git_upstream.lib.searchers import DiscardDuplicateGerritChangeId
 | 
			
		||||
@@ -31,9 +31,7 @@ from git_upstream.lib.searchers import ReverseCommitFilter
 | 
			
		||||
from git_upstream.lib.searchers import SupersededCommitFilter
 | 
			
		||||
from git_upstream.lib.searchers import UpstreamMergeBaseSearcher
 | 
			
		||||
from git_upstream.lib.utils import GitMixin
 | 
			
		||||
from git_upstream import log
 | 
			
		||||
from git_upstream.log import LogDedentMixin
 | 
			
		||||
from git_upstream import subcommand
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ImportUpstreamError(GitUpstreamError):
 | 
			
		||||
@@ -519,45 +517,7 @@ class LocateChangesWalk(LocateChangesStrategy):
 | 
			
		||||
        return super(LocateChangesWalk, self).filtered_iter()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@subcommand.arg('-d', '--dry-run', dest='dry_run', action='store_true',
 | 
			
		||||
                default=False,
 | 
			
		||||
                help='Only print out the list of commits that would be '
 | 
			
		||||
                     'applied.')
 | 
			
		||||
@subcommand.arg('-i', '--interactive', action='store_true', default=False,
 | 
			
		||||
                help='Let the user edit the list of commits before applying.')
 | 
			
		||||
@subcommand.arg('-f', '--force', dest='force', required=False,
 | 
			
		||||
                action='store_true', default=False,
 | 
			
		||||
                help='Force overwrite of existing import branch if it exists.')
 | 
			
		||||
@subcommand.arg('--merge', dest='merge', required=False, action='store_true',
 | 
			
		||||
                default=True,
 | 
			
		||||
                help='Merge the resulting import branch into the target branch'
 | 
			
		||||
                     ' once complete')
 | 
			
		||||
@subcommand.arg('--no-merge', dest='merge', required=False,
 | 
			
		||||
                action='store_false',
 | 
			
		||||
                help="Disable merge of the resulting import branch")
 | 
			
		||||
@subcommand.arg('-s', '--strategy', metavar='<strategy>',
 | 
			
		||||
                choices=ImportStrategiesFactory.list_strategies(),
 | 
			
		||||
                default=LocateChangesWalk.get_strategy_name(),
 | 
			
		||||
                help='Use the given strategy to re-apply locally carried '
 | 
			
		||||
                     'changes to the import branch. (default: %(default)s)')
 | 
			
		||||
@subcommand.arg('--search-refs', action='append_replace', metavar='<pattern>',
 | 
			
		||||
                default=['upstream/*'], dest='search_refs',
 | 
			
		||||
                help='Refs to search for previous import commit. May be '
 | 
			
		||||
                     'specified multiple times.')
 | 
			
		||||
@subcommand.arg('--into', dest='branch', metavar='<branch>', default='HEAD',
 | 
			
		||||
                help='Branch to take changes from, and replace with imported '
 | 
			
		||||
                     'branch.')
 | 
			
		||||
@subcommand.arg('--import-branch', metavar='<import-branch>',
 | 
			
		||||
                help='Name of import branch to use',
 | 
			
		||||
                default='import/{describe}')
 | 
			
		||||
@subcommand.arg('upstream_branch', metavar='<upstream-branch>', nargs='?',
 | 
			
		||||
                default='upstream/master',
 | 
			
		||||
                help='Upstream branch to import. Must be specified if '
 | 
			
		||||
                     'you wish to provide additional branches.')
 | 
			
		||||
@subcommand.arg('branches', metavar='<branches>', nargs='*',
 | 
			
		||||
                help='Branches to additionally merge into the import branch '
 | 
			
		||||
                     'using default git merging behaviour')
 | 
			
		||||
def do_import(args):
 | 
			
		||||
class ImportCommand(LogDedentMixin, GitUpstreamCommand):
 | 
			
		||||
    """Import code from specified upstream branch.
 | 
			
		||||
 | 
			
		||||
    Creates an import branch from the specified upstream branch, and optionally
 | 
			
		||||
@@ -569,81 +529,136 @@ def do_import(args):
 | 
			
		||||
    Once complete it will merge and replace the contents of the target branch
 | 
			
		||||
    with those from the import branch, unless --no-merge is specified.
 | 
			
		||||
    """
 | 
			
		||||
    name = "import"
 | 
			
		||||
 | 
			
		||||
    logger = log.get_logger('%s.%s' % (__name__,
 | 
			
		||||
                                       inspect.stack()[0][0].f_code.co_name))
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super(ImportCommand, self).__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    import_upstream = ImportUpstream(branch=args.branch,
 | 
			
		||||
                                     upstream=args.upstream_branch,
 | 
			
		||||
                                     import_branch=args.import_branch,
 | 
			
		||||
                                     extra_branches=args.branches)
 | 
			
		||||
        self.parser.add_argument(
 | 
			
		||||
            '-i', '--interactive', action='store_true', default=False,
 | 
			
		||||
            help='Let the user edit the list of commits before applying.')
 | 
			
		||||
        self.parser.add_argument(
 | 
			
		||||
            '-d', '--dry-run', dest='dry_run', action='store_true',
 | 
			
		||||
            default=False,
 | 
			
		||||
            help='Only print out the list of commits that would be applied.')
 | 
			
		||||
        self.parser.add_argument(
 | 
			
		||||
            '-f', '--force', dest='force', required=False,
 | 
			
		||||
            action='store_true', default=False,
 | 
			
		||||
            help='Force overwrite of existing import branch if it exists.')
 | 
			
		||||
        self.parser.add_argument(
 | 
			
		||||
            '--merge', dest='merge', required=False, action='store_true',
 | 
			
		||||
            default=True,
 | 
			
		||||
            help='Merge the resulting import branch into the target branch '
 | 
			
		||||
                 'once complete')
 | 
			
		||||
        self.parser.add_argument(
 | 
			
		||||
            '--no-merge', dest='merge', required=False, action='store_false',
 | 
			
		||||
            help='Disable merge of the resulting import branch')
 | 
			
		||||
        self.parser.add_argument(
 | 
			
		||||
            '--search-refs', action='append_replace', metavar='<pattern>',
 | 
			
		||||
            default=['upstream/*'], dest='search_refs',
 | 
			
		||||
            help='Refs to search for previous import commit. May be '
 | 
			
		||||
                 'specified multiple times.')
 | 
			
		||||
        self.parser.add_argument(
 | 
			
		||||
            '-s', '--strategy', metavar='<strategy>',
 | 
			
		||||
            choices=ImportStrategiesFactory.list_strategies(),
 | 
			
		||||
            default=LocateChangesWalk.get_strategy_name(),
 | 
			
		||||
            help='Use the given strategy to re-apply locally carried '
 | 
			
		||||
                 'changes to the import branch. (default: %(default)s)')
 | 
			
		||||
        self.parser.add_argument(
 | 
			
		||||
            '--into', dest='branch', metavar='<branch>', default='HEAD',
 | 
			
		||||
            help='Branch to take changes from, and replace with imported '
 | 
			
		||||
                 'branch.')
 | 
			
		||||
        self.parser.add_argument(
 | 
			
		||||
            '--import-branch', metavar='<import-branch>',
 | 
			
		||||
            default='import/{describe}', help='Name of import branch to use')
 | 
			
		||||
        self.parser.add_argument(
 | 
			
		||||
            'upstream_branch', metavar='<upstream-branch>', nargs='?',
 | 
			
		||||
            default='upstream/master',
 | 
			
		||||
            help='Upstream branch to import. Must be specified if you wish to '
 | 
			
		||||
                 'provide additional branches.')
 | 
			
		||||
        self.parser.add_argument(
 | 
			
		||||
            'branches', metavar='<branches>', nargs='*',
 | 
			
		||||
            help='Branches to additionally merge into the import branch using '
 | 
			
		||||
                 'default git merging behaviour')
 | 
			
		||||
 | 
			
		||||
    logger.notice("Searching for previous import")
 | 
			
		||||
    strategy = ImportStrategiesFactory.create_strategy(
 | 
			
		||||
        args.strategy, branch=args.branch, upstream=args.upstream_branch,
 | 
			
		||||
        search_refs=args.search_refs)
 | 
			
		||||
    def run(self, args):
 | 
			
		||||
 | 
			
		||||
    if len(strategy) == 0:
 | 
			
		||||
        raise ImportUpstreamError("Cannot find previous import")
 | 
			
		||||
        import_upstream = ImportUpstream(
 | 
			
		||||
            branch=args.branch,
 | 
			
		||||
            upstream=args.upstream_branch,
 | 
			
		||||
            import_branch=args.import_branch,
 | 
			
		||||
            extra_branches=args.branches)
 | 
			
		||||
 | 
			
		||||
    # if last commit in the strategy was a merge, then the additional branches
 | 
			
		||||
    # that were merged in previously can be extracted based on the commits
 | 
			
		||||
    # merged.
 | 
			
		||||
    prev_import_merge = strategy[-1]
 | 
			
		||||
    if len(prev_import_merge.parents) > 1:
 | 
			
		||||
        idxs = [idx for idx, commit in enumerate(prev_import_merge.parents)
 | 
			
		||||
                if commit.hexsha != strategy.searcher.commit.hexsha]
 | 
			
		||||
        self.log.notice("Searching for previous import")
 | 
			
		||||
        strategy = ImportStrategiesFactory.create_strategy(
 | 
			
		||||
            args.strategy, branch=args.branch, upstream=args.upstream_branch,
 | 
			
		||||
            search_refs=args.search_refs)
 | 
			
		||||
 | 
			
		||||
        if idxs:
 | 
			
		||||
            additional_commits = [prev_import_merge.parents[i] for i in idxs]
 | 
			
		||||
            if additional_commits and len(args.branches) == 0:
 | 
			
		||||
                logger.warning("""\
 | 
			
		||||
                    **************** WARNING ****************
 | 
			
		||||
                    Previous import merged additional branches but none have
 | 
			
		||||
                    been specified on the command line for this import.\n""")
 | 
			
		||||
        if len(strategy) == 0:
 | 
			
		||||
            raise ImportUpstreamError("Cannot find previous import")
 | 
			
		||||
 | 
			
		||||
    if args.dry_run:
 | 
			
		||||
        commit_list = [c.hexsha[:6] + " - " + c.summary[:60] +
 | 
			
		||||
                       (c.summary[60:] and "...")
 | 
			
		||||
                       for c in list(strategy.filtered_iter())]
 | 
			
		||||
        logger.notice("""\
 | 
			
		||||
            Requested a dry-run: printing the list of commit that should be
 | 
			
		||||
            rebased
 | 
			
		||||
        # if last commit in the strategy was a merge, then the additional
 | 
			
		||||
        # branches that were merged in previously can be extracted based on
 | 
			
		||||
        # the commits merged.
 | 
			
		||||
        prev_import_merge = strategy[-1]
 | 
			
		||||
        if len(prev_import_merge.parents) > 1:
 | 
			
		||||
            idxs = [idx for idx, commit in enumerate(prev_import_merge.parents)
 | 
			
		||||
                    if commit.hexsha != strategy.searcher.commit.hexsha]
 | 
			
		||||
 | 
			
		||||
                %s
 | 
			
		||||
            """, "\n    ".join(commit_list))
 | 
			
		||||
        return True
 | 
			
		||||
            if idxs:
 | 
			
		||||
                additional_commits = [prev_import_merge.parents[i]
 | 
			
		||||
                                      for i in idxs]
 | 
			
		||||
                if additional_commits and len(args.branches) == 0:
 | 
			
		||||
                    self.log.warning("""\
 | 
			
		||||
                        **************** WARNING ****************
 | 
			
		||||
                        Previous import merged additional branches but none
 | 
			
		||||
                        have been specified on the command line for this
 | 
			
		||||
                        import.\n""")
 | 
			
		||||
 | 
			
		||||
    logger.notice("Starting import of upstream")
 | 
			
		||||
    import_upstream.create_import(force=args.force)
 | 
			
		||||
    logger.notice("Successfully created import branch")
 | 
			
		||||
        if args.dry_run:
 | 
			
		||||
            commit_list = [c.hexsha[:6] + " - " + c.summary[:60] +
 | 
			
		||||
                           (c.summary[60:] and "...")
 | 
			
		||||
                           for c in list(strategy.filtered_iter())]
 | 
			
		||||
            self.log.notice("""\
 | 
			
		||||
                Requested a dry-run: printing the list of commit that should be
 | 
			
		||||
                rebased
 | 
			
		||||
 | 
			
		||||
    if not import_upstream.apply(strategy, args.interactive):
 | 
			
		||||
        logger.notice("Import cancelled")
 | 
			
		||||
        return False
 | 
			
		||||
                    %s
 | 
			
		||||
                """, "\n    ".join(commit_list))
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
    if not args.merge:
 | 
			
		||||
        logger.notice(
 | 
			
		||||
            """\
 | 
			
		||||
            Import complete, not merging to target branch '%s' as requested.
 | 
			
		||||
            """, args.branch)
 | 
			
		||||
        return True
 | 
			
		||||
        self.log.notice("Starting import of upstream")
 | 
			
		||||
        import_upstream.create_import(force=args.force)
 | 
			
		||||
        self.log.notice("Successfully created import branch")
 | 
			
		||||
 | 
			
		||||
    logger.notice("Merging import to requested branch '%s'", args.branch)
 | 
			
		||||
    if import_upstream.finish():
 | 
			
		||||
        logger.notice(
 | 
			
		||||
            """\
 | 
			
		||||
            Successfully finished import:
 | 
			
		||||
                target branch: '%s'
 | 
			
		||||
                upstream branch: '%s'
 | 
			
		||||
                import branch: '%s'""", args.branch, args.upstream_branch,
 | 
			
		||||
            import_upstream.import_branch)
 | 
			
		||||
        if args.branches:
 | 
			
		||||
            for branch in args.branches:
 | 
			
		||||
                logger.notice("    extra branch: '%s'", branch, dedent=False)
 | 
			
		||||
        return True
 | 
			
		||||
    else:
 | 
			
		||||
        return False
 | 
			
		||||
        if not import_upstream.apply(strategy, args.interactive):
 | 
			
		||||
            self.log.notice("Import cancelled")
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        if not args.merge:
 | 
			
		||||
            self.log.notice(
 | 
			
		||||
                """\
 | 
			
		||||
                Import complete, not merging to target branch '%s' as
 | 
			
		||||
                requested.
 | 
			
		||||
                """, args.branch)
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
        self.log.notice("Merging import to requested branch '%s'", args.branch)
 | 
			
		||||
        if import_upstream.finish():
 | 
			
		||||
            self.log.notice(
 | 
			
		||||
                """\
 | 
			
		||||
                Successfully finished import:
 | 
			
		||||
                    target branch: '%s'
 | 
			
		||||
                    upstream branch: '%s'
 | 
			
		||||
                    import branch: '%s'""", args.branch, args.upstream_branch,
 | 
			
		||||
                import_upstream.import_branch)
 | 
			
		||||
            if args.branches:
 | 
			
		||||
                for branch in args.branches:
 | 
			
		||||
                    self.log.notice("    extra branch: '%s'", branch,
 | 
			
		||||
                                    dedent=False)
 | 
			
		||||
            return True
 | 
			
		||||
        else:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# vim:sw=4:sts=4:ts=4:et:
 | 
			
		||||
 
 | 
			
		||||
@@ -15,19 +15,17 @@
 | 
			
		||||
# limitations under the License.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
import inspect
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
from git import BadObject
 | 
			
		||||
from git import Head
 | 
			
		||||
 | 
			
		||||
from git_upstream.commands import GitUpstreamCommand
 | 
			
		||||
from git_upstream.errors import GitUpstreamError
 | 
			
		||||
from git_upstream.lib import note  # noqa
 | 
			
		||||
from git_upstream.lib.searchers import CommitMessageSearcher
 | 
			
		||||
from git_upstream.lib.utils import GitMixin
 | 
			
		||||
from git_upstream import log
 | 
			
		||||
from git_upstream.log import LogDedentMixin
 | 
			
		||||
from git_upstream import subcommand
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    from git import BadName
 | 
			
		||||
@@ -169,40 +167,51 @@ class Supersede(LogDedentMixin, GitMixin):
 | 
			
		||||
            self.log.warning('Note has not been added')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@subcommand.arg('commit', metavar='<commit>', nargs=None,
 | 
			
		||||
                help='Commit to be marked as superseded')
 | 
			
		||||
@subcommand.arg('change_ids', metavar='<change id>', nargs='+',
 | 
			
		||||
                help='Change id which makes <commit> obsolete. The change id '
 | 
			
		||||
                     'must be present in <upstream-branch> to drop <commit>. '
 | 
			
		||||
                     'If more than one change id is specified, all must be '
 | 
			
		||||
                     'present in <upstream-branch> to drop <commit>')
 | 
			
		||||
@subcommand.arg('-f', '--force', dest='force', required=False,
 | 
			
		||||
                action='store_true', default=False,
 | 
			
		||||
                help='Apply the commit mark even if one or more change ids '
 | 
			
		||||
                     'could not be found. Use this flag carefully as commits '
 | 
			
		||||
                     'will not be dropped during import command execution as '
 | 
			
		||||
                     'long as all associated change ids are present in the '
 | 
			
		||||
                     'local copy of the upstream branch')
 | 
			
		||||
@subcommand.arg('-u', '--upstream-branch', metavar='<upstream-branch>',
 | 
			
		||||
                dest='upstream_branch', required=False,
 | 
			
		||||
                default='upstream/master',
 | 
			
		||||
                help='Search change ids values in <upstream-branch> branch '
 | 
			
		||||
                     '(default: %(default)s)')
 | 
			
		||||
def do_supersede(args):
 | 
			
		||||
    """
 | 
			
		||||
    Mark a commit as superseded by a set of change-ids.
 | 
			
		||||
class SupersedeCommand(LogDedentMixin, GitUpstreamCommand):
 | 
			
		||||
    """Mark a commit as superseded by a set of change-ids.
 | 
			
		||||
 | 
			
		||||
    Marked commits will be skipped during the upstream rebasing process.
 | 
			
		||||
    See also the "git upstream import" command.
 | 
			
		||||
    """
 | 
			
		||||
    name = "supersede"
 | 
			
		||||
 | 
			
		||||
    logger = log.get_logger('%s.%s' % (__name__,
 | 
			
		||||
                                       inspect.stack()[0][0].f_code.co_name))
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
 | 
			
		||||
    supersede = Supersede(git_object=args.commit, change_ids=args.change_ids,
 | 
			
		||||
                          upstream_branch=args.upstream_branch,
 | 
			
		||||
                          force=args.force)
 | 
			
		||||
        # make sure to correctly initialize inherited objects before performing
 | 
			
		||||
        # any computation
 | 
			
		||||
        super(SupersedeCommand, self).__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    if supersede.mark():
 | 
			
		||||
        logger.notice("Supersede mark created successfully")
 | 
			
		||||
        self.parser.add_argument(
 | 
			
		||||
            'commit', metavar='<commit>', nargs=None,
 | 
			
		||||
            help='Commit to be marked as superseded')
 | 
			
		||||
        self.parser.add_argument(
 | 
			
		||||
            'change_ids', metavar='<change id>', nargs='+',
 | 
			
		||||
            help='Change id which makes <commit> obsolete. The change id must '
 | 
			
		||||
                 'be present in <upstream-branch> to drop <commit>. If more '
 | 
			
		||||
                 'than one change id is specified, all must be present in '
 | 
			
		||||
                 '<upstream-branch> to drop <commit>')
 | 
			
		||||
        self.parser.add_argument(
 | 
			
		||||
            '-f', '--force', dest='force', required=False, action='store_true',
 | 
			
		||||
            default=False,
 | 
			
		||||
            help='Apply the commit mark even if one or more change ids could '
 | 
			
		||||
                 'not be found. Use this flag carefully as commits will not '
 | 
			
		||||
                 'be dropped during import command execution as long as all '
 | 
			
		||||
                 'associated change ids are present in the local copy of the '
 | 
			
		||||
                 'upstream branch')
 | 
			
		||||
        self.parser.add_argument(
 | 
			
		||||
            '-u', '--upstream-branch', metavar='<upstream-branch>',
 | 
			
		||||
            dest='upstream_branch', required=False, default='upstream/master',
 | 
			
		||||
            help='Search change ids values in <upstream-branch> branch '
 | 
			
		||||
                 '(default: %(default)s)')
 | 
			
		||||
 | 
			
		||||
    def run(self, args):
 | 
			
		||||
 | 
			
		||||
        supersede = Supersede(git_object=args.commit,
 | 
			
		||||
                              change_ids=args.change_ids,
 | 
			
		||||
                              upstream_branch=args.upstream_branch,
 | 
			
		||||
                              force=args.force)
 | 
			
		||||
 | 
			
		||||
        if supersede.mark():
 | 
			
		||||
            self.logger.notice("Supersede mark created successfully")
 | 
			
		||||
 | 
			
		||||
# vim:sw=4:sts=4:ts=4:et:
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,6 @@ from git_upstream import __version__
 | 
			
		||||
from git_upstream import commands
 | 
			
		||||
from git_upstream.errors import GitUpstreamError
 | 
			
		||||
from git_upstream import log
 | 
			
		||||
from git_upstream import subcommand
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    import argcomplete
 | 
			
		||||
@@ -39,7 +38,7 @@ except ImportError:
 | 
			
		||||
    argparse_loaded = False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_parser():
 | 
			
		||||
def build_parsers():
 | 
			
		||||
    parser = argparse.ArgumentParser(
 | 
			
		||||
        description=__doc__.strip(),
 | 
			
		||||
        epilog='See "%(prog)s help COMMAND" for help on a specific command.',
 | 
			
		||||
@@ -62,39 +61,11 @@ def get_parser():
 | 
			
		||||
                        help=argparse.SUPPRESS)
 | 
			
		||||
    parser.add_argument('--log-file', dest='log_file', help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
    subparsers = parser.add_subparsers(title="commands", metavar='<command>',
 | 
			
		||||
                                       dest='subcommand')
 | 
			
		||||
 | 
			
		||||
    # it would be nicer if we could hide this help command
 | 
			
		||||
    desc = help.__doc__ or ''
 | 
			
		||||
    subparser = subparsers.add_parser(
 | 
			
		||||
        'help',
 | 
			
		||||
        help=desc.strip().split('\n')[0],
 | 
			
		||||
        description=desc,
 | 
			
		||||
    )
 | 
			
		||||
    for (args, kwargs) in getattr(help, 'arguments', []):
 | 
			
		||||
        subparser.add_argument(*args, **kwargs)
 | 
			
		||||
    subparser.set_defaults(func=help)
 | 
			
		||||
 | 
			
		||||
    subcommand_parsers = commands.get_subcommands(subparsers)
 | 
			
		||||
    subcommand_parsers = commands.get_subcommands(parser)
 | 
			
		||||
 | 
			
		||||
    return subcommand_parsers, parser
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@subcommand.arg('command', metavar='<command>', nargs='?',
 | 
			
		||||
                help='Display help for <command>')
 | 
			
		||||
def help(parser, args, commands=None):
 | 
			
		||||
    """Display help about this program or one of its commands."""
 | 
			
		||||
    if getattr(args, 'command', None):
 | 
			
		||||
        if args.command in commands:
 | 
			
		||||
            commands[args.command].print_help()
 | 
			
		||||
        else:
 | 
			
		||||
            parser.error("'%s' is not a valid subcommand" %
 | 
			
		||||
                         args.command)
 | 
			
		||||
    else:
 | 
			
		||||
        parser.print_help()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def setup_console_logging(options):
 | 
			
		||||
 | 
			
		||||
    options.log_level = getattr(logging, options.log_level.upper(),
 | 
			
		||||
@@ -147,17 +118,18 @@ def main(argv=None):
 | 
			
		||||
    if not argv:
 | 
			
		||||
        argv = sys.argv[1:]
 | 
			
		||||
 | 
			
		||||
    (cmds, parser) = get_parser()
 | 
			
		||||
    (cmds, parser) = build_parsers()
 | 
			
		||||
 | 
			
		||||
    if not sys.argv:
 | 
			
		||||
        help(parser, argv)
 | 
			
		||||
    if not argv:
 | 
			
		||||
        parser.print_help()
 | 
			
		||||
        return 0
 | 
			
		||||
 | 
			
		||||
    if argparse_loaded:
 | 
			
		||||
        argcomplete.autocomplete(parser)
 | 
			
		||||
    args = parser.parse_args(argv)
 | 
			
		||||
    if args.func == help:
 | 
			
		||||
        help(parser, args, cmds)
 | 
			
		||||
 | 
			
		||||
    if args.cmd.name == "help":
 | 
			
		||||
        args.cmd.run(args, parser)
 | 
			
		||||
        return 0
 | 
			
		||||
 | 
			
		||||
    logger = setup_console_logging(args)
 | 
			
		||||
@@ -167,7 +139,9 @@ def main(argv=None):
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        args.func(args)
 | 
			
		||||
        cmd = args.cmd
 | 
			
		||||
        cmd.validate(args)
 | 
			
		||||
        cmd.run(args)
 | 
			
		||||
    except GitUpstreamError as e:
 | 
			
		||||
        logger.fatal("%s", e[0])
 | 
			
		||||
        logger.debug("Git-Upstream: %s", e[0], exc_info=e)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,26 +0,0 @@
 | 
			
		||||
#
 | 
			
		||||
# Copyright (c) 2012 OpenStack LLC.
 | 
			
		||||
#
 | 
			
		||||
# Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
# you may not use this file except in compliance with the License.
 | 
			
		||||
# You may obtain a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
# Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
# distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 | 
			
		||||
# implied.
 | 
			
		||||
# See the License for the specific language governing permissions and
 | 
			
		||||
# limitations under the License.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# following function taken from python-keystoneclient - keystoneclient/utils.py
 | 
			
		||||
# Decorator for cli-args
 | 
			
		||||
def arg(*args, **kwargs):
 | 
			
		||||
    def _decorator(func):
 | 
			
		||||
        # Because of the sematics of decorator composition if we just append
 | 
			
		||||
        # to the options list positional options will appear to be backwards.
 | 
			
		||||
        func.__dict__.setdefault('arguments', []).insert(0, (args, kwargs))
 | 
			
		||||
        return func
 | 
			
		||||
    return _decorator
 | 
			
		||||
@@ -24,13 +24,12 @@ from git_upstream import commands as c
 | 
			
		||||
class TestGetSubcommands(testtools.TestCase):
 | 
			
		||||
    """Test case for get_subcommands function"""
 | 
			
		||||
 | 
			
		||||
    _available_subcommands = ('import', 'supersede', 'drop')
 | 
			
		||||
    _available_subcommands = ('help', 'import', 'supersede', 'drop')
 | 
			
		||||
 | 
			
		||||
    def test_available_subcommands(self):
 | 
			
		||||
        """Test available subcommands"""
 | 
			
		||||
        parser = ArgumentParser()
 | 
			
		||||
        subparsers = parser.add_subparsers()
 | 
			
		||||
        subcommands = c.get_subcommands(subparsers)
 | 
			
		||||
        subcommands = c.get_subcommands(parser)
 | 
			
		||||
        self.assertEqual(len(TestGetSubcommands._available_subcommands),
 | 
			
		||||
                         len(subcommands.keys()))
 | 
			
		||||
        for command in subcommands.keys():
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,7 @@ class SubstringMatcher(object):
 | 
			
		||||
 | 
			
		||||
class TestImportCommand(BaseTestCase):
 | 
			
		||||
 | 
			
		||||
    commands, parser = main.get_parser()
 | 
			
		||||
    commands, parser = main.build_parsers()
 | 
			
		||||
 | 
			
		||||
    def test_basic(self):
 | 
			
		||||
        """Test that default behaviour and options work
 | 
			
		||||
@@ -74,7 +74,7 @@ class TestImportCommand(BaseTestCase):
 | 
			
		||||
        self._build_git_tree(tree, branches.values())
 | 
			
		||||
        self.git.tag(inspect.currentframe().f_code.co_name, 'upstream/master')
 | 
			
		||||
        args = self.parser.parse_args(['-q', 'import', 'upstream/master'])
 | 
			
		||||
        self.assertThat(args.func(args), Equals(True),
 | 
			
		||||
        self.assertThat(args.cmd.run(args), Equals(True),
 | 
			
		||||
                        "import command failed to complete succesfully")
 | 
			
		||||
 | 
			
		||||
    def test_basic_additional(self):
 | 
			
		||||
@@ -111,7 +111,7 @@ class TestImportCommand(BaseTestCase):
 | 
			
		||||
        self.git.tag(inspect.currentframe().f_code.co_name, 'upstream/master')
 | 
			
		||||
        args = self.parser.parse_args(['-q', 'import', 'upstream/master',
 | 
			
		||||
                                       'packaging/master'])
 | 
			
		||||
        self.assertThat(args.func(args), Equals(True),
 | 
			
		||||
        self.assertThat(args.cmd.run(args), Equals(True),
 | 
			
		||||
                        "import command failed to complete succesfully")
 | 
			
		||||
 | 
			
		||||
    def test_basic_additional_missed(self):
 | 
			
		||||
@@ -149,15 +149,13 @@ class TestImportCommand(BaseTestCase):
 | 
			
		||||
        self.git.tag(inspect.currentframe().f_code.co_name, 'upstream/master')
 | 
			
		||||
        args = self.parser.parse_args(['import', 'upstream/master'])
 | 
			
		||||
 | 
			
		||||
        mock_logger = mock.MagicMock()
 | 
			
		||||
        with mock.patch('git_upstream.log.get_logger',
 | 
			
		||||
                        return_value=mock_logger):
 | 
			
		||||
            self.assertThat(args.func(args), Equals(True),
 | 
			
		||||
        with mock.patch.object(args.cmd.log, 'warning') as mock_logger:
 | 
			
		||||
            self.assertThat(args.cmd.run(args), Equals(True),
 | 
			
		||||
                            "import command failed to complete succesfully")
 | 
			
		||||
 | 
			
		||||
        mock_logger.warning.assert_called_with(
 | 
			
		||||
            SubstringMatcher(
 | 
			
		||||
                containing="Previous import merged additional"))
 | 
			
		||||
            mock_logger.assert_called_with(
 | 
			
		||||
                SubstringMatcher(
 | 
			
		||||
                    containing="Previous import merged additional"))
 | 
			
		||||
 | 
			
		||||
    def test_import_switch_branches_search(self):
 | 
			
		||||
        """Test that the import sub-command can correctly switch branches when
 | 
			
		||||
@@ -206,7 +204,7 @@ class TestImportCommand(BaseTestCase):
 | 
			
		||||
        self._build_git_tree(tree, branches.values())
 | 
			
		||||
        self.git.tag(inspect.currentframe().f_code.co_name, 'upstream/master')
 | 
			
		||||
        args = self.parser.parse_args(['-q', 'import'])
 | 
			
		||||
        self.assertThat(args.func(args), Equals(True),
 | 
			
		||||
        self.assertThat(args.cmd.run(args), Equals(True),
 | 
			
		||||
                        "import command failed to complete succesfully")
 | 
			
		||||
        changes = list(Commit.iter_items(
 | 
			
		||||
            self.repo, 'upstream/master..master^2'))
 | 
			
		||||
@@ -274,7 +272,7 @@ class TestImportCommand(BaseTestCase):
 | 
			
		||||
        self.git.tag(inspect.currentframe().f_code.co_name, 'custom/master')
 | 
			
		||||
        args = self.parser.parse_args(['-q', 'import',
 | 
			
		||||
                                       '--into=master', 'custom/master'])
 | 
			
		||||
        self.assertThat(args.func(args), Equals(True),
 | 
			
		||||
        self.assertThat(args.cmd.run(args), Equals(True),
 | 
			
		||||
                        "import command failed to complete succesfully")
 | 
			
		||||
        changes = list(Commit.iter_items(
 | 
			
		||||
            self.repo, 'custom/master..master^2'))
 | 
			
		||||
@@ -343,7 +341,7 @@ class TestImportCommand(BaseTestCase):
 | 
			
		||||
                                       '--search-refs=custom/*',
 | 
			
		||||
                                       '--search-refs=custom-d/*',
 | 
			
		||||
                                       '--into=master', 'custom/master'])
 | 
			
		||||
        self.assertThat(args.func(args), Equals(True),
 | 
			
		||||
        self.assertThat(args.cmd.run(args), Equals(True),
 | 
			
		||||
                        "import command failed to complete succesfully")
 | 
			
		||||
        changes = list(Commit.iter_items(
 | 
			
		||||
            self.repo, 'custom/master..master^2'))
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user