Passed
Push — develop ( 4d6495...2dddb7 )
by Jace
03:19
created

gitman.git   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 165
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 94
dl 0
loc 165
ccs 75
cts 75
cp 1
rs 10
c 0
b 0
f 0
wmc 30

12 Functions

Rating   Name   Duplication   Size   Complexity  
A get_branch() 0 3 1
A get_tag() 0 4 1
A update() 0 15 3
A git() 0 2 1
A get_hash() 0 3 1
B clone() 0 31 6
A is_sha() 0 8 1
A _get_sha_from_rev() 0 10 2
A valid() 0 10 3
B changes() 0 26 6
A fetch() 0 12 4
A get_url() 0 3 1
1
"""Utilities to call Git commands."""
2
3 1
import logging
4 1
import os
5 1
import re
6
from contextlib import suppress
7 1
8 1
from . import common, settings
9 1
from .exceptions import ShellError
10
from .shell import call
11
12 1
13
log = logging.getLogger(__name__)
14
15 1
16 1
def git(*args, **kwargs):
17
    return call('git', *args, **kwargs)
18
19 1
20
def clone(repo, path, *, cache=settings.CACHE, sparse_paths=None, rev=None):
21 1
    """Clone a new Git repository."""
22
    log.debug("Creating a new repository...")
23 1
24 1
    name = repo.split('/')[-1]
25 1
    if name.endswith(".git"):
26
        name = name[:-4]
27 1
28 1
    reference = os.path.join(cache, name + ".reference")
29 1
    if not os.path.isdir(reference):
30
        git('clone', '--mirror', repo, reference)
31 1
32
    normpath = os.path.normpath(path)
33
    if sparse_paths:
34 1
        os.mkdir(normpath)
35
        git('-C', normpath, 'init')
36 1
        git('-C', normpath, 'config', 'core.sparseCheckout', 'true')
37 1
        git('-C', normpath, 'remote', 'add', '-f', 'origin', reference)
38 1
39 1
        with open("%s/%s/.git/info/sparse-checkout" %
40 1
                  (os.getcwd(), normpath), 'w') as fd:
41 1
            fd.writelines(sparse_paths)
42 1
        with open("%s/%s/.git/objects/info/alternates" %
43
                  (os.getcwd(), normpath), 'w') as fd:
44 1
            fd.write("%s/objects" % reference)
45 1
46
        # We use directly the revision requested here in order to respect,
47
        # that not all repos have `master` as their default branch
48 1
        git('-C', normpath, 'pull', 'origin', rev)
49
    else:
50 1
        git('clone', '--reference', reference, repo, os.path.normpath(path))
51
52 1
53 1
def is_sha(rev):
54 1
    """Heuristically determine whether a revision corresponds to a commit SHA.
55 1
56
    Any sequence of 7 to 40 hexadecimal digits will be recognized as a
57 1
    commit SHA. The minimum of 7 digits is not an arbitrary choice, it
58
    is the default length for short SHAs in Git.
59
    """
60 1
    return re.match('^[0-9a-f]{7,40}$', rev) is not None
61
62 1
63
def fetch(repo, rev=None):
64 1
    """Fetch the latest changes from the remote repository."""
65
    git('remote', 'set-url', 'origin', repo)
66 1
    args = ['fetch', '--tags', '--force', '--prune', 'origin']
67
    if rev:
68
        if is_sha(rev):
69 1
            pass  # fetch only works with a SHA if already present locally
70
        elif '@' in rev:
71
            pass  # fetch doesn't work with rev-parse
72 1
        else:
73
            args.append(rev)
74 1
    git(*args)
75 1
76
77
def valid():
78 1
    """Confirm the current directory is a valid working tree."""
79
    log.debug("Checking for a valid working tree...")
80 1
81 1
    try:
82 1
        git('rev-parse', '--is-inside-work-tree', _show=False)
83 1
    except ShellError:
84
        return False
85 1
    else:
86
        return True
87
88 1
89
def changes(include_untracked=False, display_status=True, _show=False):
90 1
    """Determine if there are changes in the working tree."""
91
    status = False
92 1
93 1
    try:
94 1
        # Refresh changes
95
        git('update-index', '-q', '--refresh', _show=False)
96 1
97 1
        # Check for uncommitted changes
98 1
        git('diff-index', '--quiet', 'HEAD', _show=_show)
99
100 1
        # Check for untracked files
101
        lines = git('ls-files', '--others', '--exclude-standard', _show=_show)
102 1
103
    except ShellError:
104
        status = True
105 1
106
    else:
107 1
        status = bool(lines) and include_untracked
108
109
    if status and display_status:
110 1
        with suppress(ShellError):
111
            lines = git('status', _show=True)
112 1
            common.show(*lines, color='git_changes')
113
114
    return status
115 1
116
117 1
def update(rev, *, clean=True, fetch=False):  # pylint: disable=redefined-outer-name
118
    """Update the working tree to the specified revision."""
119
    hide = {'_show': False, '_ignore': True}
120
121 1
    git('stash', **hide)
122
    if clean:
123 1
        git('clean', '--force', '-d', '-x', _show=False)
124
125
    rev = _get_sha_from_rev(rev)
126 1
    git('checkout', '--force', rev)
127
    git('branch', '--set-upstream-to', 'origin/' + rev, **hide)
128 1
129 1
    if fetch:
130 1
        # if `rev` was a branch it might be tracking something older
131 1
        git('pull', '--ff-only', '--no-rebase', **hide)
132 1
133 1
134
def get_url():
135 1
    """Get the current repository's URL."""
136
    return git('config', '--get', 'remote.origin.url', _show=False)[0]
137
138
139
def get_hash(_show=False):
140
    """Get the current working tree's hash."""
141
    return git('rev-parse', 'HEAD', _show=_show)[0]
142
143
144
def get_tag():
145
    """Get the current working tree's tag (if on a tag)."""
146
    return git('describe', '--tags', '--exact-match',
147
               _show=False, _ignore=True)[0]
148
149
150
def get_branch():
151
    """Get the current working tree's branch."""
152
    return git('rev-parse', '--abbrev-ref', 'HEAD', _show=False)[0]
153
154
155
def _get_sha_from_rev(rev):
156
    """Get a rev-parse string's hash."""
157
    if '@{' in rev:  # TODO: use regex for this
158
        parts = rev.split('@')
159
        branch = parts[0]
160
        date = parts[1].strip("{}")
161
        git('checkout', '--force', branch, _show=False)
162
        rev = git('rev-list', '-n', '1', '--before={!r}'.format(date),
163
                  branch, _show=False)[0]
164
    return rev
165