1

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

8
# This file is released into the public domain. Generated by
9 33
# versioneer-0.18 (https://github.com/warner/python-versioneer)
10

11
"""Git implementation of _version.py."""
12

13
import errno
14
import os
15
import re
16
import subprocess
17
import sys
18
import runpy
19

20 33

21 33
def get_keywords():
22
    """Get the keywords needed to look up the version information."""
23
    # these strings will be replaced by git during git-archive.
24
    # setup.py/versioneer.py will grep for the variable names, so they must
25
    # each be defined on a line of their own. _version.py will just call
26
    # get_keywords().
27
    git_refnames = "$Format:%d$"
28
    git_full = "$Format:%H$"
29
    git_date = "$Format:%ci$"
30
    keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
31
    return keywords
32

33

34
class VersioneerConfig:
35
    """Container for Versioneer configuration parameters."""
36

37

38
def get_config():
39
    """Create, populate and return the VersioneerConfig() object."""
40
    # these strings are filled in when 'setup.py versioneer' creates
41
    # _version.py
42
    cfg = VersioneerConfig()
43
    cfg.VCS = "git"
44
    cfg.style = "pep440"
45
    cfg.tag_prefix = ""
46
    cfg.parentdir_prefix = ""
47
    cfg.versionfile_source = "nibabel/_version.py"
48
    cfg.verbose = False
49
    return cfg
50

51

52
class NotThisMethod(Exception):
53
    """Exception raised if a method is not valid for the current scenario."""
54

55

56
LONG_VERSION_PY = {}
57
HANDLERS = {}
58

59

60
def register_vcs_handler(vcs, method):  # decorator
61
    """Decorator to mark a method as the handler for a particular VCS."""
62
    def decorate(f):
63
        """Store f in HANDLERS[vcs][method]."""
64
        if vcs not in HANDLERS:
65
            HANDLERS[vcs] = {}
66
        HANDLERS[vcs][method] = f
67
        return f
68
    return decorate
69

70

71
def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
72
                env=None):
73
    """Call the given command(s)."""
74
    assert isinstance(commands, list)
75
    p = None
76
    for c in commands:
77
        try:
78
            dispcmd = str([c] + args)
79
            # remember shell=False, so use git.cmd on windows, not just git
80
            p = subprocess.Popen([c] + args, cwd=cwd, env=env,
81
                                 stdout=subprocess.PIPE,
82
                                 stderr=(subprocess.PIPE if hide_stderr
83
                                         else None))
84
            break
85
        except EnvironmentError:
86
            e = sys.exc_info()[1]
87
            if e.errno == errno.ENOENT:
88
                continue
89
            if verbose:
90
                print("unable to run %s" % dispcmd)
91
                print(e)
92
            return None, None
93
    else:
94
        if verbose:
95
            print("unable to find command, tried %s" % (commands,))
96
        return None, None
97
    stdout = p.communicate()[0].strip()
98
    if sys.version_info[0] >= 3:
99
        stdout = stdout.decode()
100
    if p.returncode != 0:
101
        if verbose:
102
            print("unable to run %s (error)" % dispcmd)
103
            print("stdout was %s" % stdout)
104
        return None, p.returncode
105
    return stdout, p.returncode
106

107

108
def versions_from_parentdir(parentdir_prefix, root, verbose):
109
    """Try to determine the version from the parent directory name.
110

111
    Source tarballs conventionally unpack into a directory that includes both
112
    the project name and a version string. We will also support searching up
113
    two directory levels for an appropriately named parent directory
114
    """
115
    rootdirs = []
116

117
    for i in range(3):
118
        dirname = os.path.basename(root)
119
        if dirname.startswith(parentdir_prefix):
120
            return {"version": dirname[len(parentdir_prefix):],
121
                    "full-revisionid": None,
122
                    "dirty": False, "error": None, "date": None}
123
        else:
124
            rootdirs.append(root)
125
            root = os.path.dirname(root)  # up a level
126

127
    if verbose:
128
        print("Tried directories %s but none started with prefix %s" %
129
              (str(rootdirs), parentdir_prefix))
130
    raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
131

132

133
@register_vcs_handler("git", "get_keywords")
134
def git_get_keywords(versionfile_abs):
135
    """Extract version information from the given file."""
136
    # the code embedded in _version.py can just fetch the value of these
137
    # keywords. When used from setup.py, we don't want to import _version.py,
138
    # so we do it with a regexp instead. This function is not used from
139
    # _version.py.
140
    keywords = {}
141
    try:
142
        f = open(versionfile_abs, "r")
143
        for line in f.readlines():
144
            if line.strip().startswith("git_refnames ="):
145
                mo = re.search(r'=\s*"(.*)"', line)
146
                if mo:
147
                    keywords["refnames"] = mo.group(1)
148
            if line.strip().startswith("git_full ="):
149
                mo = re.search(r'=\s*"(.*)"', line)
150
                if mo:
151
                    keywords["full"] = mo.group(1)
152
            if line.strip().startswith("git_date ="):
153
                mo = re.search(r'=\s*"(.*)"', line)
154
                if mo:
155
                    keywords["date"] = mo.group(1)
156
        f.close()
157
    except EnvironmentError:
158
        pass
159
    # CJM: Nibabel hack to ensure we can git-archive off-release versions and
160
    # revert to old X.Y.Zdev versions + githash
161
    try:
162
        rel = runpy.run_path(os.path.join(os.path.dirname(versionfile_abs), "info.py"))
163
        keywords["fallback"] = rel["VERSION"]
164
    except (FileNotFoundError, KeyError):
165
        pass
166
    return keywords
167

168

169
@register_vcs_handler("git", "keywords")
170
def git_versions_from_keywords(keywords, tag_prefix, verbose):
171
    """Get version information from git keywords."""
172
    # CJM: Nibabel fix to avoid hitting unguarded dictionary lookup, better explanation
173
    if "refnames" not in keywords:
174
        raise NotThisMethod("Short version file found")
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
            # CJM: Nibabel fix to filter out refs that exactly match prefix
212
            # or that don't start with a number once the prefix is stripped
213
            # (Mostly a concern when prefix is '')
214
            if not re.match(r'\d', r):
215
                continue
216
            if verbose:
217
                print("picking %s" % r)
218
            return {"version": r,
219
                    "full-revisionid": keywords["full"].strip(),
220
                    "dirty": False, "error": None,
221
                    "date": date}
222
    # no suitable tags, so inspect ./info.py
223
    if verbose:
224
        print("no suitable tags, falling back to info.VERSION or 0+unknown")
225
    return {"version": keywords.get("fallback", "0+unknown"),
226
            "full-revisionid": keywords["full"].strip(),
227
            "dirty": False, "error": "no suitable tags", "date": None}
228

229

230
@register_vcs_handler("git", "pieces_from_vcs")
231
def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
232
    """Get version from 'git describe' in the root of the source tree.
233

234
    This only gets called if the git-archive 'subst' keywords were *not*
235
    expanded, and _version.py hasn't already been rewritten with a short
236
    version string, meaning we're inside a checked out source tree.
237
    """
238
    GITS = ["git"]
239
    if sys.platform == "win32":
240
        GITS = ["git.cmd", "git.exe"]
241

242
    out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root,
243
                          hide_stderr=True)
244
    if rc != 0:
245
        if verbose:
246
            print("Directory %s not under git control" % root)
247
        raise NotThisMethod("'git rev-parse --git-dir' returned error")
248

249
    # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
250
    # if there isn't one, this yields HEX[-dirty] (no NUM)
251
    describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
252
                                          "--always", "--long",
253
                                          "--match", "%s*" % tag_prefix],
254
                                   cwd=root)
255
    # --long was added in git-1.5.5
256
    if describe_out is None:
257
        raise NotThisMethod("'git describe' failed")
258
    describe_out = describe_out.strip()
259
    full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
260
    if full_out is None:
261
        raise NotThisMethod("'git rev-parse' failed")
262
    full_out = full_out.strip()
263

264
    pieces = {}
265
    pieces["long"] = full_out
266
    pieces["short"] = full_out[:7]  # maybe improved later
267
    pieces["error"] = None
268

269
    # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
270
    # TAG might have hyphens.
271
    git_describe = describe_out
272

273
    # look for -dirty suffix
274
    dirty = git_describe.endswith("-dirty")
275
    pieces["dirty"] = dirty
276
    if dirty:
277
        git_describe = git_describe[:git_describe.rindex("-dirty")]
278

279
    # now we have TAG-NUM-gHEX or HEX
280

281
    if "-" in git_describe:
282
        # TAG-NUM-gHEX
283
        mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
284
        if not mo:
285
            # unparseable. Maybe git-describe is misbehaving?
286
            pieces["error"] = ("unable to parse git-describe output: '%s'"
287
                               % describe_out)
288
            return pieces
289

290
        # tag
291
        full_tag = mo.group(1)
292
        if not full_tag.startswith(tag_prefix):
293
            if verbose:
294
                fmt = "tag '%s' doesn't start with prefix '%s'"
295
                print(fmt % (full_tag, tag_prefix))
296
            pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
297
                               % (full_tag, tag_prefix))
298
            return pieces
299
        pieces["closest-tag"] = full_tag[len(tag_prefix):]
300

301
        # distance: number of commits since tag
302
        pieces["distance"] = int(mo.group(2))
303

304
        # commit: short hex revision ID
305
        pieces["short"] = mo.group(3)
306

307
    else:
308
        # HEX: no tags
309
        pieces["closest-tag"] = None
310
        count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"],
311
                                    cwd=root)
312
        pieces["distance"] = int(count_out)  # total number of commits
313

314
    # commit date: see ISO-8601 comment in git_versions_from_keywords()
315
    date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"],
316
                       cwd=root)[0].strip()
317
    pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
318

319
    return pieces
320

321

322
def plus_or_dot(pieces):
323
    """Return a + if we don't already have one, else return a ."""
324
    if "+" in pieces.get("closest-tag", ""):
325
        return "."
326
    return "+"
327

328

329
def render_pep440(pieces):
330
    """Build up version string, with post-release "local version identifier".
331

332
    Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
333
    get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
334

335
    Exceptions:
336
    1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
337
    """
338
    if pieces["closest-tag"]:
339
        rendered = pieces["closest-tag"]
340
        if pieces["distance"] or pieces["dirty"]:
341
            rendered += plus_or_dot(pieces)
342
            rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
343
            if pieces["dirty"]:
344
                rendered += ".dirty"
345
    else:
346
        # exception #1
347
        rendered = "0+untagged.%d.g%s" % (pieces["distance"],
348
                                          pieces["short"])
349
        if pieces["dirty"]:
350
            rendered += ".dirty"
351
    return rendered
352

353

354
def render_pep440_pre(pieces):
355
    """TAG[.post.devDISTANCE] -- No -dirty.
356

357
    Exceptions:
358
    1: no tags. 0.post.devDISTANCE
359
    """
360
    if pieces["closest-tag"]:
361
        rendered = pieces["closest-tag"]
362
        if pieces["distance"]:
363
            rendered += ".post.dev%d" % pieces["distance"]
364
    else:
365
        # exception #1
366
        rendered = "0.post.dev%d" % pieces["distance"]
367
    return rendered
368

369

370
def render_pep440_post(pieces):
371
    """TAG[.postDISTANCE[.dev0]+gHEX] .
372

373
    The ".dev0" means dirty. Note that .dev0 sorts backwards
374
    (a dirty tree will appear "older" than the corresponding clean one),
375
    but you shouldn't be releasing software with -dirty anyways.
376

377
    Exceptions:
378
    1: no tags. 0.postDISTANCE[.dev0]
379
    """
380
    if pieces["closest-tag"]:
381
        rendered = pieces["closest-tag"]
382
        if pieces["distance"] or pieces["dirty"]:
383
            rendered += ".post%d" % pieces["distance"]
384
            if pieces["dirty"]:
385
                rendered += ".dev0"
386
            rendered += plus_or_dot(pieces)
387
            rendered += "g%s" % pieces["short"]
388
    else:
389
        # exception #1
390
        rendered = "0.post%d" % pieces["distance"]
391
        if pieces["dirty"]:
392
            rendered += ".dev0"
393
        rendered += "+g%s" % pieces["short"]
394
    return rendered
395

396

397
def render_pep440_old(pieces):
398
    """TAG[.postDISTANCE[.dev0]] .
399

400
    The ".dev0" means dirty.
401

402
    Eexceptions:
403
    1: no tags. 0.postDISTANCE[.dev0]
404
    """
405
    if pieces["closest-tag"]:
406
        rendered = pieces["closest-tag"]
407
        if pieces["distance"] or pieces["dirty"]:
408
            rendered += ".post%d" % pieces["distance"]
409
            if pieces["dirty"]:
410
                rendered += ".dev0"
411
    else:
412
        # exception #1
413
        rendered = "0.post%d" % pieces["distance"]
414
        if pieces["dirty"]:
415
            rendered += ".dev0"
416
    return rendered
417

418

419
def render_git_describe(pieces):
420
    """TAG[-DISTANCE-gHEX][-dirty].
421

422
    Like 'git describe --tags --dirty --always'.
423

424
    Exceptions:
425
    1: no tags. HEX[-dirty]  (note: no 'g' prefix)
426
    """
427
    if pieces["closest-tag"]:
428
        rendered = pieces["closest-tag"]
429
        if pieces["distance"]:
430
            rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
431
    else:
432
        # exception #1
433
        rendered = pieces["short"]
434
    if pieces["dirty"]:
435
        rendered += "-dirty"
436
    return rendered
437

438

439
def render_git_describe_long(pieces):
440
    """TAG-DISTANCE-gHEX[-dirty].
441

442
    Like 'git describe --tags --dirty --always -long'.
443
    The distance/hash is unconditional.
444

445
    Exceptions:
446
    1: no tags. HEX[-dirty]  (note: no 'g' prefix)
447
    """
448
    if pieces["closest-tag"]:
449
        rendered = pieces["closest-tag"]
450
        rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
451
    else:
452
        # exception #1
453
        rendered = pieces["short"]
454
    if pieces["dirty"]:
455
        rendered += "-dirty"
456
    return rendered
457

458

459
def render(pieces, style):
460
    """Render the given version pieces into the requested style."""
461
    if pieces["error"]:
462
        return {"version": "unknown",
463
                "full-revisionid": pieces.get("long"),
464
                "dirty": None,
465
                "error": pieces["error"],
466
                "date": None}
467

468
    if not style or style == "default":
469
        style = "pep440"  # the default
470

471
    if style == "pep440":
472
        rendered = render_pep440(pieces)
473
    elif style == "pep440-pre":
474
        rendered = render_pep440_pre(pieces)
475
    elif style == "pep440-post":
476
        rendered = render_pep440_post(pieces)
477
    elif style == "pep440-old":
478
        rendered = render_pep440_old(pieces)
479
    elif style == "git-describe":
480
        rendered = render_git_describe(pieces)
481
    elif style == "git-describe-long":
482
        rendered = render_git_describe_long(pieces)
483
    else:
484
        raise ValueError("unknown style '%s'" % style)
485

486
    return {"version": rendered, "full-revisionid": pieces["long"],
487
            "dirty": pieces["dirty"], "error": None,
488
            "date": pieces.get("date")}
489

490

491
def get_versions():
492
    """Get version information or return default if unable to do so."""
493
    # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
494
    # __file__, we can work backwards from there to the root. Some
495
    # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which
496
    # case we can only use expanded keywords.
497

498
    cfg = get_config()
499
    verbose = cfg.verbose
500

501
    try:
502
        return git_versions_from_keywords(get_keywords(), cfg.tag_prefix,
503
                                          verbose)
504
    except NotThisMethod:
505
        pass
506

507
    try:
508
        root = os.path.realpath(__file__)
509
        # versionfile_source is the relative path from the top of the source
510
        # tree (where the .git directory might live) to this file. Invert
511
        # this to find the root from __file__.
512
        for i in cfg.versionfile_source.split('/'):
513
            root = os.path.dirname(root)
514
    except NameError:
515
        return {"version": "0+unknown", "full-revisionid": None,
516
                "dirty": None,
517
                "error": "unable to find root of source tree",
518
                "date": None}
519

520
    try:
521
        pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
522
        return render(pieces, cfg.style)
523
    except NotThisMethod:
524
        pass
525

526
    try:
527
        if cfg.parentdir_prefix:
528
            return versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
529
    except NotThisMethod:
530
        pass
531

532
    return {"version": "0+unknown", "full-revisionid": None,
533
            "dirty": None,
534
            "error": "unable to compute version", "date": None}

Read our documentation on viewing source code .

Loading