Files
git-upstream/ghp/commands/import_upstream.py
Darragh Bailey 57bf4a6f5e Create a logging mixin class that auto dedents log messages
Construct a metaclass that can be used to create subclasses of the
logging.Logger class which wraps all methods for logging output with a
function that initially dedents the message.

Change-Id: Ice5dd54dbdeabb086115953d30369dd933c7a374
2013-07-24 07:45:21 +01:00

268 lines
9.9 KiB
Python

#
# Copyright (c) 2012 Hewlett-Packard
#
# 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 ghp.errors import HpgitError
from ghp.log import LogDedentMixin
from ghp import subcommand, log
from git import Repo, GitCommandError
import inspect
import os
class ImportUpstreamError(HpgitError):
"""Exception thrown by L{ImportUpstream}"""
pass
class ImportUpstream(LogDedentMixin):
"""
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):
self._branch = branch
self._upstream = upstream
self._import_branch = import_branch
self._extra_branches = extra_branches
self._repo = Repo(os.environ.get('GIT_WORK_TREE', os.path.curdir))
self._git = self.repo.git
# make sure to correctly initialise inherited objects before performing
# any computation
super(ImportUpstream, self).__init__()
if self.repo.bare:
raise ImportUpstreamError("Cannot perform imports in bare repos")
if self.branch == 'HEAD':
self._branch = self.repo.active_branch
branches = {
'upstream': self.upstream,
'branch': self.branch
}
if self._extra_branches != []:
branches.update({'extra branch %d' % idx: value
for (idx, value) in enumerate(self.extra_branches, 1)})
for branch_type, branch in branches.iteritems():
if not any(head for head in self.repo.heads if head.name == branch):
msg = "Specified %s branch not found: %s"
self.log.error(msg, branch_type, branch)
raise ImportUpstreamError(msg % (branch_type, branch))
@staticmethod
def __setup__(self, argparser):
pass
@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
@property
def repo(self):
"""Git repository object for performing operations"""
return self._repo
@property
def git(self):
"""
Git command object for performing direct git operations using
python-git.
"""
return self._git
def create_import(self, commit=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.
"""
# use describe directly in order to be certain about unique identifying the
# commit
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)
import_describe = self.git.describe(commit)
self._import_branch = self.import_branch.format(import_describe)
self.log.debug("Creating and switching to import branch '%s' created "
"from '%s' (%s)", self.import_branch, self.upstream, commit)
self.log.info(
"""\
Checking if import branch '%s' already exists:
git branch --list %s
""", self.import_branch, self.import_branch)
if self.git.show_ref("refs/heads/" + self.import_branch, verify=True,
with_exceptions=False) and not force:
msg = "Import branch '%s' already exists, use force to replace"
self.log.error(msg, self.import_branch)
raise ImportUpstreamError(msg % self.import_branch)
if self.repo.active_branch == self.import_branch:
self.log.info(
"""\
Resetting import branch '%s' to specified commit '%s'
git reset --hard %s
""", self.import_branch, commit, commit)
self.git.reset(commit, hard=True)
elif checkout:
checkout_args = dict(b=True)
if force:
checkout_args = dict(B=True)
self.log.info(
"""\
Checking out import branch '%s' using specified commit '%s'
git checkout %s %s %s
""", self.import_branch, commit, checkout_args,
self.import_branch, commit)
self.git.checkout(self.import_branch, commit, **checkout_args)
else:
self.log.info(
"""\
Creating import branch '%s' from specified commit '%s'
git branch --force %s %s
""", self.import_branch, commit, self.import_branch, commit)
self.git.branch(self.import_branch, commit, force=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), self.import_branch,
self.import_branch, " ".join(self.extra_branches))
self.git.checkout(self.import_branch)
self.git.merge(*self.extra_branches)
def find_changes(self):
pass
def start(self, args):
"""Start import of upstream"""
commit = args.get('upstream-commit', args.get('upstream-branch', None))
self.create_import(commit, checkout=args['checkout'], force=args['force'])
def resume(self, args):
"""Resume previous partial import"""
def finish(self, args):
"""
Finish merge according to the selected strategy while performing
suitable verification checks.
"""
@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=False,
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('--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/{0}')
@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_upstream(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 specificed 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.getLogger('%s.%s' % (__name__,
inspect.stack()[0][0].f_code.co_name))
importupstream = ImportUpstream(branch=args.branch,
upstream=args.upstream_branch,
import_branch=args.import_branch,
extra_branches=args.branches)
commit = getattr(args, 'upstream_commit', None) or args.upstream_branch
logger.notice("Starting import of upstream")
importupstream.create_import(commit,
checkout=getattr(args, 'checkout', None),
force=getattr(args, 'force', None))
logger.notice("Successfully created import branch")
# vim:sw=4:sts=4:ts=4:et: