blob: 8bd4e67e67db2e0fa10422784c6e331d94786adc [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import os
Anders Björklund8e0fe192020-02-18 14:08:35 +010016import re
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070017import sys
18import subprocess
Shawn O. Pearcefb231612009-04-10 18:53:46 -070019import tempfile
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -070020from signal import SIGTERM
Renaud Paquay2e702912016-11-01 11:23:38 -070021
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070022from error import GitError
Mike Frysinger71b0f312019-09-30 22:39:49 -040023from git_refs import HEAD
Renaud Paquay2e702912016-11-01 11:23:38 -070024import platform_utils
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040025from repo_trace import REPO_TRACE, IsTrace, Trace
Conley Owensff0a3c82014-01-30 14:46:03 -080026from wrapper import Wrapper
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070027
28GIT = 'git'
Mike Frysinger82caef62020-02-11 18:51:08 -050029# NB: These do not need to be kept in sync with the repo launcher script.
30# These may be much newer as it allows the repo launcher to roll between
31# different repo releases while source versions might require a newer git.
32#
33# The soft version is when we start warning users that the version is old and
34# we'll be dropping support for it. We'll refuse to work with versions older
35# than the hard version.
36#
37# git-1.7 is in (EOL) Ubuntu Precise. git-1.9 is in Ubuntu Trusty.
38MIN_GIT_VERSION_SOFT = (1, 9, 1)
39MIN_GIT_VERSION_HARD = (1, 7, 2)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070040GIT_DIR = 'GIT_DIR'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070041
42LAST_GITDIR = None
43LAST_CWD = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070044
Shawn O. Pearcefb231612009-04-10 18:53:46 -070045_ssh_proxy_path = None
46_ssh_sock_path = None
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -070047_ssh_clients = []
Anders Björklund8e0fe192020-02-18 14:08:35 +010048_ssh_version = None
49
50
51def _run_ssh_version():
52 """run ssh -V to display the version number"""
53 return subprocess.check_output(['ssh', '-V'], stderr=subprocess.STDOUT).decode()
54
55
56def _parse_ssh_version(ver_str=None):
57 """parse a ssh version string into a tuple"""
58 if ver_str is None:
59 ver_str = _run_ssh_version()
60 m = re.match(r'^OpenSSH_([0-9.]+)(p[0-9]+)?\s', ver_str)
61 if m:
62 return tuple(int(x) for x in m.group(1).split('.'))
63 else:
64 return ()
65
66
67def ssh_version():
68 """return ssh version as a tuple"""
69 global _ssh_version
70 if _ssh_version is None:
71 try:
72 _ssh_version = _parse_ssh_version()
73 except subprocess.CalledProcessError:
74 print('fatal: unable to detect ssh version', file=sys.stderr)
75 sys.exit(1)
76 return _ssh_version
Shawn O. Pearcefb231612009-04-10 18:53:46 -070077
David Pursehouse819827a2020-02-12 15:20:19 +090078
Nico Sallembien1c85f4e2010-04-27 14:35:27 -070079def ssh_sock(create=True):
Shawn O. Pearcefb231612009-04-10 18:53:46 -070080 global _ssh_sock_path
81 if _ssh_sock_path is None:
82 if not create:
83 return None
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +020084 tmp_dir = '/tmp'
85 if not os.path.exists(tmp_dir):
86 tmp_dir = tempfile.gettempdir()
Anders Björklund8e0fe192020-02-18 14:08:35 +010087 if ssh_version() < (6, 7):
88 tokens = '%r@%h:%p'
89 else:
90 tokens = '%C' # hash of %l%h%p%r
Shawn O. Pearcefb231612009-04-10 18:53:46 -070091 _ssh_sock_path = os.path.join(
David Pursehouseabdf7502020-02-12 14:58:39 +090092 tempfile.mkdtemp('', 'ssh-', tmp_dir),
Anders Björklund8e0fe192020-02-18 14:08:35 +010093 'master-' + tokens)
Shawn O. Pearcefb231612009-04-10 18:53:46 -070094 return _ssh_sock_path
95
David Pursehouse819827a2020-02-12 15:20:19 +090096
Shawn O. Pearcefb231612009-04-10 18:53:46 -070097def _ssh_proxy():
98 global _ssh_proxy_path
99 if _ssh_proxy_path is None:
100 _ssh_proxy_path = os.path.join(
David Pursehouseabdf7502020-02-12 14:58:39 +0900101 os.path.dirname(__file__),
102 'git_ssh')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700103 return _ssh_proxy_path
104
David Pursehouse819827a2020-02-12 15:20:19 +0900105
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -0700106def _add_ssh_client(p):
107 _ssh_clients.append(p)
108
David Pursehouse819827a2020-02-12 15:20:19 +0900109
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -0700110def _remove_ssh_client(p):
111 try:
112 _ssh_clients.remove(p)
113 except ValueError:
114 pass
115
David Pursehouse819827a2020-02-12 15:20:19 +0900116
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -0700117def terminate_ssh_clients():
118 global _ssh_clients
119 for p in _ssh_clients:
120 try:
121 os.kill(p.pid, SIGTERM)
122 p.wait()
123 except OSError:
124 pass
125 _ssh_clients = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700126
David Pursehouse819827a2020-02-12 15:20:19 +0900127
Shawn O. Pearce334851e2011-09-19 08:05:31 -0700128_git_version = None
129
David Pursehouse819827a2020-02-12 15:20:19 +0900130
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700131class _GitCall(object):
Shawn O. Pearce334851e2011-09-19 08:05:31 -0700132 def version_tuple(self):
133 global _git_version
Shawn O. Pearce334851e2011-09-19 08:05:31 -0700134 if _git_version is None:
Mike Frysingerca540ae2019-07-10 15:42:30 -0400135 _git_version = Wrapper().ParseGitVersion()
Conley Owensff0a3c82014-01-30 14:46:03 -0800136 if _git_version is None:
Mike Frysingerca540ae2019-07-10 15:42:30 -0400137 print('fatal: unable to detect git version', file=sys.stderr)
Shawn O. Pearce334851e2011-09-19 08:05:31 -0700138 sys.exit(1)
139 return _git_version
140
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700141 def __getattr__(self, name):
David Pursehouse54a4e602020-02-12 14:31:05 +0900142 name = name.replace('_', '-')
David Pursehouse819827a2020-02-12 15:20:19 +0900143
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700144 def fun(*cmdv):
145 command = [name]
146 command.extend(cmdv)
147 return GitCommand(None, command).Wait() == 0
148 return fun
David Pursehouse819827a2020-02-12 15:20:19 +0900149
150
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700151git = _GitCall()
152
Mike Frysinger369814b2019-07-10 17:10:07 -0400153
Mike Frysinger71b0f312019-09-30 22:39:49 -0400154def RepoSourceVersion():
155 """Return the version of the repo.git tree."""
156 ver = getattr(RepoSourceVersion, 'version', None)
Mike Frysinger369814b2019-07-10 17:10:07 -0400157
Mike Frysinger71b0f312019-09-30 22:39:49 -0400158 # We avoid GitCommand so we don't run into circular deps -- GitCommand needs
159 # to initialize version info we provide.
160 if ver is None:
161 env = GitCommand._GetBasicEnv()
162
163 proj = os.path.dirname(os.path.abspath(__file__))
164 env[GIT_DIR] = os.path.join(proj, '.git')
Mike Frysingerf3079162021-02-16 02:38:21 -0500165 result = subprocess.run([GIT, 'describe', HEAD], stdout=subprocess.PIPE,
166 encoding='utf-8', env=env, check=False)
167 if result.returncode == 0:
168 ver = result.stdout.strip()
Mike Frysinger71b0f312019-09-30 22:39:49 -0400169 if ver.startswith('v'):
170 ver = ver[1:]
171 else:
172 ver = 'unknown'
173 setattr(RepoSourceVersion, 'version', ver)
174
175 return ver
176
177
178class UserAgent(object):
179 """Mange User-Agent settings when talking to external services
Mike Frysinger369814b2019-07-10 17:10:07 -0400180
181 We follow the style as documented here:
182 https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent
183 """
Mike Frysinger369814b2019-07-10 17:10:07 -0400184
Mike Frysinger71b0f312019-09-30 22:39:49 -0400185 _os = None
186 _repo_ua = None
Mike Frysinger2f0951b2019-07-10 17:13:46 -0400187 _git_ua = None
Mike Frysinger369814b2019-07-10 17:10:07 -0400188
Mike Frysinger71b0f312019-09-30 22:39:49 -0400189 @property
190 def os(self):
191 """The operating system name."""
192 if self._os is None:
193 os_name = sys.platform
194 if os_name.lower().startswith('linux'):
195 os_name = 'Linux'
196 elif os_name == 'win32':
197 os_name = 'Win32'
198 elif os_name == 'cygwin':
199 os_name = 'Cygwin'
200 elif os_name == 'darwin':
201 os_name = 'Darwin'
202 self._os = os_name
Mike Frysinger369814b2019-07-10 17:10:07 -0400203
Mike Frysinger71b0f312019-09-30 22:39:49 -0400204 return self._os
Mike Frysinger369814b2019-07-10 17:10:07 -0400205
Mike Frysinger71b0f312019-09-30 22:39:49 -0400206 @property
207 def repo(self):
208 """The UA when connecting directly from repo."""
209 if self._repo_ua is None:
210 py_version = sys.version_info
211 self._repo_ua = 'git-repo/%s (%s) git/%s Python/%d.%d.%d' % (
212 RepoSourceVersion(),
213 self.os,
214 git.version_tuple().full,
215 py_version.major, py_version.minor, py_version.micro)
Mike Frysinger369814b2019-07-10 17:10:07 -0400216
Mike Frysinger71b0f312019-09-30 22:39:49 -0400217 return self._repo_ua
Mike Frysinger369814b2019-07-10 17:10:07 -0400218
Mike Frysinger2f0951b2019-07-10 17:13:46 -0400219 @property
220 def git(self):
221 """The UA when running git."""
222 if self._git_ua is None:
223 self._git_ua = 'git/%s (%s) git-repo/%s' % (
224 git.version_tuple().full,
225 self.os,
226 RepoSourceVersion())
227
228 return self._git_ua
229
David Pursehouse819827a2020-02-12 15:20:19 +0900230
Mike Frysinger71b0f312019-09-30 22:39:49 -0400231user_agent = UserAgent()
Mike Frysinger369814b2019-07-10 17:10:07 -0400232
David Pursehouse819827a2020-02-12 15:20:19 +0900233
Xin Li745be2e2019-06-03 11:24:30 -0700234def git_require(min_version, fail=False, msg=''):
Shawn O. Pearce334851e2011-09-19 08:05:31 -0700235 git_version = git.version_tuple()
236 if min_version <= git_version:
Shawn O. Pearce2ec00b92009-06-12 09:32:50 -0700237 return True
238 if fail:
David Pursehouse7e6dd2d2012-10-25 12:40:51 +0900239 need = '.'.join(map(str, min_version))
Xin Li745be2e2019-06-03 11:24:30 -0700240 if msg:
241 msg = ' for ' + msg
242 print('fatal: git %s or later required%s' % (need, msg), file=sys.stderr)
Shawn O. Pearce2ec00b92009-06-12 09:32:50 -0700243 sys.exit(1)
244 return False
245
David Pursehouse819827a2020-02-12 15:20:19 +0900246
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700247class GitCommand(object):
248 def __init__(self,
249 project,
250 cmdv,
David Pursehousee5913ae2020-02-12 13:56:59 +0900251 bare=False,
Mike Frysingerf37b9822021-02-16 15:38:53 -0500252 input=None,
David Pursehousee5913ae2020-02-12 13:56:59 +0900253 capture_stdout=False,
254 capture_stderr=False,
Mike Frysinger31990f02020-02-17 01:35:18 -0500255 merge_output=False,
David Pursehousee5913ae2020-02-12 13:56:59 +0900256 disable_editor=False,
257 ssh_proxy=False,
258 cwd=None,
259 gitdir=None):
Mike Frysinger71b0f312019-09-30 22:39:49 -0400260 env = self._GetBasicEnv()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700261
262 if disable_editor:
Mike Frysinger56ce3462019-12-04 19:30:48 -0500263 env['GIT_EDITOR'] = ':'
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700264 if ssh_proxy:
Mike Frysinger56ce3462019-12-04 19:30:48 -0500265 env['REPO_SSH_SOCK'] = ssh_sock()
266 env['GIT_SSH'] = _ssh_proxy()
267 env['GIT_SSH_VARIANT'] = 'ssh'
Shawn O. Pearce62d0b102012-06-05 15:11:15 -0700268 if 'http_proxy' in env and 'darwin' == sys.platform:
Shawn O. Pearce337aee02012-06-13 10:40:46 -0700269 s = "'http.proxy=%s'" % (env['http_proxy'],)
Shawn O. Pearce62d0b102012-06-05 15:11:15 -0700270 p = env.get('GIT_CONFIG_PARAMETERS')
271 if p is not None:
272 s = p + ' ' + s
Mike Frysinger56ce3462019-12-04 19:30:48 -0500273 env['GIT_CONFIG_PARAMETERS'] = s
Dan Willemsen466b8c42015-11-25 13:26:39 -0800274 if 'GIT_ALLOW_PROTOCOL' not in env:
Mike Frysinger56ce3462019-12-04 19:30:48 -0500275 env['GIT_ALLOW_PROTOCOL'] = (
276 'file:git:http:https:ssh:persistent-http:persistent-https:sso:rpc')
277 env['GIT_HTTP_USER_AGENT'] = user_agent.git
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700278
279 if project:
280 if not cwd:
281 cwd = project.worktree
282 if not gitdir:
283 gitdir = project.gitdir
284
285 command = [GIT]
286 if bare:
287 if gitdir:
Mike Frysinger56ce3462019-12-04 19:30:48 -0500288 env[GIT_DIR] = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700289 cwd = None
John L. Villalovos9c76f672015-03-16 20:49:10 -0700290 command.append(cmdv[0])
291 # Need to use the --progress flag for fetch/clone so output will be
292 # displayed as by default git only does progress output if stderr is a TTY.
293 if sys.stderr.isatty() and cmdv[0] in ('fetch', 'clone'):
294 if '--progress' not in cmdv and '--quiet' not in cmdv:
295 command.append('--progress')
296 command.extend(cmdv[1:])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700297
Mike Frysingerf37b9822021-02-16 15:38:53 -0500298 stdin = subprocess.PIPE if input else None
Mike Frysingerc87c1862021-02-16 17:18:12 -0500299 stdout = subprocess.PIPE if capture_stdout else None
300 stderr = (subprocess.STDOUT if merge_output else
301 (subprocess.PIPE if capture_stderr else None))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700302
Shawn O. Pearcead3193a2009-04-18 09:54:51 -0700303 if IsTrace():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700304 global LAST_CWD
305 global LAST_GITDIR
306
307 dbg = ''
308
309 if cwd and LAST_CWD != cwd:
310 if LAST_GITDIR or LAST_CWD:
311 dbg += '\n'
312 dbg += ': cd %s\n' % cwd
313 LAST_CWD = cwd
314
315 if GIT_DIR in env and LAST_GITDIR != env[GIT_DIR]:
316 if LAST_GITDIR or LAST_CWD:
317 dbg += '\n'
318 dbg += ': export GIT_DIR=%s\n' % env[GIT_DIR]
319 LAST_GITDIR = env[GIT_DIR]
320
321 dbg += ': '
322 dbg += ' '.join(command)
323 if stdin == subprocess.PIPE:
324 dbg += ' 0<|'
325 if stdout == subprocess.PIPE:
326 dbg += ' 1>|'
327 if stderr == subprocess.PIPE:
328 dbg += ' 2>|'
Mike Frysinger31990f02020-02-17 01:35:18 -0500329 elif stderr == subprocess.STDOUT:
330 dbg += ' 2>&1'
Shawn O. Pearcead3193a2009-04-18 09:54:51 -0700331 Trace('%s', dbg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700332
333 try:
334 p = subprocess.Popen(command,
David Pursehousee5913ae2020-02-12 13:56:59 +0900335 cwd=cwd,
336 env=env,
Mike Frysingerc87c1862021-02-16 17:18:12 -0500337 encoding='utf-8',
338 errors='backslashreplace',
David Pursehousee5913ae2020-02-12 13:56:59 +0900339 stdin=stdin,
340 stdout=stdout,
341 stderr=stderr)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700342 except Exception as e:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700343 raise GitError('%s: %s' % (command[1], e))
344
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -0700345 if ssh_proxy:
346 _add_ssh_client(p)
347
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700348 self.process = p
Mike Frysingerf37b9822021-02-16 15:38:53 -0500349 if input:
350 if isinstance(input, str):
351 input = input.encode('utf-8')
352 p.stdin.write(input)
353 p.stdin.close()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700354
Mike Frysingerc5bbea82021-02-16 15:45:19 -0500355 try:
Mike Frysingerc87c1862021-02-16 17:18:12 -0500356 self.stdout, self.stderr = p.communicate()
Mike Frysingerc5bbea82021-02-16 15:45:19 -0500357 finally:
358 _remove_ssh_client(p)
Mike Frysingerc87c1862021-02-16 17:18:12 -0500359 self.rc = p.wait()
Mike Frysingerc5bbea82021-02-16 15:45:19 -0500360
Mike Frysinger71b0f312019-09-30 22:39:49 -0400361 @staticmethod
362 def _GetBasicEnv():
363 """Return a basic env for running git under.
364
365 This is guaranteed to be side-effect free.
366 """
367 env = os.environ.copy()
368 for key in (REPO_TRACE,
369 GIT_DIR,
370 'GIT_ALTERNATE_OBJECT_DIRECTORIES',
371 'GIT_OBJECT_DIRECTORY',
372 'GIT_WORK_TREE',
373 'GIT_GRAFT_FILE',
374 'GIT_INDEX_FILE'):
375 env.pop(key, None)
376 return env
377
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700378 def Wait(self):
Mike Frysingerc5bbea82021-02-16 15:45:19 -0500379 return self.rc