-
Notifications
You must be signed in to change notification settings - Fork 20
Expand file tree
/
Copy pathget_pep440_version.py
More file actions
141 lines (114 loc) · 5.2 KB
/
Copy pathget_pep440_version.py
File metadata and controls
141 lines (114 loc) · 5.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# Cache for PEP 440 version string
import subprocess
from typing import Dict, Optional, TypedDict
class _VersionCache(TypedDict):
version: Optional[str]
base_version: Optional[str]
_version_cache: _VersionCache = {"version": None, "base_version": None}
def get_pep440_version(base_version=None):
"""
Generate a PEP 440 compliant version string based on git information.
This function is inspired by versioneer but doesn't require the full versioneer
setup, making it easier for downstream users to adopt without additional dependencies.
The result is cached statically to avoid repeated git calls.
Args:
base_version: The base version string (e.g., "1.0.0"). If None, will try to
find the most recent version tag in git.
Returns:
A PEP 440 compliant version string that includes:
- Development release number (devN) based on commit count since base_version
- Local version identifier with git commit hash
- Dirty indicator if there are uncommitted changes
Examples:
>>> get_pep440_version("1.0.0")
"1.0.0.dev42+g1234567" # 42 commits since 1.0.0, commit hash 1234567
>>> get_pep440_version("1.0.0") # with uncommitted changes
"1.0.0.dev42+g1234567.dirty" # indicates dirty working directory
>>> get_pep440_version("1.0.0") # no git available
"1.0.0+unknown" # indicates git info not available
"""
# Check if we have a cached version for this base_version
if _version_cache["version"] is not None and _version_cache["base_version"] == base_version:
return _version_cache["version"]
try:
# Check if we're in a git repository
subprocess.run(
["git", "rev-parse", "--git-dir"],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
)
# If base_version is None, try to find the most recent version tag
if base_version is None:
try:
base_version = subprocess.check_output(
["git", "describe", "--tags", "--abbrev=0"], universal_newlines=True, stderr=subprocess.DEVNULL
).strip()
except subprocess.CalledProcessError:
# No tags found, we'll handle this case specially
base_version = None
# Get commit count since base_version
if base_version is None:
# No base version (no tags), just count all commits
count = subprocess.check_output(
["git", "rev-list", "--count", "HEAD"], universal_newlines=True, stderr=subprocess.DEVNULL
).strip()
base_version = "0.0.0" # Use this for the final version string
else:
try:
count = subprocess.check_output(
["git", "rev-list", "--count", f"{base_version}..HEAD"],
universal_newlines=True,
stderr=subprocess.DEVNULL,
).strip()
# If no commits found, try counting from the beginning
if count == "0" or not count:
count = subprocess.check_output(
["git", "rev-list", "--count", "HEAD"], universal_newlines=True, stderr=subprocess.DEVNULL
).strip()
except subprocess.CalledProcessError:
# If base_version tag doesn't exist, count all commits
count = subprocess.check_output(
["git", "rev-list", "--count", "HEAD"], universal_newlines=True, stderr=subprocess.DEVNULL
).strip()
# Get short commit hash
commit_hash = subprocess.check_output(
["git", "rev-parse", "--short", "HEAD"], universal_newlines=True, stderr=subprocess.DEVNULL
).strip()
# Check for uncommitted changes (dirty working directory)
try:
subprocess.run(
["git", "diff-index", "--quiet", "HEAD", "--"],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
dirty_suffix = ""
except subprocess.CalledProcessError:
dirty_suffix = ".dirty"
# Ensure count is a valid integer
try:
dev_count = int(count)
except (ValueError, TypeError):
dev_count = 0
# Build PEP 440 compliant version string
# Format: <base_version>.dev<count>+g<hash>[.dirty]
version_parts = [base_version]
if dev_count > 0:
version_parts.append(f".dev{dev_count}")
version_parts.append(f"+g{commit_hash}")
if dirty_suffix:
version_parts.append(dirty_suffix)
result = "".join(version_parts)
# Cache the result
_version_cache["version"] = result
_version_cache["base_version"] = base_version
return result
except (subprocess.CalledProcessError, FileNotFoundError, OSError):
# Git is not available or not a git repository
result = f"{base_version}+unknown"
# Cache the result
_version_cache["version"] = result
_version_cache["base_version"] = base_version
return result