blob: 71b464c63975baea1729cf3cad113a9980298c57 [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
Jason Changf19b3102023-09-01 16:07:34 -070016import json
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070017import os
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070018import subprocess
Mike Frysinger64477332023-08-21 21:20:32 -040019import sys
Sam Sacconed6863652022-11-15 23:57:22 +000020from typing import Any, Optional
Renaud Paquay2e702912016-11-01 11:23:38 -070021
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070022from error import GitError
Jason Changf9aacd42023-08-03 14:38:00 -070023from error import RepoExitError
Mike Frysinger71b0f312019-09-30 22:39:49 -040024from git_refs import HEAD
Jason Changf19b3102023-09-01 16:07:34 -070025from git_trace2_event_log_base import BaseEventLog
Renaud Paquay2e702912016-11-01 11:23:38 -070026import platform_utils
Mike Frysinger64477332023-08-21 21:20:32 -040027from repo_trace import IsTrace
28from repo_trace import REPO_TRACE
29from repo_trace import Trace
Conley Owensff0a3c82014-01-30 14:46:03 -080030from wrapper import Wrapper
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070031
Mike Frysinger64477332023-08-21 21:20:32 -040032
Gavin Makea2e3302023-03-11 06:46:20 +000033GIT = "git"
Mike Frysinger82caef62020-02-11 18:51:08 -050034# NB: These do not need to be kept in sync with the repo launcher script.
35# These may be much newer as it allows the repo launcher to roll between
36# different repo releases while source versions might require a newer git.
37#
38# The soft version is when we start warning users that the version is old and
39# we'll be dropping support for it. We'll refuse to work with versions older
40# than the hard version.
41#
42# git-1.7 is in (EOL) Ubuntu Precise. git-1.9 is in Ubuntu Trusty.
43MIN_GIT_VERSION_SOFT = (1, 9, 1)
44MIN_GIT_VERSION_HARD = (1, 7, 2)
Gavin Makea2e3302023-03-11 06:46:20 +000045GIT_DIR = "GIT_DIR"
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070046
47LAST_GITDIR = None
48LAST_CWD = None
Jason Changa6413f52023-07-26 13:23:40 -070049DEFAULT_GIT_FAIL_MESSAGE = "git command failure"
Jason Changf19b3102023-09-01 16:07:34 -070050ERROR_EVENT_LOGGING_PREFIX = "RepoGitCommandError"
Jason Changa6413f52023-07-26 13:23:40 -070051# Common line length limit
52GIT_ERROR_STDOUT_LINES = 1
53GIT_ERROR_STDERR_LINES = 1
Jason Changf9aacd42023-08-03 14:38:00 -070054INVALID_GIT_EXIT_CODE = 126
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070055
David Pursehouse819827a2020-02-12 15:20:19 +090056
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070057class _GitCall(object):
Gavin Makea2e3302023-03-11 06:46:20 +000058 @functools.lru_cache(maxsize=None)
59 def version_tuple(self):
60 ret = Wrapper().ParseGitVersion()
61 if ret is None:
Jason Changf9aacd42023-08-03 14:38:00 -070062 msg = "fatal: unable to detect git version"
63 print(msg, file=sys.stderr)
64 raise GitRequireError(msg)
Gavin Makea2e3302023-03-11 06:46:20 +000065 return ret
Shawn O. Pearce334851e2011-09-19 08:05:31 -070066
Gavin Makea2e3302023-03-11 06:46:20 +000067 def __getattr__(self, name):
68 name = name.replace("_", "-")
David Pursehouse819827a2020-02-12 15:20:19 +090069
Gavin Makea2e3302023-03-11 06:46:20 +000070 def fun(*cmdv):
71 command = [name]
72 command.extend(cmdv)
Jason Changf19b3102023-09-01 16:07:34 -070073 return GitCommand(None, command, add_event_log=False).Wait() == 0
Gavin Makea2e3302023-03-11 06:46:20 +000074
75 return fun
David Pursehouse819827a2020-02-12 15:20:19 +090076
77
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070078git = _GitCall()
79
Mike Frysinger369814b2019-07-10 17:10:07 -040080
Mike Frysinger71b0f312019-09-30 22:39:49 -040081def RepoSourceVersion():
Gavin Makea2e3302023-03-11 06:46:20 +000082 """Return the version of the repo.git tree."""
83 ver = getattr(RepoSourceVersion, "version", None)
Mike Frysinger369814b2019-07-10 17:10:07 -040084
Gavin Makea2e3302023-03-11 06:46:20 +000085 # We avoid GitCommand so we don't run into circular deps -- GitCommand needs
86 # to initialize version info we provide.
87 if ver is None:
88 env = GitCommand._GetBasicEnv()
Mike Frysinger71b0f312019-09-30 22:39:49 -040089
Gavin Makea2e3302023-03-11 06:46:20 +000090 proj = os.path.dirname(os.path.abspath(__file__))
91 env[GIT_DIR] = os.path.join(proj, ".git")
92 result = subprocess.run(
93 [GIT, "describe", HEAD],
94 stdout=subprocess.PIPE,
95 stderr=subprocess.DEVNULL,
96 encoding="utf-8",
97 env=env,
98 check=False,
99 )
100 if result.returncode == 0:
101 ver = result.stdout.strip()
102 if ver.startswith("v"):
103 ver = ver[1:]
104 else:
105 ver = "unknown"
106 setattr(RepoSourceVersion, "version", ver)
Mike Frysinger71b0f312019-09-30 22:39:49 -0400107
Gavin Makea2e3302023-03-11 06:46:20 +0000108 return ver
Mike Frysinger71b0f312019-09-30 22:39:49 -0400109
110
Jason Changf19b3102023-09-01 16:07:34 -0700111@functools.lru_cache(maxsize=None)
112def GetEventTargetPath():
113 """Get the 'trace2.eventtarget' path from git configuration.
114
115 Returns:
116 path: git config's 'trace2.eventtarget' path if it exists, or None
117 """
118 path = None
119 cmd = ["config", "--get", "trace2.eventtarget"]
120 # TODO(https://crbug.com/gerrit/13706): Use GitConfig when it supports
121 # system git config variables.
122 p = GitCommand(
123 None,
124 cmd,
125 capture_stdout=True,
126 capture_stderr=True,
127 bare=True,
128 add_event_log=False,
129 )
130 retval = p.Wait()
131 if retval == 0:
132 # Strip trailing carriage-return in path.
133 path = p.stdout.rstrip("\n")
134 elif retval != 1:
135 # `git config --get` is documented to produce an exit status of `1`
136 # if the requested variable is not present in the configuration.
137 # Report any other return value as an error.
138 print(
139 "repo: error: 'git config --get' call failed with return code: "
140 "%r, stderr: %r" % (retval, p.stderr),
141 file=sys.stderr,
142 )
143 return path
144
145
Mike Frysinger71b0f312019-09-30 22:39:49 -0400146class UserAgent(object):
Gavin Makea2e3302023-03-11 06:46:20 +0000147 """Mange User-Agent settings when talking to external services
Mike Frysinger369814b2019-07-10 17:10:07 -0400148
Gavin Makea2e3302023-03-11 06:46:20 +0000149 We follow the style as documented here:
150 https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent
151 """
Mike Frysinger369814b2019-07-10 17:10:07 -0400152
Gavin Makea2e3302023-03-11 06:46:20 +0000153 _os = None
154 _repo_ua = None
155 _git_ua = None
Mike Frysinger369814b2019-07-10 17:10:07 -0400156
Gavin Makea2e3302023-03-11 06:46:20 +0000157 @property
158 def os(self):
159 """The operating system name."""
160 if self._os is None:
161 os_name = sys.platform
162 if os_name.lower().startswith("linux"):
163 os_name = "Linux"
164 elif os_name == "win32":
165 os_name = "Win32"
166 elif os_name == "cygwin":
167 os_name = "Cygwin"
168 elif os_name == "darwin":
169 os_name = "Darwin"
170 self._os = os_name
Mike Frysinger369814b2019-07-10 17:10:07 -0400171
Gavin Makea2e3302023-03-11 06:46:20 +0000172 return self._os
Mike Frysinger369814b2019-07-10 17:10:07 -0400173
Gavin Makea2e3302023-03-11 06:46:20 +0000174 @property
175 def repo(self):
176 """The UA when connecting directly from repo."""
177 if self._repo_ua is None:
178 py_version = sys.version_info
179 self._repo_ua = "git-repo/%s (%s) git/%s Python/%d.%d.%d" % (
180 RepoSourceVersion(),
181 self.os,
182 git.version_tuple().full,
183 py_version.major,
184 py_version.minor,
185 py_version.micro,
186 )
Mike Frysinger369814b2019-07-10 17:10:07 -0400187
Gavin Makea2e3302023-03-11 06:46:20 +0000188 return self._repo_ua
Mike Frysinger369814b2019-07-10 17:10:07 -0400189
Gavin Makea2e3302023-03-11 06:46:20 +0000190 @property
191 def git(self):
192 """The UA when running git."""
193 if self._git_ua is None:
194 self._git_ua = "git/%s (%s) git-repo/%s" % (
195 git.version_tuple().full,
196 self.os,
197 RepoSourceVersion(),
198 )
Mike Frysinger2f0951b2019-07-10 17:13:46 -0400199
Gavin Makea2e3302023-03-11 06:46:20 +0000200 return self._git_ua
Mike Frysinger2f0951b2019-07-10 17:13:46 -0400201
David Pursehouse819827a2020-02-12 15:20:19 +0900202
Mike Frysinger71b0f312019-09-30 22:39:49 -0400203user_agent = UserAgent()
Mike Frysinger369814b2019-07-10 17:10:07 -0400204
David Pursehouse819827a2020-02-12 15:20:19 +0900205
Gavin Makea2e3302023-03-11 06:46:20 +0000206def git_require(min_version, fail=False, msg=""):
207 git_version = git.version_tuple()
208 if min_version <= git_version:
209 return True
210 if fail:
211 need = ".".join(map(str, min_version))
212 if msg:
213 msg = " for " + msg
Jason Changf9aacd42023-08-03 14:38:00 -0700214 error_msg = "fatal: git %s or later required%s" % (need, msg)
215 print(error_msg, file=sys.stderr)
216 raise GitRequireError(error_msg)
Gavin Makea2e3302023-03-11 06:46:20 +0000217 return False
Shawn O. Pearce2ec00b92009-06-12 09:32:50 -0700218
David Pursehouse819827a2020-02-12 15:20:19 +0900219
Sam Sacconed6863652022-11-15 23:57:22 +0000220def _build_env(
Sergiy Belozorov78e82ec2023-01-05 18:57:31 +0100221 _kwargs_only=(),
222 bare: Optional[bool] = False,
223 disable_editor: Optional[bool] = False,
224 ssh_proxy: Optional[Any] = None,
225 gitdir: Optional[str] = None,
Gavin Makea2e3302023-03-11 06:46:20 +0000226 objdir: Optional[str] = None,
Sam Sacconed6863652022-11-15 23:57:22 +0000227):
Gavin Makea2e3302023-03-11 06:46:20 +0000228 """Constucts an env dict for command execution."""
Sam Sacconed6863652022-11-15 23:57:22 +0000229
Gavin Makea2e3302023-03-11 06:46:20 +0000230 assert _kwargs_only == (), "_build_env only accepts keyword arguments."
Sam Sacconed6863652022-11-15 23:57:22 +0000231
Gavin Makea2e3302023-03-11 06:46:20 +0000232 env = GitCommand._GetBasicEnv()
Sam Sacconed6863652022-11-15 23:57:22 +0000233
Gavin Makea2e3302023-03-11 06:46:20 +0000234 if disable_editor:
235 env["GIT_EDITOR"] = ":"
236 if ssh_proxy:
237 env["REPO_SSH_SOCK"] = ssh_proxy.sock()
238 env["GIT_SSH"] = ssh_proxy.proxy
239 env["GIT_SSH_VARIANT"] = "ssh"
240 if "http_proxy" in env and "darwin" == sys.platform:
241 s = "'http.proxy=%s'" % (env["http_proxy"],)
242 p = env.get("GIT_CONFIG_PARAMETERS")
243 if p is not None:
244 s = p + " " + s
245 env["GIT_CONFIG_PARAMETERS"] = s
246 if "GIT_ALLOW_PROTOCOL" not in env:
247 env[
248 "GIT_ALLOW_PROTOCOL"
249 ] = "file:git:http:https:ssh:persistent-http:persistent-https:sso:rpc"
250 env["GIT_HTTP_USER_AGENT"] = user_agent.git
Sam Sacconed6863652022-11-15 23:57:22 +0000251
Gavin Makea2e3302023-03-11 06:46:20 +0000252 if objdir:
253 # Set to the place we want to save the objects.
254 env["GIT_OBJECT_DIRECTORY"] = objdir
Sam Sacconed6863652022-11-15 23:57:22 +0000255
Gavin Makea2e3302023-03-11 06:46:20 +0000256 alt_objects = os.path.join(gitdir, "objects") if gitdir else None
257 if alt_objects and os.path.realpath(alt_objects) != os.path.realpath(
258 objdir
259 ):
260 # Allow git to search the original place in case of local or unique
261 # refs that git will attempt to resolve even if we aren't fetching
262 # them.
263 env["GIT_ALTERNATE_OBJECT_DIRECTORIES"] = alt_objects
264 if bare and gitdir is not None:
265 env[GIT_DIR] = gitdir
Sam Sacconed6863652022-11-15 23:57:22 +0000266
Gavin Makea2e3302023-03-11 06:46:20 +0000267 return env
Sam Sacconed6863652022-11-15 23:57:22 +0000268
269
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700270class GitCommand(object):
Gavin Makea2e3302023-03-11 06:46:20 +0000271 """Wrapper around a single git invocation."""
Mike Frysinger790f4ce2020-12-07 22:04:55 -0500272
Gavin Makea2e3302023-03-11 06:46:20 +0000273 def __init__(
274 self,
275 project,
276 cmdv,
277 bare=False,
278 input=None,
279 capture_stdout=False,
280 capture_stderr=False,
281 merge_output=False,
282 disable_editor=False,
283 ssh_proxy=None,
284 cwd=None,
285 gitdir=None,
286 objdir=None,
Jason Changa6413f52023-07-26 13:23:40 -0700287 verify_command=False,
Jason Changf19b3102023-09-01 16:07:34 -0700288 add_event_log=True,
Gavin Makea2e3302023-03-11 06:46:20 +0000289 ):
290 if project:
291 if not cwd:
292 cwd = project.worktree
293 if not gitdir:
294 gitdir = project.gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700295
Jason Changa6413f52023-07-26 13:23:40 -0700296 self.project = project
297 self.cmdv = cmdv
298 self.verify_command = verify_command
299
Gavin Makea2e3302023-03-11 06:46:20 +0000300 # Git on Windows wants its paths only using / for reliability.
301 if platform_utils.isWindows():
302 if objdir:
303 objdir = objdir.replace("\\", "/")
304 if gitdir:
305 gitdir = gitdir.replace("\\", "/")
Sam Sacconed6863652022-11-15 23:57:22 +0000306
Gavin Makea2e3302023-03-11 06:46:20 +0000307 env = _build_env(
308 disable_editor=disable_editor,
309 ssh_proxy=ssh_proxy,
310 objdir=objdir,
311 gitdir=gitdir,
312 bare=bare,
313 )
Mike Frysinger67d6cdf2021-12-23 17:36:09 -0500314
Gavin Makea2e3302023-03-11 06:46:20 +0000315 command = [GIT]
316 if bare:
317 cwd = None
Jason Changf19b3102023-09-01 16:07:34 -0700318 command_name = cmdv[0]
319 command.append(command_name)
Gavin Makea2e3302023-03-11 06:46:20 +0000320 # Need to use the --progress flag for fetch/clone so output will be
321 # displayed as by default git only does progress output if stderr is a
322 # TTY.
Jason Changf19b3102023-09-01 16:07:34 -0700323 if sys.stderr.isatty() and command_name in ("fetch", "clone"):
Gavin Makea2e3302023-03-11 06:46:20 +0000324 if "--progress" not in cmdv and "--quiet" not in cmdv:
325 command.append("--progress")
326 command.extend(cmdv[1:])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700327
Gavin Makea2e3302023-03-11 06:46:20 +0000328 stdin = subprocess.PIPE if input else None
329 stdout = subprocess.PIPE if capture_stdout else None
330 stderr = (
331 subprocess.STDOUT
332 if merge_output
333 else (subprocess.PIPE if capture_stderr else None)
334 )
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700335
Jason Changf19b3102023-09-01 16:07:34 -0700336 event_log = (
337 BaseEventLog(env=env, add_init_count=True)
338 if add_event_log
339 else None
340 )
341
342 try:
343 self._RunCommand(
344 command,
345 env,
346 stdin=stdin,
347 stdout=stdout,
348 stderr=stderr,
349 ssh_proxy=ssh_proxy,
350 cwd=cwd,
351 input=input,
352 )
353 self.VerifyCommand()
354 except GitCommandError as e:
355 if event_log is not None:
356 error_info = json.dumps(
357 {
358 "ErrorType": type(e).__name__,
359 "Project": e.project,
360 "CommandName": command_name,
361 "Message": str(e),
362 "ReturnCode": str(e.git_rc)
363 if e.git_rc is not None
364 else None,
365 }
366 )
367 event_log.ErrorEvent(
368 f"{ERROR_EVENT_LOGGING_PREFIX}:{error_info}"
369 )
370 event_log.Write(GetEventTargetPath())
371 if isinstance(e, GitPopenCommandError):
372 raise
373
374 def _RunCommand(
375 self,
376 command,
377 env,
378 stdin=None,
379 stdout=None,
380 stderr=None,
381 ssh_proxy=None,
382 cwd=None,
383 input=None,
384 ):
Gavin Makea2e3302023-03-11 06:46:20 +0000385 dbg = ""
386 if IsTrace():
387 global LAST_CWD
388 global LAST_GITDIR
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700389
Gavin Makea2e3302023-03-11 06:46:20 +0000390 if cwd and LAST_CWD != cwd:
391 if LAST_GITDIR or LAST_CWD:
392 dbg += "\n"
393 dbg += ": cd %s\n" % cwd
394 LAST_CWD = cwd
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700395
Gavin Makea2e3302023-03-11 06:46:20 +0000396 if GIT_DIR in env and LAST_GITDIR != env[GIT_DIR]:
397 if LAST_GITDIR or LAST_CWD:
398 dbg += "\n"
399 dbg += ": export GIT_DIR=%s\n" % env[GIT_DIR]
400 LAST_GITDIR = env[GIT_DIR]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700401
Gavin Makea2e3302023-03-11 06:46:20 +0000402 if "GIT_OBJECT_DIRECTORY" in env:
403 dbg += (
404 ": export GIT_OBJECT_DIRECTORY=%s\n"
405 % env["GIT_OBJECT_DIRECTORY"]
406 )
407 if "GIT_ALTERNATE_OBJECT_DIRECTORIES" in env:
408 dbg += ": export GIT_ALTERNATE_OBJECT_DIRECTORIES=%s\n" % (
409 env["GIT_ALTERNATE_OBJECT_DIRECTORIES"]
410 )
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700411
Gavin Makea2e3302023-03-11 06:46:20 +0000412 dbg += ": "
413 dbg += " ".join(command)
414 if stdin == subprocess.PIPE:
415 dbg += " 0<|"
416 if stdout == subprocess.PIPE:
417 dbg += " 1>|"
418 if stderr == subprocess.PIPE:
419 dbg += " 2>|"
420 elif stderr == subprocess.STDOUT:
421 dbg += " 2>&1"
Mike Frysinger67d6cdf2021-12-23 17:36:09 -0500422
Gavin Makea2e3302023-03-11 06:46:20 +0000423 with Trace(
424 "git command %s %s with debug: %s", LAST_GITDIR, command, dbg
425 ):
426 try:
427 p = subprocess.Popen(
428 command,
429 cwd=cwd,
430 env=env,
431 encoding="utf-8",
432 errors="backslashreplace",
433 stdin=stdin,
434 stdout=stdout,
435 stderr=stderr,
436 )
437 except Exception as e:
Jason Changf19b3102023-09-01 16:07:34 -0700438 raise GitPopenCommandError(
Jason Changa6413f52023-07-26 13:23:40 -0700439 message="%s: %s" % (command[1], e),
Jason Changf19b3102023-09-01 16:07:34 -0700440 project=self.project.name if self.project else None,
441 command_args=self.cmdv,
Jason Changa6413f52023-07-26 13:23:40 -0700442 )
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700443
Gavin Makea2e3302023-03-11 06:46:20 +0000444 if ssh_proxy:
445 ssh_proxy.add_client(p)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700446
Gavin Makea2e3302023-03-11 06:46:20 +0000447 self.process = p
Joanna Wanga6c52f52022-11-03 16:51:19 -0400448
Gavin Makea2e3302023-03-11 06:46:20 +0000449 try:
450 self.stdout, self.stderr = p.communicate(input=input)
451 finally:
452 if ssh_proxy:
453 ssh_proxy.remove_client(p)
454 self.rc = p.wait()
Joanna Wanga6c52f52022-11-03 16:51:19 -0400455
Gavin Makea2e3302023-03-11 06:46:20 +0000456 @staticmethod
457 def _GetBasicEnv():
458 """Return a basic env for running git under.
Mike Frysingerc5bbea82021-02-16 15:45:19 -0500459
Gavin Makea2e3302023-03-11 06:46:20 +0000460 This is guaranteed to be side-effect free.
461 """
462 env = os.environ.copy()
463 for key in (
464 REPO_TRACE,
465 GIT_DIR,
466 "GIT_ALTERNATE_OBJECT_DIRECTORIES",
467 "GIT_OBJECT_DIRECTORY",
468 "GIT_WORK_TREE",
469 "GIT_GRAFT_FILE",
470 "GIT_INDEX_FILE",
471 ):
472 env.pop(key, None)
473 return env
Mike Frysinger71b0f312019-09-30 22:39:49 -0400474
Jason Changf19b3102023-09-01 16:07:34 -0700475 def VerifyCommand(self):
476 if self.rc == 0:
477 return None
Jason Changa6413f52023-07-26 13:23:40 -0700478 stdout = (
479 "\n".join(self.stdout.split("\n")[:GIT_ERROR_STDOUT_LINES])
480 if self.stdout
481 else None
482 )
Jason Changa6413f52023-07-26 13:23:40 -0700483 stderr = (
484 "\n".join(self.stderr.split("\n")[:GIT_ERROR_STDERR_LINES])
485 if self.stderr
486 else None
487 )
488 project = self.project.name if self.project else None
489 raise GitCommandError(
490 project=project,
491 command_args=self.cmdv,
492 git_rc=self.rc,
493 git_stdout=stdout,
494 git_stderr=stderr,
495 )
496
Jason Changf19b3102023-09-01 16:07:34 -0700497 def Wait(self):
498 if self.verify_command:
499 self.VerifyCommand()
500 return self.rc
501
Jason Changa6413f52023-07-26 13:23:40 -0700502
Jason Changf9aacd42023-08-03 14:38:00 -0700503class GitRequireError(RepoExitError):
504 """Error raised when git version is unavailable or invalid."""
505
506 def __init__(self, message, exit_code: int = INVALID_GIT_EXIT_CODE):
507 super().__init__(message, exit_code=exit_code)
508
509
Jason Changa6413f52023-07-26 13:23:40 -0700510class GitCommandError(GitError):
511 """
512 Error raised from a failed git command.
513 Note that GitError can refer to any Git related error (e.g. branch not
514 specified for project.py 'UploadForReview'), while GitCommandError is
515 raised exclusively from non-zero exit codes returned from git commands.
516 """
517
518 def __init__(
519 self,
520 message: str = DEFAULT_GIT_FAIL_MESSAGE,
521 git_rc: int = None,
522 git_stdout: str = None,
523 git_stderr: str = None,
524 **kwargs,
525 ):
526 super().__init__(
527 message,
528 **kwargs,
529 )
530 self.git_rc = git_rc
531 self.git_stdout = git_stdout
532 self.git_stderr = git_stderr
533
534 def __str__(self):
535 args = "[]" if not self.command_args else " ".join(self.command_args)
536 error_type = type(self).__name__
537 return f"""{error_type}: {self.message}
538 Project: {self.project}
539 Args: {args}
540 Stdout:
541{self.git_stdout}
542 Stderr:
543{self.git_stderr}"""
Jason Changf19b3102023-09-01 16:07:34 -0700544
545
546class GitPopenCommandError(GitError):
547 """
548 Error raised when subprocess.Popen fails for a GitCommand
549 """