summaryrefslogtreecommitdiffstats
path: root/git_command.py
diff options
context:
space:
mode:
Diffstat (limited to 'git_command.py')
-rw-r--r--git_command.py209
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
17from __future__ import print_function 15import functools
18import os 16import os
19import sys 17import sys
20import subprocess 18import subprocess
21import tempfile
22from signal import SIGTERM
23 19
24from error import GitError 20from error import GitError
25from git_refs import HEAD 21from git_refs import HEAD
@@ -28,75 +24,42 @@ from repo_trace import REPO_TRACE, IsTrace, Trace
28from wrapper import Wrapper 24from wrapper import Wrapper
29 25
30GIT = 'git' 26GIT = 'git'
31MIN_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.
36MIN_GIT_VERSION_SOFT = (1, 9, 1)
37MIN_GIT_VERSION_HARD = (1, 7, 2)
32GIT_DIR = 'GIT_DIR' 38GIT_DIR = 'GIT_DIR'
33 39
34LAST_GITDIR = None 40LAST_GITDIR = None
35LAST_CWD = None 41LAST_CWD = None
36 42
37_ssh_proxy_path = None
38_ssh_sock_path = None
39_ssh_clients = []
40
41def 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
54def _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
62def _add_ssh_client(p):
63 _ssh_clients.append(p)
64
65def _remove_ssh_client(p):
66 try:
67 _ssh_clients.remove(p)
68 except ValueError:
69 pass
70
71def 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
83class _GitCall(object): 44class _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
100git = _GitCall() 63git = _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
180user_agent = UserAgent() 144user_agent = UserAgent()
181 145
146
182def git_require(min_version, fail=False, msg=''): 147def 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
194def _setenv(env, name, value):
195 env[name] = value.encode()
196 159
197class GitCommand(object): 160class 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()