diff --git a/release_tools/publish.py b/release_tools/publish.py index e0abb6b..27dec3a 100755 --- a/release_tools/publish.py +++ b/release_tools/publish.py @@ -48,7 +48,9 @@ help="Do not remove changelog entries from the repository.") @click.option('--remote-branch', 'remote_branch', default="master", help="Remote branch to push. Default 'master'.") -def publish(version, author, remote, only_push, no_cleanup, remote_branch): +@click.option('--add-all', is_flag=True, + help="Add all changed files to the release commit.") +def publish(version, author, remote, only_push, no_cleanup, remote_branch, add_all): """Publish a new release. This script will generate a new release in the repository. @@ -71,6 +73,10 @@ def publish(version, author, remote, only_push, no_cleanup, remote_branch): When '--no-cleanup' argument is specified, do not remove changelog entries. + By default, the release commit will include the version, pyproject, + release notes, news and authors files. To add all changed files to + the release commit use the `--add-all` flag. + VERSION: version of the new release. AUTHOR: author of the new release (e.g. John Smith ) @@ -88,7 +94,7 @@ def publish(version, author, remote, only_push, no_cleanup, remote_branch): if not only_push: if not no_cleanup: remove_unreleased_changelog_entries(project) - add_release_files(project, version) + add_release_files(project, version, add_all) commit(project, version, author) if remote: @@ -126,28 +132,31 @@ def rollback_add_release_files(project): pass -def add_release_files(project, version): +def add_release_files(project, version, add_all): """Add to the repository all the files needed to publish a release.""" click.echo("Adding files to the release commit...", nl=False) - # Add version file - version_file = project.version_file + if add_all: + project.repo.add_all() + else: + # Add version file + version_file = project.version_file - if not version_file: - rollback_add_release_files(project) - raise click.ClickException("version file not found") + if not version_file: + rollback_add_release_files(project) + raise click.ClickException("version file not found") - project.repo.add(version_file) + project.repo.add(version_file) - # Add pyproject.toml file - pyproject_file = project.pyproject_file + # Add pyproject.toml file + pyproject_file = project.pyproject_file - if not pyproject_file: - rollback_add_release_files(project) - raise click.ClickException("pyproject file not found") + if not pyproject_file: + rollback_add_release_files(project) + raise click.ClickException("pyproject file not found") - project.repo.add(pyproject_file) + project.repo.add(pyproject_file) # Add release notes file notes_file = os.path.join(project.releases_path, version + '.md') diff --git a/release_tools/repo.py b/release_tools/repo.py index 90302c1..c287166 100644 --- a/release_tools/repo.py +++ b/release_tools/repo.py @@ -54,6 +54,10 @@ def add(self, filename): cmd = ['git', 'add', filename] self._exec(cmd, cwd=self.dirpath, env=self.gitenv) + def add_all(self): + cmd = ['git', 'add', '-A'] + self._exec(cmd, cwd=self.dirpath, env=self.gitenv) + def rm(self, filename): cmd = ['git', 'rm', '-f', filename] self._exec(cmd, cwd=self.dirpath, env=self.gitenv) diff --git a/release_tools/semverup.py b/release_tools/semverup.py index 80d3674..fa5c55a 100755 --- a/release_tools/semverup.py +++ b/release_tools/semverup.py @@ -54,7 +54,9 @@ help="Increase only the defined version.") @click.option('--pre-release', is_flag=True, help="Create a new release candidate version.") -def semverup(dry_run, bump_version, pre_release): +@click.option('--current-version', + help="Use the given version instead of the version file.") +def semverup(dry_run, bump_version, pre_release, current_version): """Increment version number following semver specification. This script will bump up the version number of a package in a @@ -67,6 +69,10 @@ def semverup(dry_run, bump_version, pre_release): properly, the script will get the type of every unreleased change stored under 'releases/unreleased' directory. + If you don't want to use the version number stored in '_version.py', + use '--current-version=' with the one you would like + to use. + Additionally, 'pyproject' file will also be updated. Take into account this file must be tracked by the repository. @@ -95,12 +101,17 @@ def semverup(dry_run, bump_version, pre_release): except RepositoryError as e: raise click.ClickException(e) - # Get the current version number - version_file = find_version_file(project) - current_version = read_version_number(version_file) - - # Get the pyproject file - pyproject_file = find_pyproject_file(project) + if current_version: + try: + current_version = semver.parse_version_info(current_version) + except ValueError: + msg = "version number '{}' is not a valid semver string" + msg = msg.format(current_version) + raise click.ClickException(msg) + else: + # Get the current version number + version_file = find_version_file(project) + current_version = read_version_number(version_file) # Determine the new version and produce the output if bump_version: @@ -109,6 +120,8 @@ def semverup(dry_run, bump_version, pre_release): new_version = determine_new_version_number(project, current_version, pre_release) if not dry_run: + # Get the pyproject file + pyproject_file = find_pyproject_file(project) write_version_number(version_file, new_version) write_version_number_pyproject(pyproject_file, new_version) diff --git a/releases/unreleased/option-to-add-all-files-to-release-commit.yml b/releases/unreleased/option-to-add-all-files-to-release-commit.yml new file mode 100644 index 0000000..130c3ce --- /dev/null +++ b/releases/unreleased/option-to-add-all-files-to-release-commit.yml @@ -0,0 +1,11 @@ +--- +title: Option to add all files to release commit +category: added +author: Eva Millán +issue: null +notes: > + To include all changed files in the release commit, + add the `--add-all` flag to the `publish` command. + This is useful for non-Python projects that may + need to change the version number in files other + than '_version.py'. diff --git a/releases/unreleased/set-current-version-manually.yml b/releases/unreleased/set-current-version-manually.yml new file mode 100644 index 0000000..c138b76 --- /dev/null +++ b/releases/unreleased/set-current-version-manually.yml @@ -0,0 +1,10 @@ +--- +title: Set current version manually in semverup +category: added +author: Eva Millán +issue: null +notes: > + Users can pass the current project version number to the + `semverup` command using the `--current-version=VERSION_NUMBER` + argument. This is useful for non-Python projects because it avoids + having to look for the version in a `_version.py` file. diff --git a/tests/test_publish.py b/tests/test_publish.py index 410c5b6..8d24c93 100644 --- a/tests/test_publish.py +++ b/tests/test_publish.py @@ -706,6 +706,40 @@ def test_publish_different_branch(self, mock_project): mock_project.return_value.repo.push.assert_any_call('myremote', 'main') mock_project.return_value.repo.push.assert_any_call('myremote', '0.8.10') + @unittest.mock.patch('release_tools.publish.read_changelog_entries') + @unittest.mock.patch('release_tools.publish.Project') + def test_add_all(self, mock_project, mock_read_changelog): + """Test if '--add-all' adds all files to the release commit.""" + version_number = "0.8.10" + + runner = click.testing.CliRunner() + + with runner.isolated_filesystem() as fs: + os.path.join(fs, 'package.json') + os.path.join(fs, 'Dockerfile') + notes_file = os.path.join(fs, version_number + '.md') + news_file = os.path.join(fs, 'NEWS') + authors_file = os.path.join(fs, 'AUTHORS') + + self.setup_release_notes(fs, notes_file, newsfile=news_file, authorsfile=authors_file) + + mock_project.return_value.unreleased_processed_entries_path = fs + mock_project.return_value.releases_path = fs + mock_project.return_value.news_file = news_file + mock_project.return_value.authors_file = authors_file + + # Run the command + result = runner.invoke(publish.publish, + [version_number, "John Smith ", + "--add-all"]) + self.assertEqual(result.exit_code, 0) + + # Check mock calls + mock_project.return_value.repo.add_all.assert_called() + mock_project.return_value.repo.add.assert_any_call(notes_file) + mock_project.return_value.repo.add.assert_any_call(news_file) + mock_project.return_value.repo.add.assert_any_call(authors_file) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_semverup.py b/tests/test_semverup.py index d8ffc4a..78a29ca 100644 --- a/tests/test_semverup.py +++ b/tests/test_semverup.py @@ -62,6 +62,9 @@ MOCK_REPOSIORY_ERROR = ( "Error: mock repository error" ) +INVALID_CURRENT_VERSION = ( + r"Error: version number 'invalid' is not a valid semver string" +) class TestSemVerUp(unittest.TestCase): @@ -879,6 +882,53 @@ def test_get_next_version(self): bump_version=case[1], do_prerelease=case[2]) + @unittest.mock.patch('release_tools.semverup.Project') + def test_current_version_arg(self, mock_project): + """Check whether it uses the given --current-version instead of version file""" + + runner = click.testing.CliRunner(mix_stderr=False) + + with runner.isolated_filesystem() as fs: + version_file = os.path.join(fs, '_version.py') + mock_project.return_value.version_file = version_file + + project_file = os.path.join(fs, 'pyproject.toml') + mock_project.return_value.pyproject_file = project_file + + dirpath = os.path.join(fs, 'releases', 'unreleased') + mock_project.return_value.unreleased_changes_path = dirpath + + self.setup_files(version_file, project_file, "0.1.0") + self.setup_unreleased_entries(dirpath) + + # Run the script command + result = runner.invoke(semverup.semverup, args=['--current-version=2.0.0', '--dry-run']) + self.assertEqual(result.exit_code, 0) + self.assertEqual(result.stdout, "2.1.0\n") + + @unittest.mock.patch('release_tools.semverup.Project') + def test_current_version_invalid_format(self, mock_project): + """Check whether it fails when --current-version has an invalid format""" + + runner = click.testing.CliRunner(mix_stderr=False) + + with runner.isolated_filesystem() as fs: + version_file = os.path.join(fs, '_version.py') + mock_project.return_value.version_file = version_file + + dirpath = os.path.join(fs, 'releases', 'unreleased') + mock_project.return_value.unreleased_changes_path = dirpath + + self.setup_version_file(version_file, "0.1.0") + self.setup_unreleased_entries(dirpath) + + # Run the script command + result = runner.invoke(semverup.semverup, args=['--current-version=invalid']) + self.assertEqual(result.exit_code, 1) + + lines = result.stderr.split('\n') + self.assertRegex(lines[-2], INVALID_CURRENT_VERSION) + if __name__ == '__main__': unittest.main()