]> git.madduck.net Git - etc/vim.git/blob - _version.py

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Fix unstable formatting involving unwrapping multiple parentheses (#836) (#961)
[etc/vim.git] / _version.py
1 # This file helps to compute a version number in source trees obtained from
2 # git-archive tarball (such as those provided by githubs download-from-tag
3 # feature). Distribution tarballs (built by setup.py sdist) and build
4 # directories (produced by setup.py build) will contain a much shorter file
5 # that just contains the computed version number.
6
7 # This file is released into the public domain. Generated by
8 # versioneer-0.18 (https://github.com/warner/python-versioneer)
9
10 """Git implementation of _version.py."""
11
12 import errno
13 import os
14 import re
15 import subprocess
16 import sys
17
18
19 def get_keywords():
20     """Get the keywords needed to look up the version information."""
21     # these strings will be replaced by git during git-archive.
22     # setup.py/versioneer.py will grep for the variable names, so they must
23     # each be defined on a line of their own. _version.py will just call
24     # get_keywords().
25     git_refnames = "$Format:%d$"
26     git_full = "$Format:%H$"
27     git_date = "$Format:%ci$"
28     keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
29     return keywords
30
31
32 class VersioneerConfig:
33     """Container for Versioneer configuration parameters."""
34
35
36 def get_config():
37     """Create, populate and return the VersioneerConfig() object."""
38     # these strings are filled in when 'setup.py versioneer' creates
39     # _version.py
40     cfg = VersioneerConfig()
41     cfg.VCS = "git"
42     cfg.style = "pep440"
43     cfg.tag_prefix = ""
44     cfg.parentdir_prefix = "None"
45     cfg.versionfile_source = "_version.py"
46     cfg.verbose = False
47     return cfg
48
49
50 class NotThisMethod(Exception):
51     """Exception raised if a method is not valid for the current scenario."""
52
53
54 LONG_VERSION_PY = {}
55 HANDLERS = {}
56
57
58 def register_vcs_handler(vcs, method):  # decorator
59     """Decorator to mark a method as the handler for a particular VCS."""
60
61     def decorate(f):
62         """Store f in HANDLERS[vcs][method]."""
63         if vcs not in HANDLERS:
64             HANDLERS[vcs] = {}
65         HANDLERS[vcs][method] = f
66         return f
67
68     return decorate
69
70
71 def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None):
72     """Call the given command(s)."""
73     assert isinstance(commands, list)
74     p = None
75     for c in commands:
76         try:
77             dispcmd = str([c] + args)
78             # remember shell=False, so use git.cmd on windows, not just git
79             p = subprocess.Popen(
80                 [c] + args,
81                 cwd=cwd,
82                 env=env,
83                 stdout=subprocess.PIPE,
84                 stderr=(subprocess.PIPE if hide_stderr else None),
85             )
86             break
87         except EnvironmentError:
88             e = sys.exc_info()[1]
89             if e.errno == errno.ENOENT:
90                 continue
91             if verbose:
92                 print("unable to run %s" % dispcmd)
93                 print(e)
94             return None, None
95     else:
96         if verbose:
97             print("unable to find command, tried %s" % (commands,))
98         return None, None
99     stdout = p.communicate()[0].strip()
100     if sys.version_info[0] >= 3:
101         stdout = stdout.decode()
102     if p.returncode != 0:
103         if verbose:
104             print("unable to run %s (error)" % dispcmd)
105             print("stdout was %s" % stdout)
106         return None, p.returncode
107     return stdout, p.returncode
108
109
110 def versions_from_parentdir(parentdir_prefix, root, verbose):
111     """Try to determine the version from the parent directory name.
112
113     Source tarballs conventionally unpack into a directory that includes both
114     the project name and a version string. We will also support searching up
115     two directory levels for an appropriately named parent directory
116     """
117     rootdirs = []
118
119     for i in range(3):
120         dirname = os.path.basename(root)
121         if dirname.startswith(parentdir_prefix):
122             return {
123                 "version": dirname[len(parentdir_prefix) :],
124                 "full-revisionid": None,
125                 "dirty": False,
126                 "error": None,
127                 "date": None,
128             }
129         else:
130             rootdirs.append(root)
131             root = os.path.dirname(root)  # up a level
132
133     if verbose:
134         print(
135             "Tried directories %s but none started with prefix %s"
136             % (str(rootdirs), parentdir_prefix)
137         )
138     raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
139
140
141 @register_vcs_handler("git", "get_keywords")
142 def git_get_keywords(versionfile_abs):
143     """Extract version information from the given file."""
144     # the code embedded in _version.py can just fetch the value of these
145     # keywords. When used from setup.py, we don't want to import _version.py,
146     # so we do it with a regexp instead. This function is not used from
147     # _version.py.
148     keywords = {}
149     try:
150         f = open(versionfile_abs, "r")
151         for line in f.readlines():
152             if line.strip().startswith("git_refnames ="):
153                 mo = re.search(r'=\s*"(.*)"', line)
154                 if mo:
155                     keywords["refnames"] = mo.group(1)
156             if line.strip().startswith("git_full ="):
157                 mo = re.search(r'=\s*"(.*)"', line)
158                 if mo:
159                     keywords["full"] = mo.group(1)
160             if line.strip().startswith("git_date ="):
161                 mo = re.search(r'=\s*"(.*)"', line)
162                 if mo:
163                     keywords["date"] = mo.group(1)
164         f.close()
165     except EnvironmentError:
166         pass
167     return keywords
168
169
170 @register_vcs_handler("git", "keywords")
171 def git_versions_from_keywords(keywords, tag_prefix, verbose):
172     """Get version information from git keywords."""
173     if not keywords:
174         raise NotThisMethod("no keywords at all, weird")
175     date = keywords.get("date")
176     if date is not None:
177         # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
178         # datestamp. However we prefer "%ci" (which expands to an "ISO-8601
179         # -like" string, which we must then edit to make compliant), because
180         # it's been around since git-1.5.3, and it's too difficult to
181         # discover which version we're using, or to work around using an
182         # older one.
183         date = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
184     refnames = keywords["refnames"].strip()
185     if refnames.startswith("$Format"):
186         if verbose:
187             print("keywords are unexpanded, not using")
188         raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
189     refs = set([r.strip() for r in refnames.strip("()").split(",")])
190     # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
191     # just "foo-1.0". If we see a "tag: " prefix, prefer those.
192     TAG = "tag: "
193     tags = set([r[len(TAG) :] for r in refs if r.startswith(TAG)])
194     if not tags:
195         # Either we're using git < 1.8.3, or there really are no tags. We use
196         # a heuristic: assume all version tags have a digit. The old git %d
197         # expansion behaves like git log --decorate=short and strips out the
198         # refs/heads/ and refs/tags/ prefixes that would let us distinguish
199         # between branches and tags. By ignoring refnames without digits, we
200         # filter out many common branch names like "release" and
201         # "stabilization", as well as "HEAD" and "master".
202         tags = set([r for r in refs if re.search(r"\d", r)])
203         if verbose:
204             print("discarding '%s', no digits" % ",".join(refs - tags))
205     if verbose:
206         print("likely tags: %s" % ",".join(sorted(tags)))
207     for ref in sorted(tags):
208         # sorting will prefer e.g. "2.0" over "2.0rc1"
209         if ref.startswith(tag_prefix):
210             r = ref[len(tag_prefix) :]
211             if verbose:
212                 print("picking %s" % r)
213             return {
214                 "version": r,
215                 "full-revisionid": keywords["full"].strip(),
216                 "dirty": False,
217                 "error": None,
218                 "date": date,
219             }
220     # no suitable tags, so version is "0+unknown", but full hex is still there
221     if verbose:
222         print("no suitable tags, using unknown + full revision id")
223     return {
224         "version": "0+unknown",
225         "full-revisionid": keywords["full"].strip(),
226         "dirty": False,
227         "error": "no suitable tags",
228         "date": None,
229     }
230
231
232 @register_vcs_handler("git", "pieces_from_vcs")
233 def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
234     """Get version from 'git describe' in the root of the source tree.
235
236     This only gets called if the git-archive 'subst' keywords were *not*
237     expanded, and _version.py hasn't already been rewritten with a short
238     version string, meaning we're inside a checked out source tree.
239     """
240     GITS = ["git"]
241     if sys.platform == "win32":
242         GITS = ["git.cmd", "git.exe"]
243
244     out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=True)
245     if rc != 0:
246         if verbose:
247             print("Directory %s not under git control" % root)
248         raise NotThisMethod("'git rev-parse --git-dir' returned error")
249
250     # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
251     # if there isn't one, this yields HEX[-dirty] (no NUM)
252     describe_out, rc = run_command(
253         GITS,
254         [
255             "describe",
256             "--tags",
257             "--dirty",
258             "--always",
259             "--long",
260             "--match",
261             "%s*" % tag_prefix,
262         ],
263         cwd=root,
264     )
265     # --long was added in git-1.5.5
266     if describe_out is None:
267         raise NotThisMethod("'git describe' failed")
268     describe_out = describe_out.strip()
269     full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
270     if full_out is None:
271         raise NotThisMethod("'git rev-parse' failed")
272     full_out = full_out.strip()
273
274     pieces = {}
275     pieces["long"] = full_out
276     pieces["short"] = full_out[:7]  # maybe improved later
277     pieces["error"] = None
278
279     # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
280     # TAG might have hyphens.
281     git_describe = describe_out
282
283     # look for -dirty suffix
284     dirty = git_describe.endswith("-dirty")
285     pieces["dirty"] = dirty
286     if dirty:
287         git_describe = git_describe[: git_describe.rindex("-dirty")]
288
289     # now we have TAG-NUM-gHEX or HEX
290
291     if "-" in git_describe:
292         # TAG-NUM-gHEX
293         mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe)
294         if not mo:
295             # unparseable. Maybe git-describe is misbehaving?
296             pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out
297             return pieces
298
299         # tag
300         full_tag = mo.group(1)
301         if not full_tag.startswith(tag_prefix):
302             if verbose:
303                 fmt = "tag '%s' doesn't start with prefix '%s'"
304                 print(fmt % (full_tag, tag_prefix))
305             pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % (
306                 full_tag,
307                 tag_prefix,
308             )
309             return pieces
310         pieces["closest-tag"] = full_tag[len(tag_prefix) :]
311
312         # distance: number of commits since tag
313         pieces["distance"] = int(mo.group(2))
314
315         # commit: short hex revision ID
316         pieces["short"] = mo.group(3)
317
318     else:
319         # HEX: no tags
320         pieces["closest-tag"] = None
321         count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], cwd=root)
322         pieces["distance"] = int(count_out)  # total number of commits
323
324     # commit date: see ISO-8601 comment in git_versions_from_keywords()
325     date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[
326         0
327     ].strip()
328     pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
329
330     return pieces
331
332
333 def plus_or_dot(pieces):
334     """Return a + if we don't already have one, else return a ."""
335     if "+" in pieces.get("closest-tag", ""):
336         return "."
337     return "+"
338
339
340 def render_pep440(pieces):
341     """Build up version string, with post-release "local version identifier".
342
343     Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
344     get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
345
346     Exceptions:
347     1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
348     """
349     if pieces["closest-tag"]:
350         rendered = pieces["closest-tag"]
351         if pieces["distance"] or pieces["dirty"]:
352             rendered += plus_or_dot(pieces)
353             rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
354             if pieces["dirty"]:
355                 rendered += ".dirty"
356     else:
357         # exception #1
358         rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
359         if pieces["dirty"]:
360             rendered += ".dirty"
361     return rendered
362
363
364 def render_pep440_pre(pieces):
365     """TAG[.post.devDISTANCE] -- No -dirty.
366
367     Exceptions:
368     1: no tags. 0.post.devDISTANCE
369     """
370     if pieces["closest-tag"]:
371         rendered = pieces["closest-tag"]
372         if pieces["distance"]:
373             rendered += ".post.dev%d" % pieces["distance"]
374     else:
375         # exception #1
376         rendered = "0.post.dev%d" % pieces["distance"]
377     return rendered
378
379
380 def render_pep440_post(pieces):
381     """TAG[.postDISTANCE[.dev0]+gHEX] .
382
383     The ".dev0" means dirty. Note that .dev0 sorts backwards
384     (a dirty tree will appear "older" than the corresponding clean one),
385     but you shouldn't be releasing software with -dirty anyways.
386
387     Exceptions:
388     1: no tags. 0.postDISTANCE[.dev0]
389     """
390     if pieces["closest-tag"]:
391         rendered = pieces["closest-tag"]
392         if pieces["distance"] or pieces["dirty"]:
393             rendered += ".post%d" % pieces["distance"]
394             if pieces["dirty"]:
395                 rendered += ".dev0"
396             rendered += plus_or_dot(pieces)
397             rendered += "g%s" % pieces["short"]
398     else:
399         # exception #1
400         rendered = "0.post%d" % pieces["distance"]
401         if pieces["dirty"]:
402             rendered += ".dev0"
403         rendered += "+g%s" % pieces["short"]
404     return rendered
405
406
407 def render_pep440_old(pieces):
408     """TAG[.postDISTANCE[.dev0]] .
409
410     The ".dev0" means dirty.
411
412     Eexceptions:
413     1: no tags. 0.postDISTANCE[.dev0]
414     """
415     if pieces["closest-tag"]:
416         rendered = pieces["closest-tag"]
417         if pieces["distance"] or pieces["dirty"]:
418             rendered += ".post%d" % pieces["distance"]
419             if pieces["dirty"]:
420                 rendered += ".dev0"
421     else:
422         # exception #1
423         rendered = "0.post%d" % pieces["distance"]
424         if pieces["dirty"]:
425             rendered += ".dev0"
426     return rendered
427
428
429 def render_git_describe(pieces):
430     """TAG[-DISTANCE-gHEX][-dirty].
431
432     Like 'git describe --tags --dirty --always'.
433
434     Exceptions:
435     1: no tags. HEX[-dirty]  (note: no 'g' prefix)
436     """
437     if pieces["closest-tag"]:
438         rendered = pieces["closest-tag"]
439         if pieces["distance"]:
440             rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
441     else:
442         # exception #1
443         rendered = pieces["short"]
444     if pieces["dirty"]:
445         rendered += "-dirty"
446     return rendered
447
448
449 def render_git_describe_long(pieces):
450     """TAG-DISTANCE-gHEX[-dirty].
451
452     Like 'git describe --tags --dirty --always -long'.
453     The distance/hash is unconditional.
454
455     Exceptions:
456     1: no tags. HEX[-dirty]  (note: no 'g' prefix)
457     """
458     if pieces["closest-tag"]:
459         rendered = pieces["closest-tag"]
460         rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
461     else:
462         # exception #1
463         rendered = pieces["short"]
464     if pieces["dirty"]:
465         rendered += "-dirty"
466     return rendered
467
468
469 def render(pieces, style):
470     """Render the given version pieces into the requested style."""
471     if pieces["error"]:
472         return {
473             "version": "unknown",
474             "full-revisionid": pieces.get("long"),
475             "dirty": None,
476             "error": pieces["error"],
477             "date": None,
478         }
479
480     if not style or style == "default":
481         style = "pep440"  # the default
482
483     if style == "pep440":
484         rendered = render_pep440(pieces)
485     elif style == "pep440-pre":
486         rendered = render_pep440_pre(pieces)
487     elif style == "pep440-post":
488         rendered = render_pep440_post(pieces)
489     elif style == "pep440-old":
490         rendered = render_pep440_old(pieces)
491     elif style == "git-describe":
492         rendered = render_git_describe(pieces)
493     elif style == "git-describe-long":
494         rendered = render_git_describe_long(pieces)
495     else:
496         raise ValueError("unknown style '%s'" % style)
497
498     return {
499         "version": rendered,
500         "full-revisionid": pieces["long"],
501         "dirty": pieces["dirty"],
502         "error": None,
503         "date": pieces.get("date"),
504     }
505
506
507 def get_versions():
508     """Get version information or return default if unable to do so."""
509     # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
510     # __file__, we can work backwards from there to the root. Some
511     # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which
512     # case we can only use expanded keywords.
513
514     cfg = get_config()
515     verbose = cfg.verbose
516
517     try:
518         return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose)
519     except NotThisMethod:
520         pass
521
522     try:
523         root = os.path.realpath(__file__)
524         # versionfile_source is the relative path from the top of the source
525         # tree (where the .git directory might live) to this file. Invert
526         # this to find the root from __file__.
527         for i in cfg.versionfile_source.split("/"):
528             root = os.path.dirname(root)
529     except NameError:
530         return {
531             "version": "0+unknown",
532             "full-revisionid": None,
533             "dirty": None,
534             "error": "unable to find root of source tree",
535             "date": None,
536         }
537
538     try:
539         pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
540         return render(pieces, cfg.style)
541     except NotThisMethod:
542         pass
543
544     try:
545         if cfg.parentdir_prefix:
546             return versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
547     except NotThisMethod:
548         pass
549
550     return {
551         "version": "0+unknown",
552         "full-revisionid": None,
553         "dirty": None,
554         "error": "unable to compute version",
555         "date": None,
556     }