Skip to content

Commit

Permalink
Handle major versions and non-rc prereleases (#43)
Browse files Browse the repository at this point in the history
* Handle major versions and non-rc prereleases

This commit adds support to the qiskit-bot release automation to handle
major version releases and non-rc pre-releases. The major version
handling is primarily for building the log command, as we need to get
the log from 'n.0.0..n-1.m.0' where m is the most recent non-prerelease.
To handle non-rc prereleases the only change is we don't create a new
branch for the pre-release. Previously all pre-releases would trigger
branch creation (given the appropriate configuration variable). But the
desired behavior is to actually only create a stable branch from a
release candidate.

* Normalize rc detection

* Update comments

* Fix tests to account for PEP440
  • Loading branch information
mtreinish authored Nov 30, 2023
1 parent d933a7a commit 3441614
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 12 deletions.
14 changes: 14 additions & 0 deletions qiskit_bot/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,20 @@ def get_git_log(repo, sha1):
return ''


def get_tags(repo):
"""Get a list of tags in creation order separated by newlines."""
LOG.info('Querying git tags for %s' % repo.local_path)
try:
res = subprocess.run(['git', 'tag', '--sort=-creatordate'],
capture_output=True, check=True, encoding="UTF8",
cwd=repo.local_path)
return res.stdout
except subprocess.CalledProcessError as e:
LOG.exception('Failed to get git log\nstdout:\n%s\nstderr:\n%s'
% (e.stdout, e.stderr))
return ''


def clean_repo(repo):
cmd = ['git', 'clean', '-fdX']
try:
Expand Down
25 changes: 20 additions & 5 deletions qiskit_bot/release_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ def create_github_release(repo, log_string, version_number, categories,
prerelease=prerelease)


def _get_log_string(version_obj):
def _get_log_string(version_obj, version_number, repo):
# If a second prerelease show log from first
if version_obj.is_prerelease and version_obj.pre[1] > 1:
old_version = (
Expand All @@ -229,10 +229,21 @@ def _get_log_string(version_obj):
f"{version_obj.epoch}.{version_obj.minor}."
f"{version_obj.micro - 1}"
)
# If a major version log between X.0.0..x-1.y.z
elif version_obj.major >= 1 and version_obj.minor == 0:
tags = git.get_tags(repo)
previous_major = version_obj.major - 1
for tag in tags.splitlines():
tag_version = parse(tag)
if tag_version.is_prerelease:
continue
if tag_version.major == previous_major:
old_version = tag
break
# If a minor release log between 0.X.0..0.X-1.0
else:
old_version = f"{version_obj.epoch}.{version_obj.minor - 1}.0"
return f"{version_obj}...{old_version}"
return f"{version_number}...{old_version}"


# This helper function must be a top-level function to be pickable for
Expand All @@ -242,7 +253,7 @@ def _finish_release__changelog_process(
):
with fasteners.InterProcessLock(os.path.join(lock_dir, repo.name)):
git.checkout_default_branch(repo, pull=True)
log_string = _get_log_string(version_obj)
log_string = _get_log_string(version_obj, version_number, repo)
categories = repo.get_local_config().get(
'categories', config.default_changelog_categories)
create_github_release(repo, log_string, version_number,
Expand Down Expand Up @@ -278,8 +289,12 @@ def finish_release(version_number, repo, conf, meta_repo):
repo_branches = [x.name for x in repo.gh_repo.get_branches()]
if int(version_number_pieces[2]) == 0 and \
branch_name not in repo_branches:
git.checkout_default_branch(repo, pull=True)
git.create_branch(branch_name, version_number, repo, push=True)
if is_prerelease and version_obj.pre[0] != "rc":
pass
else:
git.checkout_default_branch(repo, pull=True)
git.create_branch(branch_name, version_number, repo,
push=True)

if not is_postrelease:
_changelog_process = partial(
Expand Down
146 changes: 139 additions & 7 deletions tests/test_release_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -840,27 +840,49 @@ def test_bump_minor_release_from_pending_patch_release_pr(self,
def test_get_log_string(self):
version_obj = parse("0.10.2")
self.assertEqual('0.10.2...0.10.1',
release_process._get_log_string(version_obj))
release_process._get_log_string(
version_obj,
"0.10.2",
unittest.mock.MagicMock()
))
version_obj = parse("0.3.0")
self.assertEqual('0.3.0...0.2.0',
release_process._get_log_string(version_obj))
release_process._get_log_string(
version_obj,
"0.3.0",
unittest.mock.MagicMock()))
version_obj = parse("0.3.25")
self.assertEqual('0.3.25...0.3.24',
release_process._get_log_string(version_obj))
release_process._get_log_string(
version_obj,
"0.3.25",
unittest.mock.MagicMock()))
version_obj = parse("0.25.0")
self.assertEqual('0.25.0...0.24.0',
release_process._get_log_string(version_obj))
release_process._get_log_string(
version_obj,
"0.25.0",
unittest.mock.MagicMock()))

def test_get_log_string_prerelease(self):
version_obj = parse("0.25.0rc1")
self.assertEqual('0.25.0rc1...0.24.0',
release_process._get_log_string(version_obj))
release_process._get_log_string(
version_obj,
"0.25.0rc1",
unittest.mock.MagicMock()))
version_obj = parse("0.25.0rc2")
self.assertEqual('0.25.0rc2...0.25.0rc1',
release_process._get_log_string(version_obj))
release_process._get_log_string(
version_obj,
"0.25.0rc2",
unittest.mock.MagicMock()))
version_obj = parse("0.25.0b1")
self.assertEqual('0.25.0b1...0.24.0',
release_process._get_log_string(version_obj))
release_process._get_log_string(
version_obj,
"0.25.0b1",
unittest.mock.MagicMock()))

@unittest.mock.patch.object(release_process, 'git')
@unittest.mock.patch.object(release_process, 'create_github_release')
Expand Down Expand Up @@ -1755,6 +1777,116 @@ def test_finish_prerelease_with_pre_existing_branch(self, bump_meta_mock,
config.default_changelog_categories, True)
git_mock.create_branch.assert_not_called()

@unittest.mock.patch.object(release_process, 'git')
@unittest.mock.patch.object(release_process, 'create_github_release')
@unittest.mock.patch.object(release_process, 'bump_meta')
def test_finish_prerelease_non_rc(self, bump_meta_mock,
github_release_mock, git_mock):
meta_repo = unittest.mock.MagicMock()
meta_repo.repo_config = {}
meta_repo.name = 'qiskit'
repo = unittest.mock.MagicMock()
repo.name = 'qiskit-terra'
repo.repo_config = {'branch_on_release': True}
repo.get_local_config = lambda: {}
conf = {'working_dir': self.temp_dir.path}
with unittest.mock.patch.object(
release_process, 'multiprocessing'
) as mp_mock:
mp_mock.Process = ProcessMock
release_process.finish_release('0.12.0b1', repo, conf, meta_repo)
git_mock.create_branch.assert_not_called()
github_release_mock.assert_called_once_with(
repo, '0.12.0b1...0.11.0', '0.12.0b1',
config.default_changelog_categories, True)
bump_meta_mock.assert_not_called()

@unittest.mock.patch.object(release_process, 'git')
@unittest.mock.patch.object(release_process, 'create_github_release')
@unittest.mock.patch.object(release_process, 'bump_meta')
def test_finish_major_version_prerelease_non_rc(self, bump_meta_mock,
github_release_mock,
git_mock):
meta_repo = unittest.mock.MagicMock()
meta_repo.repo_config = {}
meta_repo.name = 'qiskit'
repo = unittest.mock.MagicMock()
repo.name = 'qiskit-terra'
repo.repo_config = {'branch_on_release': True}
repo.get_local_config = lambda: {}
conf = {'working_dir': self.temp_dir.path}

def tag_history(*args, **kwargs):
return """1.0.0b1
0.45.1
0.45.0
0.25.3
0.45.0rc1
0.25.2.1
0.25.2
0.25.1
0.25.0
0.25.0rc1
0.24.2
0.24.1
"""
git_mock.get_tags = tag_history
with unittest.mock.patch.object(
release_process, 'multiprocessing'
) as mp_mock:
mp_mock.Process = ProcessMock
release_process.finish_release('1.0.0b1', repo, conf, meta_repo)
git_mock.create_branch.assert_not_called()
github_release_mock.assert_called_once_with(
repo, '1.0.0b1...0.45.1', '1.0.0b1',
config.default_changelog_categories, True)
bump_meta_mock.assert_not_called()

@unittest.mock.patch.object(release_process, 'git')
@unittest.mock.patch.object(release_process, 'create_github_release')
@unittest.mock.patch.object(release_process, 'bump_meta')
def test_finish_major_version_prerelease_rc(self, bump_meta_mock,
github_release_mock,
git_mock):
meta_repo = unittest.mock.MagicMock()
meta_repo.repo_config = {}
meta_repo.name = 'qiskit'
repo = unittest.mock.MagicMock()
repo.name = 'qiskit-terra'
repo.repo_config = {'branch_on_release': True}
repo.get_local_config = lambda: {}
conf = {'working_dir': self.temp_dir.path}

def tag_history(*args, **kwargs):
return """1.0.0rc1
1.0.0b1
0.45.1
0.45.0
0.25.3
0.45.0rc1
0.25.2.1
0.25.2
0.25.1
0.25.0
0.25.0rc1
0.24.2
0.24.1
"""

git_mock.get_tags = tag_history
with unittest.mock.patch.object(
release_process, 'multiprocessing'
) as mp_mock:
mp_mock.Process = ProcessMock
release_process.finish_release('1.0.0rc1', repo, conf, meta_repo)
git_mock.create_branch.assert_called_once_with(
"stable/1.0", "1.0.0rc1", repo, push=True
)
github_release_mock.assert_called_once_with(
repo, '1.0.0rc1...0.45.1', '1.0.0rc1',
config.default_changelog_categories, True)
bump_meta_mock.assert_not_called()


class ProcessMock:

Expand Down

0 comments on commit 3441614

Please sign in to comment.