git_pieces_from_vcs()   F
last analyzed

Complexity

Conditions 11

Size

Total Lines 80

Duplication

Lines 80
Ratio 100 %

Importance

Changes 0
Metric Value
cc 11
dl 80
loc 80
rs 3.1764
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like git_pieces_from_vcs() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
2
# This file helps to compute a version number in source trees obtained from
3
# git-archive tarball (such as those provided by GitHub's 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
8
# This file is released into the public domain. Generated by
9
# versioneer-0.15 (https://github.com/warner/python-versioneer)
10
11
import errno
12
import os
13
import re
14
import subprocess
15
import sys
16
17
18
def get_keywords():
19
    # these strings will be replaced by git during git-archive.
20
    # setup.py/versioneer.py will grep for the variable names, so they must
21
    # each be defined on a line of their own. _version.py will just call
22
    # get_keywords().
23
    git_refnames = "$Format:%d$"
24
    git_full = "$Format:%H$"
25
    keywords = {"refnames": git_refnames, "full": git_full}
26
    return keywords
27
28
29
class VersioneerConfig:
30
    pass
31
32
33
def get_config():
34
    # these strings are filled in when 'setup.py versioneer' creates
35
    # _version.py
36
    cfg = VersioneerConfig()
37
    cfg.VCS = "git"
38
    cfg.style = "pep440"
39
    cfg.tag_prefix = ""
40
    cfg.parentdir_prefix = "conda-"
41
    cfg.versionfile_source = "binstar_client/_version.py"
42
    cfg.verbose = False
43
    return cfg
44
45
46
class NotThisMethod(Exception):
47
    pass
48
49
50
LONG_VERSION_PY = {}
51
HANDLERS = {}
52
53
54
def register_vcs_handler(vcs, method):  # decorator
55
    def decorate(f):
56
        if vcs not in HANDLERS:
57
            HANDLERS[vcs] = {}
58
        HANDLERS[vcs][method] = f
59
        return f
60
    return decorate
61
62
63 View Code Duplication
def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
64
    assert isinstance(commands, list)
65
    p = None
66
    for c in commands:
67
        try:
68
            dispcmd = str([c] + args)
69
            # remember shell=False, so use git.cmd on windows, not just git
70
            p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE,
71
                                 stderr=(subprocess.PIPE if hide_stderr
72
                                         else None))
73
            break
74
        except EnvironmentError:
75
            e = sys.exc_info()[1]
76
            if e.errno == errno.ENOENT:
77
                continue
78
            if verbose:
79
                print("unable to run %s" % dispcmd)
80
                print(e)
81
            return None
82
    else:
83
        if verbose:
84
            print("unable to find command, tried %s" % (commands,))
85
        return None
86
    stdout = p.communicate()[0].strip()
87
    if sys.version_info[0] >= 3:
88
        stdout = stdout.decode()
89
    if p.returncode != 0:
90
        if verbose:
91
            print("unable to run %s (error)" % dispcmd)
92
        return None
93
    return stdout
94
95
96
def versions_from_parentdir(parentdir_prefix, root, verbose):
97
    # Source tarballs conventionally unpack into a directory that includes
98
    # both the project name and a version string.
99
    dirname = os.path.basename(root)
100
    if not dirname.startswith(parentdir_prefix):
101
        if verbose:
102
            print("guessing rootdir is '%s', but '%s' doesn't start with "
103
                  "prefix '%s'" % (root, dirname, parentdir_prefix))
104
        raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
105
    return {"version": dirname[len(parentdir_prefix):],
106
            "full-revisionid": None,
107
            "dirty": False, "error": None}
108
109
110 View Code Duplication
@register_vcs_handler("git", "get_keywords")
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
111
def git_get_keywords(versionfile_abs):
112
    # the code embedded in _version.py can just fetch the value of these
113
    # keywords. When used from setup.py, we don't want to import _version.py,
114
    # so we do it with a regexp instead. This function is not used from
115
    # _version.py.
116
    keywords = {}
117
    try:
118
        f = open(versionfile_abs, "r")
119
        for line in f.readlines():
120
            if line.strip().startswith("git_refnames ="):
121
                mo = re.search(r'=\s*"(.*)"', line)
122
                if mo:
123
                    keywords["refnames"] = mo.group(1)
124
            if line.strip().startswith("git_full ="):
125
                mo = re.search(r'=\s*"(.*)"', line)
126
                if mo:
127
                    keywords["full"] = mo.group(1)
128
        f.close()
129
    except EnvironmentError:
130
        pass
131
    return keywords
132
133
134 View Code Duplication
@register_vcs_handler("git", "keywords")
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
135
def git_versions_from_keywords(keywords, tag_prefix, verbose):
136
    if not keywords:
137
        raise NotThisMethod("no keywords at all, weird")
138
    refnames = keywords["refnames"].strip()
139
    if refnames.startswith("$Format"):
140
        if verbose:
141
            print("keywords are unexpanded, not using")
142
        raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
143
    refs = set([r.strip() for r in refnames.strip("()").split(",")])
144
    # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
145
    # just "foo-1.0". If we see a "tag: " prefix, prefer those.
146
    TAG = "tag: "
147
    tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
148
    if not tags:
149
        # Either we're using git < 1.8.3, or there really are no tags. We use
150
        # a heuristic: assume all version tags have a digit. The old git %d
151
        # expansion behaves like git log --decorate=short and strips out the
152
        # refs/heads/ and refs/tags/ prefixes that would let us distinguish
153
        # between branches and tags. By ignoring refnames without digits, we
154
        # filter out many common branch names like "release" and
155
        # "stabilization", as well as "HEAD" and "master".
156
        tags = set([r for r in refs if re.search(r'\d', r)])
157
        if verbose:
158
            print("discarding '%s', no digits" % ",".join(refs-tags))
159
    if verbose:
160
        print("likely tags: %s" % ",".join(sorted(tags)))
161
    for ref in sorted(tags):
162
        # sorting will prefer e.g. "2.0" over "2.0rc1"
163
        if ref.startswith(tag_prefix):
164
            r = ref[len(tag_prefix):]
165
            if verbose:
166
                print("picking %s" % r)
167
            return {"version": r,
168
                    "full-revisionid": keywords["full"].strip(),
169
                    "dirty": False, "error": None
170
                    }
171
    # no suitable tags, so version is "0+unknown", but full hex is still there
172
    if verbose:
173
        print("no suitable tags, using unknown + full revision id")
174
    return {"version": "0+unknown",
175
            "full-revisionid": keywords["full"].strip(),
176
            "dirty": False, "error": "no suitable tags"}
177
178
179 View Code Duplication
@register_vcs_handler("git", "pieces_from_vcs")
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
180
def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
181
    # this runs 'git' from the root of the source tree. This only gets called
182
    # if the git-archive 'subst' keywords were *not* expanded, and
183
    # _version.py hasn't already been rewritten with a short version string,
184
    # meaning we're inside a checked out source tree.
185
186
    if not os.path.exists(os.path.join(root, ".git")):
187
        if verbose:
188
            print("no .git in %s" % root)
189
        raise NotThisMethod("no .git directory")
190
191
    GITS = ["git"]
192
    if sys.platform == "win32":
193
        GITS = ["git.cmd", "git.exe"]
194
    # if there is a tag, this yields TAG-NUM-gHEX[-dirty]
195
    # if there are no tags, this yields HEX[-dirty] (no NUM)
196
    describe_out = run_command(GITS, ["describe", "--tags", "--dirty",
197
                                      "--always", "--long"],
198
                               cwd=root)
199
    # --long was added in git-1.5.5
200
    if describe_out is None:
201
        raise NotThisMethod("'git describe' failed")
202
    describe_out = describe_out.strip()
203
    full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
204
    if full_out is None:
205
        raise NotThisMethod("'git rev-parse' failed")
206
    full_out = full_out.strip()
207
208
    pieces = {}
209
    pieces["long"] = full_out
210
    pieces["short"] = full_out[:7]  # maybe improved later
211
    pieces["error"] = None
212
213
    # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
214
    # TAG might have hyphens.
215
    git_describe = describe_out
216
217
    # look for -dirty suffix
218
    dirty = git_describe.endswith("-dirty")
219
    pieces["dirty"] = dirty
220
    if dirty:
221
        git_describe = git_describe[:git_describe.rindex("-dirty")]
222
223
    # now we have TAG-NUM-gHEX or HEX
224
225
    if "-" in git_describe:
226
        # TAG-NUM-gHEX
227
        mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
228
        if not mo:
229
            # unparseable. Maybe git-describe is misbehaving?
230
            pieces["error"] = ("unable to parse git-describe output: '%s'"
231
                               % describe_out)
232
            return pieces
233
234
        # tag
235
        full_tag = mo.group(1)
236
        if not full_tag.startswith(tag_prefix):
237
            if verbose:
238
                fmt = "tag '%s' doesn't start with prefix '%s'"
239
                print(fmt % (full_tag, tag_prefix))
240
            pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
241
                               % (full_tag, tag_prefix))
242
            return pieces
243
        pieces["closest-tag"] = full_tag[len(tag_prefix):]
244
245
        # distance: number of commits since tag
246
        pieces["distance"] = int(mo.group(2))
247
248
        # commit: short hex revision ID
249
        pieces["short"] = mo.group(3)
250
251
    else:
252
        # HEX: no tags
253
        pieces["closest-tag"] = None
254
        count_out = run_command(GITS, ["rev-list", "HEAD", "--count"],
255
                                cwd=root)
256
        pieces["distance"] = int(count_out)  # total number of commits
257
258
    return pieces
259
260
261
def plus_or_dot(pieces):
262
    if "+" in pieces.get("closest-tag", ""):
263
        return "."
264
    return "+"
265
266
267 View Code Duplication
def render_pep440(pieces):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
268
    # now build up version string, with post-release "local version
269
    # identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
270
    # get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
271
272
    # exceptions:
273
    # 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
274
275
    if pieces["closest-tag"]:
276
        rendered = pieces["closest-tag"]
277
        if pieces["distance"] or pieces["dirty"]:
278
            rendered += plus_or_dot(pieces)
279
            rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
280
            if pieces["dirty"]:
281
                rendered += ".dirty"
282
    else:
283
        # exception #1
284
        rendered = "0+untagged.%d.g%s" % (pieces["distance"],
285
                                          pieces["short"])
286
        if pieces["dirty"]:
287
            rendered += ".dirty"
288
    return rendered
289
290
291
def render_pep440_pre(pieces):
292
    # TAG[.post.devDISTANCE] . No -dirty
293
294
    # exceptions:
295
    # 1: no tags. 0.post.devDISTANCE
296
297
    if pieces["closest-tag"]:
298
        rendered = pieces["closest-tag"]
299
        if pieces["distance"]:
300
            rendered += ".post.dev%d" % pieces["distance"]
301
    else:
302
        # exception #1
303
        rendered = "0.post.dev%d" % pieces["distance"]
304
    return rendered
305
306
307 View Code Duplication
def render_pep440_post(pieces):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
308
    # TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that
309
    # .dev0 sorts backwards (a dirty tree will appear "older" than the
310
    # corresponding clean one), but you shouldn't be releasing software with
311
    # -dirty anyways.
312
313
    # exceptions:
314
    # 1: no tags. 0.postDISTANCE[.dev0]
315
316
    if pieces["closest-tag"]:
317
        rendered = pieces["closest-tag"]
318
        if pieces["distance"] or pieces["dirty"]:
319
            rendered += ".post%d" % pieces["distance"]
320
            if pieces["dirty"]:
321
                rendered += ".dev0"
322
            rendered += plus_or_dot(pieces)
323
            rendered += "g%s" % pieces["short"]
324
    else:
325
        # exception #1
326
        rendered = "0.post%d" % pieces["distance"]
327
        if pieces["dirty"]:
328
            rendered += ".dev0"
329
        rendered += "+g%s" % pieces["short"]
330
    return rendered
331
332
333
def render_pep440_old(pieces):
334
    # TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty.
335
336
    # exceptions:
337
    # 1: no tags. 0.postDISTANCE[.dev0]
338
339
    if pieces["closest-tag"]:
340
        rendered = pieces["closest-tag"]
341
        if pieces["distance"] or pieces["dirty"]:
342
            rendered += ".post%d" % pieces["distance"]
343
            if pieces["dirty"]:
344
                rendered += ".dev0"
345
    else:
346
        # exception #1
347
        rendered = "0.post%d" % pieces["distance"]
348
        if pieces["dirty"]:
349
            rendered += ".dev0"
350
    return rendered
351
352
353
def render_git_describe(pieces):
354
    # TAG[-DISTANCE-gHEX][-dirty], like 'git describe --tags --dirty
355
    # --always'
356
357
    # exceptions:
358
    # 1: no tags. HEX[-dirty]  (note: no 'g' prefix)
359
360
    if pieces["closest-tag"]:
361
        rendered = pieces["closest-tag"]
362
        if pieces["distance"]:
363
            rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
364
    else:
365
        # exception #1
366
        rendered = pieces["short"]
367
    if pieces["dirty"]:
368
        rendered += "-dirty"
369
    return rendered
370
371
372
def render_git_describe_long(pieces):
373
    # TAG-DISTANCE-gHEX[-dirty], like 'git describe --tags --dirty
374
    # --always -long'. The distance/hash is unconditional.
375
376
    # exceptions:
377
    # 1: no tags. HEX[-dirty]  (note: no 'g' prefix)
378
379
    if pieces["closest-tag"]:
380
        rendered = pieces["closest-tag"]
381
        rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
382
    else:
383
        # exception #1
384
        rendered = pieces["short"]
385
    if pieces["dirty"]:
386
        rendered += "-dirty"
387
    return rendered
388
389
390 View Code Duplication
def render(pieces, style):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
391
    if pieces["error"]:
392
        return {"version": "unknown",
393
                "full-revisionid": pieces.get("long"),
394
                "dirty": None,
395
                "error": pieces["error"]}
396
397
    if not style or style == "default":
398
        style = "pep440"  # the default
399
400
    if style == "pep440":
401
        rendered = render_pep440(pieces)
402
    elif style == "pep440-pre":
403
        rendered = render_pep440_pre(pieces)
404
    elif style == "pep440-post":
405
        rendered = render_pep440_post(pieces)
406
    elif style == "pep440-old":
407
        rendered = render_pep440_old(pieces)
408
    elif style == "git-describe":
409
        rendered = render_git_describe(pieces)
410
    elif style == "git-describe-long":
411
        rendered = render_git_describe_long(pieces)
412
    else:
413
        raise ValueError("unknown style '%s'" % style)
414
415
    return {"version": rendered, "full-revisionid": pieces["long"],
416
            "dirty": pieces["dirty"], "error": None}
417
418
419
def get_versions():
420
    # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
421
    # __file__, we can work backwards from there to the root. Some
422
    # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which
423
    # case we can only use expanded keywords.
424
425
    cfg = get_config()
426
    verbose = cfg.verbose
427
428
    try:
429
        return git_versions_from_keywords(get_keywords(), cfg.tag_prefix,
430
                                          verbose)
431
    except NotThisMethod:
432
        pass
433
434
    try:
435
        root = os.path.realpath(__file__)
436
        # versionfile_source is the relative path from the top of the source
437
        # tree (where the .git directory might live) to this file. Invert
438
        # this to find the root from __file__.
439
        for i in cfg.versionfile_source.split('/'):
440
            root = os.path.dirname(root)
441
    except NameError:
442
        return {"version": "0+unknown", "full-revisionid": None,
443
                "dirty": None,
444
                "error": "unable to find root of source tree"}
445
446
    try:
447
        pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
448
        return render(pieces, cfg.style)
449
    except NotThisMethod:
450
        pass
451
452
    try:
453
        if cfg.parentdir_prefix:
454
            return versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
455
    except NotThisMethod:
456
        pass
457
458
    return {"version": "0+unknown", "full-revisionid": None,
459
            "dirty": None,
460
            "error": "unable to compute version"}
461