Skip to content

Commit

Permalink
feat: Add CLI and configuration option -n, --versioning to select…
Browse files Browse the repository at this point in the history
… versioning scheme
  • Loading branch information
pawamoy committed Mar 31, 2024
1 parent f7c8bfa commit f9c91f1
Show file tree
Hide file tree
Showing 7 changed files with 263 additions and 21 deletions.
6 changes: 3 additions & 3 deletions README.md
Expand Up @@ -19,8 +19,7 @@ Automatic Changelog generator using Jinja2 templates. From git logs to change lo
- Git service/provider agnostic,
plus references parsing (issues, commits, etc.).
Built-in [GitHub][github-refs], [Gitlab][gitlab-refs] and [Bitbucket][bitbucket-refs] support.
- Understands [Semantic Versioning][semantic-versioning]:
major/minor/patch for versions and commits.
- Understands [SemVer][semver] and [PEP 440][pep-440] versioning schemes.
Guesses next version based on last commits.
- Parses [Git trailers][git-trailers], allowing to reference
issues, PRs, etc., in your commit messages
Expand All @@ -37,13 +36,14 @@ Automatic Changelog generator using Jinja2 templates. From git logs to change lo
[keep-a-changelog]: http://keepachangelog.com/en/1.0.0/
[angular]: https://github.com/angular/angular/blob/master/CHANGELOG.md
[conventional-changelog]: https://github.com/conventional-changelog/conventional-changelog
[semantic-versioning]: http://semver.org/spec/v2.0.0.html
[semver]: http://semver.org/spec/v2.0.0.html
[angular-convention]: https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit
[conventional-commit]: https://www.conventionalcommits.org/en/v1.0.0/
[github-refs]: https://help.github.com/articles/autolinked-references-and-urls/
[gitlab-refs]: https://docs.gitlab.com/ce/user/markdown.html#special-gitlab-references
[bitbucket-refs]: https://support.atlassian.com/bitbucket-cloud/docs/markup-comments
[git-trailers]: https://git-scm.com/docs/git-interpret-trailers
[pep-440]: https://peps.python.org/pep-0440/

[issue-14]: https://github.com/pawamoy/git-changelog/issues/14
[issue-19]: https://github.com/pawamoy/git-changelog/issues/19
Expand Down
1 change: 1 addition & 0 deletions docs/cli.md
Expand Up @@ -43,6 +43,7 @@ option_to_docs = {
"sections": "#choose-the-sections-to-render",
"template": "#choose-a-changelog-template",
"version_regex": "#update-changelog-in-place",
"versioning": "#choose-a-versioning-scheme",
"zerover": "#zerover",
}

Expand Down
164 changes: 164 additions & 0 deletions docs/usage.md
Expand Up @@ -74,6 +74,7 @@ build_and_render(
parse_trailers=True,
parse_refs=False,
sections=("build", "deps", "feat", "fix", "refactor"),
versioning="pep440",
bump="auto",
in_place=True,
)
Expand Down Expand Up @@ -129,6 +130,7 @@ repository = "."
sections = ["fix", "maint"]
template = "angular"
version-regex = "^## \\\\[(?P<version>v?[^\\\\]]+)"
versioning = "semver"
zerover = true
```

Expand All @@ -150,6 +152,7 @@ repository = "."
sections = "fix,maint"
template = "keepachangelog"
version-regex = "^## \\\\[(?P<version>v?[^\\\\]]+)"
versioning = "semver"
zerover = true
```

Expand Down Expand Up @@ -421,8 +424,12 @@ git-changelog --filter-commits "2c0dbb8.."
## Understand the relationship with SemVer

[(--bump)](cli.md#bump)<br>
[(--versioning)](cli.md#versioning)<br>
[(--zerover)](cli.md#zerover)

*Although git-changelog supports several [versioning schemes](#choose-a-versioning-scheme),
SemVer plays a particular role when managing versions.*

[SemVer][semver], or Semantic Versioning, helps users of tools and libraries
understand the impact of version changes. To quote SemVer itself:

Expand Down Expand Up @@ -461,6 +468,10 @@ git-changelog --bump minor # 1.2.3 -> 1.3.0
git-changelog --bump patch # 1.2.3 -> 1.2.4
```

As different schemes have different bumping strategies,
the selected scheme will affect the `--bump` option.
See [PEP 440 strategies](#pep-440) and [SemVer strategies](#semver).

### ZeroVer

Note that by default, "ZeroVer" mode is activated,
Expand Down Expand Up @@ -503,6 +514,153 @@ to bump to v1, set `zerover = false` and commit it as a breaking change.
Once v1 is released, the setting has no use anymore, and you can remove it
from your configuration file.

## Choose a versioning scheme

[(--bump)](cli.md#bump)<br>
[(--versioning)](cli.md#versioning)<br>
[(--zerover)](cli.md#zerover)

*git-changelog* currently supports the following versioning schemes:

- `pep440`, see [PEP 440][pep440]
- `semver`, see [SemVer][semver]

Versioning schemes are useful to *git-changelog* when grouping commits
from your Git history into versions, and when bumping versions.

To choose a specific scheme, use the `-n`, `--versioning` CLI option:

```bash
git-changelog -n pep440
```

For backward compatibility reasons, it uses the SemVer scheme by default.

As different schemes have different bumping strategies,
the selected scheme will affect the `--bump` option.

### PEP 440

The bumping strategies supported by the PEP 440 scheme
are described in the table below.
Bumping a specific part of the version will remove or reset the parts
on its right to 0.

Strategy | Example | Description
--------------------- | --------------------- | -----------
`auto` | - | Guess which of major, minor or micro to bump<br>thanks to the Git history and commit message conventions.
`epoch` | `1!1``2!1` | Bump [epoch][pep440-epoch], keeping [final release][pep440-release] only.
`release` | `1rc2``1` | Bump version to a [final release][pep440-release].
`major` | `1.1``2.0` | Bump major version.
`minor` | `1.1.1``1.2.0` | Bump minor version.
`micro` (or `patch`) | `1.1.1.1``1.1.2.0` | Bump micro version.
`pre` | `1a0``1a1` | Bump current [pre-release][pep440-pre] (alpha `a`, beta `b` or release candidate `rc`).
`alpha` | `1a0``1a1` | Bump current alpha pre-release.
`beta` | `1b0``1b1` | Bump current beta pre-release.
`candidate` | `1rc0``1rc1` | Bump current candidate pre-release.
`post` | `1``1.post0` | Bump to a [post-release][pep440-post].
`dev` | `1.dev0``1.dev1` | Bump current [dev-release][pep440-dev].
`auto+alpha` | - | Guess major/minor/micro bump, and set it to alpha pre-release.
`auto+beta` | - | Guess major/minor/micro bump, and set it to beta pre-release.
`auto+candidate` | - | Guess major/minor/micro bump, and set it to candidate pre-release.
`auto+dev` | - | Guess major/minor/micro bump, and set it to dev-release.
`auto+alpha+dev` | - | Guess major/minor/micro bump, and set it to alpha pre-release and dev-release.
`auto+beta+dev` | - | Guess major/minor/micro bump, and set it to beta pre-release and dev-release.
`auto+candidate+dev` | - | Guess major/minor/micro bump, and set it to candidate pre-release and dev-release.
`major+alpha` | `1``2a0` | Bump major version and set it to alpha pre-release.
`major+beta` | `1``2b0` | Bump major version and set it to beta pre-release.
`major+candidate` | `1``2rc0` | Bump major version and set it to candidate pre-release.
`major+dev` | `1``2.dev0` | Bump major version and set it to dev-release.
`major+alpha+dev` | `1``2a0.dev0` | Bump major version and set it to alpha pre-release and dev-release.
`major+beta+dev` | `1``2b0.dev0` | Bump major version and set it to beta pre-release and dev-release.
`major+candidate+dev` | `1``2rc0.dev0` | Bump major version and set it to candidate pre-release and dev-release.
`minor+alpha` | `1``1.1a0` | Bump minor version and set it to alpha pre-release.
`minor+beta` | `1``1.1b0` | Bump minor version and set it to beta pre-release.
`minor+candidate` | `1``1.1rc0` | Bump minor version and set it to candidate pre-release.
`minor+dev` | `1``1.1.dev0` | Bump minor version and set it to dev-release.
`minor+alpha+dev` | `1``1.1a0.dev0` | Bump minor version and set it to alpha pre-release and dev-release.
`minor+beta+dev` | `1``1.1b0.dev0` | Bump minor version and set it to beta pre-release and dev-release.
`minor+candidate+dev` | `1``1.1rc0.dev0` | Bump minor version and set it to candidate pre-release and dev-release.
`micro+alpha` | `1``1.0.1a0` | Bump micro version and set it to alpha pre-release.
`micro+beta` | `1``1.0.1b0` | Bump micro version and set it to beta pre-release.
`micro+candidate` | `1``1.0.1rc0` | Bump micro version and set it to candidate pre-release.
`micro+dev` | `1``1.0.1.dev0` | Bump micro version and set it to dev-release.
`micro+alpha+dev` | `1``1.0.1a0.dev0` | Bump micro version and set it to alpha pre-release and dev-release.
`micro+beta+dev` | `1``1.0.1b0.dev0` | Bump micro version and set it to beta pre-release and dev-release.
`micro+candidate+dev` | `1``1.0.1rc0.dev0` | Bump micro version and set it to candidate pre-release and dev-release.
`alpha+dev` | `1a0``1a1.dev0` | Bump current alpha pre-release and set it to a dev-release.
`beta+dev` | `1b0``1b1.dev0` | Bump current beta pre-release and set it to a dev-release.
`candidate+dev` | `1rc0``1rc1.dev0` | Bump current candidate pre-release and set it to a dev-release.

Try it out:

```pyodide install="git-changelog"
from git_changelog.versioning import bump_pep440
# "auto" strategies are not directly supported by this function
print(bump_pep440("1.2.3", "minor+alpha"))
```

The `v` prefix will be preserved when bumping a version: `v1` -> `v2`.

The bumping strategies for PEP 440 try to make the most sense,
allowing you to bump in a semantic way and preventing version downgrade mistakes.
Specifically, it is not possible:

- to bump from a final release version to a pre-release or a dev-release version
- to bump from a pre-release version to a lower pre-release version or a dev-version
- more generally, to bump from any version to any lower version

If you need to "bump" to a version that is lower than the latest released one,
you must explicitely pass the version to the `--bump` option:

```bash
# latest release is 1.1
git-changelog --bump 1.0
```

### SemVer

The bumping strategies supported by the SemVer scheme
are described in the table below.
Bumping a specific part of the version will remove or reset the parts
on its right to 0.

Strategy | Example | Description
--------------------- | --------------------- | -----------
`auto` | - | Guess which of major, minor or patch to bump<br>thanks to the Git history and commit message conventions.
`major` | `1.1.1``2.0.0` | Bump major version.
`minor` | `1.1.1``1.2.0` | Bump minor version.
`patch` | `1.1.1``1.1.2` | Bump micro version.
`release` | `1.1.1-a2``1.1.1` | Bump version to a final release (remove pre-release and build metadata).

Try it out:

```pyodide install="git-changelog"
from git_changelog.versioning import bump_semver
# the "auto" strategy is not directly supported by this function
print(bump_semver("1.2.3", "minor"))
```

The `v` prefix will be preserved when bumping a version: `v1.0.0` -> `v2.0.0`.

The bumping strategies for SemVer will prevent you from bumping from any version to a lower one.
It does not support bump pre-release metadata or build metadata
because these are not standardized.

If you need to "bump" to a version that is lower than the latest released one,
or to add pre-release or build metadata,
you must explicitely pass the version to the `--bump` option:

```bash
# downgrade
git-changelog --bump 1.1.0

# add pre-release metadata
git-changelog --bump 2.0.0-alpha1
```

## Parse additional information in commit messages

*git-changelog* is able to parse the body of commit messages
Expand Down Expand Up @@ -707,3 +865,9 @@ and `--marker-line`.
[keepachangelog-template]: https://github.com/pawamoy/git-changelog/tree/main/src/git_changelog/templates/keepachangelog.md
[builtin-templates]: https://github.com/pawamoy/git-changelog/tree/main/src/git_changelog/templates
[control-whitespace]: https://jinja.palletsprojects.com/en/3.1.x/templates/#whitespace-control
[pep440]: https://peps.python.org/pep-0440/
[pep440-epoch]: https://peps.python.org/pep-0440/#version-epochs
[pep440-pre]: https://peps.python.org/pep-0440/#pre-releases
[pep440-post]: https://peps.python.org/pep-0440/#post-releases
[pep440-dev]: https://peps.python.org/pep-0440/#developmental-releases
[pep440-release]: https://peps.python.org/pep-0440/#final-releases
45 changes: 31 additions & 14 deletions src/git_changelog/build.py
Expand Up @@ -203,6 +203,7 @@ def __init__(
bump: str | None = None,
zerover: bool = True,
filter_commits: str | None = None,
versioning: Literal["semver", "pep440"] = "semver",
):
"""Initialization method.
Expand Down Expand Up @@ -271,8 +272,14 @@ def __init__(
self.tag_commits: list[Commit] = [commit for commit in self.commits[1:] if commit.tag]
self.tag_commits.insert(0, self.commits[0])

# get version parser based on selected versioning scheme
version_parser, version_bumper = {
"semver": (parse_semver, bump_semver),
"pep440": (parse_pep440, bump_pep440),
}[versioning]

# apply dates to commits and group them by version
v_list, v_dict = self._group_commits_by_version()
v_list, v_dict = self._group_commits_by_version(version_parser=version_parser)
self.versions_list = v_list
self.versions_dict = v_dict

Expand All @@ -286,7 +293,7 @@ def __init__(
if bump is None:
bump = "auto"
if bump:
self._bump(bump)
self._bump(bump, version_bumper=version_bumper)

# fix a single, initial version to the user specified version or 0.1.0 if none is specified
self._fix_single_version(bump)
Expand Down Expand Up @@ -399,12 +406,19 @@ def parse_commits(self) -> list[Commit]:

return list(commits_map.values())

def _group_commits_by_version(self) -> tuple[list[Version], dict[str, Version]]:
def _group_commits_by_version(
self,
version_parser: Callable[[str], tuple[ParsedVersion, str]],
) -> tuple[list[Version], dict[str, Version]]:
"""Group commits into versions.
Commits are assigned to the version they were first released with.
A commit is assigned to exactly one version.
Parameters:
version_parser: Version parser to use when grouping commits by versions.
Versions that cannot be parsed by the given parser will be ignored.
Returns:
versions_list: The list of versions order descending by timestamp.
versions_dict: A dictionary of versions with the tag name as keys.
Expand All @@ -422,14 +436,14 @@ def _group_commits_by_version(self) -> tuple[list[Version], dict[str, Version]]:

# Find all commits for this version by following the commit graph.
version.add_commit(tag_commit)
previous_semver: SemverVersion | None = None
previous_parsed_version: ParsedVersion | None = None
next_commits = tag_commit.parent_commits # Always new: we can mutate it.
while next_commits:
next_commit = next_commits.pop(0)
if next_commit.tag:
semver, _ = parse_version(next_commit.tag)
if not previous_semver or semver.compare(previous_semver) > 0:
previous_semver = semver
parsed_version, _ = version_parser(next_commit.tag)
if not previous_parsed_version or parsed_version > previous_parsed_version:
previous_parsed_version = parsed_version
previous_versions[version.tag] = next_commit.tag
elif not next_commit.version:
version.add_commit(next_commit)
Expand Down Expand Up @@ -470,10 +484,11 @@ def _assign_previous_versions(self, versions_dict: dict[str, Version], previous_
target=version.tag or "HEAD",
)

def _bump(self, version: str) -> None:
def _bump(self, version: str, version_bumper: VersionBumper) -> None:
last_version = self.versions_list[0]
if not last_version.tag and last_version.previous_version:
last_tag = last_version.previous_version.tag
version, *plus = version.split("+")
if version == "auto":
# guess the next version number based on last version and recent commits
version = "patch"
Expand All @@ -483,14 +498,16 @@ def _bump(self, version: str) -> None:
break
if commit.convention["is_minor"]:
version = "minor"
if version in {"major", "minor", "patch"}:
# bump version (don't fail on non-semver versions)
try:
last_version.planned_tag = bump(last_tag, version, zerover=self.zerover) # type: ignore[arg-type]
except ValueError:
return
version = "+".join((version, *plus))
if version in version_bumper.strategies:
# bump version
last_version.planned_tag = version_bumper(last_tag, version, zerover=self.zerover)
else:
# user specified version
try:
version_bumper(version)
except ValueError as error:
raise ValueError(f"{error}; typo in bumping strategy? Check the CLI help and our docs") from error
last_version.planned_tag = version
# update URLs
if self.provider:
Expand Down

0 comments on commit f9c91f1

Please sign in to comment.