blob: 3a3bb34d2648b955fb07603a6e82f1b7fb2a14f1 [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
Mike Frysinger8e768ea2021-05-06 00:28:32 -040015import functools
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070016import os
17import sys
18import subprocess
Sam Sacconed6863652022-11-15 23:57:22 +000019from typing import Any, Optional
Renaud Paquay2e702912016-11-01 11:23:38 -070020
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021from error import GitError
Mike Frysinger71b0f312019-09-30 22:39:49 -040022from git_refs import HEAD
Renaud Paquay2e702912016-11-01 11:23:38 -070023import platform_utils
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040024from repo_trace import REPO_TRACE, IsTrace, Trace
Conley Owensff0a3c82014-01-30 14:46:03 -080025from wrapper import Wrapper
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070026
27GIT = 'git'
Mike Frysinger82caef62020-02-11 18:51:08 -050028# NB: These do not need to be kept in sync with the repo launcher script.
29# These may be much newer as it allows the repo launcher to roll between
30# different repo releases while source versions might require a newer git.
31#
32# The soft version is when we start warning users that the version is old and
33# we'll be dropping support for it. We'll refuse to work with versions older
34# than the hard version.
35#
36# git-1.7 is in (EOL) Ubuntu Precise. git-1.9 is in Ubuntu Trusty.
37MIN_GIT_VERSION_SOFT = (1, 9, 1)
38MIN_GIT_VERSION_HARD = (1, 7, 2)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070039GIT_DIR = 'GIT_DIR'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070040
41LAST_GITDIR = None
42LAST_CWD = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070043
David Pursehouse819827a2020-02-12 15:20:19 +090044
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070045class _GitCall(object):
Mike Frysinger8e768ea2021-05-06 00:28:32 -040046 @functools.lru_cache(maxsize=None)
Shawn O. Pearce334851e2011-09-19 08:05:31 -070047 def version_tuple(self):
Mike Frysinger8e768ea2021-05-06 00:28:32 -040048 ret = Wrapper().ParseGitVersion()
49 if ret is None:
50 print('fatal: unable to detect git version', file=sys.stderr)
51 sys.exit(1)
52 return ret
Shawn O. Pearce334851e2011-09-19 08:05:31 -070053
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070054 def __getattr__(self, name):
David Pursehouse54a4e602020-02-12 14:31:05 +090055 name = name.replace('_', '-')
David Pursehouse819827a2020-02-12 15:20:19 +090056
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070057 def fun(*cmdv):
58 command = [name]
59 command.extend(cmdv)
60 return GitCommand(None, command).Wait() == 0
61 return fun
David Pursehouse819827a2020-02-12 15:20:19 +090062
63
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070064git = _GitCall()
65
Mike Frysinger369814b2019-07-10 17:10:07 -040066
Mike Frysinger71b0f312019-09-30 22:39:49 -040067def RepoSourceVersion():
68 """Return the version of the repo.git tree."""
69 ver = getattr(RepoSourceVersion, 'version', None)
Mike Frysinger369814b2019-07-10 17:10:07 -040070
Mike Frysinger71b0f312019-09-30 22:39:49 -040071 # We avoid GitCommand so we don't run into circular deps -- GitCommand needs
72 # to initialize version info we provide.
73 if ver is None:
74 env = GitCommand._GetBasicEnv()
75
76 proj = os.path.dirname(os.path.abspath(__file__))
77 env[GIT_DIR] = os.path.join(proj, '.git')
Mike Frysingerf3079162021-02-16 02:38:21 -050078 result = subprocess.run([GIT, 'describe', HEAD], stdout=subprocess.PIPE,
Xin Li0cb6e922021-06-16 10:19:00 -070079 stderr=subprocess.DEVNULL, encoding='utf-8',
80 env=env, check=False)
Mike Frysingerf3079162021-02-16 02:38:21 -050081 if result.returncode == 0:
82 ver = result.stdout.strip()
Mike Frysinger71b0f312019-09-30 22:39:49 -040083 if ver.startswith('v'):
84 ver = ver[1:]
85 else:
86 ver = 'unknown'
87 setattr(RepoSourceVersion, 'version', ver)
88
89 return ver
90
91
92class UserAgent(object):
93 """Mange User-Agent settings when talking to external services
Mike Frysinger369814b2019-07-10 17:10:07 -040094
95 We follow the style as documented here:
96 https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent
97 """
Mike Frysinger369814b2019-07-10 17:10:07 -040098
Mike Frysinger71b0f312019-09-30 22:39:49 -040099 _os = None
100 _repo_ua = None
Mike Frysinger2f0951b2019-07-10 17:13:46 -0400101 _git_ua = None
Mike Frysinger369814b2019-07-10 17:10:07 -0400102
Mike Frysinger71b0f312019-09-30 22:39:49 -0400103 @property
104 def os(self):
105 """The operating system name."""
106 if self._os is None:
107 os_name = sys.platform
108 if os_name.lower().startswith('linux'):
109 os_name = 'Linux'
110 elif os_name == 'win32':
111 os_name = 'Win32'
112 elif os_name == 'cygwin':
113 os_name = 'Cygwin'
114 elif os_name == 'darwin':
115 os_name = 'Darwin'
116 self._os = os_name
Mike Frysinger369814b2019-07-10 17:10:07 -0400117
Mike Frysinger71b0f312019-09-30 22:39:49 -0400118 return self._os
Mike Frysinger369814b2019-07-10 17:10:07 -0400119
Mike Frysinger71b0f312019-09-30 22:39:49 -0400120 @property
121 def repo(self):
122 """The UA when connecting directly from repo."""
123 if self._repo_ua is None:
124 py_version = sys.version_info
125 self._repo_ua = 'git-repo/%s (%s) git/%s Python/%d.%d.%d' % (
126 RepoSourceVersion(),
127 self.os,
128 git.version_tuple().full,
129 py_version.major, py_version.minor, py_version.micro)
Mike Frysinger369814b2019-07-10 17:10:07 -0400130
Mike Frysinger71b0f312019-09-30 22:39:49 -0400131 return self._repo_ua
Mike Frysinger369814b2019-07-10 17:10:07 -0400132
Mike Frysinger2f0951b2019-07-10 17:13:46 -0400133 @property
134 def git(self):
135 """The UA when running git."""
136 if self._git_ua is None:
137 self._git_ua = 'git/%s (%s) git-repo/%s' % (
138 git.version_tuple().full,
139 self.os,
140 RepoSourceVersion())
141
142 return self._git_ua
143
David Pursehouse819827a2020-02-12 15:20:19 +0900144
Mike Frysinger71b0f312019-09-30 22:39:49 -0400145user_agent = UserAgent()
Mike Frysinger369814b2019-07-10 17:10:07 -0400146
David Pursehouse819827a2020-02-12 15:20:19 +0900147
Xin Li745be2e2019-06-03 11:24:30 -0700148def git_require(min_version, fail=False, msg=''):
Shawn O. Pearce334851e2011-09-19 08:05:31 -0700149 git_version = git.version_tuple()
150 if min_version <= git_version:
Shawn O. Pearce2ec00b92009-06-12 09:32:50 -0700151 return True
152 if fail:
David Pursehouse7e6dd2d2012-10-25 12:40:51 +0900153 need = '.'.join(map(str, min_version))
Xin Li745be2e2019-06-03 11:24:30 -0700154 if msg:
155 msg = ' for ' + msg
156 print('fatal: git %s or later required%s' % (need, msg), file=sys.stderr)
Shawn O. Pearce2ec00b92009-06-12 09:32:50 -0700157 sys.exit(1)
158 return False
159
David Pursehouse819827a2020-02-12 15:20:19 +0900160
Sam Sacconed6863652022-11-15 23:57:22 +0000161def _build_env(
162 _kwargs_only=(),
163 bare: Optional[bool] = False,
164 disable_editor: Optional[bool] = False,
165 ssh_proxy: Optional[Any] = None,
166 gitdir: Optional[str] = None,
167 objdir: Optional[str] = None
168):
169 """Constucts an env dict for command execution."""
170
171 assert _kwargs_only == (), '_build_env only accepts keyword arguments.'
172
173 env = GitCommand._GetBasicEnv()
174
175 if disable_editor:
176 env['GIT_EDITOR'] = ':'
177 if ssh_proxy:
178 env['REPO_SSH_SOCK'] = ssh_proxy.sock()
179 env['GIT_SSH'] = ssh_proxy.proxy
180 env['GIT_SSH_VARIANT'] = 'ssh'
181 if 'http_proxy' in env and 'darwin' == sys.platform:
182 s = "'http.proxy=%s'" % (env['http_proxy'],)
183 p = env.get('GIT_CONFIG_PARAMETERS')
184 if p is not None:
185 s = p + ' ' + s
186 env['GIT_CONFIG_PARAMETERS'] = s
187 if 'GIT_ALLOW_PROTOCOL' not in env:
188 env['GIT_ALLOW_PROTOCOL'] = (
189 'file:git:http:https:ssh:persistent-http:persistent-https:sso:rpc')
190 env['GIT_HTTP_USER_AGENT'] = user_agent.git
191
192 if objdir:
193 # Set to the place we want to save the objects.
194 env['GIT_OBJECT_DIRECTORY'] = objdir
195
196 alt_objects = os.path.join(gitdir, 'objects') if gitdir else None
197 if (alt_objects and
198 os.path.realpath(alt_objects) != os.path.realpath(objdir)):
199 # Allow git to search the original place in case of local or unique refs
200 # that git will attempt to resolve even if we aren't fetching them.
201 env['GIT_ALTERNATE_OBJECT_DIRECTORIES'] = alt_objects
202 if bare and gitdir is not None:
203 env[GIT_DIR] = gitdir
204
205 return env
206
207
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700208class GitCommand(object):
Mike Frysinger790f4ce2020-12-07 22:04:55 -0500209 """Wrapper around a single git invocation."""
210
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700211 def __init__(self,
212 project,
213 cmdv,
David Pursehousee5913ae2020-02-12 13:56:59 +0900214 bare=False,
Mike Frysingerf37b9822021-02-16 15:38:53 -0500215 input=None,
David Pursehousee5913ae2020-02-12 13:56:59 +0900216 capture_stdout=False,
217 capture_stderr=False,
Mike Frysinger31990f02020-02-17 01:35:18 -0500218 merge_output=False,
David Pursehousee5913ae2020-02-12 13:56:59 +0900219 disable_editor=False,
Mike Frysinger339f2df2021-05-06 00:44:42 -0400220 ssh_proxy=None,
David Pursehousee5913ae2020-02-12 13:56:59 +0900221 cwd=None,
Mike Frysinger67d6cdf2021-12-23 17:36:09 -0500222 gitdir=None,
223 objdir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700224
225 if project:
226 if not cwd:
227 cwd = project.worktree
228 if not gitdir:
229 gitdir = project.gitdir
Sam Sacconed6863652022-11-15 23:57:22 +0000230
Mike Frysinger67d6cdf2021-12-23 17:36:09 -0500231 # Git on Windows wants its paths only using / for reliability.
232 if platform_utils.isWindows():
233 if objdir:
234 objdir = objdir.replace('\\', '/')
235 if gitdir:
236 gitdir = gitdir.replace('\\', '/')
237
Sam Sacconed6863652022-11-15 23:57:22 +0000238 env = _build_env(
239 disable_editor=disable_editor,
240 ssh_proxy=ssh_proxy,
241 objdir=objdir,
242 gitdir=gitdir,
243 bare=bare,
244 )
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700245
246 command = [GIT]
247 if bare:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700248 cwd = None
John L. Villalovos9c76f672015-03-16 20:49:10 -0700249 command.append(cmdv[0])
250 # Need to use the --progress flag for fetch/clone so output will be
251 # displayed as by default git only does progress output if stderr is a TTY.
252 if sys.stderr.isatty() and cmdv[0] in ('fetch', 'clone'):
253 if '--progress' not in cmdv and '--quiet' not in cmdv:
254 command.append('--progress')
255 command.extend(cmdv[1:])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700256
Mike Frysingerf37b9822021-02-16 15:38:53 -0500257 stdin = subprocess.PIPE if input else None
Mike Frysingerc87c1862021-02-16 17:18:12 -0500258 stdout = subprocess.PIPE if capture_stdout else None
259 stderr = (subprocess.STDOUT if merge_output else
260 (subprocess.PIPE if capture_stderr else None))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700261
Joanna Wanga6c52f52022-11-03 16:51:19 -0400262 dbg = ''
Shawn O. Pearcead3193a2009-04-18 09:54:51 -0700263 if IsTrace():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700264 global LAST_CWD
265 global LAST_GITDIR
266
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700267 if cwd and LAST_CWD != cwd:
268 if LAST_GITDIR or LAST_CWD:
269 dbg += '\n'
270 dbg += ': cd %s\n' % cwd
271 LAST_CWD = cwd
272
273 if GIT_DIR in env and LAST_GITDIR != env[GIT_DIR]:
274 if LAST_GITDIR or LAST_CWD:
275 dbg += '\n'
276 dbg += ': export GIT_DIR=%s\n' % env[GIT_DIR]
277 LAST_GITDIR = env[GIT_DIR]
278
Mike Frysinger67d6cdf2021-12-23 17:36:09 -0500279 if 'GIT_OBJECT_DIRECTORY' in env:
280 dbg += ': export GIT_OBJECT_DIRECTORY=%s\n' % env['GIT_OBJECT_DIRECTORY']
281 if 'GIT_ALTERNATE_OBJECT_DIRECTORIES' in env:
282 dbg += ': export GIT_ALTERNATE_OBJECT_DIRECTORIES=%s\n' % env['GIT_ALTERNATE_OBJECT_DIRECTORIES']
283
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700284 dbg += ': '
285 dbg += ' '.join(command)
286 if stdin == subprocess.PIPE:
287 dbg += ' 0<|'
288 if stdout == subprocess.PIPE:
289 dbg += ' 1>|'
290 if stderr == subprocess.PIPE:
291 dbg += ' 2>|'
Mike Frysinger31990f02020-02-17 01:35:18 -0500292 elif stderr == subprocess.STDOUT:
293 dbg += ' 2>&1'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700294
Joanna Wanga6c52f52022-11-03 16:51:19 -0400295 with Trace('git command %s %s with debug: %s', LAST_GITDIR, command, dbg):
296 try:
297 p = subprocess.Popen(command,
298 cwd=cwd,
299 env=env,
300 encoding='utf-8',
301 errors='backslashreplace',
302 stdin=stdin,
303 stdout=stdout,
304 stderr=stderr)
305 except Exception as e:
306 raise GitError('%s: %s' % (command[1], e))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700307
Mike Frysinger339f2df2021-05-06 00:44:42 -0400308 if ssh_proxy:
Joanna Wanga6c52f52022-11-03 16:51:19 -0400309 ssh_proxy.add_client(p)
310
311 self.process = p
312
313 try:
314 self.stdout, self.stderr = p.communicate(input=input)
315 finally:
316 if ssh_proxy:
317 ssh_proxy.remove_client(p)
318 self.rc = p.wait()
Mike Frysingerc5bbea82021-02-16 15:45:19 -0500319
Mike Frysinger71b0f312019-09-30 22:39:49 -0400320 @staticmethod
321 def _GetBasicEnv():
322 """Return a basic env for running git under.
323
324 This is guaranteed to be side-effect free.
325 """
326 env = os.environ.copy()
327 for key in (REPO_TRACE,
328 GIT_DIR,
329 'GIT_ALTERNATE_OBJECT_DIRECTORIES',
330 'GIT_OBJECT_DIRECTORY',
331 'GIT_WORK_TREE',
332 'GIT_GRAFT_FILE',
333 'GIT_INDEX_FILE'):
334 env.pop(key, None)
335 return env
336
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700337 def Wait(self):
Mike Frysingerc5bbea82021-02-16 15:45:19 -0500338 return self.rc