Remove the unnecessary additional call to 'git checkout' to update the working tree to the current index when finishing the import merge. Add the necessary options to the read-tree call to update both. Additionally add some basic tests to ensure no behavioural change. Change-Id: I4bd77aaf0b6c9b71e717d74db92af0d897d9c7b3
		
			
				
	
	
		
			645 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			645 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#
 | 
						|
# Copyright (c) 2012, 2013, 2014 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.errors import GitUpstreamError
 | 
						|
from git_upstream.log import LogDedentMixin
 | 
						|
from git_upstream.lib.utils import GitMixin
 | 
						|
from git_upstream.lib.rebaseeditor import RebaseEditor
 | 
						|
from git_upstream import subcommand, log
 | 
						|
from git_upstream.lib.searchers import UpstreamMergeBaseSearcher
 | 
						|
 | 
						|
from abc import ABCMeta, abstractmethod
 | 
						|
from collections import Sequence
 | 
						|
from git import GitCommandError
 | 
						|
 | 
						|
import inspect
 | 
						|
 | 
						|
 | 
						|
class ImportUpstreamError(GitUpstreamError):
 | 
						|
    """Exception thrown by L{ImportUpstream}"""
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
class ImportUpstream(LogDedentMixin, GitMixin):
 | 
						|
    """
 | 
						|
    Import code from an upstream project and merge in additional branches
 | 
						|
    to create a new branch unto which changes that are not upstream but are
 | 
						|
    on the local branch are applied.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, branch=None, upstream=None, import_branch=None,
 | 
						|
                 extra_branches=None, *args, **kwargs):
 | 
						|
        if not extra_branches:
 | 
						|
            extra_branches = []
 | 
						|
        self._branch = branch
 | 
						|
        self._upstream = upstream
 | 
						|
        self._import_branch = import_branch
 | 
						|
        self._extra_branches = extra_branches
 | 
						|
 | 
						|
        # make sure to correctly initialise inherited objects before performing
 | 
						|
        # any computation
 | 
						|
        super(ImportUpstream, self).__init__(*args, **kwargs)
 | 
						|
 | 
						|
        # test that we can use this git repo
 | 
						|
        if not self.is_detached():
 | 
						|
            raise ImportUpstreamError("In 'detached HEAD' state")
 | 
						|
 | 
						|
        if self.repo.bare:
 | 
						|
            raise ImportUpstreamError("Cannot perform imports in bare repos")
 | 
						|
 | 
						|
        if self.branch == 'HEAD':
 | 
						|
            self._branch = str(self.repo.active_branch)
 | 
						|
 | 
						|
        # validate branches exist and log all failures
 | 
						|
        branches = [
 | 
						|
            self.branch,
 | 
						|
            self.upstream
 | 
						|
        ]
 | 
						|
        branches.extend(self.extra_branches)
 | 
						|
 | 
						|
        invalid_ref = False
 | 
						|
        for branch in branches:
 | 
						|
            if not any(head for head in self.repo.heads
 | 
						|
                       if head.name == branch):
 | 
						|
                msg = "Specified ref does not exist: '%s'"
 | 
						|
                self.log.error(msg, branch)
 | 
						|
                invalid_ref = True
 | 
						|
 | 
						|
        if invalid_ref:
 | 
						|
            raise ImportUpstreamError("Invalid ref")
 | 
						|
 | 
						|
    @property
 | 
						|
    def branch(self):
 | 
						|
        """Branch to search for branch changes to apply when importing."""
 | 
						|
        return self._branch
 | 
						|
 | 
						|
    @property
 | 
						|
    def upstream(self):
 | 
						|
        """Branch containing the upstream project code base to track."""
 | 
						|
        return self._upstream
 | 
						|
 | 
						|
    @property
 | 
						|
    def import_branch(self):
 | 
						|
        """
 | 
						|
        Pattern to use to generate the name, or user specified branch name
 | 
						|
        to use for import.
 | 
						|
        """
 | 
						|
        return self._import_branch
 | 
						|
 | 
						|
    @property
 | 
						|
    def extra_branches(self):
 | 
						|
        """
 | 
						|
        Branch containing the additional branches to be merged with the
 | 
						|
        upstream when importing.
 | 
						|
        """
 | 
						|
        return self._extra_branches
 | 
						|
 | 
						|
    def _set_branch(self, branch, commit, checkout=False, force=False):
 | 
						|
 | 
						|
        if str(self.repo.active_branch) == branch:
 | 
						|
            self.log.info(
 | 
						|
                """\
 | 
						|
                Resetting branch '%s' to specified commit '%s'
 | 
						|
                    git reset --hard %s
 | 
						|
                """, branch, commit, commit)
 | 
						|
            self.git.reset(commit, hard=True)
 | 
						|
        elif checkout:
 | 
						|
            if force:
 | 
						|
                checkout_opt = '-B'
 | 
						|
            else:
 | 
						|
                checkout_opt = '-b'
 | 
						|
 | 
						|
            self.log.info(
 | 
						|
                """\
 | 
						|
                Checking out branch '%s' using specified commit '%s'
 | 
						|
                    git checkout %s %s %s
 | 
						|
                """, branch, commit, checkout_opt, branch, commit)
 | 
						|
            self.git.checkout(checkout_opt, branch, commit)
 | 
						|
        else:
 | 
						|
            self.log.info(
 | 
						|
                """\
 | 
						|
                Creating  branch '%s' from specified commit '%s'
 | 
						|
                    git branch --force %s %s
 | 
						|
                """, branch, commit, branch, commit)
 | 
						|
            self.git.branch(branch, commit, force=force)
 | 
						|
 | 
						|
    def create_import(self, commit=None, import_branch=None, checkout=False,
 | 
						|
                      force=False):
 | 
						|
        """
 | 
						|
        Create the import branch from the specified commit.
 | 
						|
 | 
						|
        If the branch already exists abort if force is false
 | 
						|
            If current branch, reset the head to the specified commit
 | 
						|
            If checkout is true, switch and reset the branch to the commit
 | 
						|
            Otherwise just reset the branch to the specified commit
 | 
						|
        If the branch doesn't exist, create it and switch to it
 | 
						|
        automatically if checkout is true.
 | 
						|
        """
 | 
						|
 | 
						|
        if not commit:
 | 
						|
            commit = self.upstream
 | 
						|
 | 
						|
        try:
 | 
						|
            self.git.show_ref(commit, quiet=True, heads=True)
 | 
						|
 | 
						|
        except GitCommandError as e:
 | 
						|
            msg = "Invalid commit '%s' specified to import from"
 | 
						|
            self.log.error(msg, commit)
 | 
						|
            raise ImportUpstreamError((msg + ": %s"), commit, e)
 | 
						|
 | 
						|
        if not import_branch:
 | 
						|
            import_branch = self.import_branch
 | 
						|
 | 
						|
        # use describe in order to be certain about unique identifying 'commit'
 | 
						|
        # Create a describe string with the following format:
 | 
						|
        #    <describe upstream>[-<extra branch abbref hash>]*
 | 
						|
        #
 | 
						|
        # Simply appends the 7 character ref abbreviation for each extra branch
 | 
						|
        # prefixed with '-', for each extra branch in the order they are given.
 | 
						|
        describe_commit = self.git.describe(commit, tags=True,
 | 
						|
                                            with_exceptions=False)
 | 
						|
        if not describe_commit:
 | 
						|
            self.log.warning("No tag describes the upstream branch")
 | 
						|
            describe_commit = self.git.describe(commit, always=True, tags=True)
 | 
						|
 | 
						|
        self.log.info("""\
 | 
						|
                    Using '%s' to describe:
 | 
						|
                        %s
 | 
						|
                    """, describe_commit, commit)
 | 
						|
        describe_branches = [describe_commit]
 | 
						|
 | 
						|
        describe_branches.extend([self.git.rev_parse(b, short=True)
 | 
						|
                                  for b in self.extra_branches])
 | 
						|
        import_describe = "-".join(describe_branches)
 | 
						|
        self._import_branch = self.import_branch.format(
 | 
						|
            describe=import_describe)
 | 
						|
 | 
						|
        self._import_branch = import_branch.format(describe=import_describe)
 | 
						|
        base = self._import_branch + "-base"
 | 
						|
        self.log.debug("Creating and switching to import branch base '%s' "
 | 
						|
                       "created from '%s' (%s)", base, self.upstream, commit)
 | 
						|
 | 
						|
        self.log.info(
 | 
						|
            """\
 | 
						|
            Checking if import branch '%s' already exists:
 | 
						|
                git branch --list %s
 | 
						|
            """, base, base)
 | 
						|
        if self.git.show_ref("refs/heads/" + base, verify=True,
 | 
						|
                             with_exceptions=False) and not force:
 | 
						|
            msg = "Import branch '%s' already exists, set 'force' to replace"
 | 
						|
            self.log.error(msg, self.import_branch)
 | 
						|
            raise ImportUpstreamError(msg % self.import_branch)
 | 
						|
 | 
						|
        self._set_branch(base, commit, checkout, force)
 | 
						|
 | 
						|
        if self.extra_branches:
 | 
						|
            self.log.info(
 | 
						|
                """\
 | 
						|
                Merging additional branch(es) '%s' into import branch '%s'
 | 
						|
                    git checkout %s
 | 
						|
                    git merge %s
 | 
						|
                """, ", ".join(self.extra_branches), base, base,
 | 
						|
                " ".join(self.extra_branches))
 | 
						|
            self.git.checkout(base)
 | 
						|
            self.git.merge(*self.extra_branches)
 | 
						|
 | 
						|
    def _linearise(self, branch, sequence, previous_import):
 | 
						|
 | 
						|
        counter = len(sequence) - 1
 | 
						|
        ancestors = set()
 | 
						|
 | 
						|
        self._set_branch(branch, previous_import, checkout=True, force=True)
 | 
						|
        root = previous_import.hexsha
 | 
						|
        while counter > 0:
 | 
						|
            # add commit to list of ancestors to check
 | 
						|
            ancestors.add(root)
 | 
						|
 | 
						|
            # look for merge commits that are not part of ancestry path
 | 
						|
            for idx in xrange(counter - 1, -1, -1):
 | 
						|
                commit = sequence[idx]
 | 
						|
                # if there is only one parent, no need to check the others
 | 
						|
                if len(commit.parents) < 2:
 | 
						|
                    ancestors.add(commit.hexsha)
 | 
						|
                elif any(p.hexsha not in ancestors for p in commit.parents):
 | 
						|
                    self.log.debug("Rebase upto commit SHA1: %s",
 | 
						|
                                   commit.hexsha)
 | 
						|
                    idx = idx + 1
 | 
						|
                    break
 | 
						|
                else:
 | 
						|
                    ancestors.add(commit.hexsha)
 | 
						|
            tip = sequence[idx].hexsha
 | 
						|
 | 
						|
            self.log.info("Rebasing from %s to %s", root, tip)
 | 
						|
            previous = self.git.rev_parse(branch)
 | 
						|
            self.log.info("Rebasing onto '%s'", previous)
 | 
						|
            if root == previous and idx == 0:
 | 
						|
                # special case, we are already linear
 | 
						|
                self.log.info("Already in a linear layout")
 | 
						|
                return
 | 
						|
            self._set_branch(branch, tip, force=True)
 | 
						|
            try:
 | 
						|
                self.log.debug(
 | 
						|
                    """\
 | 
						|
                        git rebase -p --onto=%s \\
 | 
						|
                            %s %s
 | 
						|
                    """, previous, root, branch)
 | 
						|
                self.git.rebase(root, branch, onto=previous, p=True)
 | 
						|
            except:
 | 
						|
                self.git.rebase(abort=True, with_exceptions=False)
 | 
						|
                raise
 | 
						|
            counter = idx - 1
 | 
						|
            # set root commit for next loop
 | 
						|
            root = sequence[counter].hexsha
 | 
						|
 | 
						|
    def apply(self, strategy, interactive=False):
 | 
						|
        """Apply list of commits given onto latest import of upstream"""
 | 
						|
 | 
						|
        commit_list = list(strategy.filtered_iter())
 | 
						|
        if len(commit_list) == 0:
 | 
						|
            self.log.notice("There are no local changes to be applied!")
 | 
						|
            return False
 | 
						|
 | 
						|
        self.log.debug(
 | 
						|
            """\
 | 
						|
            Should apply the following list of commits
 | 
						|
                %s
 | 
						|
            """, "\n    ".join([c.hexsha for c in commit_list]))
 | 
						|
 | 
						|
        base = self.import_branch + "-base"
 | 
						|
 | 
						|
        self._set_branch(self.import_branch, self.branch, force=True)
 | 
						|
        self.log.info(
 | 
						|
            """\
 | 
						|
            Creating import branch '%s' from specified commit '%s' in prep to
 | 
						|
            linearize the local changes before transposing to the new upstream:
 | 
						|
                git branch --force %s %s
 | 
						|
            """, self.import_branch, self.branch, self.import_branch,
 | 
						|
            self.branch)
 | 
						|
 | 
						|
        self.log.notice("Attempting to linearise previous changes")
 | 
						|
        # attempt to silently linearize the current carried changes as a branch
 | 
						|
        # based on the previous located import commit. This provides a sane
 | 
						|
        # abort result for if the user needs to abort the rebase of this branch
 | 
						|
        # onto the new point upstream that was requested to import from.
 | 
						|
        try:
 | 
						|
            self._linearise(self.import_branch, strategy,
 | 
						|
                            strategy.searcher.commit)
 | 
						|
        except:
 | 
						|
            # Could ask user if they want to try and use the non clean route
 | 
						|
            # provided they don't mind that 'git rebase --abort' will result
 | 
						|
            # in a virtually useless local import branch
 | 
						|
            self.log.warning(
 | 
						|
                """\
 | 
						|
 | 
						|
                Exception occurred during linearisation of local changes on to
 | 
						|
                previous import to simplify behaviour should user need to abort
 | 
						|
                the rebase that applies these changes to the latest import
 | 
						|
                point. Attempting to tidy up state.
 | 
						|
 | 
						|
                Do not Ctrl+C unless you wish to need to clean up your git
 | 
						|
                repository by hand.
 | 
						|
 | 
						|
                """)
 | 
						|
            # reset head back to the tip of the changes to be rebased
 | 
						|
            self._set_branch(self.import_branch, self.branch, force=True)
 | 
						|
 | 
						|
        rebase = RebaseEditor(interactive, repo=self.repo)
 | 
						|
        if len(commit_list):
 | 
						|
            first = commit_list[0]
 | 
						|
 | 
						|
            self.log.info(
 | 
						|
                """\
 | 
						|
                Rebase changes, dropping merges through editor:
 | 
						|
                    git rebase --onto %s \\
 | 
						|
                        %s %s
 | 
						|
                """, base, first.parents[0].hexsha, self.import_branch)
 | 
						|
            status, out, err = rebase.run(commit_list,
 | 
						|
                                          first.parents[0].hexsha,
 | 
						|
                                          self.import_branch,
 | 
						|
                                          onto=base)
 | 
						|
            if status:
 | 
						|
                if err and err.startswith("Nothing to do"):
 | 
						|
                    # cancelled by user
 | 
						|
                    self.log.notice("Cancelled by user")
 | 
						|
                    return False
 | 
						|
 | 
						|
                self.log.error("Rebase failed, will need user intervention to "
 | 
						|
                               "resolve.")
 | 
						|
                if out:
 | 
						|
                    self.log.notice(out)
 | 
						|
                if err:
 | 
						|
                    self.log.notice(err)
 | 
						|
 | 
						|
                # once we support resuming/finishing add a message here to tell
 | 
						|
                # the user to rerun this tool with the appropriate options to
 | 
						|
                # complete
 | 
						|
                return False
 | 
						|
 | 
						|
            self.log.notice("Successfully applied all locally carried changes")
 | 
						|
        else:
 | 
						|
            self.log.warning("Warning, nothing to do: locally carried " +
 | 
						|
                             "changes already rebased onto " + self.upstream)
 | 
						|
        return True
 | 
						|
 | 
						|
    def resume(self, args):
 | 
						|
        """Resume previous partial import"""
 | 
						|
        raise NotImplementedError
 | 
						|
 | 
						|
    def finish(self):
 | 
						|
        """
 | 
						|
        Finish merge according to the selected strategy while performing
 | 
						|
        suitable verification checks.
 | 
						|
        """
 | 
						|
        self.log.info("No verification checks enabled")
 | 
						|
        self.git.checkout(self.branch)
 | 
						|
        current_sha = self.git.rev_parse("HEAD")
 | 
						|
 | 
						|
        try:
 | 
						|
            self.log.info(
 | 
						|
                """\
 | 
						|
                Merging by inverting the 'ours' strategy discard all changes
 | 
						|
                and replace existing branch contents with the new import.
 | 
						|
                """)
 | 
						|
            self.log.info(
 | 
						|
                """\
 | 
						|
                Merging import branch to HEAD and ignoring changes:
 | 
						|
                    git merge -s ours --no-commit %s
 | 
						|
                """, self.import_branch)
 | 
						|
            self.git.merge('-s', 'ours', self.import_branch, no_commit=True)
 | 
						|
            self.log.info(
 | 
						|
                """\
 | 
						|
                Replacing tree contents with those from the import branch:
 | 
						|
                    git read-tree -u --reset %s
 | 
						|
                """, self.import_branch)
 | 
						|
            self.git.read_tree(self.import_branch, u=True, reset=True)
 | 
						|
            self.log.info(
 | 
						|
                """\
 | 
						|
                Committing merge commit:
 | 
						|
                    git commit --no-edit
 | 
						|
                """)
 | 
						|
            self.git.commit(no_edit=True)
 | 
						|
            # finally test that everything worked correctly by comparing if
 | 
						|
            # the tree object id's match
 | 
						|
            if self.git.rev_parse("HEAD^{tree}") != \
 | 
						|
                    self.git.rev_parse("%s^{tree}" % self.import_branch):
 | 
						|
                raise ImportUpstreamError(
 | 
						|
                    "Resulting tree does not match import")
 | 
						|
        except (GitCommandError, ImportUpstreamError):
 | 
						|
            self.log.error(
 | 
						|
                """\
 | 
						|
                Failed to finish import by merging branch:
 | 
						|
                    '%s'
 | 
						|
                into and replacing the contents of:
 | 
						|
                    '%s'
 | 
						|
                """, self.import_branch, self.branch)
 | 
						|
            self._set_branch(self.branch, current_sha, force=True)
 | 
						|
            return False
 | 
						|
        except:
 | 
						|
            self.log.exception("Unknown exception during finish")
 | 
						|
            self._set_branch(self.branch, current_sha, force=True)
 | 
						|
            raise
 | 
						|
        return True
 | 
						|
 | 
						|
 | 
						|
class ImportStrategiesFactory(object):
 | 
						|
    __strategies = None
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def create_strategy(cls, type, *args, **kwargs):
 | 
						|
        if type in cls.list_strategies():
 | 
						|
            return cls.__strategies[type](*args, **kwargs)
 | 
						|
        else:
 | 
						|
            raise RuntimeError("No class implements the requested strategy: "
 | 
						|
                               "{0}".format(type))
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def list_strategies(cls):
 | 
						|
        cls.__strategies = {
 | 
						|
            subclass._strategy: subclass
 | 
						|
            for subclass in LocateChangesStrategy.__subclasses__()
 | 
						|
            if subclass._strategy}
 | 
						|
        return cls.__strategies.keys()
 | 
						|
 | 
						|
 | 
						|
from git_upstream.lib.searchers import (NoMergeCommitFilter,
 | 
						|
                                        ReverseCommitFilter,
 | 
						|
                                        DiscardDuplicateGerritChangeId,
 | 
						|
                                        SupersededCommitFilter,
 | 
						|
                                        DroppedCommitFilter)
 | 
						|
 | 
						|
 | 
						|
class LocateChangesStrategy(GitMixin, Sequence):
 | 
						|
    """
 | 
						|
    Base class that needs to be extended with the specific strategy on how to
 | 
						|
    handle changes locally that are not yet upstream.
 | 
						|
    """
 | 
						|
    __metaclass__ = ABCMeta
 | 
						|
 | 
						|
    @abstractmethod
 | 
						|
    def __init__(self, git=None, *args, **kwargs):
 | 
						|
        """
 | 
						|
        Initialize an empty filters list
 | 
						|
        """
 | 
						|
        self.data = None
 | 
						|
        self.filters = []
 | 
						|
        super(LocateChangesStrategy, self).__init__(*args, **kwargs)
 | 
						|
 | 
						|
    def __getitem__(self, key):
 | 
						|
        if not self.data:
 | 
						|
            self.data = self._popdata()
 | 
						|
        return self.data[key]
 | 
						|
 | 
						|
    def __len__(self):
 | 
						|
        if not self.data:
 | 
						|
            self.data = self._popdata()
 | 
						|
        return len(self.data)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def get_strategy_name(cls):
 | 
						|
        return cls._strategy
 | 
						|
 | 
						|
    def filtered_iter(self):
 | 
						|
        # chain the filters as generators so that we don't need to allocate new
 | 
						|
        # lists for each step in the filter chain.
 | 
						|
        commit_list = self
 | 
						|
        for f in self.filters:
 | 
						|
            commit_list = f.filter(commit_list)
 | 
						|
 | 
						|
        return commit_list
 | 
						|
 | 
						|
    def filtered_list(self):
 | 
						|
 | 
						|
        return list(self.filtered_iter())
 | 
						|
 | 
						|
    def _popdata(self):
 | 
						|
        """
 | 
						|
        Should return the list of commits from the searcher object
 | 
						|
        """
 | 
						|
        return self.searcher.list()
 | 
						|
 | 
						|
 | 
						|
class LocateChangesWalk(LocateChangesStrategy):
 | 
						|
    """
 | 
						|
    """
 | 
						|
 | 
						|
    _strategy = "drop"
 | 
						|
 | 
						|
    def __init__(self, branch="HEAD", search_ref=None, *args, **kwargs):
 | 
						|
        self.searcher = UpstreamMergeBaseSearcher(branch=branch,
 | 
						|
                                                  pattern=search_ref)
 | 
						|
        self.search_ref = search_ref
 | 
						|
        super(LocateChangesWalk, self).__init__(*args, **kwargs)
 | 
						|
 | 
						|
    def filtered_iter(self):
 | 
						|
        # may wish to make class used to remove duplicate objects configurable
 | 
						|
        # through git-upstream specific 'git config' settings
 | 
						|
        if self.search_ref:
 | 
						|
            self.filters.append(
 | 
						|
                DiscardDuplicateGerritChangeId(self.search_ref,
 | 
						|
                                               limit=self.searcher.commit))
 | 
						|
        self.filters.append(NoMergeCommitFilter())
 | 
						|
        self.filters.append(ReverseCommitFilter())
 | 
						|
        self.filters.append(DroppedCommitFilter())
 | 
						|
        self.filters.append(
 | 
						|
            SupersededCommitFilter(self.search_ref,
 | 
						|
                                   limit=self.searcher.commit))
 | 
						|
 | 
						|
        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('--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):
 | 
						|
    """
 | 
						|
    Import code from specified upstream branch.
 | 
						|
 | 
						|
    Creates an import branch from the specified upstream branch, and optionally
 | 
						|
    merges additional branches given as arguments. Current branch, unless
 | 
						|
    overridden by the --into option, is used as the target branch from which a
 | 
						|
    list of changes to apply onto the new import is constructed based on the
 | 
						|
    the specified strategy.
 | 
						|
 | 
						|
    Once complete it will merge and replace the contents of the target branch
 | 
						|
    with those from the import branch, unless --no-merge is specified.
 | 
						|
    """
 | 
						|
 | 
						|
    logger = log.get_logger('%s.%s' % (__name__,
 | 
						|
                                       inspect.stack()[0][0].f_code.co_name))
 | 
						|
 | 
						|
    import_upstream = ImportUpstream(branch=args.branch,
 | 
						|
                                     upstream=args.upstream_branch,
 | 
						|
                                     import_branch=args.import_branch,
 | 
						|
                                     extra_branches=args.branches)
 | 
						|
 | 
						|
    logger.notice("Searching for previous import")
 | 
						|
    strategy = ImportStrategiesFactory.create_strategy(
 | 
						|
        args.strategy, branch=args.branch, search_ref=args.upstream_branch)
 | 
						|
 | 
						|
    if len(strategy) == 0:
 | 
						|
        raise ImportUpstreamError("Cannot find previous import")
 | 
						|
 | 
						|
    # 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:
 | 
						|
        idx = next((idx for idx, commit in enumerate(prev_import_merge.parents)
 | 
						|
                    if commit.hexsha == strategy.searcher.commit.hexsha), None)
 | 
						|
 | 
						|
        if idx:
 | 
						|
            additional_commits = prev_import_merge.parents[idx + 1:]
 | 
						|
            if additional_commits and not args.branches:
 | 
						|
                logger.warning("""\
 | 
						|
                    **************** WARNING ****************
 | 
						|
                    Previous import merged additional branches but non have
 | 
						|
                    been specified on the command line for this import.\n""")
 | 
						|
 | 
						|
    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
 | 
						|
 | 
						|
                %s
 | 
						|
            """, "\n    ".join(commit_list))
 | 
						|
        return True
 | 
						|
 | 
						|
    logger.notice("Starting import of upstream")
 | 
						|
    import_upstream.create_import(force=args.force)
 | 
						|
    logger.notice("Successfully created import branch")
 | 
						|
 | 
						|
    if not import_upstream.apply(strategy, args.interactive):
 | 
						|
        logger.notice("Import cancelled")
 | 
						|
        return False
 | 
						|
 | 
						|
    if not args.merge:
 | 
						|
        logger.notice(
 | 
						|
            """\
 | 
						|
            Import complete, not merging to target branch '%s' as requested.
 | 
						|
            """, args.branch)
 | 
						|
        return True
 | 
						|
 | 
						|
    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)
 | 
						|
 | 
						|
 | 
						|
# vim:sw=4:sts=4:ts=4:et:
 |