Coverage for encodermap/_version.py: 32%

361 statements  

« 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. 

28 

29# This file is released into the public domain. 

30# Generated by versioneer-0.29 

31# https://github.com/python-versioneer/python-versioneer 

32 

33"""Encodermap's versioning follows semantic versioning guidelines. 

34Read more about them here: https://semver.org/ 

35 

36tldr: 

37Given a version number MAJOR.MINOR.PATCH, increment the: 

38 

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. 

42 

43Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format. 

44 

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 

47 

48""" 

49 

50# Future Imports at the top 

51from __future__ import annotations 

52 

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 

62 

63 

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 

75 

76 

77class VersioneerConfig: 

78 """Container for Versioneer configuration parameters.""" 

79 

80 VCS: str 

81 style: str 

82 tag_prefix: str 

83 parentdir_prefix: str 

84 versionfile_source: str 

85 verbose: bool 

86 

87 

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 

100 

101 

102class NotThisMethod(Exception): 

103 """Exception raised if a method is not valid for the current scenario.""" 

104 

105 

106LONG_VERSION_PY: dict[str, str] = {} 

107HANDLERS: dict[str, dict[str, Callable]] = {} 

108 

109 

110def register_vcs_handler(vcs: str, method: str) -> Callable: # decorator 

111 """Create decorator to mark a method as the handler of a VCS.""" 

112 

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 

119 

120 return decorate 

121 

122 

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 

134 

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 

141 

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 

173 

174 

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. 

181 

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 = [] 

187 

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 

200 

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") 

207 

208 

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 

235 

236 

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] 

251 

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 } 

310 

311 

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. 

317 

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"] 

325 

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) 

332 

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") 

338 

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() 

362 

363 pieces: dict[str, Any] = {} 

364 pieces["long"] = full_out 

365 pieces["short"] = full_out[:7] # maybe improved later 

366 pieces["error"] = None 

367 

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() 

373 

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") 

383 

384 # Remove the first line if we're running detached 

385 if "(" in branches[0]: 

386 branches.pop(0) 

387 

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] 

397 

398 pieces["branch"] = branch_name 

399 

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 

403 

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")] 

409 

410 # now we have TAG-NUM-gHEX or HEX 

411 

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 

419 

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) :] 

432 

433 # distance: number of commits since tag 

434 pieces["distance"] = int(mo.group(2)) 

435 

436 # commit: short hex revision ID 

437 pieces["short"] = mo.group(3) 

438 

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 

444 

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) 

451 

452 return pieces 

453 

454 

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 "+" 

460 

461 

462def render_pep440(pieces: dict[str, Any]) -> str: 

463 """Build up version string, with post-release "local version identifier". 

464 

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 

467 

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 

484 

485 

486def render_pep440_branch(pieces: dict[str, Any]) -> str: 

487 """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . 

488 

489 The ".dev0" means not master branch. Note that .dev0 sorts backwards 

490 (a feature branch will appear "older" than the master branch). 

491 

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 

513 

514 

515def pep440_split_post(ver: str) -> tuple[str, Optional[int]]: 

516 """Split pep440 version string at the post-release segment. 

517 

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 

523 

524 

525def render_pep440_pre(pieces: dict[str, Any]) -> str: 

526 """TAG[.postN.devDISTANCE] -- No -dirty. 

527 

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 

547 

548 

549def render_pep440_post(pieces: dict[str, Any]) -> str: 

550 """TAG[.postDISTANCE[.dev0]+gHEX] . 

551 

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. 

555 

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 

574 

575 

576def render_pep440_post_branch(pieces: dict[str, Any]) -> str: 

577 """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . 

578 

579 The ".dev0" means not master branch. 

580 

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 

603 

604 

605def render_pep440_old(pieces: dict[str, Any]) -> str: 

606 """TAG[.postDISTANCE[.dev0]] . 

607 

608 The ".dev0" means dirty. 

609 

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 

625 

626 

627def render_git_describe(pieces: dict[str, Any]) -> str: 

628 """TAG[-DISTANCE-gHEX][-dirty]. 

629 

630 Like 'git describe --tags --dirty --always'. 

631 

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 

645 

646 

647def render_git_describe_long(pieces: dict[str, Any]) -> str: 

648 """TAG-DISTANCE-gHEX[-dirty]. 

649 

650 Like 'git describe --tags --dirty --always -long'. 

651 The distance/hash is unconditional. 

652 

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 

665 

666 

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 } 

677 

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 

680 

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) 

699 

700 return { 

701 "version": rendered, 

702 "full-revisionid": pieces["long"], 

703 "dirty": pieces["dirty"], 

704 "error": None, 

705 "date": pieces.get("date"), 

706 } 

707 

708 

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. 

715 

716 cfg = get_config() 

717 verbose = cfg.verbose 

718 

719 try: 

720 return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose) 

721 except NotThisMethod: 

722 pass 

723 

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 } 

739 

740 try: 

741 pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) 

742 return render(pieces, cfg.style) 

743 except NotThisMethod: 

744 pass 

745 

746 try: 

747 if cfg.parentdir_prefix: 

748 return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) 

749 except NotThisMethod: 

750 pass 

751 

752 return { 

753 "version": "0+unknown", 

754 "full-revisionid": None, 

755 "dirty": None, 

756 "error": "unable to compute version", 

757 "date": None, 

758 }