Fix CI failures
1 |
# Licensed under a 3-clause BSD style license - see LICENSE.rst
|
|
2 |
|
|
3 | 20 |
"""
|
4 |
Utilities for retrieving revision information from a project's git repository.
|
|
5 |
"""
|
|
6 |
|
|
7 |
# Do not remove the following comment; it is used by
|
|
8 |
# astropy_helpers.version_helpers to determine the beginning of the code in
|
|
9 |
# this module
|
|
10 |
|
|
11 |
# BEGIN
|
|
12 |
|
|
13 | 20 |
import locale |
14 | 20 |
import os |
15 | 20 |
import subprocess |
16 | 20 |
import warnings |
17 |
|
|
18 | 20 |
__all__ = ['get_git_devstr'] |
19 |
|
|
20 |
|
|
21 | 20 |
def _decode_stdio(stream): |
22 | 20 |
try: |
23 | 20 |
stdio_encoding = locale.getdefaultlocale()[1] or 'utf-8' |
24 |
except ValueError: |
|
25 |
stdio_encoding = 'utf-8' |
|
26 |
|
|
27 | 20 |
try: |
28 | 20 |
text = stream.decode(stdio_encoding) |
29 |
except UnicodeDecodeError: |
|
30 |
# Final fallback
|
|
31 |
text = stream.decode('latin1') |
|
32 |
|
|
33 | 20 |
return text |
34 |
|
|
35 |
|
|
36 | 20 |
def update_git_devstr(version, path=None): |
37 |
"""
|
|
38 |
Updates the git revision string if and only if the path is being imported
|
|
39 |
directly from a git working copy. This ensures that the revision number in
|
|
40 |
the version string is accurate.
|
|
41 |
"""
|
|
42 |
|
|
43 |
try: |
|
44 |
# Quick way to determine if we're in git or not - returns '' if not
|
|
45 |
devstr = get_git_devstr(sha=True, show_warning=False, path=path) |
|
46 |
except OSError: |
|
47 |
return version |
|
48 |
|
|
49 |
if not devstr: |
|
50 |
# Probably not in git so just pass silently
|
|
51 |
return version |
|
52 |
|
|
53 |
if 'dev' in version: # update to the current git revision |
|
54 |
version_base = version.split('.dev', 1)[0] |
|
55 |
devstr = get_git_devstr(sha=False, show_warning=False, path=path) |
|
56 |
|
|
57 |
return version_base + '.dev' + devstr |
|
58 |
else: |
|
59 |
# otherwise it's already the true/release version
|
|
60 |
return version |
|
61 |
|
|
62 |
|
|
63 | 20 |
def get_git_devstr(sha=False, show_warning=True, path=None): |
64 |
"""
|
|
65 |
Determines the number of revisions in this repository.
|
|
66 |
|
|
67 |
Parameters
|
|
68 |
----------
|
|
69 |
sha : bool
|
|
70 |
If True, the full SHA1 hash will be returned. Otherwise, the total
|
|
71 |
count of commits in the repository will be used as a "revision
|
|
72 |
number".
|
|
73 |
|
|
74 |
show_warning : bool
|
|
75 |
If True, issue a warning if git returns an error code, otherwise errors
|
|
76 |
pass silently.
|
|
77 |
|
|
78 |
path : str or None
|
|
79 |
If a string, specifies the directory to look in to find the git
|
|
80 |
repository. If `None`, the current working directory is used, and must
|
|
81 |
be the root of the git repository.
|
|
82 |
If given a filename it uses the directory containing that file.
|
|
83 |
|
|
84 |
Returns
|
|
85 |
-------
|
|
86 |
devversion : str
|
|
87 |
Either a string with the revision number (if `sha` is False), the
|
|
88 |
SHA1 hash of the current commit (if `sha` is True), or an empty string
|
|
89 |
if git version info could not be identified.
|
|
90 |
|
|
91 |
"""
|
|
92 |
|
|
93 | 20 |
if path is None: |
94 | 20 |
path = os.getcwd() |
95 |
|
|
96 | 20 |
if not os.path.isdir(path): |
97 |
path = os.path.abspath(os.path.dirname(path)) |
|
98 |
|
|
99 | 20 |
if sha: |
100 |
# Faster for getting just the hash of HEAD
|
|
101 | 20 |
cmd = ['rev-parse', 'HEAD'] |
102 |
else: |
|
103 | 20 |
cmd = ['rev-list', '--count', 'HEAD'] |
104 |
|
|
105 | 20 |
def run_git(cmd): |
106 | 20 |
try: |
107 | 20 |
p = subprocess.Popen(['git'] + cmd, cwd=path, |
108 |
stdout=subprocess.PIPE, |
|
109 |
stderr=subprocess.PIPE, |
|
110 |
stdin=subprocess.PIPE) |
|
111 | 20 |
stdout, stderr = p.communicate() |
112 |
except OSError as e: |
|
113 |
if show_warning: |
|
114 |
warnings.warn('Error running git: ' + str(e)) |
|
115 |
return (None, b'', b'') |
|
116 |
|
|
117 | 20 |
if p.returncode == 128: |
118 | 20 |
if show_warning: |
119 | 20 |
warnings.warn('No git repository present at {0!r}! Using ' |
120 |
'default dev version.'.format(path)) |
|
121 | 20 |
return (p.returncode, b'', b'') |
122 | 20 |
if p.returncode == 129: |
123 |
if show_warning: |
|
124 |
warnings.warn('Your git looks old (does it support {0}?); ' |
|
125 |
'consider upgrading to v1.7.2 or '
|
|
126 |
'later.'.format(cmd[0])) |
|
127 |
return (p.returncode, stdout, stderr) |
|
128 | 20 |
elif p.returncode != 0: |
129 |
if show_warning: |
|
130 |
warnings.warn('Git failed while determining revision ' |
|
131 |
'count: {0}'.format(_decode_stdio(stderr))) |
|
132 |
return (p.returncode, stdout, stderr) |
|
133 |
|
|
134 | 20 |
return p.returncode, stdout, stderr |
135 |
|
|
136 | 20 |
returncode, stdout, stderr = run_git(cmd) |
137 |
|
|
138 | 20 |
if not sha and returncode == 128: |
139 |
# git returns 128 if the command is not run from within a git
|
|
140 |
# repository tree. In this case, a warning is produced above but we
|
|
141 |
# return the default dev version of '0'.
|
|
142 | 20 |
return '0' |
143 | 20 |
elif not sha and returncode == 129: |
144 |
# git returns 129 if a command option failed to parse; in
|
|
145 |
# particular this could happen in git versions older than 1.7.2
|
|
146 |
# where the --count option is not supported
|
|
147 |
# Also use --abbrev-commit and --abbrev=0 to display the minimum
|
|
148 |
# number of characters needed per-commit (rather than the full hash)
|
|
149 |
cmd = ['rev-list', '--abbrev-commit', '--abbrev=0', 'HEAD'] |
|
150 |
returncode, stdout, stderr = run_git(cmd) |
|
151 |
# Fall back on the old method of getting all revisions and counting
|
|
152 |
# the lines
|
|
153 |
if returncode == 0: |
|
154 |
return str(stdout.count(b'\n')) |
|
155 |
else: |
|
156 |
return '' |
|
157 | 20 |
elif sha: |
158 | 20 |
return _decode_stdio(stdout)[:40] |
159 |
else: |
|
160 | 20 |
return _decode_stdio(stdout).strip() |
161 |
|
|
162 |
|
|
163 |
# This function is tested but it is only ever executed within a subprocess when
|
|
164 |
# creating a fake package, so it doesn't get picked up by coverage metrics.
|
|
165 |
def _get_repo_path(pathname, levels=None): # pragma: no cover |
|
166 |
"""
|
|
167 |
Given a file or directory name, determine the root of the git repository
|
|
168 |
this path is under. If given, this won't look any higher than ``levels``
|
|
169 |
(that is, if ``levels=0`` then the given path must be the root of the git
|
|
170 |
repository and is returned if so.
|
|
171 |
|
|
172 |
Returns `None` if the given path could not be determined to belong to a git
|
|
173 |
repo.
|
|
174 |
"""
|
|
175 |
|
|
176 |
if os.path.isfile(pathname): |
|
177 |
current_dir = os.path.abspath(os.path.dirname(pathname)) |
|
178 |
elif os.path.isdir(pathname): |
|
179 |
current_dir = os.path.abspath(pathname) |
|
180 |
else: |
|
181 |
return None |
|
182 |
|
|
183 |
current_level = 0 |
|
184 |
|
|
185 |
while levels is None or current_level <= levels: |
|
186 |
if os.path.exists(os.path.join(current_dir, '.git')): |
|
187 |
return current_dir |
|
188 |
|
|
189 |
current_level += 1 |
|
190 |
if current_dir == os.path.dirname(current_dir): |
|
191 |
break
|
|
192 |
|
|
193 |
current_dir = os.path.dirname(current_dir) |
|
194 |
|
|
195 |
return None |
Read our documentation on viewing source code .