gitcalver 20260418.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gitcalver-20260418.5/.github/workflows/ci.yml +71 -0
- gitcalver-20260418.5/.gitignore +10 -0
- gitcalver-20260418.5/.ruff.toml +41 -0
- gitcalver-20260418.5/LICENSE +19 -0
- gitcalver-20260418.5/Makefile +15 -0
- gitcalver-20260418.5/PKG-INFO +174 -0
- gitcalver-20260418.5/README.md +155 -0
- gitcalver-20260418.5/pyproject.toml +59 -0
- gitcalver-20260418.5/src/gitcalver/__init__.py +50 -0
- gitcalver-20260418.5/src/gitcalver/__main__.py +7 -0
- gitcalver-20260418.5/src/gitcalver/_branch.py +59 -0
- gitcalver-20260418.5/src/gitcalver/_errors.py +13 -0
- gitcalver-20260418.5/src/gitcalver/_format.py +24 -0
- gitcalver-20260418.5/src/gitcalver/_git.py +130 -0
- gitcalver-20260418.5/src/gitcalver/_hatch_hooks.py +18 -0
- gitcalver-20260418.5/src/gitcalver/_hatch_source.py +28 -0
- gitcalver-20260418.5/src/gitcalver/_version.py +183 -0
- gitcalver-20260418.5/src/gitcalver/cli.py +178 -0
- gitcalver-20260418.5/src/gitcalver/py.typed +0 -0
- gitcalver-20260418.5/tests/_helpers.py +66 -0
- gitcalver-20260418.5/tests/conftest.py +21 -0
- gitcalver-20260418.5/tests/test_gitcalver.py +1092 -0
- gitcalver-20260418.5/uv.lock +380 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-24.04
|
|
10
|
+
strategy:
|
|
11
|
+
matrix:
|
|
12
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
15
|
+
with:
|
|
16
|
+
fetch-depth: 0
|
|
17
|
+
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
18
|
+
with:
|
|
19
|
+
enable-cache: true
|
|
20
|
+
cache-suffix: ${{ matrix.python-version }}
|
|
21
|
+
- run: uv python install ${{ matrix.python-version }}
|
|
22
|
+
- run: make test
|
|
23
|
+
env:
|
|
24
|
+
UV_PYTHON: ${{ matrix.python-version }}
|
|
25
|
+
|
|
26
|
+
lint:
|
|
27
|
+
runs-on: ubuntu-24.04
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
30
|
+
with:
|
|
31
|
+
fetch-depth: 0
|
|
32
|
+
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
33
|
+
with:
|
|
34
|
+
enable-cache: true
|
|
35
|
+
- run: uv python install 3.14
|
|
36
|
+
- run: make lint
|
|
37
|
+
|
|
38
|
+
build:
|
|
39
|
+
name: Build distribution
|
|
40
|
+
needs: [test, lint]
|
|
41
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
42
|
+
runs-on: ubuntu-24.04
|
|
43
|
+
steps:
|
|
44
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
45
|
+
with:
|
|
46
|
+
fetch-depth: 0
|
|
47
|
+
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
48
|
+
with:
|
|
49
|
+
enable-cache: true
|
|
50
|
+
- run: uv python install 3.14
|
|
51
|
+
- run: uv build
|
|
52
|
+
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
|
53
|
+
with:
|
|
54
|
+
name: python-package-distributions
|
|
55
|
+
path: dist/
|
|
56
|
+
|
|
57
|
+
publish-to-pypi:
|
|
58
|
+
name: Publish to PyPI
|
|
59
|
+
needs: build
|
|
60
|
+
runs-on: ubuntu-24.04
|
|
61
|
+
environment:
|
|
62
|
+
name: pypi
|
|
63
|
+
url: https://pypi.org/p/gitcalver
|
|
64
|
+
permissions:
|
|
65
|
+
id-token: write
|
|
66
|
+
steps:
|
|
67
|
+
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
|
68
|
+
with:
|
|
69
|
+
name: python-package-distributions
|
|
70
|
+
path: dist/
|
|
71
|
+
- uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Copyright © 2026 Michael Shields
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
# Match requires-python: support all non-EOL Python versions.
|
|
5
|
+
target-version = "py310"
|
|
6
|
+
|
|
7
|
+
[lint]
|
|
8
|
+
select = [
|
|
9
|
+
"ALL",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
ignore = [
|
|
13
|
+
"ANN401", # typing.Any
|
|
14
|
+
"C90", # Cyclomatic complexity
|
|
15
|
+
"D", # pydocstyle (disable docstring rules)
|
|
16
|
+
"PLR09", # "Too many" branches, arguments, etc.
|
|
17
|
+
"S603", # subprocess output
|
|
18
|
+
"S607", # subprocess using path
|
|
19
|
+
"RUF001", # ambiguous Unicode character in string
|
|
20
|
+
"RUF002", # ambiguous Unicode character in docstring
|
|
21
|
+
"RUF003", # ambiguous Unicode character in comment
|
|
22
|
+
"A002", # shadowing builtins (dir, format) -- intentional API design
|
|
23
|
+
"COM812", # trailing comma -- conflicts with formatter
|
|
24
|
+
"FBT", # boolean arguments -- acceptable for internal functions
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[lint.per-file-ignores]
|
|
28
|
+
"src/gitcalver/_hatch_hooks.py" = [
|
|
29
|
+
"PLC0415", # import not at top level -- lazy import for plugin registration
|
|
30
|
+
]
|
|
31
|
+
"src/gitcalver/cli.py" = [
|
|
32
|
+
"T201", # print() -- CLI entry point
|
|
33
|
+
]
|
|
34
|
+
"tests/**" = [
|
|
35
|
+
"S101", # assert
|
|
36
|
+
"PLR2004", # magic values
|
|
37
|
+
"INP001", # implicit namespace package (tests/ is intentionally not a package)
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[lint.isort]
|
|
41
|
+
known-local-folder = ["_helpers"]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright © 2026 Michael Shields
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gitcalver
|
|
3
|
+
Version: 20260418.5
|
|
4
|
+
Summary: Deterministic calendar versioning from git history
|
|
5
|
+
Project-URL: Homepage, https://gitcalver.org
|
|
6
|
+
Project-URL: Source, https://github.com/gitcalver/python
|
|
7
|
+
Project-URL: Issues, https://github.com/gitcalver/python/issues
|
|
8
|
+
Author-email: Michael Shields <shields@gitcalver.org>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: calendar-versioning,calver,git,versioning
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
16
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# gitcalver
|
|
21
|
+
|
|
22
|
+
A Python implementation of [GitCalVer](https://gitcalver.org), which derives
|
|
23
|
+
calendar-based version numbers from git history.
|
|
24
|
+
|
|
25
|
+
Each commit on the default branch gets a unique, strictly increasing version of
|
|
26
|
+
the form `YYYYMMDD.N`, where `N` is the number of commits on that UTC date.
|
|
27
|
+
|
|
28
|
+
See the [GitCalVer specification](https://gitcalver.org) for full details.
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
uv add gitcalver
|
|
34
|
+
# or
|
|
35
|
+
pip install gitcalver
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
See [Requirements](#requirements) below.
|
|
39
|
+
|
|
40
|
+
## CLI usage
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
gitcalver [OPTIONS] [REVISION | VERSION]
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
With no arguments, prints the version for `HEAD`:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
$ gitcalver
|
|
50
|
+
20260411.3
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Pass a revision to compute its version:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
$ gitcalver HEAD~1
|
|
57
|
+
20260411.2
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Version prefix
|
|
61
|
+
|
|
62
|
+
Use `--prefix` to prepend a literal string:
|
|
63
|
+
|
|
64
|
+
| Use case | Command | Example output |
|
|
65
|
+
|----------|------------------------------|------------------|
|
|
66
|
+
| Default | `gitcalver` | `20260411.3` |
|
|
67
|
+
| SemVer | `gitcalver --prefix "0."` | `0.20260411.3` |
|
|
68
|
+
| Go | `gitcalver --prefix "v0."` | `v0.20260411.3` |
|
|
69
|
+
|
|
70
|
+
### Dirty workspace
|
|
71
|
+
|
|
72
|
+
By default, `gitcalver` exits with status 2 if the workspace has uncommitted
|
|
73
|
+
changes. Use `--dirty STRING` to produce a version instead; the output will
|
|
74
|
+
include the given string and a short commit hash
|
|
75
|
+
(e.g. `--dirty "-dirty"` produces `20260411.3-dirty.abc1234`).
|
|
76
|
+
|
|
77
|
+
Use `--no-dirty-hash` with `--dirty` to suppress the hash suffix.
|
|
78
|
+
Use `--no-dirty` to explicitly refuse dirty versions (overrides `--dirty`).
|
|
79
|
+
|
|
80
|
+
Dirty versions are a convenience and are not necessarily unique.
|
|
81
|
+
|
|
82
|
+
### Reverse lookup
|
|
83
|
+
|
|
84
|
+
Pass a version number to get the corresponding commit hash:
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
$ gitcalver 20260411.3
|
|
88
|
+
a1b2c3d4e5f6...
|
|
89
|
+
|
|
90
|
+
$ gitcalver --short --prefix "0." 0.20260411.3
|
|
91
|
+
a1b2c3d
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
If the version was generated with `--prefix`, pass the same `--prefix` for
|
|
95
|
+
reverse lookup. Dirty versions cannot be reversed.
|
|
96
|
+
|
|
97
|
+
### Options
|
|
98
|
+
|
|
99
|
+
| Option | Description |
|
|
100
|
+
|---------------------|------------------------------------------------|
|
|
101
|
+
| `--prefix PREFIX` | Literal string prepended to version |
|
|
102
|
+
| `--dirty STRING` | Enable dirty versions; append `STRING.HASH` |
|
|
103
|
+
| `--no-dirty` | Refuse dirty versions (overrides `--dirty`) |
|
|
104
|
+
| `--no-dirty-hash` | Suppress `.HASH` suffix (requires `--dirty`) |
|
|
105
|
+
| `--branch BRANCH` | Base branch name; overrides auto-detection. This is the branch versions are minted on, not the branch you are working on. |
|
|
106
|
+
| `--short` | Output short commit hash (reverse lookup mode) |
|
|
107
|
+
| `--help` | Show help |
|
|
108
|
+
|
|
109
|
+
### Exit codes
|
|
110
|
+
|
|
111
|
+
| Code | Meaning |
|
|
112
|
+
|------|----------------------------------------|
|
|
113
|
+
| 0 | Success |
|
|
114
|
+
| 1 | Error (not a git repo, no commits, non-monotonic dates, shallow clone) |
|
|
115
|
+
| 2 | Dirty workspace or off default branch (without `--dirty`) |
|
|
116
|
+
| 3 | Cannot trace to default branch |
|
|
117
|
+
|
|
118
|
+
## Python API
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
import gitcalver
|
|
122
|
+
|
|
123
|
+
# Forward: compute a version for HEAD (or a specific revision).
|
|
124
|
+
version = gitcalver.get_version(repo="/path/to/repo")
|
|
125
|
+
# e.g. "20260411.3"
|
|
126
|
+
|
|
127
|
+
version = gitcalver.get_version(
|
|
128
|
+
repo="/path/to/repo",
|
|
129
|
+
revision="HEAD~1",
|
|
130
|
+
prefix="v0.",
|
|
131
|
+
dirty="-dirty",
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Reverse: resolve a version back to a commit hash.
|
|
135
|
+
commit = gitcalver.find_commit("20260411.3", repo="/path/to/repo")
|
|
136
|
+
|
|
137
|
+
# If the version was generated with --prefix, pass the same prefix:
|
|
138
|
+
commit = gitcalver.find_commit(
|
|
139
|
+
"v0.20260411.3", prefix="v0.", repo="/path/to/repo"
|
|
140
|
+
)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Errors are raised as `gitcalver.ExitError`, which carries a `code` attribute
|
|
144
|
+
matching the CLI exit codes above.
|
|
145
|
+
|
|
146
|
+
## Hatch plugin
|
|
147
|
+
|
|
148
|
+
`gitcalver` ships a [Hatch](https://hatch.pypa.io/) version source plugin. To
|
|
149
|
+
use it in `pyproject.toml`:
|
|
150
|
+
|
|
151
|
+
```toml
|
|
152
|
+
[build-system]
|
|
153
|
+
requires = ["hatchling", "gitcalver"]
|
|
154
|
+
build-backend = "hatchling.build"
|
|
155
|
+
|
|
156
|
+
[tool.hatch.version]
|
|
157
|
+
source = "gitcalver"
|
|
158
|
+
# Optional:
|
|
159
|
+
# prefix = "0."
|
|
160
|
+
# dirty = "-dirty"
|
|
161
|
+
# no-dirty-hash = true
|
|
162
|
+
# branch = "main"
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Requirements
|
|
166
|
+
|
|
167
|
+
- Python 3.10+
|
|
168
|
+
- `git` on `$PATH`
|
|
169
|
+
- Full commit history (shallow clones made with `--depth` are rejected; partial
|
|
170
|
+
clones made with `--filter=blob:none` are fine)
|
|
171
|
+
|
|
172
|
+
## License
|
|
173
|
+
|
|
174
|
+
MIT
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# gitcalver
|
|
2
|
+
|
|
3
|
+
A Python implementation of [GitCalVer](https://gitcalver.org), which derives
|
|
4
|
+
calendar-based version numbers from git history.
|
|
5
|
+
|
|
6
|
+
Each commit on the default branch gets a unique, strictly increasing version of
|
|
7
|
+
the form `YYYYMMDD.N`, where `N` is the number of commits on that UTC date.
|
|
8
|
+
|
|
9
|
+
See the [GitCalVer specification](https://gitcalver.org) for full details.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
uv add gitcalver
|
|
15
|
+
# or
|
|
16
|
+
pip install gitcalver
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
See [Requirements](#requirements) below.
|
|
20
|
+
|
|
21
|
+
## CLI usage
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
gitcalver [OPTIONS] [REVISION | VERSION]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
With no arguments, prints the version for `HEAD`:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
$ gitcalver
|
|
31
|
+
20260411.3
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Pass a revision to compute its version:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
$ gitcalver HEAD~1
|
|
38
|
+
20260411.2
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Version prefix
|
|
42
|
+
|
|
43
|
+
Use `--prefix` to prepend a literal string:
|
|
44
|
+
|
|
45
|
+
| Use case | Command | Example output |
|
|
46
|
+
|----------|------------------------------|------------------|
|
|
47
|
+
| Default | `gitcalver` | `20260411.3` |
|
|
48
|
+
| SemVer | `gitcalver --prefix "0."` | `0.20260411.3` |
|
|
49
|
+
| Go | `gitcalver --prefix "v0."` | `v0.20260411.3` |
|
|
50
|
+
|
|
51
|
+
### Dirty workspace
|
|
52
|
+
|
|
53
|
+
By default, `gitcalver` exits with status 2 if the workspace has uncommitted
|
|
54
|
+
changes. Use `--dirty STRING` to produce a version instead; the output will
|
|
55
|
+
include the given string and a short commit hash
|
|
56
|
+
(e.g. `--dirty "-dirty"` produces `20260411.3-dirty.abc1234`).
|
|
57
|
+
|
|
58
|
+
Use `--no-dirty-hash` with `--dirty` to suppress the hash suffix.
|
|
59
|
+
Use `--no-dirty` to explicitly refuse dirty versions (overrides `--dirty`).
|
|
60
|
+
|
|
61
|
+
Dirty versions are a convenience and are not necessarily unique.
|
|
62
|
+
|
|
63
|
+
### Reverse lookup
|
|
64
|
+
|
|
65
|
+
Pass a version number to get the corresponding commit hash:
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
$ gitcalver 20260411.3
|
|
69
|
+
a1b2c3d4e5f6...
|
|
70
|
+
|
|
71
|
+
$ gitcalver --short --prefix "0." 0.20260411.3
|
|
72
|
+
a1b2c3d
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
If the version was generated with `--prefix`, pass the same `--prefix` for
|
|
76
|
+
reverse lookup. Dirty versions cannot be reversed.
|
|
77
|
+
|
|
78
|
+
### Options
|
|
79
|
+
|
|
80
|
+
| Option | Description |
|
|
81
|
+
|---------------------|------------------------------------------------|
|
|
82
|
+
| `--prefix PREFIX` | Literal string prepended to version |
|
|
83
|
+
| `--dirty STRING` | Enable dirty versions; append `STRING.HASH` |
|
|
84
|
+
| `--no-dirty` | Refuse dirty versions (overrides `--dirty`) |
|
|
85
|
+
| `--no-dirty-hash` | Suppress `.HASH` suffix (requires `--dirty`) |
|
|
86
|
+
| `--branch BRANCH` | Base branch name; overrides auto-detection. This is the branch versions are minted on, not the branch you are working on. |
|
|
87
|
+
| `--short` | Output short commit hash (reverse lookup mode) |
|
|
88
|
+
| `--help` | Show help |
|
|
89
|
+
|
|
90
|
+
### Exit codes
|
|
91
|
+
|
|
92
|
+
| Code | Meaning |
|
|
93
|
+
|------|----------------------------------------|
|
|
94
|
+
| 0 | Success |
|
|
95
|
+
| 1 | Error (not a git repo, no commits, non-monotonic dates, shallow clone) |
|
|
96
|
+
| 2 | Dirty workspace or off default branch (without `--dirty`) |
|
|
97
|
+
| 3 | Cannot trace to default branch |
|
|
98
|
+
|
|
99
|
+
## Python API
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
import gitcalver
|
|
103
|
+
|
|
104
|
+
# Forward: compute a version for HEAD (or a specific revision).
|
|
105
|
+
version = gitcalver.get_version(repo="/path/to/repo")
|
|
106
|
+
# e.g. "20260411.3"
|
|
107
|
+
|
|
108
|
+
version = gitcalver.get_version(
|
|
109
|
+
repo="/path/to/repo",
|
|
110
|
+
revision="HEAD~1",
|
|
111
|
+
prefix="v0.",
|
|
112
|
+
dirty="-dirty",
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Reverse: resolve a version back to a commit hash.
|
|
116
|
+
commit = gitcalver.find_commit("20260411.3", repo="/path/to/repo")
|
|
117
|
+
|
|
118
|
+
# If the version was generated with --prefix, pass the same prefix:
|
|
119
|
+
commit = gitcalver.find_commit(
|
|
120
|
+
"v0.20260411.3", prefix="v0.", repo="/path/to/repo"
|
|
121
|
+
)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Errors are raised as `gitcalver.ExitError`, which carries a `code` attribute
|
|
125
|
+
matching the CLI exit codes above.
|
|
126
|
+
|
|
127
|
+
## Hatch plugin
|
|
128
|
+
|
|
129
|
+
`gitcalver` ships a [Hatch](https://hatch.pypa.io/) version source plugin. To
|
|
130
|
+
use it in `pyproject.toml`:
|
|
131
|
+
|
|
132
|
+
```toml
|
|
133
|
+
[build-system]
|
|
134
|
+
requires = ["hatchling", "gitcalver"]
|
|
135
|
+
build-backend = "hatchling.build"
|
|
136
|
+
|
|
137
|
+
[tool.hatch.version]
|
|
138
|
+
source = "gitcalver"
|
|
139
|
+
# Optional:
|
|
140
|
+
# prefix = "0."
|
|
141
|
+
# dirty = "-dirty"
|
|
142
|
+
# no-dirty-hash = true
|
|
143
|
+
# branch = "main"
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Requirements
|
|
147
|
+
|
|
148
|
+
- Python 3.10+
|
|
149
|
+
- `git` on `$PATH`
|
|
150
|
+
- Full commit history (shallow clones made with `--depth` are rejected; partial
|
|
151
|
+
clones made with `--filter=blob:none` are fine)
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
MIT
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Copyright © 2026 Michael Shields
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
[project]
|
|
5
|
+
name = "gitcalver"
|
|
6
|
+
dynamic = ["version"]
|
|
7
|
+
description = "Deterministic calendar versioning from git history"
|
|
8
|
+
# Support all non-EOL Python versions.
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
authors = [
|
|
14
|
+
{name = "Michael Shields", email = "shields@gitcalver.org"},
|
|
15
|
+
]
|
|
16
|
+
keywords = ["versioning", "calver", "git", "calendar-versioning"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 4 - Beta",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Topic :: Software Development :: Build Tools",
|
|
22
|
+
"Topic :: Software Development :: Version Control :: Git",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
Homepage = "https://gitcalver.org"
|
|
27
|
+
Source = "https://github.com/gitcalver/python"
|
|
28
|
+
Issues = "https://github.com/gitcalver/python/issues"
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
gitcalver = "gitcalver.cli:main"
|
|
32
|
+
|
|
33
|
+
[project.entry-points.hatch]
|
|
34
|
+
gitcalver = "gitcalver._hatch_hooks"
|
|
35
|
+
|
|
36
|
+
[build-system]
|
|
37
|
+
requires = ["hatchling"]
|
|
38
|
+
build-backend = "hatchling.build"
|
|
39
|
+
|
|
40
|
+
[tool.hatch.version]
|
|
41
|
+
source = "code"
|
|
42
|
+
path = "src/gitcalver/__init__.py"
|
|
43
|
+
expression = "get_version(dirty='+dirty')"
|
|
44
|
+
search-paths = ["src"]
|
|
45
|
+
|
|
46
|
+
[tool.hatch.build.targets.wheel]
|
|
47
|
+
packages = ["src/gitcalver"]
|
|
48
|
+
|
|
49
|
+
[dependency-groups]
|
|
50
|
+
dev = [
|
|
51
|
+
"hatchling>=1.1.0",
|
|
52
|
+
"pytest>=8",
|
|
53
|
+
"pytest-cov>=7.1.0",
|
|
54
|
+
"ruff>=0.11",
|
|
55
|
+
"ty>=0.0.1a7",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
[tool.pytest.ini_options]
|
|
59
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Copyright © 2026 Michael Shields
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
from gitcalver._errors import EXIT_DIRTY, EXIT_ERROR, EXIT_WRONG_BRANCH, ExitError
|
|
5
|
+
from gitcalver._format import Format
|
|
6
|
+
from gitcalver._version import forward, reverse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_version(
|
|
10
|
+
*,
|
|
11
|
+
revision: str | None = None,
|
|
12
|
+
prefix: str = "",
|
|
13
|
+
dirty: str = "",
|
|
14
|
+
dirty_hash: bool = True,
|
|
15
|
+
branch: str | None = None,
|
|
16
|
+
repo: str | None = None,
|
|
17
|
+
) -> str:
|
|
18
|
+
fmt = Format(prefix=prefix, dirty_suffix=dirty or None, dirty_hash=dirty_hash)
|
|
19
|
+
return forward(
|
|
20
|
+
dir=repo,
|
|
21
|
+
revision=revision,
|
|
22
|
+
fmt=fmt,
|
|
23
|
+
branch_override=branch or None,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def find_commit(
|
|
28
|
+
version: str,
|
|
29
|
+
*,
|
|
30
|
+
prefix: str = "",
|
|
31
|
+
branch: str | None = None,
|
|
32
|
+
repo: str | None = None,
|
|
33
|
+
short: bool = False,
|
|
34
|
+
) -> str:
|
|
35
|
+
return reverse(
|
|
36
|
+
dir=repo,
|
|
37
|
+
version_str=version.removeprefix(prefix),
|
|
38
|
+
branch_override=branch or None,
|
|
39
|
+
short=short,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
"EXIT_DIRTY",
|
|
45
|
+
"EXIT_ERROR",
|
|
46
|
+
"EXIT_WRONG_BRANCH",
|
|
47
|
+
"ExitError",
|
|
48
|
+
"find_commit",
|
|
49
|
+
"get_version",
|
|
50
|
+
]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Copyright © 2026 Michael Shields
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
from gitcalver import _git
|
|
5
|
+
from gitcalver._errors import ExitError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def detect_branch(
|
|
9
|
+
dir: str | None = None, override: str | None = None
|
|
10
|
+
) -> tuple[str, str]:
|
|
11
|
+
if override is not None:
|
|
12
|
+
if "/" in override:
|
|
13
|
+
candidates = [override]
|
|
14
|
+
else:
|
|
15
|
+
candidates = [
|
|
16
|
+
f"refs/remotes/origin/{override}",
|
|
17
|
+
f"refs/heads/{override}",
|
|
18
|
+
]
|
|
19
|
+
for ref in candidates:
|
|
20
|
+
hash_ = _git.try_ref_hash(ref, dir=dir)
|
|
21
|
+
if hash_ is not None:
|
|
22
|
+
name = override.rsplit("/", 1)[-1]
|
|
23
|
+
return name, hash_
|
|
24
|
+
msg = f"branch not found: {override}"
|
|
25
|
+
raise ExitError(msg)
|
|
26
|
+
|
|
27
|
+
target = _git.symbolic_ref("refs/remotes/origin/HEAD", dir=dir)
|
|
28
|
+
if target:
|
|
29
|
+
hash_ = _git.try_ref_hash(target, dir=dir)
|
|
30
|
+
if hash_ is not None:
|
|
31
|
+
name = target.removeprefix("refs/remotes/origin/")
|
|
32
|
+
return name, hash_
|
|
33
|
+
|
|
34
|
+
for name in ("main", "master"):
|
|
35
|
+
hash_ = _git.try_ref_hash(f"refs/remotes/origin/{name}", dir=dir)
|
|
36
|
+
if hash_ is not None:
|
|
37
|
+
return name, hash_
|
|
38
|
+
|
|
39
|
+
for name in ("main", "master"):
|
|
40
|
+
hash_ = _git.try_ref_hash(f"refs/heads/{name}", dir=dir)
|
|
41
|
+
if hash_ is not None:
|
|
42
|
+
return name, hash_
|
|
43
|
+
|
|
44
|
+
msg = "cannot determine default branch"
|
|
45
|
+
raise ExitError(msg)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def is_on_branch(
|
|
49
|
+
target_hash: str,
|
|
50
|
+
branch_hash: str,
|
|
51
|
+
dir: str | None = None,
|
|
52
|
+
) -> bool:
|
|
53
|
+
# Optimization: git treats a commit as its own ancestor, so the
|
|
54
|
+
# is_ancestor call below would handle this case correctly, but
|
|
55
|
+
# this avoids spawning git for the common same-hash case.
|
|
56
|
+
if target_hash == branch_hash:
|
|
57
|
+
return True
|
|
58
|
+
|
|
59
|
+
return _git.is_ancestor(target_hash, branch_hash, dir=dir)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Copyright © 2026 Michael Shields
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
EXIT_ERROR = 1
|
|
5
|
+
EXIT_DIRTY = 2
|
|
6
|
+
EXIT_WRONG_BRANCH = 3
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ExitError(Exception):
|
|
10
|
+
def __init__(self, message: str, code: int = EXIT_ERROR) -> None:
|
|
11
|
+
super().__init__(message)
|
|
12
|
+
self.code = code
|
|
13
|
+
self.message = message
|