diff options
Diffstat (limited to 'git_command.py')
-rw-r--r-- | git_command.py | 209 |
1 files changed, 77 insertions, 132 deletions
diff --git a/git_command.py b/git_command.py index dc542c36..95db91f2 100644 --- a/git_command.py +++ b/git_command.py | |||
@@ -1,5 +1,3 @@ | |||
1 | # -*- coding:utf-8 -*- | ||
2 | # | ||
3 | # Copyright (C) 2008 The Android Open Source Project | 1 | # Copyright (C) 2008 The Android Open Source Project |
4 | # | 2 | # |
5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
@@ -14,12 +12,10 @@ | |||
14 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
15 | # limitations under the License. | 13 | # limitations under the License. |
16 | 14 | ||
17 | from __future__ import print_function | 15 | import functools |
18 | import os | 16 | import os |
19 | import sys | 17 | import sys |
20 | import subprocess | 18 | import subprocess |
21 | import tempfile | ||
22 | from signal import SIGTERM | ||
23 | 19 | ||
24 | from error import GitError | 20 | from error import GitError |
25 | from git_refs import HEAD | 21 | from git_refs import HEAD |
@@ -28,75 +24,42 @@ from repo_trace import REPO_TRACE, IsTrace, Trace | |||
28 | from wrapper import Wrapper | 24 | from wrapper import Wrapper |
29 | 25 | ||
30 | GIT = 'git' | 26 | GIT = 'git' |
31 | MIN_GIT_VERSION = (1, 5, 4) | 27 | # NB: These do not need to be kept in sync with the repo launcher script. |
28 | # These may be much newer as it allows the repo launcher to roll between | ||
29 | # different repo releases while source versions might require a newer git. | ||
30 | # | ||
31 | # The soft version is when we start warning users that the version is old and | ||
32 | # we'll be dropping support for it. We'll refuse to work with versions older | ||
33 | # than the hard version. | ||
34 | # | ||
35 | # git-1.7 is in (EOL) Ubuntu Precise. git-1.9 is in Ubuntu Trusty. | ||
36 | MIN_GIT_VERSION_SOFT = (1, 9, 1) | ||
37 | MIN_GIT_VERSION_HARD = (1, 7, 2) | ||
32 | GIT_DIR = 'GIT_DIR' | 38 | GIT_DIR = 'GIT_DIR' |
33 | 39 | ||
34 | LAST_GITDIR = None | 40 | LAST_GITDIR = None |
35 | LAST_CWD = None | 41 | LAST_CWD = None |
36 | 42 | ||
37 | _ssh_proxy_path = None | ||
38 | _ssh_sock_path = None | ||
39 | _ssh_clients = [] | ||
40 | |||
41 | def ssh_sock(create=True): | ||
42 | global _ssh_sock_path | ||
43 | if _ssh_sock_path is None: | ||
44 | if not create: | ||
45 | return None | ||
46 | tmp_dir = '/tmp' | ||
47 | if not os.path.exists(tmp_dir): | ||
48 | tmp_dir = tempfile.gettempdir() | ||
49 | _ssh_sock_path = os.path.join( | ||
50 | tempfile.mkdtemp('', 'ssh-', tmp_dir), | ||
51 | 'master-%r@%h:%p') | ||
52 | return _ssh_sock_path | ||
53 | |||
54 | def _ssh_proxy(): | ||
55 | global _ssh_proxy_path | ||
56 | if _ssh_proxy_path is None: | ||
57 | _ssh_proxy_path = os.path.join( | ||
58 | os.path.dirname(__file__), | ||
59 | 'git_ssh') | ||
60 | return _ssh_proxy_path | ||
61 | |||
62 | def _add_ssh_client(p): | ||
63 | _ssh_clients.append(p) | ||
64 | |||
65 | def _remove_ssh_client(p): | ||
66 | try: | ||
67 | _ssh_clients.remove(p) | ||
68 | except ValueError: | ||
69 | pass | ||
70 | |||
71 | def terminate_ssh_clients(): | ||
72 | global _ssh_clients | ||
73 | for p in _ssh_clients: | ||
74 | try: | ||
75 | os.kill(p.pid, SIGTERM) | ||
76 | p.wait() | ||
77 | except OSError: | ||
78 | pass | ||
79 | _ssh_clients = [] | ||
80 | |||
81 | _git_version = None | ||
82 | 43 | ||
83 | class _GitCall(object): | 44 | class _GitCall(object): |
45 | @functools.lru_cache(maxsize=None) | ||
84 | def version_tuple(self): | 46 | def version_tuple(self): |
85 | global _git_version | 47 | ret = Wrapper().ParseGitVersion() |
86 | if _git_version is None: | 48 | if ret is None: |
87 | _git_version = Wrapper().ParseGitVersion() | 49 | print('fatal: unable to detect git version', file=sys.stderr) |
88 | if _git_version is None: | 50 | sys.exit(1) |
89 | print('fatal: unable to detect git version', file=sys.stderr) | 51 | return ret |
90 | sys.exit(1) | ||
91 | return _git_version | ||
92 | 52 | ||
93 | def __getattr__(self, name): | 53 | def __getattr__(self, name): |
94 | name = name.replace('_','-') | 54 | name = name.replace('_', '-') |
55 | |||
95 | def fun(*cmdv): | 56 | def fun(*cmdv): |
96 | command = [name] | 57 | command = [name] |
97 | command.extend(cmdv) | 58 | command.extend(cmdv) |
98 | return GitCommand(None, command).Wait() == 0 | 59 | return GitCommand(None, command).Wait() == 0 |
99 | return fun | 60 | return fun |
61 | |||
62 | |||
100 | git = _GitCall() | 63 | git = _GitCall() |
101 | 64 | ||
102 | 65 | ||
@@ -111,11 +74,11 @@ def RepoSourceVersion(): | |||
111 | 74 | ||
112 | proj = os.path.dirname(os.path.abspath(__file__)) | 75 | proj = os.path.dirname(os.path.abspath(__file__)) |
113 | env[GIT_DIR] = os.path.join(proj, '.git') | 76 | env[GIT_DIR] = os.path.join(proj, '.git') |
114 | 77 | result = subprocess.run([GIT, 'describe', HEAD], stdout=subprocess.PIPE, | |
115 | p = subprocess.Popen([GIT, 'describe', HEAD], stdout=subprocess.PIPE, | 78 | stderr=subprocess.DEVNULL, encoding='utf-8', |
116 | env=env) | 79 | env=env, check=False) |
117 | if p.wait() == 0: | 80 | if result.returncode == 0: |
118 | ver = p.stdout.read().strip().decode('utf-8') | 81 | ver = result.stdout.strip() |
119 | if ver.startswith('v'): | 82 | if ver.startswith('v'): |
120 | ver = ver[1:] | 83 | ver = ver[1:] |
121 | else: | 84 | else: |
@@ -177,8 +140,10 @@ class UserAgent(object): | |||
177 | 140 | ||
178 | return self._git_ua | 141 | return self._git_ua |
179 | 142 | ||
143 | |||
180 | user_agent = UserAgent() | 144 | user_agent = UserAgent() |
181 | 145 | ||
146 | |||
182 | def git_require(min_version, fail=False, msg=''): | 147 | def git_require(min_version, fail=False, msg=''): |
183 | git_version = git.version_tuple() | 148 | git_version = git.version_tuple() |
184 | if min_version <= git_version: | 149 | if min_version <= git_version: |
@@ -191,42 +156,38 @@ def git_require(min_version, fail=False, msg=''): | |||
191 | sys.exit(1) | 156 | sys.exit(1) |
192 | return False | 157 | return False |
193 | 158 | ||
194 | def _setenv(env, name, value): | ||
195 | env[name] = value.encode() | ||
196 | 159 | ||
197 | class GitCommand(object): | 160 | class GitCommand(object): |
198 | def __init__(self, | 161 | def __init__(self, |
199 | project, | 162 | project, |
200 | cmdv, | 163 | cmdv, |
201 | bare = False, | 164 | bare=False, |
202 | provide_stdin = False, | 165 | input=None, |
203 | capture_stdout = False, | 166 | capture_stdout=False, |
204 | capture_stderr = False, | 167 | capture_stderr=False, |
205 | disable_editor = False, | 168 | merge_output=False, |
206 | ssh_proxy = False, | 169 | disable_editor=False, |
207 | cwd = None, | 170 | ssh_proxy=None, |
208 | gitdir = None): | 171 | cwd=None, |
172 | gitdir=None): | ||
209 | env = self._GetBasicEnv() | 173 | env = self._GetBasicEnv() |
210 | 174 | ||
211 | # If we are not capturing std* then need to print it. | ||
212 | self.tee = {'stdout': not capture_stdout, 'stderr': not capture_stderr} | ||
213 | |||
214 | if disable_editor: | 175 | if disable_editor: |
215 | _setenv(env, 'GIT_EDITOR', ':') | 176 | env['GIT_EDITOR'] = ':' |
216 | if ssh_proxy: | 177 | if ssh_proxy: |
217 | _setenv(env, 'REPO_SSH_SOCK', ssh_sock()) | 178 | env['REPO_SSH_SOCK'] = ssh_proxy.sock() |
218 | _setenv(env, 'GIT_SSH', _ssh_proxy()) | 179 | env['GIT_SSH'] = ssh_proxy.proxy |
219 | _setenv(env, 'GIT_SSH_VARIANT', 'ssh') | 180 | env['GIT_SSH_VARIANT'] = 'ssh' |
220 | if 'http_proxy' in env and 'darwin' == sys.platform: | 181 | if 'http_proxy' in env and 'darwin' == sys.platform: |
221 | s = "'http.proxy=%s'" % (env['http_proxy'],) | 182 | s = "'http.proxy=%s'" % (env['http_proxy'],) |
222 | p = env.get('GIT_CONFIG_PARAMETERS') | 183 | p = env.get('GIT_CONFIG_PARAMETERS') |
223 | if p is not None: | 184 | if p is not None: |
224 | s = p + ' ' + s | 185 | s = p + ' ' + s |
225 | _setenv(env, 'GIT_CONFIG_PARAMETERS', s) | 186 | env['GIT_CONFIG_PARAMETERS'] = s |
226 | if 'GIT_ALLOW_PROTOCOL' not in env: | 187 | if 'GIT_ALLOW_PROTOCOL' not in env: |
227 | _setenv(env, 'GIT_ALLOW_PROTOCOL', | 188 | env['GIT_ALLOW_PROTOCOL'] = ( |
228 | 'file:git:http:https:ssh:persistent-http:persistent-https:sso:rpc') | 189 | 'file:git:http:https:ssh:persistent-http:persistent-https:sso:rpc') |
229 | _setenv(env, 'GIT_HTTP_USER_AGENT', user_agent.git) | 190 | env['GIT_HTTP_USER_AGENT'] = user_agent.git |
230 | 191 | ||
231 | if project: | 192 | if project: |
232 | if not cwd: | 193 | if not cwd: |
@@ -237,7 +198,10 @@ class GitCommand(object): | |||
237 | command = [GIT] | 198 | command = [GIT] |
238 | if bare: | 199 | if bare: |
239 | if gitdir: | 200 | if gitdir: |
240 | _setenv(env, GIT_DIR, gitdir) | 201 | # Git on Windows wants its paths only using / for reliability. |
202 | if platform_utils.isWindows(): | ||
203 | gitdir = gitdir.replace('\\', '/') | ||
204 | env[GIT_DIR] = gitdir | ||
241 | cwd = None | 205 | cwd = None |
242 | command.append(cmdv[0]) | 206 | command.append(cmdv[0]) |
243 | # Need to use the --progress flag for fetch/clone so output will be | 207 | # Need to use the --progress flag for fetch/clone so output will be |
@@ -247,13 +211,10 @@ class GitCommand(object): | |||
247 | command.append('--progress') | 211 | command.append('--progress') |
248 | command.extend(cmdv[1:]) | 212 | command.extend(cmdv[1:]) |
249 | 213 | ||
250 | if provide_stdin: | 214 | stdin = subprocess.PIPE if input else None |
251 | stdin = subprocess.PIPE | 215 | stdout = subprocess.PIPE if capture_stdout else None |
252 | else: | 216 | stderr = (subprocess.STDOUT if merge_output else |
253 | stdin = None | 217 | (subprocess.PIPE if capture_stderr else None)) |
254 | |||
255 | stdout = subprocess.PIPE | ||
256 | stderr = subprocess.PIPE | ||
257 | 218 | ||
258 | if IsTrace(): | 219 | if IsTrace(): |
259 | global LAST_CWD | 220 | global LAST_CWD |
@@ -281,23 +242,38 @@ class GitCommand(object): | |||
281 | dbg += ' 1>|' | 242 | dbg += ' 1>|' |
282 | if stderr == subprocess.PIPE: | 243 | if stderr == subprocess.PIPE: |
283 | dbg += ' 2>|' | 244 | dbg += ' 2>|' |
245 | elif stderr == subprocess.STDOUT: | ||
246 | dbg += ' 2>&1' | ||
284 | Trace('%s', dbg) | 247 | Trace('%s', dbg) |
285 | 248 | ||
286 | try: | 249 | try: |
287 | p = subprocess.Popen(command, | 250 | p = subprocess.Popen(command, |
288 | cwd = cwd, | 251 | cwd=cwd, |
289 | env = env, | 252 | env=env, |
290 | stdin = stdin, | 253 | encoding='utf-8', |
291 | stdout = stdout, | 254 | errors='backslashreplace', |
292 | stderr = stderr) | 255 | stdin=stdin, |
256 | stdout=stdout, | ||
257 | stderr=stderr) | ||
293 | except Exception as e: | 258 | except Exception as e: |
294 | raise GitError('%s: %s' % (command[1], e)) | 259 | raise GitError('%s: %s' % (command[1], e)) |
295 | 260 | ||
296 | if ssh_proxy: | 261 | if ssh_proxy: |
297 | _add_ssh_client(p) | 262 | ssh_proxy.add_client(p) |
298 | 263 | ||
299 | self.process = p | 264 | self.process = p |
300 | self.stdin = p.stdin | 265 | if input: |
266 | if isinstance(input, str): | ||
267 | input = input.encode('utf-8') | ||
268 | p.stdin.write(input) | ||
269 | p.stdin.close() | ||
270 | |||
271 | try: | ||
272 | self.stdout, self.stderr = p.communicate() | ||
273 | finally: | ||
274 | if ssh_proxy: | ||
275 | ssh_proxy.remove_client(p) | ||
276 | self.rc = p.wait() | ||
301 | 277 | ||
302 | @staticmethod | 278 | @staticmethod |
303 | def _GetBasicEnv(): | 279 | def _GetBasicEnv(): |
@@ -317,35 +293,4 @@ class GitCommand(object): | |||
317 | return env | 293 | return env |
318 | 294 | ||
319 | def Wait(self): | 295 | def Wait(self): |
320 | try: | 296 | return self.rc |
321 | p = self.process | ||
322 | rc = self._CaptureOutput() | ||
323 | finally: | ||
324 | _remove_ssh_client(p) | ||
325 | return rc | ||
326 | |||
327 | def _CaptureOutput(self): | ||
328 | p = self.process | ||
329 | s_in = platform_utils.FileDescriptorStreams.create() | ||
330 | s_in.add(p.stdout, sys.stdout, 'stdout') | ||
331 | s_in.add(p.stderr, sys.stderr, 'stderr') | ||
332 | self.stdout = '' | ||
333 | self.stderr = '' | ||
334 | |||
335 | while not s_in.is_done: | ||
336 | in_ready = s_in.select() | ||
337 | for s in in_ready: | ||
338 | buf = s.read() | ||
339 | if not buf: | ||
340 | s_in.remove(s) | ||
341 | continue | ||
342 | if not hasattr(buf, 'encode'): | ||
343 | buf = buf.decode() | ||
344 | if s.std_name == 'stdout': | ||
345 | self.stdout += buf | ||
346 | else: | ||
347 | self.stderr += buf | ||
348 | if self.tee[s.std_name]: | ||
349 | s.dest.write(buf) | ||
350 | s.dest.flush() | ||
351 | return p.wait() | ||