def prepare_release(release_type): """Bump the version, update the changelog and open a PR.""" remote = 'origin' if any_uncommitted_changes(): raise CommandError( 'There are uncommitted changes. Please commit, stash or delete them and try again.', ) subprocess.run(['git', 'fetch'], check=True) subprocess.run(['git', 'checkout', f'{remote}/develop'], check=True, capture_output=True) new_version = get_next_version(release_type) branch = f'changelog/{new_version}' pr_title = f'Prepare for release {new_version}' pr_body = f'This bumps the version and adds the changelog for version {new_version}.' commit_message = f"""{pr_title}\n\n{pr_body}""" if local_branch_exists(branch): raise CommandError( f'Branch {branch} already exists locally. Please delete it and try again.', ) if remote_branch_exists(branch): raise CommandError( f'Branch {branch} already exists remotely. Please delete it on GitHub and try again.', ) if not list_news_fragments(): raise CommandError('There are no news fragments.') subprocess.run(['git', 'checkout', '-b', branch, f'{remote}/develop'], check=True) subprocess.run(['towncrier', '--version', str(new_version), '--yes'], check=True) remaining_news_fragment_paths = list_news_fragments() if remaining_news_fragment_paths: joined_paths = '\n'.join( str(path) for path in remaining_news_fragment_paths) raise CommandError( 'These news fragments were left behind:\n\n' f'{joined_paths}\n\n' 'They may be misnamed. Please investigate.', ) set_current_version(new_version) subprocess.run(['git', 'add', VERSION_FILE_PATH], check=True) subprocess.run(['git', 'commit', '-m', commit_message], check=True) subprocess.run(['git', 'push', '--set-upstream', remote, branch], check=True) escaped_branch_name = quote(branch) params = { 'expand': '1', 'title': pr_title, 'body': pr_body, } webbrowser.open( f'{BASE_GITHUB_REPO_URL}/compare/{escaped_branch_name}?{urlencode(params)}' ) return branch
def create_release_branch(): """Create and push a release branch.""" remote = 'origin' if any_uncommitted_changes(): raise CommandError( 'There are uncommitted changes. Please commit, stash or delete them and try again.', ) subprocess.run(['git', 'fetch'], check=True) subprocess.run(['git', 'checkout', f'{remote}/develop'], check=True, capture_output=True) version = get_current_version() branch = f'release/{version}' tag = f'v{version}' if remote_tag_exists(tag): raise CommandError( f'A remote tag {tag} currently exists. It looks like version {version} has ' f'already been released.', ) news_fragment_paths = list_news_fragments() if news_fragment_paths: joined_paths = '\n'.join(str(path) for path in news_fragment_paths) raise CommandError( 'These are news fragments on origin/develop:\n\n' f'{joined_paths}\n\n' 'Is the changelog up to date?', ) if local_branch_exists(branch): raise CommandError( f'Branch {branch} already exists locally. Please delete it and try again.', ) if remote_branch_exists(branch): raise CommandError( f'Branch {branch} already exists remotely. Please delete it on GitHub and try again.', ) subprocess.run(['git', 'checkout', '-b', branch, f'{remote}/develop'], check=True) subprocess.run(['git', 'push', '--set-upstream', remote, branch], check=True) params = { 'expand': '1', 'title': f'Release {version}', 'body': PR_BODY_TEMPLATE.format(version=version, release_guide_url=RELEASE_GUIDE_URL), } encoded_params = urlencode(params) escaped_branch_name = quote(branch) webbrowser.open( f'{GITHUB_BASE_REPO_URL}/compare/master...{escaped_branch_name}?{encoded_params}', ) return branch
def test_any_uncommitted_changes(stdout, expected_result, mock_subprocess_run): """Test that any_uncommitted_changes() returns the expected value in various scenarios.""" mock_subprocess_run.return_value = subprocess.CompletedProcess( (), 0, stdout=stdout) assert any_uncommitted_changes() == expected_result