Files
git-upstream/ghp/commands/supersede.py
Davide Guerri 24df2f38af Add drop and supersede commands
* supersede command allows the marking of commits as superceded by a
  set of upstream changes
* drop command allows the marking of commits that should be dropped

Both commands add git notes to a given sha1 in the upstream-merge
namespace (refs/notes/upstream-merge).
The notes are read by the import-upstream command during import and
the appropriate actions are then taken.

Add functional and unit tests for the two newly created commands.

JIRA: CICD-248
Change-Id: I6f69dd890af18e77a9affdb958afde1ec8b1cab8
2014-02-17 15:03:49 +00:00

194 lines
7.2 KiB
Python

#
# Copyright (c) 2013, 2014 Hewlett-Packard Development Company, L.P.
#
# Confidential computer software. Valid license from HP required for
# possession, use or copying. Consistent with FAR 12.211 and 12.212,
# Commercial Computer Software, Computer Software Documentation, and
# Technical Data for Commercial Items are licensed to the U.S. Government
# under vendor's standard commercial license.
#
from ghp.errors import HpgitError
from ghp.log import LogDedentMixin
from ghp.lib.utils import GitMixin
from ghp.lib.searchers import CommitMessageSearcher
from ghp import subcommand, log
from git import BadObject, Head
import inspect
import re
class SupersedeError(HpgitError):
"""Exception thrown by L{Supersede}"""
pass
class Supersede(LogDedentMixin, GitMixin):
"""
Mark a commit as superseded.
The mark is applied by means of a note in upstream-merge namespace
(refs/notes/upstream-merge).
The note will contain one or more of the following headers:
Superseded-by: I82ef79c3621dacf619a02404f16464877a06f158
"""
SUPERSEDE_HEADER = 'Superseded-by:'
NOTE_REF = 'refs/notes/upstream-merge'
CHANGE_ID_REGEX = '^I[0-9a-f]{6,40}$'
CHANGE_ID_HEADER_REGEX_FMT = '^Change-Id:\s*%s'
CHANGE_ID_HEADER_REGEX = '^Change-Id:\s*(I[0-9a-f]{6,40})$'
def __init__(self, git_object=None, change_ids=list(),
upstream_branch=None, force=False, *args, **kwargs):
# make sure to correctly initialize inherited objects before performing
# any computation
super(Supersede, self).__init__(*args, **kwargs)
# test commit parameter
if not git_object:
raise SupersedeError("Commit should be provided")
# test that we can use this git repo
if not self.is_detached():
raise SupersedeError("In 'detached HEAD' state")
# To Do: check if it possible and useful.
if self.repo.bare:
raise SupersedeError("Cannot add notes in bare repositories")
if not upstream_branch:
raise SupersedeError("Missing upstream_branch parameter")
try:
# test commit "id" presence
self._commit = self.repo.commit(git_object)
except BadObject:
raise SupersedeError("Commit '%s' not found (or ambiguous)" %
git_object)
# test change_ids parameter
if len(change_ids) == 0:
raise SupersedeError("At least one change id should be provided")
self._upstream_branch = upstream_branch
self._change_ids = change_ids
git_branch = Head(self.repo, 'refs/heads/%s' % upstream_branch)
for change_id in change_ids:
# Check change id format
if not re.match(Supersede.CHANGE_ID_REGEX, change_id,
re.IGNORECASE):
raise SupersedeError("Invalid Change Id '%s'" % change_id)
# Check if change id is actually present in some commit
# reachable from <upstream_branch>
try:
change_commit = CommitMessageSearcher(
repo=self.repo, branch=git_branch,
pattern=Supersede.CHANGE_ID_HEADER_REGEX_FMT %
change_id).find()
self.log.debug("Change-id '%s' found in commit '%s'" %
(change_id, change_commit))
except RuntimeError as e:
if force:
self.log.warn("Warning: change-id '%s' not found in '%s'" %
(change_id, upstream_branch))
else:
raise SupersedeError(
"Change-Id '%s' not found in branch '%s'" %
(change_id, upstream_branch))
@property
def commit(self):
"""Commit to be marked as superseded."""
return self._commit
@property
def change_ids(self):
"""Change ids that make a commit obsolete."""
return self._change_ids
@property
def change_ids_branch(self):
"""Branch to search for change ids"""
return self._upstream_branch
def check_duplicates(self):
"""
Check if a supersede header is already present in the note containing
one of change ids passed on the command line
"""
note = self.commit.note(note_ref=Supersede.NOTE_REF)
if note:
pattern = '^%s\s?(%s)$' % (Supersede.SUPERSEDE_HEADER,
'|'.join(self.change_ids))
m = re.search(pattern, note, re.MULTILINE | re.IGNORECASE)
if m:
self.log.warning(
("Change-Id '%s' already present in the note for commit" +
" '%s'") % (m.group(1), self.commit))
return False
return True
def mark(self):
"""Create the note for the given commit with the given change-ids."""
self.log.debug("Creating a note for commit '%s'", self.commit)
if self.check_duplicates():
git_note = ''
for change_id in self.change_ids:
git_note += '%s %s\n' % (Supersede.SUPERSEDE_HEADER, change_id)
self.log.debug('With the following content:')
self.log.debug(git_note)
self.commit.append_note(git_note, note_ref=Supersede.NOTE_REF)
else:
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-upstream 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.
Marked commits will be skipped during the upstream rebasing process.
See also the "git hp import-upstream" command.
"""
logger = log.getLogger('%s.%s' % (__name__,
inspect.stack()[0][0].f_code.co_name))
supersede = Supersede(git_object=args.commit, change_ids=args.change_ids,
upstream_branch=args.upstream_branch,
force=args.force)
if supersede.mark():
logger.notice("Supersede mark created successfully")
# vim:sw=4:sts=4:ts=4:et: