Skip to content

Commit 772b8e5

Browse files
committed
feat: Add CLI and configuration option -n, --versioning to select versioning scheme
1 parent f33bc14 commit 772b8e5

File tree

7 files changed

+263
-21
lines changed

7 files changed

+263
-21
lines changed

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ Automatic Changelog generator using Jinja2 templates. From git logs to change lo
1919
- Git service/provider agnostic,
2020
plus references parsing (issues, commits, etc.).
2121
Built-in [GitHub][github-refs], [Gitlab][gitlab-refs] and [Bitbucket][bitbucket-refs] support.
22-
- Understands [Semantic Versioning][semantic-versioning]:
23-
major/minor/patch for versions and commits.
22+
- Understands [SemVer][semver] and [PEP 440][pep-440] versioning schemes.
2423
Guesses next version based on last commits.
2524
- Parses [Git trailers][git-trailers], allowing to reference
2625
issues, PRs, etc., in your commit messages
@@ -37,13 +36,14 @@ Automatic Changelog generator using Jinja2 templates. From git logs to change lo
3736
[keep-a-changelog]: http://keepachangelog.com/en/1.0.0/
3837
[angular]: https://github.com/angular/angular/blob/master/CHANGELOG.md
3938
[conventional-changelog]: https://github.com/conventional-changelog/conventional-changelog
40-
[semantic-versioning]: http://semver.org/spec/v2.0.0.html
39+
[semver]: http://semver.org/spec/v2.0.0.html
4140
[angular-convention]: https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit
4241
[conventional-commit]: https://www.conventionalcommits.org/en/v1.0.0/
4342
[github-refs]: https://help.github.com/articles/autolinked-references-and-urls/
4443
[gitlab-refs]: https://docs.gitlab.com/ce/user/markdown.html#special-gitlab-references
4544
[bitbucket-refs]: https://support.atlassian.com/bitbucket-cloud/docs/markup-comments
4645
[git-trailers]: https://git-scm.com/docs/git-interpret-trailers
46+
[pep-440]: https://peps.python.org/pep-0440/
4747

4848
[issue-14]: https://github.com/pawamoy/git-changelog/issues/14
4949
[issue-19]: https://github.com/pawamoy/git-changelog/issues/19

docs/cli.md

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ option_to_docs = {
4343
"sections": "#choose-the-sections-to-render",
4444
"template": "#choose-a-changelog-template",
4545
"version_regex": "#update-changelog-in-place",
46+
"versioning": "#choose-a-versioning-scheme",
4647
"zerover": "#zerover",
4748
}
4849

docs/usage.md

+164
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ build_and_render(
7474
parse_trailers=True,
7575
parse_refs=False,
7676
sections=("build", "deps", "feat", "fix", "refactor"),
77+
versioning="pep440",
7778
bump="auto",
7879
in_place=True,
7980
)
@@ -129,6 +130,7 @@ repository = "."
129130
sections = ["fix", "maint"]
130131
template = "angular"
131132
version-regex = "^## \\\\[(?P<version>v?[^\\\\]]+)"
133+
versioning = "semver"
132134
zerover = true
133135
```
134136

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

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

423426
[(--bump)](cli.md#bump)<br>
427+
[(--versioning)](cli.md#versioning)<br>
424428
[(--zerover)](cli.md#zerover)
425429

430+
*Although git-changelog supports several [versioning schemes](#choose-a-versioning-scheme),
431+
SemVer plays a particular role when managing versions.*
432+
426433
[SemVer][semver], or Semantic Versioning, helps users of tools and libraries
427434
understand the impact of version changes. To quote SemVer itself:
428435

@@ -461,6 +468,10 @@ git-changelog --bump minor # 1.2.3 -> 1.3.0
461468
git-changelog --bump patch # 1.2.3 -> 1.2.4
462469
```
463470

471+
As different schemes have different bumping strategies,
472+
the selected scheme will affect the `--bump` option.
473+
See [PEP 440 strategies](#pep-440) and [SemVer strategies](#semver).
474+
464475
### ZeroVer
465476

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

517+
## Choose a versioning scheme
518+
519+
[(--bump)](cli.md#bump)<br>
520+
[(--versioning)](cli.md#versioning)<br>
521+
[(--zerover)](cli.md#zerover)
522+
523+
*git-changelog* currently supports the following versioning schemes:
524+
525+
- `pep440`, see [PEP 440][pep440]
526+
- `semver`, see [SemVer][semver]
527+
528+
Versioning schemes are useful to *git-changelog* when grouping commits
529+
from your Git history into versions, and when bumping versions.
530+
531+
To choose a specific scheme, use the `-n`, `--versioning` CLI option:
532+
533+
```bash
534+
git-changelog -n pep440
535+
```
536+
537+
For backward compatibility reasons, it uses the SemVer scheme by default.
538+
539+
As different schemes have different bumping strategies,
540+
the selected scheme will affect the `--bump` option.
541+
542+
### PEP 440
543+
544+
The bumping strategies supported by the PEP 440 scheme
545+
are described in the table below.
546+
Bumping a specific part of the version will remove or reset the parts
547+
on its right to 0.
548+
549+
Strategy | Example | Description
550+
--------------------- | --------------------- | -----------
551+
`auto` | - | Guess which of major, minor or micro to bump<br>thanks to the Git history and commit message conventions.
552+
`epoch` | `1!1``2!1` | Bump [epoch][pep440-epoch], keeping [final release][pep440-release] only.
553+
`release` | `1rc2``1` | Bump version to a [final release][pep440-release].
554+
`major` | `1.1``2.0` | Bump major version.
555+
`minor` | `1.1.1``1.2.0` | Bump minor version.
556+
`micro` (or `patch`) | `1.1.1.1``1.1.2.0` | Bump micro version.
557+
`pre` | `1a0``1a1` | Bump current [pre-release][pep440-pre] (alpha `a`, beta `b` or release candidate `rc`).
558+
`alpha` | `1a0``1a1` | Bump current alpha pre-release.
559+
`beta` | `1b0``1b1` | Bump current beta pre-release.
560+
`candidate` | `1rc0``1rc1` | Bump current candidate pre-release.
561+
`post` | `1``1.post0` | Bump to a [post-release][pep440-post].
562+
`dev` | `1.dev0``1.dev1` | Bump current [dev-release][pep440-dev].
563+
`auto+alpha` | - | Guess major/minor/micro bump, and set it to alpha pre-release.
564+
`auto+beta` | - | Guess major/minor/micro bump, and set it to beta pre-release.
565+
`auto+candidate` | - | Guess major/minor/micro bump, and set it to candidate pre-release.
566+
`auto+dev` | - | Guess major/minor/micro bump, and set it to dev-release.
567+
`auto+alpha+dev` | - | Guess major/minor/micro bump, and set it to alpha pre-release and dev-release.
568+
`auto+beta+dev` | - | Guess major/minor/micro bump, and set it to beta pre-release and dev-release.
569+
`auto+candidate+dev` | - | Guess major/minor/micro bump, and set it to candidate pre-release and dev-release.
570+
`major+alpha` | `1``2a0` | Bump major version and set it to alpha pre-release.
571+
`major+beta` | `1``2b0` | Bump major version and set it to beta pre-release.
572+
`major+candidate` | `1``2rc0` | Bump major version and set it to candidate pre-release.
573+
`major+dev` | `1``2.dev0` | Bump major version and set it to dev-release.
574+
`major+alpha+dev` | `1``2a0.dev0` | Bump major version and set it to alpha pre-release and dev-release.
575+
`major+beta+dev` | `1``2b0.dev0` | Bump major version and set it to beta pre-release and dev-release.
576+
`major+candidate+dev` | `1``2rc0.dev0` | Bump major version and set it to candidate pre-release and dev-release.
577+
`minor+alpha` | `1``1.1a0` | Bump minor version and set it to alpha pre-release.
578+
`minor+beta` | `1``1.1b0` | Bump minor version and set it to beta pre-release.
579+
`minor+candidate` | `1``1.1rc0` | Bump minor version and set it to candidate pre-release.
580+
`minor+dev` | `1``1.1.dev0` | Bump minor version and set it to dev-release.
581+
`minor+alpha+dev` | `1``1.1a0.dev0` | Bump minor version and set it to alpha pre-release and dev-release.
582+
`minor+beta+dev` | `1``1.1b0.dev0` | Bump minor version and set it to beta pre-release and dev-release.
583+
`minor+candidate+dev` | `1``1.1rc0.dev0` | Bump minor version and set it to candidate pre-release and dev-release.
584+
`micro+alpha` | `1``1.0.1a0` | Bump micro version and set it to alpha pre-release.
585+
`micro+beta` | `1``1.0.1b0` | Bump micro version and set it to beta pre-release.
586+
`micro+candidate` | `1``1.0.1rc0` | Bump micro version and set it to candidate pre-release.
587+
`micro+dev` | `1``1.0.1.dev0` | Bump micro version and set it to dev-release.
588+
`micro+alpha+dev` | `1``1.0.1a0.dev0` | Bump micro version and set it to alpha pre-release and dev-release.
589+
`micro+beta+dev` | `1``1.0.1b0.dev0` | Bump micro version and set it to beta pre-release and dev-release.
590+
`micro+candidate+dev` | `1``1.0.1rc0.dev0` | Bump micro version and set it to candidate pre-release and dev-release.
591+
`alpha+dev` | `1a0``1a1.dev0` | Bump current alpha pre-release and set it to a dev-release.
592+
`beta+dev` | `1b0``1b1.dev0` | Bump current beta pre-release and set it to a dev-release.
593+
`candidate+dev` | `1rc0``1rc1.dev0` | Bump current candidate pre-release and set it to a dev-release.
594+
595+
Try it out:
596+
597+
```pyodide install="git-changelog"
598+
from git_changelog.versioning import bump_pep440
599+
600+
# "auto" strategies are not directly supported by this function
601+
print(bump_pep440("1.2.3", "minor+alpha"))
602+
```
603+
604+
The `v` prefix will be preserved when bumping a version: `v1` -> `v2`.
605+
606+
The bumping strategies for PEP 440 try to make the most sense,
607+
allowing you to bump in a semantic way and preventing version downgrade mistakes.
608+
Specifically, it is not possible:
609+
610+
- to bump from a final release version to a pre-release or a dev-release version
611+
- to bump from a pre-release version to a lower pre-release version or a dev-version
612+
- more generally, to bump from any version to any lower version
613+
614+
If you need to "bump" to a version that is lower than the latest released one,
615+
you must explicitely pass the version to the `--bump` option:
616+
617+
```bash
618+
# latest release is 1.1
619+
git-changelog --bump 1.0
620+
```
621+
622+
### SemVer
623+
624+
The bumping strategies supported by the SemVer scheme
625+
are described in the table below.
626+
Bumping a specific part of the version will remove or reset the parts
627+
on its right to 0.
628+
629+
Strategy | Example | Description
630+
--------------------- | --------------------- | -----------
631+
`auto` | - | Guess which of major, minor or patch to bump<br>thanks to the Git history and commit message conventions.
632+
`major` | `1.1.1``2.0.0` | Bump major version.
633+
`minor` | `1.1.1``1.2.0` | Bump minor version.
634+
`patch` | `1.1.1``1.1.2` | Bump micro version.
635+
`release` | `1.1.1-a2``1.1.1` | Bump version to a final release (remove pre-release and build metadata).
636+
637+
Try it out:
638+
639+
```pyodide install="git-changelog"
640+
from git_changelog.versioning import bump_semver
641+
642+
# the "auto" strategy is not directly supported by this function
643+
print(bump_semver("1.2.3", "minor"))
644+
```
645+
646+
The `v` prefix will be preserved when bumping a version: `v1.0.0` -> `v2.0.0`.
647+
648+
The bumping strategies for SemVer will prevent you from bumping from any version to a lower one.
649+
It does not support bump pre-release metadata or build metadata
650+
because these are not standardized.
651+
652+
If you need to "bump" to a version that is lower than the latest released one,
653+
or to add pre-release or build metadata,
654+
you must explicitely pass the version to the `--bump` option:
655+
656+
```bash
657+
# downgrade
658+
git-changelog --bump 1.1.0
659+
660+
# add pre-release metadata
661+
git-changelog --bump 2.0.0-alpha1
662+
```
663+
506664
## Parse additional information in commit messages
507665

508666
*git-changelog* is able to parse the body of commit messages
@@ -707,3 +865,9 @@ and `--marker-line`.
707865
[keepachangelog-template]: https://github.com/pawamoy/git-changelog/tree/main/src/git_changelog/templates/keepachangelog.md
708866
[builtin-templates]: https://github.com/pawamoy/git-changelog/tree/main/src/git_changelog/templates
709867
[control-whitespace]: https://jinja.palletsprojects.com/en/3.1.x/templates/#whitespace-control
868+
[pep440]: https://peps.python.org/pep-0440/
869+
[pep440-epoch]: https://peps.python.org/pep-0440/#version-epochs
870+
[pep440-pre]: https://peps.python.org/pep-0440/#pre-releases
871+
[pep440-post]: https://peps.python.org/pep-0440/#post-releases
872+
[pep440-dev]: https://peps.python.org/pep-0440/#developmental-releases
873+
[pep440-release]: https://peps.python.org/pep-0440/#final-releases

src/git_changelog/build.py

+31-14
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ def __init__(
203203
bump: str | None = None,
204204
zerover: bool = True,
205205
filter_commits: str | None = None,
206+
versioning: Literal["semver", "pep440"] = "semver",
206207
):
207208
"""Initialization method.
208209
@@ -271,8 +272,14 @@ def __init__(
271272
self.tag_commits: list[Commit] = [commit for commit in self.commits[1:] if commit.tag]
272273
self.tag_commits.insert(0, self.commits[0])
273274

275+
# get version parser based on selected versioning scheme
276+
version_parser, version_bumper = {
277+
"semver": (parse_semver, bump_semver),
278+
"pep440": (parse_pep440, bump_pep440),
279+
}[versioning]
280+
274281
# apply dates to commits and group them by version
275-
v_list, v_dict = self._group_commits_by_version()
282+
v_list, v_dict = self._group_commits_by_version(version_parser=version_parser)
276283
self.versions_list = v_list
277284
self.versions_dict = v_dict
278285

@@ -286,7 +293,7 @@ def __init__(
286293
if bump is None:
287294
bump = "auto"
288295
if bump:
289-
self._bump(bump)
296+
self._bump(bump, version_bumper=version_bumper)
290297

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

400407
return list(commits_map.values())
401408

402-
def _group_commits_by_version(self) -> tuple[list[Version], dict[str, Version]]:
409+
def _group_commits_by_version(
410+
self,
411+
version_parser: Callable[[str], tuple[ParsedVersion, str]],
412+
) -> tuple[list[Version], dict[str, Version]]:
403413
"""Group commits into versions.
404414
405415
Commits are assigned to the version they were first released with.
406416
A commit is assigned to exactly one version.
407417
418+
Parameters:
419+
version_parser: Version parser to use when grouping commits by versions.
420+
Versions that cannot be parsed by the given parser will be ignored.
421+
408422
Returns:
409423
versions_list: The list of versions order descending by timestamp.
410424
versions_dict: A dictionary of versions with the tag name as keys.
@@ -422,14 +436,14 @@ def _group_commits_by_version(self) -> tuple[list[Version], dict[str, Version]]:
422436

423437
# Find all commits for this version by following the commit graph.
424438
version.add_commit(tag_commit)
425-
previous_semver: SemverVersion | None = None
439+
previous_parsed_version: ParsedVersion | None = None
426440
next_commits = tag_commit.parent_commits # Always new: we can mutate it.
427441
while next_commits:
428442
next_commit = next_commits.pop(0)
429443
if next_commit.tag:
430-
semver, _ = parse_version(next_commit.tag)
431-
if not previous_semver or semver.compare(previous_semver) > 0:
432-
previous_semver = semver
444+
parsed_version, _ = version_parser(next_commit.tag)
445+
if not previous_parsed_version or parsed_version > previous_parsed_version:
446+
previous_parsed_version = parsed_version
433447
previous_versions[version.tag] = next_commit.tag
434448
elif not next_commit.version:
435449
version.add_commit(next_commit)
@@ -470,10 +484,11 @@ def _assign_previous_versions(self, versions_dict: dict[str, Version], previous_
470484
target=version.tag or "HEAD",
471485
)
472486

473-
def _bump(self, version: str) -> None:
487+
def _bump(self, version: str, version_bumper: VersionBumper) -> None:
474488
last_version = self.versions_list[0]
475489
if not last_version.tag and last_version.previous_version:
476490
last_tag = last_version.previous_version.tag
491+
version, *plus = version.split("+")
477492
if version == "auto":
478493
# guess the next version number based on last version and recent commits
479494
version = "patch"
@@ -483,14 +498,16 @@ def _bump(self, version: str) -> None:
483498
break
484499
if commit.convention["is_minor"]:
485500
version = "minor"
486-
if version in {"major", "minor", "patch"}:
487-
# bump version (don't fail on non-semver versions)
488-
try:
489-
last_version.planned_tag = bump(last_tag, version, zerover=self.zerover) # type: ignore[arg-type]
490-
except ValueError:
491-
return
501+
version = "+".join((version, *plus))
502+
if version in version_bumper.strategies:
503+
# bump version
504+
last_version.planned_tag = version_bumper(last_tag, version, zerover=self.zerover)
492505
else:
493506
# user specified version
507+
try:
508+
version_bumper(version)
509+
except ValueError as error:
510+
raise ValueError(f"{error}; typo in bumping strategy? Check the CLI help and our docs") from error
494511
last_version.planned_tag = version
495512
# update URLs
496513
if self.provider:

0 commit comments

Comments
 (0)