2 # This file helps to compute a version number in source trees obtained from
3 # git-archive tarball (such as those provided by githubs download-from-tag
4 # feature). Distribution tarballs (built by setup.py sdist) and build
5 # directories (produced by setup.py build) will contain a much shorter file
6 # that just contains the computed version number.
8 # This file is released into the public domain.
9 # Generated by versioneer-0.29
10 # https://github.com/python-versioneer/python-versioneer
12 """Git implementation of _version.py."""
19 from typing
import Any
, Callable
, Dict
, List
, Optional
, Tuple
23 def get_keywords() -> Dict
[str, str]:
24 """Get the keywords needed to look up the version information."""
25 # these strings will be replaced by git during git-archive.
26 # setup.py/versioneer.py will grep for the variable names, so they must
27 # each be defined on a line of their own. _version.py will just call
29 git_refnames
= "$Format:%d$"
30 git_full
= "$Format:%H$"
31 git_date
= "$Format:%ci$"
32 keywords
= {"refnames": git_refnames
, "full": git_full
, "date": git_date
}
36 class VersioneerConfig
:
37 """Container for Versioneer configuration parameters."""
43 versionfile_source
: str
47 def get_config() -> VersioneerConfig
:
48 """Create, populate and return the VersioneerConfig() object."""
49 # these strings are filled in when 'setup.py versioneer' creates
51 cfg
= VersioneerConfig()
54 cfg
.tag_prefix
= "deploy-"
55 cfg
.parentdir_prefix
= "inboxen-"
56 cfg
.versionfile_source
= "inboxen/_version.py"
61 class NotThisMethod(Exception):
62 """Exception raised if a method is not valid for the current scenario."""
65 LONG_VERSION_PY
: Dict
[str, str] = {}
66 HANDLERS
: Dict
[str, Dict
[str, Callable
]] = {}
69 def register_vcs_handler(vcs
: str, method
: str) -> Callable
: # decorator
70 """Create decorator to mark a method as the handler of a VCS."""
71 def decorate(f
: Callable
) -> Callable
:
72 """Store f in HANDLERS[vcs][method]."""
73 if vcs
not in HANDLERS
:
75 HANDLERS
[vcs
][method
] = f
83 cwd
: Optional
[str] = None,
84 verbose
: bool = False,
85 hide_stderr
: bool = False,
86 env
: Optional
[Dict
[str, str]] = None,
87 ) -> Tuple
[Optional
[str], Optional
[int]]:
88 """Call the given command(s)."""
89 assert isinstance(commands
, list)
92 popen_kwargs
: Dict
[str, Any
] = {}
93 if sys
.platform
== "win32":
94 # This hides the console window if pythonw.exe is used
95 startupinfo
= subprocess
.STARTUPINFO()
96 startupinfo
.dwFlags |
= subprocess
.STARTF_USESHOWWINDOW
97 popen_kwargs
["startupinfo"] = startupinfo
99 for command
in commands
:
101 dispcmd
= str([command
] + args
)
102 # remember shell=False, so use git.cmd on windows, not just git
103 process
= subprocess
.Popen([command
] + args
, cwd
=cwd
, env
=env
,
104 stdout
=subprocess
.PIPE
,
105 stderr
=(subprocess
.PIPE
if hide_stderr
106 else None), **popen_kwargs
)
109 if e
.errno
== errno
.ENOENT
:
112 print("unable to run %s" % dispcmd
)
117 print("unable to find command, tried %s" % (commands
,))
119 stdout
= process
.communicate()[0].strip().decode()
120 if process
.returncode
!= 0:
122 print("unable to run %s (error)" % dispcmd
)
123 print("stdout was %s" % stdout
)
124 return None, process
.returncode
125 return stdout
, process
.returncode
128 def versions_from_parentdir(
129 parentdir_prefix
: str,
133 """Try to determine the version from the parent directory name.
135 Source tarballs conventionally unpack into a directory that includes both
136 the project name and a version string. We will also support searching up
137 two directory levels for an appropriately named parent directory
142 dirname
= os
.path
.basename(root
)
143 if dirname
.startswith(parentdir_prefix
):
144 return {"version": dirname
[len(parentdir_prefix
):],
145 "full-revisionid": None,
146 "dirty": False, "error": None, "date": None}
147 rootdirs
.append(root
)
148 root
= os
.path
.dirname(root
) # up a level
151 print("Tried directories %s but none started with prefix %s" %
152 (str(rootdirs
), parentdir_prefix
))
153 raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
156 @register_vcs_handler("git", "get_keywords")
157 def git_get_keywords(versionfile_abs
: str) -> Dict
[str, str]:
158 """Extract version information from the given file."""
159 # the code embedded in _version.py can just fetch the value of these
160 # keywords. When used from setup.py, we don't want to import _version.py,
161 # so we do it with a regexp instead. This function is not used from
163 keywords
: Dict
[str, str] = {}
165 with
open(versionfile_abs
, "r") as fobj
:
167 if line
.strip().startswith("git_refnames ="):
168 mo
= re
.search(r
'=\s*"(.*)"', line
)
170 keywords
["refnames"] = mo
.group(1)
171 if line
.strip().startswith("git_full ="):
172 mo
= re
.search(r
'=\s*"(.*)"', line
)
174 keywords
["full"] = mo
.group(1)
175 if line
.strip().startswith("git_date ="):
176 mo
= re
.search(r
'=\s*"(.*)"', line
)
178 keywords
["date"] = mo
.group(1)
184 @register_vcs_handler("git", "keywords")
185 def git_versions_from_keywords(
186 keywords
: Dict
[str, str],
190 """Get version information from git keywords."""
191 if "refnames" not in keywords
:
192 raise NotThisMethod("Short version file found")
193 date
= keywords
.get("date")
195 # Use only the last line. Previous lines may contain GPG signature
197 date
= date
.splitlines()[-1]
199 # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
200 # datestamp. However we prefer "%ci" (which expands to an "ISO-8601
201 # -like" string, which we must then edit to make compliant), because
202 # it's been around since git-1.5.3, and it's too difficult to
203 # discover which version we're using, or to work around using an
205 date
= date
.strip().replace(" ", "T", 1).replace(" ", "", 1)
206 refnames
= keywords
["refnames"].strip()
207 if refnames
.startswith("$Format"):
209 print("keywords are unexpanded, not using")
210 raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
211 refs
= {r
.strip() for r
in refnames
.strip("()").split(",")}
212 # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
213 # just "foo-1.0". If we see a "tag: " prefix, prefer those.
215 tags
= {r
[len(TAG
):] for r
in refs
if r
.startswith(TAG
)}
217 # Either we're using git < 1.8.3, or there really are no tags. We use
218 # a heuristic: assume all version tags have a digit. The old git %d
219 # expansion behaves like git log --decorate=short and strips out the
220 # refs/heads/ and refs/tags/ prefixes that would let us distinguish
221 # between branches and tags. By ignoring refnames without digits, we
222 # filter out many common branch names like "release" and
223 # "stabilization", as well as "HEAD" and "master".
224 tags
= {r
for r
in refs
if re
.search(r
'\d', r
)}
226 print("discarding '%s', no digits" % ",".join(refs
- tags
))
228 print("likely tags: %s" % ",".join(sorted(tags
)))
229 for ref
in sorted(tags
):
230 # sorting will prefer e.g. "2.0" over "2.0rc1"
231 if ref
.startswith(tag_prefix
):
232 r
= ref
[len(tag_prefix
):]
233 # Filter out refs that exactly match prefix or that don't start
234 # with a number once the prefix is stripped (mostly a concern
236 if not re
.match(r
'\d', r
):
239 print("picking %s" % r
)
240 return {"version": r
,
241 "full-revisionid": keywords
["full"].strip(),
242 "dirty": False, "error": None,
244 # no suitable tags, so version is "0+unknown", but full hex is still there
246 print("no suitable tags, using unknown + full revision id")
247 return {"version": "0+unknown",
248 "full-revisionid": keywords
["full"].strip(),
249 "dirty": False, "error": "no suitable tags", "date": None}
252 @register_vcs_handler("git", "pieces_from_vcs")
253 def git_pieces_from_vcs(
257 runner
: Callable
= run_command
259 """Get version from 'git describe' in the root of the source tree.
261 This only gets called if the git-archive 'subst' keywords were *not*
262 expanded, and _version.py hasn't already been rewritten with a short
263 version string, meaning we're inside a checked out source tree.
266 if sys
.platform
== "win32":
267 GITS
= ["git.cmd", "git.exe"]
269 # GIT_DIR can interfere with correct operation of Versioneer.
270 # It may be intended to be passed to the Versioneer-versioned project,
271 # but that should not change where we get our version from.
272 env
= os
.environ
.copy()
273 env
.pop("GIT_DIR", None)
274 runner
= functools
.partial(runner
, env
=env
)
276 _
, rc
= runner(GITS
, ["rev-parse", "--git-dir"], cwd
=root
,
277 hide_stderr
=not verbose
)
280 print("Directory %s not under git control" % root
)
281 raise NotThisMethod("'git rev-parse --git-dir' returned error")
283 # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
284 # if there isn't one, this yields HEX[-dirty] (no NUM)
285 describe_out
, rc
= runner(GITS
, [
286 "describe", "--tags", "--dirty", "--always", "--long",
287 "--match", f
"{tag_prefix}[[:digit:]]*"
289 # --long was added in git-1.5.5
290 if describe_out
is None:
291 raise NotThisMethod("'git describe' failed")
292 describe_out
= describe_out
.strip()
293 full_out
, rc
= runner(GITS
, ["rev-parse", "HEAD"], cwd
=root
)
295 raise NotThisMethod("'git rev-parse' failed")
296 full_out
= full_out
.strip()
298 pieces
: Dict
[str, Any
] = {}
299 pieces
["long"] = full_out
300 pieces
["short"] = full_out
[:7] # maybe improved later
301 pieces
["error"] = None
303 branch_name
, rc
= runner(GITS
, ["rev-parse", "--abbrev-ref", "HEAD"],
305 # --abbrev-ref was added in git-1.6.3
306 if rc
!= 0 or branch_name
is None:
307 raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
308 branch_name
= branch_name
.strip()
310 if branch_name
== "HEAD":
311 # If we aren't exactly on a branch, pick a branch which represents
312 # the current commit. If all else fails, we are on a branchless
314 branches
, rc
= runner(GITS
, ["branch", "--contains"], cwd
=root
)
315 # --contains was added in git-1.5.4
316 if rc
!= 0 or branches
is None:
317 raise NotThisMethod("'git branch --contains' returned error")
318 branches
= branches
.split("\n")
320 # Remove the first line if we're running detached
321 if "(" in branches
[0]:
324 # Strip off the leading "* " from the list of branches.
325 branches
= [branch
[2:] for branch
in branches
]
326 if "master" in branches
:
327 branch_name
= "master"
331 # Pick the first branch that is returned. Good or bad.
332 branch_name
= branches
[0]
334 pieces
["branch"] = branch_name
336 # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
337 # TAG might have hyphens.
338 git_describe
= describe_out
340 # look for -dirty suffix
341 dirty
= git_describe
.endswith("-dirty")
342 pieces
["dirty"] = dirty
344 git_describe
= git_describe
[:git_describe
.rindex("-dirty")]
346 # now we have TAG-NUM-gHEX or HEX
348 if "-" in git_describe
:
350 mo
= re
.search(r
'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe
)
352 # unparsable. Maybe git-describe is misbehaving?
353 pieces
["error"] = ("unable to parse git-describe output: '%s'"
358 full_tag
= mo
.group(1)
359 if not full_tag
.startswith(tag_prefix
):
361 fmt
= "tag '%s' doesn't start with prefix '%s'"
362 print(fmt
% (full_tag
, tag_prefix
))
363 pieces
["error"] = ("tag '%s' doesn't start with prefix '%s'"
364 % (full_tag
, tag_prefix
))
366 pieces
["closest-tag"] = full_tag
[len(tag_prefix
):]
368 # distance: number of commits since tag
369 pieces
["distance"] = int(mo
.group(2))
371 # commit: short hex revision ID
372 pieces
["short"] = mo
.group(3)
376 pieces
["closest-tag"] = None
377 out
, rc
= runner(GITS
, ["rev-list", "HEAD", "--left-right"], cwd
=root
)
378 pieces
["distance"] = len(out
.split()) # total number of commits
380 # commit date: see ISO-8601 comment in git_versions_from_keywords()
381 date
= runner(GITS
, ["show", "-s", "--format=%ci", "HEAD"], cwd
=root
)[0].strip()
382 # Use only the last line. Previous lines may contain GPG signature
384 date
= date
.splitlines()[-1]
385 pieces
["date"] = date
.strip().replace(" ", "T", 1).replace(" ", "", 1)
390 def plus_or_dot(pieces
: Dict
[str, Any
]) -> str:
391 """Return a + if we don't already have one, else return a ."""
392 if "+" in pieces
.get("closest-tag", ""):
397 def render_pep440(pieces
: Dict
[str, Any
]) -> str:
398 """Build up version string, with post-release "local version identifier".
400 Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
401 get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
404 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
406 if pieces
["closest-tag"]:
407 rendered
= pieces
["closest-tag"]
408 if pieces
["distance"] or pieces
["dirty"]:
409 rendered
+= plus_or_dot(pieces
)
410 rendered
+= "%d.g%s" % (pieces
["distance"], pieces
["short"])
415 rendered
= "0+untagged.%d.g%s" % (pieces
["distance"],
422 def render_pep440_branch(pieces
: Dict
[str, Any
]) -> str:
423 """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
425 The ".dev0" means not master branch. Note that .dev0 sorts backwards
426 (a feature branch will appear "older" than the master branch).
429 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
431 if pieces
["closest-tag"]:
432 rendered
= pieces
["closest-tag"]
433 if pieces
["distance"] or pieces
["dirty"]:
434 if pieces
["branch"] != "master":
436 rendered
+= plus_or_dot(pieces
)
437 rendered
+= "%d.g%s" % (pieces
["distance"], pieces
["short"])
443 if pieces
["branch"] != "master":
445 rendered
+= "+untagged.%d.g%s" % (pieces
["distance"],
452 def pep440_split_post(ver
: str) -> Tuple
[str, Optional
[int]]:
453 """Split pep440 version string at the post-release segment.
455 Returns the release segments before the post-release and the
456 post-release version number (or -1 if no post-release segment is present).
458 vc
= str.split(ver
, ".post")
459 return vc
[0], int(vc
[1] or 0) if len(vc
) == 2 else None
462 def render_pep440_pre(pieces
: Dict
[str, Any
]) -> str:
463 """TAG[.postN.devDISTANCE] -- No -dirty.
466 1: no tags. 0.post0.devDISTANCE
468 if pieces
["closest-tag"]:
469 if pieces
["distance"]:
470 # update the post release segment
471 tag_version
, post_version
= pep440_split_post(pieces
["closest-tag"])
472 rendered
= tag_version
473 if post_version
is not None:
474 rendered
+= ".post%d.dev%d" % (post_version
+ 1, pieces
["distance"])
476 rendered
+= ".post0.dev%d" % (pieces
["distance"])
478 # no commits, use the tag as the version
479 rendered
= pieces
["closest-tag"]
482 rendered
= "0.post0.dev%d" % pieces
["distance"]
486 def render_pep440_post(pieces
: Dict
[str, Any
]) -> str:
487 """TAG[.postDISTANCE[.dev0]+gHEX] .
489 The ".dev0" means dirty. Note that .dev0 sorts backwards
490 (a dirty tree will appear "older" than the corresponding clean one),
491 but you shouldn't be releasing software with -dirty anyways.
494 1: no tags. 0.postDISTANCE[.dev0]
496 if pieces
["closest-tag"]:
497 rendered
= pieces
["closest-tag"]
498 if pieces
["distance"] or pieces
["dirty"]:
499 rendered
+= ".post%d" % pieces
["distance"]
502 rendered
+= plus_or_dot(pieces
)
503 rendered
+= "g%s" % pieces
["short"]
506 rendered
= "0.post%d" % pieces
["distance"]
509 rendered
+= "+g%s" % pieces
["short"]
513 def render_pep440_post_branch(pieces
: Dict
[str, Any
]) -> str:
514 """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
516 The ".dev0" means not master branch.
519 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
521 if pieces
["closest-tag"]:
522 rendered
= pieces
["closest-tag"]
523 if pieces
["distance"] or pieces
["dirty"]:
524 rendered
+= ".post%d" % pieces
["distance"]
525 if pieces
["branch"] != "master":
527 rendered
+= plus_or_dot(pieces
)
528 rendered
+= "g%s" % pieces
["short"]
533 rendered
= "0.post%d" % pieces
["distance"]
534 if pieces
["branch"] != "master":
536 rendered
+= "+g%s" % pieces
["short"]
542 def render_pep440_old(pieces
: Dict
[str, Any
]) -> str:
543 """TAG[.postDISTANCE[.dev0]] .
545 The ".dev0" means dirty.
548 1: no tags. 0.postDISTANCE[.dev0]
550 if pieces
["closest-tag"]:
551 rendered
= pieces
["closest-tag"]
552 if pieces
["distance"] or pieces
["dirty"]:
553 rendered
+= ".post%d" % pieces
["distance"]
558 rendered
= "0.post%d" % pieces
["distance"]
564 def render_git_describe(pieces
: Dict
[str, Any
]) -> str:
565 """TAG[-DISTANCE-gHEX][-dirty].
567 Like 'git describe --tags --dirty --always'.
570 1: no tags. HEX[-dirty] (note: no 'g' prefix)
572 if pieces
["closest-tag"]:
573 rendered
= pieces
["closest-tag"]
574 if pieces
["distance"]:
575 rendered
+= "-%d-g%s" % (pieces
["distance"], pieces
["short"])
578 rendered
= pieces
["short"]
584 def render_git_describe_long(pieces
: Dict
[str, Any
]) -> str:
585 """TAG-DISTANCE-gHEX[-dirty].
587 Like 'git describe --tags --dirty --always -long'.
588 The distance/hash is unconditional.
591 1: no tags. HEX[-dirty] (note: no 'g' prefix)
593 if pieces
["closest-tag"]:
594 rendered
= pieces
["closest-tag"]
595 rendered
+= "-%d-g%s" % (pieces
["distance"], pieces
["short"])
598 rendered
= pieces
["short"]
604 def render(pieces
: Dict
[str, Any
], style
: str) -> Dict
[str, Any
]:
605 """Render the given version pieces into the requested style."""
607 return {"version": "unknown",
608 "full-revisionid": pieces
.get("long"),
610 "error": pieces
["error"],
613 if not style
or style
== "default":
614 style
= "pep440" # the default
616 if style
== "pep440":
617 rendered
= render_pep440(pieces
)
618 elif style
== "pep440-branch":
619 rendered
= render_pep440_branch(pieces
)
620 elif style
== "pep440-pre":
621 rendered
= render_pep440_pre(pieces
)
622 elif style
== "pep440-post":
623 rendered
= render_pep440_post(pieces
)
624 elif style
== "pep440-post-branch":
625 rendered
= render_pep440_post_branch(pieces
)
626 elif style
== "pep440-old":
627 rendered
= render_pep440_old(pieces
)
628 elif style
== "git-describe":
629 rendered
= render_git_describe(pieces
)
630 elif style
== "git-describe-long":
631 rendered
= render_git_describe_long(pieces
)
633 raise ValueError("unknown style '%s'" % style
)
635 return {"version": rendered
, "full-revisionid": pieces
["long"],
636 "dirty": pieces
["dirty"], "error": None,
637 "date": pieces
.get("date")}
640 def get_versions() -> Dict
[str, Any
]:
641 """Get version information or return default if unable to do so."""
642 # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
643 # __file__, we can work backwards from there to the root. Some
644 # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which
645 # case we can only use expanded keywords.
648 verbose
= cfg
.verbose
651 return git_versions_from_keywords(get_keywords(), cfg
.tag_prefix
,
653 except NotThisMethod
:
657 root
= os
.path
.realpath(__file__
)
658 # versionfile_source is the relative path from the top of the source
659 # tree (where the .git directory might live) to this file. Invert
660 # this to find the root from __file__.
661 for _
in cfg
.versionfile_source
.split('/'):
662 root
= os
.path
.dirname(root
)
664 return {"version": "0+unknown", "full-revisionid": None,
666 "error": "unable to find root of source tree",
670 pieces
= git_pieces_from_vcs(cfg
.tag_prefix
, root
, verbose
)
671 return render(pieces
, cfg
.style
)
672 except NotThisMethod
:
676 if cfg
.parentdir_prefix
:
677 return versions_from_parentdir(cfg
.parentdir_prefix
, root
, verbose
)
678 except NotThisMethod
:
681 return {"version": "0+unknown", "full-revisionid": None,
683 "error": "unable to compute version", "date": None}