Merge "Add drop and supersede commands"
This commit is contained in:
@@ -19,7 +19,7 @@ function test_help_output() {
|
|||||||
"$help1" != "$help2" -o "$help2" != "$help3" ] && return 1 || return 0
|
"$help1" != "$help2" -o "$help2" != "$help3" ] && return 1 || return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
for com in "" "import-upstream" ; do
|
for com in "" "import-upstream" "drop" "supersede" ; do
|
||||||
test_help_output $com && log INFO "test_help_output::${com:-null} passed." || \
|
test_help_output $com && log INFO "test_help_output::${com:-null} passed." || \
|
||||||
log ERROR "test_help_output::${com:-null} failed!"
|
log ERROR "test_help_output::${com:-null} failed!"
|
||||||
done
|
done
|
||||||
|
169
functional-tests/050-test_supersede.sh
Executable file
169
functional-tests/050-test_supersede.sh
Executable file
@@ -0,0 +1,169 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
BASE_DIR=$(cd $(dirname $0); pwd -P)
|
||||||
|
|
||||||
|
# Include and run common test functions and initializations
|
||||||
|
source $BASE_DIR/libs/logging.lib
|
||||||
|
source $BASE_DIR/libs/utils.lib
|
||||||
|
|
||||||
|
REPO_NAME="empty-repo"
|
||||||
|
UPSTREAM_REPO=$(git rev-parse --show-toplevel)
|
||||||
|
TEST_BASE_REF="2c4bf67b5c416adfb162d9ca1fb4b0bf353fbb2a"
|
||||||
|
TEST_REBASE_REF="fd3524e1b7353cda228b6fb73c3a2d34a4fee4de"
|
||||||
|
VALID_CHID="I82ef79c3621dacf619a02404f16464877a06f158"
|
||||||
|
VALID_CHID2="I2492200f8e6fb0cc470cc376eb17a39b4b3033ff"
|
||||||
|
INVALID_CHID="I0123456789abcdef0123456789abcdef01234567"
|
||||||
|
|
||||||
|
SUCCESS_SHA1="b8c16b3dd8883d02b4b65882ad5467c9f5e7beb9 ?-"
|
||||||
|
|
||||||
|
function _common() {
|
||||||
|
prepare_for_hpgit $TEST_DIR $REPO_NAME $UPSTREAM_REPO $TEST_BASE_REF \
|
||||||
|
$TEST_NAME
|
||||||
|
|
||||||
|
pushd $TEST_DIR/$REPO_NAME >/dev/null
|
||||||
|
|
||||||
|
log DEBUG "Creating a local patches"
|
||||||
|
cat <<EOP | patch -tsp1 || return 1
|
||||||
|
diff --git a/setup.py b/setup.py
|
||||||
|
index 170ec46..251e1dd 100644
|
||||||
|
--- a/setup.py
|
||||||
|
+++ b/setup.py
|
||||||
|
@@ -28,6 +28,8 @@ setup(
|
||||||
|
version=version.version,
|
||||||
|
author="Darragh Bailey",
|
||||||
|
author_email="dbailey@hp.com",
|
||||||
|
+ maintainer="Davide Guerri",
|
||||||
|
+ maintainer_email="davide.guerri@hp.com",
|
||||||
|
description=("Tool supporting HPCloud git workflows."),
|
||||||
|
license="Proprietary",
|
||||||
|
keywords="git hpcloud workflow",
|
||||||
|
EOP
|
||||||
|
git commit -a -m "Add maintainer info" --quiet || return 1
|
||||||
|
|
||||||
|
cat <<EOP | patch -tsp1 || return 1
|
||||||
|
diff --git a/nothing b/nothing
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..9dafe9b
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/nothing
|
||||||
|
@@ -0,0 +1 @@
|
||||||
|
+nothing
|
||||||
|
EOP
|
||||||
|
git add nothing
|
||||||
|
git commit -a -m "Add nothing" --quiet || return 1
|
||||||
|
git push -u origin master --quiet >/dev/null || return 1
|
||||||
|
|
||||||
|
git checkout master --quiet || return 1
|
||||||
|
|
||||||
|
log DEBUG "Rebasing local patches onto upstream version $TEST_REBASE_REF"
|
||||||
|
git branch import/$TEST_NAME-new $TEST_REBASE_REF --quiet || return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_existing_changeid() {
|
||||||
|
log DEBUG "Starting $TEST_NAME::$FUNCNAME"
|
||||||
|
|
||||||
|
_common || return 1
|
||||||
|
|
||||||
|
local commit_sha1=$(git log -1 --format='%H')
|
||||||
|
|
||||||
|
git-hp supersede $commit_sha1 $VALID_CHID -u import/$TEST_NAME-new \
|
||||||
|
>/dev/null || return 1
|
||||||
|
|
||||||
|
git-hp import-upstream import/$TEST_NAME-new >/dev/null || return 1
|
||||||
|
|
||||||
|
git show --numstat | grep '0\s\s*1\s\s*nothing' >/dev/null
|
||||||
|
if [ "$?" -ne 0 ]; then
|
||||||
|
popd >/dev/null
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
popd >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_non_existing_changeid() {
|
||||||
|
log DEBUG "Starting $TEST_NAME::$FUNCNAME"
|
||||||
|
|
||||||
|
_common || return 1
|
||||||
|
|
||||||
|
local commit_sha1=$(git log -1 --format='%H')
|
||||||
|
|
||||||
|
git-hp supersede $commit_sha1 $INVALID_CHID -u import/$TEST_NAME-new 2>&1 | \
|
||||||
|
grep "CRITICAL: Change-Id '$INVALID_CHID' not found in branch \
|
||||||
|
'import/$TEST_NAME-new'" >/dev/null
|
||||||
|
if [ "$?" -ne 0 ]; then
|
||||||
|
popd >/dev/null
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
popd >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_non_existing_changeid_force() {
|
||||||
|
log DEBUG "Starting $TEST_NAME::$FUNCNAME"
|
||||||
|
|
||||||
|
_common || return 1
|
||||||
|
|
||||||
|
local commit_sha1=$(git log -1 --format='%H')
|
||||||
|
|
||||||
|
git-hp supersede $commit_sha1 $INVALID_CHID -u import/$TEST_NAME-new -f \
|
||||||
|
>/dev/null || return 1
|
||||||
|
|
||||||
|
git-hp -vv import-upstream import/$TEST_NAME-new | \
|
||||||
|
grep -e "Including commit '[0-9a-f][0-9a-f]* Add nothing'" \
|
||||||
|
>/dev/null
|
||||||
|
if [ "$?" -ne 0 ]; then
|
||||||
|
popd >/dev/null
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
popd >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_multiple_changeids() {
|
||||||
|
log DEBUG "Starting $TEST_NAME::$FUNCNAME"
|
||||||
|
|
||||||
|
_common || return 1
|
||||||
|
|
||||||
|
local commit_sha1=$(git log -1 --format='%H')
|
||||||
|
|
||||||
|
git-hp supersede $commit_sha1 $VALID_CHID $VALID_CHID2 \
|
||||||
|
-u import/$TEST_NAME-new >/dev/null || return 1
|
||||||
|
|
||||||
|
git-hp -vv import-upstream import/$TEST_NAME-new >/dev/null || return 1
|
||||||
|
|
||||||
|
git show --numstat | grep '0\s\s*1\s\s*nothing' >/dev/null
|
||||||
|
if [ "$?" -ne 0 ]; then
|
||||||
|
popd >/dev/null
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
popd >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_one_non_exsisting_changeid() {
|
||||||
|
log DEBUG "Starting $TEST_NAME::$FUNCNAME"
|
||||||
|
|
||||||
|
_common || return 1
|
||||||
|
|
||||||
|
local commit_sha1=$(git log -1 --format='%H')
|
||||||
|
|
||||||
|
git-hp supersede $commit_sha1 $VALID_CHID $INVALID_CHID \
|
||||||
|
-u import/$TEST_NAME-new 2>&1 | \
|
||||||
|
grep "CRITICAL: Change-Id '$INVALID_CHID' not found in branch \
|
||||||
|
'import/$TEST_NAME-new'" >/dev/null
|
||||||
|
if [ "$?" -ne 0 ]; then
|
||||||
|
popd >/dev/null
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
popd >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
TESTS="test_existing_changeid test_non_existing_changeid \
|
||||||
|
test_non_existing_changeid_force test_multiple_changeids \
|
||||||
|
test_one_non_exsisting_changeid"
|
||||||
|
|
||||||
|
for test in $TESTS; do
|
||||||
|
$test && log INFO "$TEST_NAME::$test() passed." || \
|
||||||
|
log ERROR "$TEST_NAME::$test() failed!"
|
||||||
|
done
|
104
functional-tests/060-test_drop.sh
Executable file
104
functional-tests/060-test_drop.sh
Executable file
@@ -0,0 +1,104 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
BASE_DIR=$(cd $(dirname $0); pwd -P)
|
||||||
|
|
||||||
|
# Include and run common test functions and initializations
|
||||||
|
source $BASE_DIR/libs/logging.lib
|
||||||
|
source $BASE_DIR/libs/utils.lib
|
||||||
|
|
||||||
|
REPO_NAME="empty-repo"
|
||||||
|
UPSTREAM_REPO=$(git rev-parse --show-toplevel)
|
||||||
|
TEST_BASE_REF="2c4bf67b5c416adfb162d9ca1fb4b0bf353fbb2a"
|
||||||
|
TEST_REBASE_REF="fd3524e1b7353cda228b6fb73c3a2d34a4fee4de"
|
||||||
|
|
||||||
|
SUCCESS_SHA1="b8c16b3dd8883d02b4b65882ad5467c9f5e7beb9 ?-"
|
||||||
|
|
||||||
|
function _common() {
|
||||||
|
prepare_for_hpgit $TEST_DIR $REPO_NAME $UPSTREAM_REPO $TEST_BASE_REF \
|
||||||
|
$TEST_NAME
|
||||||
|
|
||||||
|
pushd $TEST_DIR/$REPO_NAME >/dev/null
|
||||||
|
|
||||||
|
log DEBUG "Creating a local patches"
|
||||||
|
cat <<EOP | patch -tsp1 || return 1
|
||||||
|
diff --git a/setup.py b/setup.py
|
||||||
|
index 170ec46..251e1dd 100644
|
||||||
|
--- a/setup.py
|
||||||
|
+++ b/setup.py
|
||||||
|
@@ -28,6 +28,8 @@ setup(
|
||||||
|
version=version.version,
|
||||||
|
author="Darragh Bailey",
|
||||||
|
author_email="dbailey@hp.com",
|
||||||
|
+ maintainer="Davide Guerri",
|
||||||
|
+ maintainer_email="davide.guerri@hp.com",
|
||||||
|
description=("Tool supporting HPCloud git workflows."),
|
||||||
|
license="Proprietary",
|
||||||
|
keywords="git hpcloud workflow",
|
||||||
|
EOP
|
||||||
|
git commit -a -m "Add maintainer info" --quiet || return 1
|
||||||
|
|
||||||
|
cat <<EOP | patch -tsp1 || return 1
|
||||||
|
diff --git a/nothing b/nothing
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..9dafe9b
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/nothing
|
||||||
|
@@ -0,0 +1 @@
|
||||||
|
+nothing
|
||||||
|
EOP
|
||||||
|
git add nothing
|
||||||
|
git commit -a -m "Add nothing" --quiet || return 1
|
||||||
|
git push -u origin master --quiet >/dev/null || return 1
|
||||||
|
|
||||||
|
git checkout master --quiet || return 1
|
||||||
|
|
||||||
|
log DEBUG "Rebasing local patches onto upstream version $TEST_REBASE_REF"
|
||||||
|
git branch import/$TEST_NAME-new $TEST_REBASE_REF --quiet || return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_new() {
|
||||||
|
log DEBUG "Starting $TEST_NAME::$FUNCNAME"
|
||||||
|
|
||||||
|
_common || return 1
|
||||||
|
|
||||||
|
local commit_sha1=$(git log -1 --format='%H')
|
||||||
|
|
||||||
|
git-hp drop $commit_sha1
|
||||||
|
|
||||||
|
git-hp import-upstream import/$TEST_NAME-new >/dev/null || return 1
|
||||||
|
|
||||||
|
git show --numstat | grep '0\s\s*1\s\s*nothing' >/dev/null
|
||||||
|
if [ "$?" -ne 0 ]; then
|
||||||
|
popd >/dev/null
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
popd >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_already_present() {
|
||||||
|
log DEBUG "Starting $TEST_NAME::$FUNCNAME"
|
||||||
|
|
||||||
|
_common || return 1
|
||||||
|
|
||||||
|
local commit_sha1=$(git log -1 --format='%H')
|
||||||
|
|
||||||
|
git-hp drop $commit_sha1
|
||||||
|
|
||||||
|
git-hp drop $commit_sha1 2>&1 | \
|
||||||
|
grep "Drop note has not been added as '$commit_sha1' already has one" \
|
||||||
|
>/dev/null
|
||||||
|
if [ "$?" -ne 0 ]; then
|
||||||
|
popd >/dev/null
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
popd >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
TESTS="test_new test_already_present"
|
||||||
|
|
||||||
|
for test in $TESTS; do
|
||||||
|
$test && log INFO "$TEST_NAME::$test() passed." || \
|
||||||
|
log ERROR "$TEST_NAME::$test() failed!"
|
||||||
|
done
|
134
ghp/commands/drop.py
Normal file
134
ghp/commands/drop.py
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
#
|
||||||
|
# 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 import subcommand, log
|
||||||
|
|
||||||
|
from git import BadObject, util
|
||||||
|
|
||||||
|
import inspect
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
class DropError(HpgitError):
|
||||||
|
"""Exception thrown by L{Drop}"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Drop(LogDedentMixin, GitMixin):
|
||||||
|
"""Mark a commit to be dropped on next import.
|
||||||
|
|
||||||
|
Mark a commit as to be dropped.
|
||||||
|
|
||||||
|
The mark is applied by means of a note in upstream-merge namespace
|
||||||
|
(refs/notes/upstream-merge).
|
||||||
|
|
||||||
|
The note will contain the following header:
|
||||||
|
|
||||||
|
Dropped: Walter White <heisenberg@hp.com>
|
||||||
|
|
||||||
|
"""
|
||||||
|
DROP_HEADER = 'Dropped:'
|
||||||
|
NOTE_REF = 'refs/notes/upstream-merge'
|
||||||
|
|
||||||
|
def __init__(self, git_object=None, author=None, *args, **kwargs):
|
||||||
|
|
||||||
|
# make sure to correctly initialize inherited objects before performing
|
||||||
|
# any computation
|
||||||
|
super(Drop, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# test parameters
|
||||||
|
if not git_object:
|
||||||
|
raise DropError("Commit should be provided")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# test commit "id" presence
|
||||||
|
self._commit = self.repo.commit(git_object)
|
||||||
|
except BadObject:
|
||||||
|
raise DropError("Commit '%s' not found (or ambiguous)" % git_object)
|
||||||
|
|
||||||
|
if not author:
|
||||||
|
self._author = '%s <%s>' % (self.repo.git.config('user.name'),
|
||||||
|
self.repo.git.config('user.email'))
|
||||||
|
else:
|
||||||
|
self._author = author
|
||||||
|
|
||||||
|
# test that we can use this git repo
|
||||||
|
if not self.is_detached():
|
||||||
|
raise DropError("In 'detached HEAD' state")
|
||||||
|
|
||||||
|
# To Do: check if it possible and useful.
|
||||||
|
if self.repo.bare:
|
||||||
|
raise DropError("Cannot add notes in bare repositories")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def commit(self):
|
||||||
|
"""Commit to be marked as dropped."""
|
||||||
|
return self._commit
|
||||||
|
|
||||||
|
@property
|
||||||
|
def author(self):
|
||||||
|
"""Commit to be marked as dropped."""
|
||||||
|
return self._author
|
||||||
|
|
||||||
|
def check_duplicates(self):
|
||||||
|
"""Check if a dropped header is already present"""
|
||||||
|
note = self.commit.note(note_ref=Drop.NOTE_REF)
|
||||||
|
if note:
|
||||||
|
pattern = '^%s\s*(.+)' % Drop.DROP_HEADER
|
||||||
|
m = re.search(pattern, note, re.MULTILINE | re.IGNORECASE)
|
||||||
|
if m:
|
||||||
|
self.log.warning(
|
||||||
|
"""Drop header already present in the note for commit '%s':
|
||||||
|
%s""" % (self.commit, m.group(1)))
|
||||||
|
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 = '%s %s\n' % (Drop.DROP_HEADER, self.author)
|
||||||
|
self.log.debug('With the following content:')
|
||||||
|
self.log.debug(git_note)
|
||||||
|
self.commit.append_note(git_note, note_ref=Drop.NOTE_REF)
|
||||||
|
else:
|
||||||
|
self.log.warning(
|
||||||
|
"Drop note has not been added as '%s' already has one" %
|
||||||
|
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.
|
||||||
|
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))
|
||||||
|
|
||||||
|
drop = Drop(git_object=args.commit, author=args.author)
|
||||||
|
|
||||||
|
if drop.mark():
|
||||||
|
logger.notice("Drop mark created successfully")
|
||||||
|
|
||||||
|
# vim:sw=4:sts=4:ts=4:et:
|
@@ -428,7 +428,8 @@ class ImportStrategiesFactory(object):
|
|||||||
|
|
||||||
|
|
||||||
from ghp.lib.searchers import (NoMergeCommitFilter, ReverseCommitFilter,
|
from ghp.lib.searchers import (NoMergeCommitFilter, ReverseCommitFilter,
|
||||||
DiscardDuplicateGerritChangeId)
|
DiscardDuplicateGerritChangeId,
|
||||||
|
SupersededCommitFilter, DroppedCommitFilter)
|
||||||
|
|
||||||
|
|
||||||
class LocateChangesStrategy(GitMixin, Sequence):
|
class LocateChangesStrategy(GitMixin, Sequence):
|
||||||
@@ -502,6 +503,9 @@ class LocateChangesWalk(LocateChangesStrategy):
|
|||||||
limit=self.searcher.commit))
|
limit=self.searcher.commit))
|
||||||
self.filters.append(NoMergeCommitFilter())
|
self.filters.append(NoMergeCommitFilter())
|
||||||
self.filters.append(ReverseCommitFilter())
|
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()
|
return super(LocateChangesWalk, self).filtered_iter()
|
||||||
|
|
||||||
@@ -591,7 +595,6 @@ def do_import_upstream(args):
|
|||||||
""", "\n ".join(commit_list))
|
""", "\n ".join(commit_list))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
logger.notice("Starting import of upstream")
|
logger.notice("Starting import of upstream")
|
||||||
importupstream.create_import(force=args.force)
|
importupstream.create_import(force=args.force)
|
||||||
logger.notice("Successfully created import branch")
|
logger.notice("Successfully created import branch")
|
||||||
|
193
ghp/commands/supersede.py
Normal file
193
ghp/commands/supersede.py
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
#
|
||||||
|
# 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:
|
@@ -7,3 +7,5 @@
|
|||||||
# Technical Data for Commercial Items are licensed to the U.S. Government
|
# Technical Data for Commercial Items are licensed to the U.S. Government
|
||||||
# under vendor's standard commercial license.
|
# under vendor's standard commercial license.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from ghp.lib import note
|
||||||
|
52
ghp/lib/note.py
Normal file
52
ghp/lib/note.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
from ghp.errors import HpgitError
|
||||||
|
from git import base, GitCommandError
|
||||||
|
|
||||||
|
class NoteAlreadyExistsError(HpgitError):
|
||||||
|
"""Exception thrown by note related commands"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def add_note(self, message, force=False, note_ref='refs/notes/commits'):
|
||||||
|
"""
|
||||||
|
Add a note to an object, tossing a NoteError exception if the object is
|
||||||
|
already annotated.
|
||||||
|
:param message: note message
|
||||||
|
:param force: if true, any existing note will be overwritten
|
||||||
|
:param note_ref: ref to use for notes. Defaults to refs/notes/commits
|
||||||
|
"""
|
||||||
|
if force:
|
||||||
|
self.repo.git.notes('--ref', note_ref, 'add', '-f', '-m', message,
|
||||||
|
str(self))
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self.repo.git.notes('--ref', note_ref, 'add', '-m', message,
|
||||||
|
str(self))
|
||||||
|
except GitCommandError as e:
|
||||||
|
if e.status == 1:
|
||||||
|
raise NoteAlreadyExistsError(e.message)
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def append_note(self, message, note_ref='refs/notes/commits'):
|
||||||
|
"""Add a note to an object
|
||||||
|
:param message: note message
|
||||||
|
:param note_ref: ref to use for notes. Defaults to refs/notes/commits
|
||||||
|
"""
|
||||||
|
self.repo.git.notes('--ref', note_ref, 'append', '-m', message, str(self))
|
||||||
|
|
||||||
|
def note_message(self, note_ref='refs/notes/commits'):
|
||||||
|
"""
|
||||||
|
Return note message
|
||||||
|
:param note_ref: ref to use for notes. Defaults to refs/notes/commits
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self.repo.git.notes('--ref', note_ref, 'show', str(self))
|
||||||
|
except GitCommandError as e:
|
||||||
|
if e.status == 1:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
base.Object.add_note = add_note
|
||||||
|
base.Object.append_note = append_note
|
||||||
|
base.Object.note = note_message
|
@@ -16,6 +16,8 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
from ghp.lib.pygitcompat import HpgitCompatCommit as Commit
|
from ghp.lib.pygitcompat import HpgitCompatCommit as Commit
|
||||||
|
|
||||||
|
from git import Head
|
||||||
|
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@@ -348,7 +350,7 @@ class CommitMessageSearcher(LogDedentMixin, Searcher):
|
|||||||
if not self.commit:
|
if not self.commit:
|
||||||
raise RuntimeError("Failed to locate a pattern match")
|
raise RuntimeError("Failed to locate a pattern match")
|
||||||
|
|
||||||
self.log.notice("Commit matching search pattern is: '%s'", self.commit.hexsha)
|
self.log.debug("Commit matching search pattern is: '%s'", self.commit.hexsha)
|
||||||
|
|
||||||
return self.commit.hexsha
|
return self.commit.hexsha
|
||||||
|
|
||||||
@@ -382,6 +384,150 @@ class CommitFilter(object):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SupersededCommitFilter(LogDedentMixin, GitMixin, CommitFilter):
|
||||||
|
"""
|
||||||
|
Prunes all commits that have a note with the "Superseded-by:" header
|
||||||
|
containing a Change-Id present in upstream tracking branch
|
||||||
|
|
||||||
|
:param string search_ref: git reference to search for ChangeIds (required).
|
||||||
|
:param Commit limit: commit object to ignore searching history after
|
||||||
|
(optional).
|
||||||
|
"""
|
||||||
|
|
||||||
|
SUPERSEDE_HEADER = 'Superseded-by:'
|
||||||
|
NOTE_REF = 'refs/notes/upstream-merge'
|
||||||
|
|
||||||
|
def __init__(self, search_ref, limit=None, *args, **kwargs):
|
||||||
|
|
||||||
|
super(SupersededCommitFilter, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if not self.is_valid_commit(search_ref):
|
||||||
|
raise ValueError("Invalid value for 'search_ref': %s" % search_ref)
|
||||||
|
self.search_ref = search_ref
|
||||||
|
|
||||||
|
if limit:
|
||||||
|
if not hasattr(limit, 'hexsha'):
|
||||||
|
raise ValueError(
|
||||||
|
"Invalid object: no hexsha attribute for 'limit'")
|
||||||
|
if not self.is_valid_commit(limit.hexsha):
|
||||||
|
raise ValueError("'limit' object does not contain a valid SHA1")
|
||||||
|
self.limit = limit
|
||||||
|
|
||||||
|
self._regex = None
|
||||||
|
|
||||||
|
def _get_rev_range(self):
|
||||||
|
|
||||||
|
if self.limit:
|
||||||
|
return "%s..%s" % (self.limit.hexsha, self.search_ref)
|
||||||
|
else:
|
||||||
|
return self.search_ref
|
||||||
|
|
||||||
|
def _get_change_id(self, commit):
|
||||||
|
"""
|
||||||
|
Returns the Change-Id string from the footer of the given commit.
|
||||||
|
|
||||||
|
Will ignore any instances outside of the footer section
|
||||||
|
"""
|
||||||
|
# read the commit message in reverse to access the
|
||||||
|
# footer first but ignore subject and first blank line
|
||||||
|
for line in reversed(commit.message.splitlines()[1:]):
|
||||||
|
line = line.strip()
|
||||||
|
# exit on the first blank line found since that indicates
|
||||||
|
# we're reached the top of the footer section
|
||||||
|
if not line:
|
||||||
|
break
|
||||||
|
|
||||||
|
cid = re.search('^Change-Id:\s*(.+)$', line, re.IGNORECASE)
|
||||||
|
if cid:
|
||||||
|
return cid.group(1)
|
||||||
|
return
|
||||||
|
|
||||||
|
def filter(self, commit_iter):
|
||||||
|
|
||||||
|
self.log.info(
|
||||||
|
"""\
|
||||||
|
Filtering out all commits marked with a Superseded-by Change-Id
|
||||||
|
which is present in '%s'
|
||||||
|
""", self.search_ref)
|
||||||
|
|
||||||
|
supersede_re = re.compile('^%s\s*(.+)\s*$' %
|
||||||
|
SupersededCommitFilter.SUPERSEDE_HEADER,
|
||||||
|
re.IGNORECASE | re.MULTILINE)
|
||||||
|
|
||||||
|
for commit in commit_iter:
|
||||||
|
commit_note = commit.note(note_ref=SupersededCommitFilter.NOTE_REF)
|
||||||
|
# include non-annotated commits
|
||||||
|
if not commit_note:
|
||||||
|
yield commit
|
||||||
|
continue
|
||||||
|
|
||||||
|
# include annotated commits which don't have a SUPERSEDE_HEADER
|
||||||
|
superseding_change_ids = supersede_re.findall(commit_note)
|
||||||
|
if not superseding_change_ids:
|
||||||
|
yield commit
|
||||||
|
continue
|
||||||
|
|
||||||
|
# search for all the change-ids in matches (egrep regex)
|
||||||
|
commits_grep_re = '^Change-Id:\\s*\(%s\)\\s*$' % \
|
||||||
|
'|'.join(superseding_change_ids)
|
||||||
|
|
||||||
|
# retrieve all matching commits because we need to check
|
||||||
|
# each match for whether the changeId is actually in
|
||||||
|
# the footer or just included as a reference.
|
||||||
|
matching_commits = Commit.iter_items(self.repo,
|
||||||
|
self._get_rev_range(),
|
||||||
|
regexp_ignore_case=True,
|
||||||
|
grep=commits_grep_re)
|
||||||
|
|
||||||
|
for possible in matching_commits:
|
||||||
|
change_id = self._get_change_id(possible)
|
||||||
|
if change_id:
|
||||||
|
superseding_change_ids.remove(change_id)
|
||||||
|
|
||||||
|
# include commits which have some superseding change-ids not
|
||||||
|
# present in upstream
|
||||||
|
if superseding_change_ids:
|
||||||
|
self.log.debug(
|
||||||
|
"""\
|
||||||
|
Including commit '%s %s'
|
||||||
|
because the following superseding change-ids have not been
|
||||||
|
found:
|
||||||
|
%s
|
||||||
|
""", commit.hexsha[:7], commit.message.splitlines()[0],
|
||||||
|
'\n'.join(superseding_change_ids))
|
||||||
|
yield commit
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.log.debug(
|
||||||
|
"""\
|
||||||
|
Filtering out commit '%s %s'
|
||||||
|
because it has been marked as superseded by the following
|
||||||
|
note:
|
||||||
|
%s
|
||||||
|
""", commit.hexsha[:7], commit.message.splitlines()[0],
|
||||||
|
commit_note)
|
||||||
|
|
||||||
|
class DroppedCommitFilter(LogDedentMixin, CommitFilter):
|
||||||
|
"""
|
||||||
|
Prunes all commits that have a note with the Dropped: header
|
||||||
|
"""
|
||||||
|
|
||||||
|
DROPPED_HEADER = 'Dropped:'
|
||||||
|
NOTE_REF = 'refs/notes/upstream-merge'
|
||||||
|
|
||||||
|
def filter(self, commit_iter):
|
||||||
|
for commit in commit_iter:
|
||||||
|
commit_note = commit.note(note_ref=DroppedCommitFilter.NOTE_REF)
|
||||||
|
if not commit_note:
|
||||||
|
yield commit
|
||||||
|
elif not re.match('^%s.+' % DroppedCommitFilter.DROPPED_HEADER,
|
||||||
|
commit_note, re.IGNORECASE | re.MULTILINE):
|
||||||
|
yield commit
|
||||||
|
else:
|
||||||
|
self.log.debug("Dropping commit '%s' as requested:", commit)
|
||||||
|
self.log.debug(commit_note)
|
||||||
|
|
||||||
|
|
||||||
class MergeCommitFilter(CommitFilter):
|
class MergeCommitFilter(CommitFilter):
|
||||||
"""
|
"""
|
||||||
Includes only those commits that have more than one parent listed (merges)
|
Includes only those commits that have more than one parent listed (merges)
|
||||||
|
@@ -18,7 +18,7 @@ from ghp import commands as c
|
|||||||
class TestGetSubcommands(testtools.TestCase):
|
class TestGetSubcommands(testtools.TestCase):
|
||||||
"""Test case for get_subcommands function"""
|
"""Test case for get_subcommands function"""
|
||||||
|
|
||||||
_available_subcommands = ('import-upstream',)
|
_available_subcommands = ('import-upstream', 'supersede' ,'drop')
|
||||||
|
|
||||||
def test_available_subcommands(self):
|
def test_available_subcommands(self):
|
||||||
"""Test available subcommands"""
|
"""Test available subcommands"""
|
||||||
|
67
tests/test_drop.py
Normal file
67
tests/test_drop.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2012, 2013 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""Tests the drop module"""
|
||||||
|
import testtools
|
||||||
|
from ghp.commands import drop as d
|
||||||
|
from git import repo as r
|
||||||
|
from git import GitCommandError
|
||||||
|
|
||||||
|
class TestDrop(testtools.TestCase):
|
||||||
|
"""Test case for Drop class"""
|
||||||
|
|
||||||
|
first_commit = "bd6b9eefe961abe8c15cb5dc6905b92e14714a4e"
|
||||||
|
second_commit = "05fac847a5629e36050dcd69b9a782b2645d3cc7"
|
||||||
|
invalid_commit = "this_is_an_invalid_commit"
|
||||||
|
author="Walter White <heisenberg@hp.com>"
|
||||||
|
note_ref = 'refs/notes/upstream-merge'
|
||||||
|
|
||||||
|
def test_valid_parameters(self):
|
||||||
|
"""Test drop initialization and read properties"""
|
||||||
|
|
||||||
|
repo=r.Repo('.')
|
||||||
|
automatic_author='%s <%s>' % (repo.git.config('user.name'),
|
||||||
|
repo.git.config('user.email'))
|
||||||
|
t = d.Drop(git_object=TestDrop.first_commit)
|
||||||
|
self.assertEquals(t.author, automatic_author)
|
||||||
|
|
||||||
|
t = d.Drop(git_object=TestDrop.first_commit, author=TestDrop.author)
|
||||||
|
self.assertEquals(t.author, TestDrop.author)
|
||||||
|
|
||||||
|
def test_invalid_commit(self):
|
||||||
|
"""Test drop initialization with invalid commit"""
|
||||||
|
|
||||||
|
self.assertRaises(d.DropError, d.Drop,
|
||||||
|
git_object=TestDrop.invalid_commit)
|
||||||
|
|
||||||
|
def test_mark(self):
|
||||||
|
"""Test drop mark"""
|
||||||
|
|
||||||
|
t = d.Drop(git_object=TestDrop.first_commit, author=TestDrop.author)
|
||||||
|
|
||||||
|
repo = r.Repo('.')
|
||||||
|
try:
|
||||||
|
# Older git versions don't support --ignore-missing so we need to
|
||||||
|
# catch GitCommandError exception
|
||||||
|
repo.git.notes('--ref', TestDrop.note_ref, 'remove',
|
||||||
|
TestDrop.first_commit)
|
||||||
|
except GitCommandError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
t.mark()
|
||||||
|
|
||||||
|
self.assertRegexpMatches(
|
||||||
|
'^Dropped: %s' % TestDrop.author,
|
||||||
|
repo.git.notes('--ref', TestDrop.note_ref, 'show',
|
||||||
|
TestDrop.first_commit)
|
||||||
|
)
|
||||||
|
|
||||||
|
repo.git.notes('--ref', TestDrop.note_ref, 'remove',
|
||||||
|
TestDrop.first_commit)
|
112
tests/test_supersede.py
Normal file
112
tests/test_supersede.py
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2012, 2013 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""Tests the supersede module"""
|
||||||
|
|
||||||
|
import testtools
|
||||||
|
from ghp.commands import supersede as s
|
||||||
|
from git import repo as r
|
||||||
|
from git import GitCommandError
|
||||||
|
|
||||||
|
class TestSupersede(testtools.TestCase):
|
||||||
|
"""Test case for Supersede class"""
|
||||||
|
|
||||||
|
first_commit = "bd6b9eefe961abe8c15cb5dc6905b92e14714a4e"
|
||||||
|
second_commit = "05fac847a5629e36050dcd69b9a782b2645d3cc7"
|
||||||
|
invalid_commit = "this_is_an_invalid_commit"
|
||||||
|
first_change_ids = ("Ia028d7afc9df2a599a52b1b17858037fab4e3f44",)
|
||||||
|
second_change_ids = ("Iebd1f5aa789dcd9574a00bb8837e4596bf55fa88",
|
||||||
|
"I4ab003213c40b0375283a15e2967d11e0351feb1")
|
||||||
|
invalid_change_ids = ("this_is_an_invalid_change_id",)
|
||||||
|
change_ids_branch = "master"
|
||||||
|
invalid_change_ids_branch = "this_is_an_invalid_change_ids_branch"
|
||||||
|
note_ref = 'refs/notes/upstream-merge'
|
||||||
|
|
||||||
|
def test_valid_parameters(self):
|
||||||
|
"""Test supersede initialization and read properties"""
|
||||||
|
|
||||||
|
t = s.Supersede(git_object=TestSupersede.first_commit,
|
||||||
|
change_ids=TestSupersede.first_change_ids,
|
||||||
|
upstream_branch=TestSupersede.change_ids_branch)
|
||||||
|
|
||||||
|
self.assertEquals(str(t.commit), TestSupersede.first_commit)
|
||||||
|
self.assertNotEqual(str(t.commit), TestSupersede.second_commit)
|
||||||
|
self.assertEqual(str(t.change_ids_branch),
|
||||||
|
TestSupersede.change_ids_branch)
|
||||||
|
self.assertNotEqual(str(t.change_ids_branch),
|
||||||
|
TestSupersede.invalid_change_ids_branch)
|
||||||
|
|
||||||
|
def test_invalid_commit(self):
|
||||||
|
"""Test supersede initialization with invalid commit"""
|
||||||
|
|
||||||
|
self.assertRaises(s.SupersedeError, s.Supersede,
|
||||||
|
git_object=TestSupersede.invalid_commit,
|
||||||
|
change_ids=TestSupersede.first_change_ids,
|
||||||
|
upstream_branch=TestSupersede.change_ids_branch)
|
||||||
|
|
||||||
|
def test_multiple_change_id(self):
|
||||||
|
"""Test supersede initialization with multiple change ids"""
|
||||||
|
|
||||||
|
t = s.Supersede(git_object=TestSupersede.first_commit,
|
||||||
|
change_ids=TestSupersede.second_change_ids,
|
||||||
|
upstream_branch=TestSupersede.change_ids_branch)
|
||||||
|
|
||||||
|
self.assertEquals(str(t.commit), TestSupersede.first_commit)
|
||||||
|
self.assertNotEqual(str(t.commit), TestSupersede.second_commit)
|
||||||
|
|
||||||
|
def test_invalid_cids(self):
|
||||||
|
"""Test supersede initialization with invalid cids"""
|
||||||
|
|
||||||
|
self.assertRaises(s.SupersedeError, s.Supersede,
|
||||||
|
git_object=TestSupersede.first_commit,
|
||||||
|
change_ids=TestSupersede.invalid_change_ids,
|
||||||
|
upstream_branch=TestSupersede.change_ids_branch)
|
||||||
|
|
||||||
|
def test_default_upstream_branch(self):
|
||||||
|
"""Test supersede initialization with no branch name"""
|
||||||
|
|
||||||
|
self.assertRaises(s.SupersedeError, s.Supersede,
|
||||||
|
git_object=TestSupersede.first_commit,
|
||||||
|
change_ids=TestSupersede.invalid_change_ids,
|
||||||
|
upstream_branch=
|
||||||
|
TestSupersede.invalid_change_ids_branch)
|
||||||
|
|
||||||
|
def test_no_upstream_branch(self):
|
||||||
|
"""Test supersede initialization with invalid branch name"""
|
||||||
|
|
||||||
|
self.assertRaises(s.SupersedeError, s.Supersede,
|
||||||
|
git_object=TestSupersede.first_commit,
|
||||||
|
change_ids=TestSupersede.invalid_change_ids)
|
||||||
|
|
||||||
|
def test_mark(self):
|
||||||
|
"""Test Supersede mark"""
|
||||||
|
|
||||||
|
t = s.Supersede(git_object=TestSupersede.first_commit,
|
||||||
|
change_ids=TestSupersede.first_change_ids,
|
||||||
|
upstream_branch=TestSupersede.change_ids_branch)
|
||||||
|
|
||||||
|
repo = r.Repo('.')
|
||||||
|
try:
|
||||||
|
# Older git versions don't support --ignore-missing
|
||||||
|
repo.git.notes('--ref', TestSupersede.note_ref, 'remove',
|
||||||
|
TestSupersede.first_commit)
|
||||||
|
except GitCommandError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
t.mark()
|
||||||
|
|
||||||
|
self.assertRegexpMatches(
|
||||||
|
'^Superseded-by: %s' % TestSupersede.first_change_ids,
|
||||||
|
repo.git.notes('--ref', TestSupersede.note_ref, 'show',
|
||||||
|
TestSupersede.first_commit)
|
||||||
|
)
|
||||||
|
|
||||||
|
repo.git.notes('--ref', TestSupersede.note_ref, 'remove',
|
||||||
|
TestSupersede.first_commit)
|
Reference in New Issue
Block a user