ignore null-merges
OpenStack used to use null-merges to bring final release tags from stable branches back into the master branch. This confuses the regular traversal because it makes that stable branch appear to be part of master and/or the later stable branch. Update the scanner so that when it hit one of those merge commits, it skips it and take the first parent so it continues to traverse the branch being scanned. Change-Id: I90722a3946f691e8f58a52e68ee455d6530f047a Closes-Bug: #1695057 Signed-off-by: Doug Hellmann <doug@doughellmann.com>
This commit is contained in:
@ -282,6 +282,22 @@ The following options are configurable:
|
||||
order in which the final report will be generated. A prelude section will
|
||||
always be automatically inserted before the first element of this list.
|
||||
|
||||
`ignore_null_merges`
|
||||
|
||||
OpenStack used to use null-merges to bring final release tags from
|
||||
stable branches back into the master branch. This confuses the
|
||||
regular traversal because it makes that stable branch appear to be
|
||||
part of master and/or the later stable branch. This option allows us
|
||||
to ignore those.
|
||||
|
||||
When this option is set to True, any merge commits with no changes
|
||||
and in which the second or later parent is tagged are considered
|
||||
"null-merges" that bring the tag information into the current branch
|
||||
but nothing else.
|
||||
|
||||
Defaults to ``True``.
|
||||
|
||||
|
||||
Debugging
|
||||
=========
|
||||
|
||||
|
18
releasenotes/notes/ignore-null-merges-56b7a8ed9b20859e.yaml
Normal file
18
releasenotes/notes/ignore-null-merges-56b7a8ed9b20859e.yaml
Normal file
@ -0,0 +1,18 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
By default, reno now ignores "null" merge commits that bring in
|
||||
tags from other threads. The new configuration option
|
||||
``ignore_null_merges`` controls this behavior. Setting the flag to
|
||||
False restores the previous behavior in which the null-merge
|
||||
commits were traversed like any other merge commit.
|
||||
upgrade:
|
||||
- |
|
||||
The new configuration option ``ignore_null_merges`` causes the
|
||||
scanner to ignore merge commits with no changes when one of the
|
||||
parents being merged in has a release tag on it.
|
||||
fixes:
|
||||
- |
|
||||
This release fixes a problem with the scanner that may have caused
|
||||
it to stop scanning a branch prematurely when the tag from another
|
||||
branch had been merged into the history.
|
@ -155,6 +155,18 @@ class Config(object):
|
||||
['fixes', 'Bug Fixes'],
|
||||
['other', 'Other Notes'],
|
||||
],
|
||||
|
||||
# When this option is set to True, any merge commits with no
|
||||
# changes and in which the second or later parent is tagged
|
||||
# are considered "null-merges" that bring the tag information
|
||||
# into the current branch but nothing else.
|
||||
#
|
||||
# OpenStack used to use null-merges to bring final release
|
||||
# tags from stable branches back into the master branch. This
|
||||
# confuses the regular traversal because it makes that stable
|
||||
# branch appear to be part of master and/or the later stable
|
||||
# branch. This option allows us to ignore those.
|
||||
'ignore_null_merges': True,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
|
@ -685,9 +685,55 @@ class Scanner(object):
|
||||
todo = collections.deque()
|
||||
todo.appendleft(head)
|
||||
|
||||
ignore_null_merges = self.conf.ignore_null_merges
|
||||
if ignore_null_merges:
|
||||
LOG.debug('ignoring null-merge commits')
|
||||
|
||||
while todo:
|
||||
sha = todo.popleft()
|
||||
entry = all[sha]
|
||||
null_merge = False
|
||||
|
||||
# OpenStack used to use null-merges to bring final release
|
||||
# tags from stable branches back into the master
|
||||
# branch. This confuses the regular traversal because it
|
||||
# makes that stable branch appear to be part of master
|
||||
# and/or the later stable branch. When we hit one of those
|
||||
# tags, skip it and take the first parent.
|
||||
if ignore_null_merges and len(entry.commit.parents) > 1:
|
||||
# Look for tags on the 2nd and later parents. The
|
||||
# first parent is part of the branch we were
|
||||
# originally trying to traverse, and any tags on it
|
||||
# need to be kept.
|
||||
for p in entry.commit.parents[1:]:
|
||||
t = self._get_valid_tags_on_commit(p)
|
||||
# If we have a tag being merged in, we need to
|
||||
# include a check to verify that this is actually
|
||||
# a null-merge (there are no changes).
|
||||
if t and not entry.changes():
|
||||
LOG.debug(
|
||||
'treating %s as a null-merge because '
|
||||
'parent %s has tag(s) %s',
|
||||
sha, p, t,
|
||||
)
|
||||
null_merge = True
|
||||
break
|
||||
if null_merge:
|
||||
# Make it look like the parent entries that we're
|
||||
# going to skip have been emitted so the
|
||||
# bookkeeping for children works properly and we
|
||||
# can continue past the merge.
|
||||
emitted.update(set(entry.commit.parents[1:]))
|
||||
# Make it look like the current entry was emitted
|
||||
# so the bookkeeping for children works properly
|
||||
# and we can continue past the merge.
|
||||
emitted.add(sha)
|
||||
# Now set up the first parent so it is processed
|
||||
# later.
|
||||
first_parent = entry.commit.parents[0]
|
||||
if first_parent not in todo:
|
||||
todo.appendleft(first_parent)
|
||||
continue
|
||||
|
||||
# If a node has multiple children, it is the start point
|
||||
# for a branch that was merged back into the rest of the
|
||||
|
@ -1010,6 +1010,83 @@ class MergeCommitTest(Base):
|
||||
)
|
||||
|
||||
|
||||
class NullMergeTest(Base):
|
||||
|
||||
def setUp(self):
|
||||
super(NullMergeTest, self).setUp()
|
||||
self.repo.add_file('ignore-0.txt')
|
||||
self.n1 = self._add_notes_file()
|
||||
self.repo.git('tag', '-s', '-m', 'first tag', '1.0.0')
|
||||
|
||||
# Create a branch, add a note, and tag it.
|
||||
self.repo.git('checkout', '-b', 'test_ignore_null_merge')
|
||||
self.n2 = self._add_notes_file()
|
||||
self.repo.git('tag', '-s', '-m', 'second tag', '2.0.0')
|
||||
|
||||
# Move back to master and advance it.
|
||||
self.repo.git('checkout', 'master')
|
||||
self.repo.add_file('ignore-1.txt')
|
||||
self.n3 = self._add_notes_file()
|
||||
|
||||
# Merge only the tag from the first branch back into master.
|
||||
self.repo.git(
|
||||
'merge', '--no-ff', '--strategy', 'ours', '2.0.0',
|
||||
)
|
||||
|
||||
# Add another note file.
|
||||
self.n4 = self._add_notes_file()
|
||||
self.repo.git('tag', '-s', '-m', 'third tag', '3.0.0')
|
||||
|
||||
self.repo.git('log', '--decorate', '--oneline', '--graph', '--all')
|
||||
# The results should look like:
|
||||
#
|
||||
# * afea344 (HEAD -> master, tag: 3.0.0) add slug-0000000000000004.yaml
|
||||
# * 7bb295c Merge tag '2.0.0'
|
||||
# |\
|
||||
# | * 260c80b (tag: 2.0.0, test_ignore_null_merge) add slug-0000000000000002.yaml # noqa
|
||||
# * | 5981ae3 add slug-0000000000000003.yaml
|
||||
# * | 00f9376 add ignore-1.txt
|
||||
# |/
|
||||
# * d24faf9 (tag: 1.0.0) add slug-0000000000000001.yaml
|
||||
# * 6c221cd add ignore-0.txt
|
||||
|
||||
def test_ignore(self):
|
||||
# The scanner should skip over the null-merge and include the
|
||||
# notes that come before the version being merged in, up to
|
||||
# the base of the previous branch.
|
||||
self.scanner = scanner.Scanner(self.c)
|
||||
raw_results = self.scanner.get_notes_by_version()
|
||||
results = {
|
||||
k: [f for (f, n) in v]
|
||||
for (k, v) in raw_results.items()
|
||||
}
|
||||
self.assertEqual(
|
||||
{'1.0.0': [self.n1],
|
||||
'3.0.0': [self.n3, self.n4]},
|
||||
results,
|
||||
)
|
||||
|
||||
def test_follow(self):
|
||||
# The scanner should not skip over the null-merge. The output
|
||||
# should include the 2.0.0 tag that was merged in, as well as
|
||||
# the earlier 1.0.0 version.
|
||||
self.c.override(
|
||||
ignore_null_merges=False,
|
||||
)
|
||||
self.scanner = scanner.Scanner(self.c)
|
||||
raw_results = self.scanner.get_notes_by_version()
|
||||
results = {
|
||||
k: [f for (f, n) in v]
|
||||
for (k, v) in raw_results.items()
|
||||
}
|
||||
self.assertEqual(
|
||||
{'1.0.0': [self.n1],
|
||||
'2.0.0': [self.n2, self.n3],
|
||||
'3.0.0': [self.n4]},
|
||||
results,
|
||||
)
|
||||
|
||||
|
||||
class UniqueIdTest(Base):
|
||||
|
||||
def test_legacy(self):
|
||||
|
Reference in New Issue
Block a user