Coverage for encodermap/_version.py: 32%
361 statements
« prev ^ index » next coverage.py v7.4.1, created at 2025-05-15 21:06 +0200
« prev ^ index » next coverage.py v7.4.1, created at 2025-05-15 21:06 +0200
1# -*- coding: utf-8 -*-
2# encodermap/_version.py
3################################################################################
4# EncoderMap: A python library for dimensionality reduction.
5#
6# Copyright 2019-2024 University of Konstanz and the Authors
7#
8# Authors:
9# Kevin Sawade, Tobias Lemke
10#
11# Encodermap is free software: you can redistribute it and/or modify
12# it under the terms of the GNU Lesser General Public License as
13# published by the Free Software Foundation, either version 2.1
14# of the License, or (at your option) any later version.
15# This package is distributed in the hope that it will be useful to other
16# researches. IT DOES NOT COME WITH ANY WARRANTY WHATSOEVER; without even the
17# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18# See the GNU Lesser General Public License for more details.
19#
20# See <http://www.gnu.org/licenses/>.
21################################################################################
22#
23# This file helps to compute a version number in source trees obtained from
24# git-archive tarball (such as those provided by GitHub's download-from-tag
25# feature). Distribution tarballs (built by setup.py sdist) and build
26# directories (produced by setup.py build) will contain a much shorter file
27# that just contains the computed version number.
29# This file is released into the public domain.
30# Generated by versioneer-0.29
31# https://github.com/python-versioneer/python-versioneer
33"""Encodermap's versioning follows semantic versioning guidelines.
34Read more about them here: https://semver.org/
36tldr:
37Given a version number MAJOR.MINOR.PATCH, increment the:
39* MAJOR version when you make incompatible API changes,
40* MINOR version when you add functionality in a backwards compatible manner, and
41* PATCH version when you make backwards compatible bug fixes.
43Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.
45Current example: Currently I am writing this documentation. Writing this will not break an API, nor
46does it add functionality, nor does it fixes bugs. Thus, the version stays at 3.0.0
48"""
50# Future Imports at the top
51from __future__ import annotations
53# Standard Library Imports
54import errno
55import functools
56import os
57import re
58import subprocess
59import sys
60from collections.abc import Callable
61from typing import Any, Optional
64def get_keywords() -> dict[str, str]:
65 """Get the keywords needed to look up the version information."""
66 # these strings will be replaced by git during git-archive.
67 # setup.py/versioneer.py will grep for the variable names, so they must
68 # each be defined on a line of their own. _version.py will just call
69 # get_keywords().
70 git_refnames = "$Format:%d$"
71 git_full = "$Format:%H$"
72 git_date = "$Format:%ci$"
73 keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
74 return keywords
77class VersioneerConfig:
78 """Container for Versioneer configuration parameters."""
80 VCS: str
81 style: str
82 tag_prefix: str
83 parentdir_prefix: str
84 versionfile_source: str
85 verbose: bool
88def get_config() -> VersioneerConfig:
89 """Create, populate and return the VersioneerConfig() object."""
90 # these strings are filled in when 'setup.py versioneer' creates
91 # _version.py
92 cfg = VersioneerConfig()
93 cfg.VCS = "git"
94 cfg.style = "pep440"
95 cfg.tag_prefix = ""
96 cfg.parentdir_prefix = "None"
97 cfg.versionfile_source = "encodermap/_version.py"
98 cfg.verbose = False
99 return cfg
102class NotThisMethod(Exception):
103 """Exception raised if a method is not valid for the current scenario."""
106LONG_VERSION_PY: dict[str, str] = {}
107HANDLERS: dict[str, dict[str, Callable]] = {}
110def register_vcs_handler(vcs: str, method: str) -> Callable: # decorator
111 """Create decorator to mark a method as the handler of a VCS."""
113 def decorate(f: Callable) -> Callable:
114 """Store f in HANDLERS[vcs][method]."""
115 if vcs not in HANDLERS:
116 HANDLERS[vcs] = {}
117 HANDLERS[vcs][method] = f
118 return f
120 return decorate
123def run_command(
124 commands: list[str],
125 args: list[str],
126 cwd: Optional[str] = None,
127 verbose: bool = False,
128 hide_stderr: bool = False,
129 env: Optional[dict[str, str]] = None,
130) -> tuple[Optional[str], Optional[int]]:
131 """Call the given command(s)."""
132 assert isinstance(commands, list)
133 process = None
135 popen_kwargs: dict[str, Any] = {}
136 if sys.platform == "win32": 136 ↛ 138line 136 didn't jump to line 138, because the condition on line 136 was never true
137 # This hides the console window if pythonw.exe is used
138 startupinfo = subprocess.STARTUPINFO()
139 startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
140 popen_kwargs["startupinfo"] = startupinfo
142 for command in commands: 142 ↛ 163line 142 didn't jump to line 163, because the loop on line 142 didn't complete
143 try:
144 dispcmd = str([command] + args)
145 # remember shell=False, so use git.cmd on windows, not just git
146 process = subprocess.Popen(
147 [command] + args,
148 cwd=cwd,
149 env=env,
150 stdout=subprocess.PIPE,
151 stderr=(subprocess.PIPE if hide_stderr else None),
152 **popen_kwargs,
153 )
154 break
155 except OSError as e:
156 if e.errno == errno.ENOENT:
157 continue
158 if verbose:
159 print("unable to run %s" % dispcmd)
160 print(e)
161 return None, None
162 else:
163 if verbose:
164 print("unable to find command, tried %s" % (commands,))
165 return None, None
166 stdout = process.communicate()[0].strip().decode()
167 if process.returncode != 0: 167 ↛ 168line 167 didn't jump to line 168, because the condition on line 167 was never true
168 if verbose:
169 print("unable to run %s (error)" % dispcmd)
170 print("stdout was %s" % stdout)
171 return None, process.returncode
172 return stdout, process.returncode
175def versions_from_parentdir(
176 parentdir_prefix: str,
177 root: str,
178 verbose: bool,
179) -> dict[str, Any]:
180 """Try to determine the version from the parent directory name.
182 Source tarballs conventionally unpack into a directory that includes both
183 the project name and a version string. We will also support searching up
184 two directory levels for an appropriately named parent directory
185 """
186 rootdirs = []
188 for _ in range(3):
189 dirname = os.path.basename(root)
190 if dirname.startswith(parentdir_prefix):
191 return {
192 "version": dirname[len(parentdir_prefix) :],
193 "full-revisionid": None,
194 "dirty": False,
195 "error": None,
196 "date": None,
197 }
198 rootdirs.append(root)
199 root = os.path.dirname(root) # up a level
201 if verbose:
202 print(
203 "Tried directories %s but none started with prefix %s"
204 % (str(rootdirs), parentdir_prefix)
205 )
206 raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
209@register_vcs_handler("git", "get_keywords")
210def git_get_keywords(versionfile_abs: str) -> dict[str, str]:
211 """Extract version information from the given file."""
212 # the code embedded in _version.py can just fetch the value of these
213 # keywords. When used from setup.py, we don't want to import _version.py,
214 # so we do it with a regexp instead. This function is not used from
215 # _version.py.
216 keywords: dict[str, str] = {}
217 try:
218 with open(versionfile_abs, "r") as fobj:
219 for line in fobj:
220 if line.strip().startswith("git_refnames ="):
221 mo = re.search(r'=\s*"(.*)"', line)
222 if mo:
223 keywords["refnames"] = mo.group(1)
224 if line.strip().startswith("git_full ="):
225 mo = re.search(r'=\s*"(.*)"', line)
226 if mo:
227 keywords["full"] = mo.group(1)
228 if line.strip().startswith("git_date ="):
229 mo = re.search(r'=\s*"(.*)"', line)
230 if mo:
231 keywords["date"] = mo.group(1)
232 except OSError:
233 pass
234 return keywords
237@register_vcs_handler("git", "keywords")
238def git_versions_from_keywords(
239 keywords: dict[str, str],
240 tag_prefix: str,
241 verbose: bool,
242) -> dict[str, Any]:
243 """Get version information from git keywords."""
244 if "refnames" not in keywords: 244 ↛ 245line 244 didn't jump to line 245, because the condition on line 244 was never true
245 raise NotThisMethod("Short version file found")
246 date = keywords.get("date")
247 if date is not None: 247 ↛ 259line 247 didn't jump to line 259, because the condition on line 247 was never false
248 # Use only the last line. Previous lines may contain GPG signature
249 # information.
250 date = date.splitlines()[-1]
252 # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
253 # datestamp. However we prefer "%ci" (which expands to an "ISO-8601
254 # -like" string, which we must then edit to make compliant), because
255 # it's been around since git-1.5.3, and it's too difficult to
256 # discover which version we're using, or to work around using an
257 # older one.
258 date = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
259 refnames = keywords["refnames"].strip()
260 if refnames.startswith("$Format"): 260 ↛ 264line 260 didn't jump to line 264, because the condition on line 260 was never false
261 if verbose: 261 ↛ 262line 261 didn't jump to line 262, because the condition on line 261 was never true
262 print("keywords are unexpanded, not using")
263 raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
264 refs = {r.strip() for r in refnames.strip("()").split(",")}
265 # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
266 # just "foo-1.0". If we see a "tag: " prefix, prefer those.
267 TAG = "tag: "
268 tags = {r[len(TAG) :] for r in refs if r.startswith(TAG)}
269 if not tags:
270 # Either we're using git < 1.8.3, or there really are no tags. We use
271 # a heuristic: assume all version tags have a digit. The old git %d
272 # expansion behaves like git log --decorate=short and strips out the
273 # refs/heads/ and refs/tags/ prefixes that would let us distinguish
274 # between branches and tags. By ignoring refnames without digits, we
275 # filter out many common branch names like "release" and
276 # "stabilization", as well as "HEAD" and "master".
277 tags = {r for r in refs if re.search(r"\d", r)}
278 if verbose:
279 print("discarding '%s', no digits" % ",".join(refs - tags))
280 if verbose:
281 print("likely tags: %s" % ",".join(sorted(tags)))
282 for ref in sorted(tags):
283 # sorting will prefer e.g. "2.0" over "2.0rc1"
284 if ref.startswith(tag_prefix):
285 r = ref[len(tag_prefix) :]
286 # Filter out refs that exactly match prefix or that don't start
287 # with a number once the prefix is stripped (mostly a concern
288 # when prefix is '')
289 if not re.match(r"\d", r):
290 continue
291 if verbose:
292 print("picking %s" % r)
293 return {
294 "version": r,
295 "full-revisionid": keywords["full"].strip(),
296 "dirty": False,
297 "error": None,
298 "date": date,
299 }
300 # no suitable tags, so version is "0+unknown", but full hex is still there
301 if verbose:
302 print("no suitable tags, using unknown + full revision id")
303 return {
304 "version": "0+unknown",
305 "full-revisionid": keywords["full"].strip(),
306 "dirty": False,
307 "error": "no suitable tags",
308 "date": None,
309 }
312@register_vcs_handler("git", "pieces_from_vcs")
313def git_pieces_from_vcs(
314 tag_prefix: str, root: str, verbose: bool, runner: Callable = run_command
315) -> dict[str, Any]:
316 """Get version from 'git describe' in the root of the source tree.
318 This only gets called if the git-archive 'subst' keywords were *not*
319 expanded, and _version.py hasn't already been rewritten with a short
320 version string, meaning we're inside a checked out source tree.
321 """
322 GITS = ["git"]
323 if sys.platform == "win32": 323 ↛ 324line 323 didn't jump to line 324, because the condition on line 323 was never true
324 GITS = ["git.cmd", "git.exe"]
326 # GIT_DIR can interfere with correct operation of Versioneer.
327 # It may be intended to be passed to the Versioneer-versioned project,
328 # but that should not change where we get our version from.
329 env = os.environ.copy()
330 env.pop("GIT_DIR", None)
331 runner = functools.partial(runner, env=env)
333 _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=not verbose)
334 if rc != 0: 334 ↛ 335line 334 didn't jump to line 335, because the condition on line 334 was never true
335 if verbose:
336 print("Directory %s not under git control" % root)
337 raise NotThisMethod("'git rev-parse --git-dir' returned error")
339 # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
340 # if there isn't one, this yields HEX[-dirty] (no NUM)
341 describe_out, rc = runner(
342 GITS,
343 [
344 "describe",
345 "--tags",
346 "--dirty",
347 "--always",
348 "--long",
349 "--match",
350 f"{tag_prefix}[[:digit:]]*",
351 ],
352 cwd=root,
353 )
354 # --long was added in git-1.5.5
355 if describe_out is None: 355 ↛ 356line 355 didn't jump to line 356, because the condition on line 355 was never true
356 raise NotThisMethod("'git describe' failed")
357 describe_out = describe_out.strip()
358 full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
359 if full_out is None: 359 ↛ 360line 359 didn't jump to line 360, because the condition on line 359 was never true
360 raise NotThisMethod("'git rev-parse' failed")
361 full_out = full_out.strip()
363 pieces: dict[str, Any] = {}
364 pieces["long"] = full_out
365 pieces["short"] = full_out[:7] # maybe improved later
366 pieces["error"] = None
368 branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], cwd=root)
369 # --abbrev-ref was added in git-1.6.3
370 if rc != 0 or branch_name is None: 370 ↛ 371line 370 didn't jump to line 371, because the condition on line 370 was never true
371 raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
372 branch_name = branch_name.strip()
374 if branch_name == "HEAD": 374 ↛ 378line 374 didn't jump to line 378, because the condition on line 374 was never true
375 # If we aren't exactly on a branch, pick a branch which represents
376 # the current commit. If all else fails, we are on a branchless
377 # commit.
378 branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
379 # --contains was added in git-1.5.4
380 if rc != 0 or branches is None:
381 raise NotThisMethod("'git branch --contains' returned error")
382 branches = branches.split("\n")
384 # Remove the first line if we're running detached
385 if "(" in branches[0]:
386 branches.pop(0)
388 # Strip off the leading "* " from the list of branches.
389 branches = [branch[2:] for branch in branches]
390 if "master" in branches:
391 branch_name = "master"
392 elif not branches:
393 branch_name = None
394 else:
395 # Pick the first branch that is returned. Good or bad.
396 branch_name = branches[0]
398 pieces["branch"] = branch_name
400 # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
401 # TAG might have hyphens.
402 git_describe = describe_out
404 # look for -dirty suffix
405 dirty = git_describe.endswith("-dirty")
406 pieces["dirty"] = dirty
407 if dirty: 407 ↛ 412line 407 didn't jump to line 412, because the condition on line 407 was never false
408 git_describe = git_describe[: git_describe.rindex("-dirty")]
410 # now we have TAG-NUM-gHEX or HEX
412 if "-" in git_describe: 412 ↛ 441line 412 didn't jump to line 441, because the condition on line 412 was never false
413 # TAG-NUM-gHEX
414 mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe)
415 if not mo: 415 ↛ 417line 415 didn't jump to line 417, because the condition on line 415 was never true
416 # unparsable. Maybe git-describe is misbehaving?
417 pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out
418 return pieces
420 # tag
421 full_tag = mo.group(1)
422 if not full_tag.startswith(tag_prefix): 422 ↛ 423line 422 didn't jump to line 423, because the condition on line 422 was never true
423 if verbose:
424 fmt = "tag '%s' doesn't start with prefix '%s'"
425 print(fmt % (full_tag, tag_prefix))
426 pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % (
427 full_tag,
428 tag_prefix,
429 )
430 return pieces
431 pieces["closest-tag"] = full_tag[len(tag_prefix) :]
433 # distance: number of commits since tag
434 pieces["distance"] = int(mo.group(2))
436 # commit: short hex revision ID
437 pieces["short"] = mo.group(3)
439 else:
440 # HEX: no tags
441 pieces["closest-tag"] = None
442 out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
443 pieces["distance"] = len(out.split()) # total number of commits
445 # commit date: see ISO-8601 comment in git_versions_from_keywords()
446 date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip()
447 # Use only the last line. Previous lines may contain GPG signature
448 # information.
449 date = date.splitlines()[-1]
450 pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
452 return pieces
455def plus_or_dot(pieces: dict[str, Any]) -> str:
456 """Return a + if we don't already have one, else return a ."""
457 if "+" in pieces.get("closest-tag", ""): 457 ↛ 458line 457 didn't jump to line 458, because the condition on line 457 was never true
458 return "."
459 return "+"
462def render_pep440(pieces: dict[str, Any]) -> str:
463 """Build up version string, with post-release "local version identifier".
465 Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
466 get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
468 Exceptions:
469 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
470 """
471 if pieces["closest-tag"]: 471 ↛ 480line 471 didn't jump to line 480, because the condition on line 471 was never false
472 rendered = pieces["closest-tag"]
473 if pieces["distance"] or pieces["dirty"]: 473 ↛ 483line 473 didn't jump to line 483, because the condition on line 473 was never false
474 rendered += plus_or_dot(pieces)
475 rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
476 if pieces["dirty"]: 476 ↛ 483line 476 didn't jump to line 483, because the condition on line 476 was never false
477 rendered += ".dirty"
478 else:
479 # exception #1
480 rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
481 if pieces["dirty"]:
482 rendered += ".dirty"
483 return rendered
486def render_pep440_branch(pieces: dict[str, Any]) -> str:
487 """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
489 The ".dev0" means not master branch. Note that .dev0 sorts backwards
490 (a feature branch will appear "older" than the master branch).
492 Exceptions:
493 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
494 """
495 if pieces["closest-tag"]:
496 rendered = pieces["closest-tag"]
497 if pieces["distance"] or pieces["dirty"]:
498 if pieces["branch"] != "master":
499 rendered += ".dev0"
500 rendered += plus_or_dot(pieces)
501 rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
502 if pieces["dirty"]:
503 rendered += ".dirty"
504 else:
505 # exception #1
506 rendered = "0"
507 if pieces["branch"] != "master":
508 rendered += ".dev0"
509 rendered += "+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
510 if pieces["dirty"]:
511 rendered += ".dirty"
512 return rendered
515def pep440_split_post(ver: str) -> tuple[str, Optional[int]]:
516 """Split pep440 version string at the post-release segment.
518 Returns the release segments before the post-release and the
519 post-release version number (or -1 if no post-release segment is present).
520 """
521 vc = str.split(ver, ".post")
522 return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
525def render_pep440_pre(pieces: dict[str, Any]) -> str:
526 """TAG[.postN.devDISTANCE] -- No -dirty.
528 Exceptions:
529 1: no tags. 0.post0.devDISTANCE
530 """
531 if pieces["closest-tag"]:
532 if pieces["distance"]:
533 # update the post release segment
534 tag_version, post_version = pep440_split_post(pieces["closest-tag"])
535 rendered = tag_version
536 if post_version is not None:
537 rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"])
538 else:
539 rendered += ".post0.dev%d" % (pieces["distance"])
540 else:
541 # no commits, use the tag as the version
542 rendered = pieces["closest-tag"]
543 else:
544 # exception #1
545 rendered = "0.post0.dev%d" % pieces["distance"]
546 return rendered
549def render_pep440_post(pieces: dict[str, Any]) -> str:
550 """TAG[.postDISTANCE[.dev0]+gHEX] .
552 The ".dev0" means dirty. Note that .dev0 sorts backwards
553 (a dirty tree will appear "older" than the corresponding clean one),
554 but you shouldn't be releasing software with -dirty anyways.
556 Exceptions:
557 1: no tags. 0.postDISTANCE[.dev0]
558 """
559 if pieces["closest-tag"]:
560 rendered = pieces["closest-tag"]
561 if pieces["distance"] or pieces["dirty"]:
562 rendered += ".post%d" % pieces["distance"]
563 if pieces["dirty"]:
564 rendered += ".dev0"
565 rendered += plus_or_dot(pieces)
566 rendered += "g%s" % pieces["short"]
567 else:
568 # exception #1
569 rendered = "0.post%d" % pieces["distance"]
570 if pieces["dirty"]:
571 rendered += ".dev0"
572 rendered += "+g%s" % pieces["short"]
573 return rendered
576def render_pep440_post_branch(pieces: dict[str, Any]) -> str:
577 """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
579 The ".dev0" means not master branch.
581 Exceptions:
582 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
583 """
584 if pieces["closest-tag"]:
585 rendered = pieces["closest-tag"]
586 if pieces["distance"] or pieces["dirty"]:
587 rendered += ".post%d" % pieces["distance"]
588 if pieces["branch"] != "master":
589 rendered += ".dev0"
590 rendered += plus_or_dot(pieces)
591 rendered += "g%s" % pieces["short"]
592 if pieces["dirty"]:
593 rendered += ".dirty"
594 else:
595 # exception #1
596 rendered = "0.post%d" % pieces["distance"]
597 if pieces["branch"] != "master":
598 rendered += ".dev0"
599 rendered += "+g%s" % pieces["short"]
600 if pieces["dirty"]:
601 rendered += ".dirty"
602 return rendered
605def render_pep440_old(pieces: dict[str, Any]) -> str:
606 """TAG[.postDISTANCE[.dev0]] .
608 The ".dev0" means dirty.
610 Exceptions:
611 1: no tags. 0.postDISTANCE[.dev0]
612 """
613 if pieces["closest-tag"]:
614 rendered = pieces["closest-tag"]
615 if pieces["distance"] or pieces["dirty"]:
616 rendered += ".post%d" % pieces["distance"]
617 if pieces["dirty"]:
618 rendered += ".dev0"
619 else:
620 # exception #1
621 rendered = "0.post%d" % pieces["distance"]
622 if pieces["dirty"]:
623 rendered += ".dev0"
624 return rendered
627def render_git_describe(pieces: dict[str, Any]) -> str:
628 """TAG[-DISTANCE-gHEX][-dirty].
630 Like 'git describe --tags --dirty --always'.
632 Exceptions:
633 1: no tags. HEX[-dirty] (note: no 'g' prefix)
634 """
635 if pieces["closest-tag"]:
636 rendered = pieces["closest-tag"]
637 if pieces["distance"]:
638 rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
639 else:
640 # exception #1
641 rendered = pieces["short"]
642 if pieces["dirty"]:
643 rendered += "-dirty"
644 return rendered
647def render_git_describe_long(pieces: dict[str, Any]) -> str:
648 """TAG-DISTANCE-gHEX[-dirty].
650 Like 'git describe --tags --dirty --always -long'.
651 The distance/hash is unconditional.
653 Exceptions:
654 1: no tags. HEX[-dirty] (note: no 'g' prefix)
655 """
656 if pieces["closest-tag"]:
657 rendered = pieces["closest-tag"]
658 rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
659 else:
660 # exception #1
661 rendered = pieces["short"]
662 if pieces["dirty"]:
663 rendered += "-dirty"
664 return rendered
667def render(pieces: dict[str, Any], style: str) -> dict[str, Any]:
668 """Render the given version pieces into the requested style."""
669 if pieces["error"]: 669 ↛ 670line 669 didn't jump to line 670, because the condition on line 669 was never true
670 return {
671 "version": "unknown",
672 "full-revisionid": pieces.get("long"),
673 "dirty": None,
674 "error": pieces["error"],
675 "date": None,
676 }
678 if not style or style == "default": 678 ↛ 679line 678 didn't jump to line 679, because the condition on line 678 was never true
679 style = "pep440" # the default
681 if style == "pep440": 681 ↛ 683line 681 didn't jump to line 683, because the condition on line 681 was never false
682 rendered = render_pep440(pieces)
683 elif style == "pep440-branch":
684 rendered = render_pep440_branch(pieces)
685 elif style == "pep440-pre":
686 rendered = render_pep440_pre(pieces)
687 elif style == "pep440-post":
688 rendered = render_pep440_post(pieces)
689 elif style == "pep440-post-branch":
690 rendered = render_pep440_post_branch(pieces)
691 elif style == "pep440-old":
692 rendered = render_pep440_old(pieces)
693 elif style == "git-describe":
694 rendered = render_git_describe(pieces)
695 elif style == "git-describe-long":
696 rendered = render_git_describe_long(pieces)
697 else:
698 raise ValueError("unknown style '%s'" % style)
700 return {
701 "version": rendered,
702 "full-revisionid": pieces["long"],
703 "dirty": pieces["dirty"],
704 "error": None,
705 "date": pieces.get("date"),
706 }
709def get_versions() -> dict[str, Any]:
710 """Get version information or return default if unable to do so."""
711 # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
712 # __file__, we can work backwards from there to the root. Some
713 # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which
714 # case we can only use expanded keywords.
716 cfg = get_config()
717 verbose = cfg.verbose
719 try:
720 return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose)
721 except NotThisMethod:
722 pass
724 try:
725 root = os.path.realpath(__file__)
726 # versionfile_source is the relative path from the top of the source
727 # tree (where the .git directory might live) to this file. Invert
728 # this to find the root from __file__.
729 for _ in cfg.versionfile_source.split("/"):
730 root = os.path.dirname(root)
731 except NameError:
732 return {
733 "version": "0+unknown",
734 "full-revisionid": None,
735 "dirty": None,
736 "error": "unable to find root of source tree",
737 "date": None,
738 }
740 try:
741 pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
742 return render(pieces, cfg.style)
743 except NotThisMethod:
744 pass
746 try:
747 if cfg.parentdir_prefix:
748 return versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
749 except NotThisMethod:
750 pass
752 return {
753 "version": "0+unknown",
754 "full-revisionid": None,
755 "dirty": None,
756 "error": "unable to compute version",
757 "date": None,
758 }