blob: fe1e48d65da4ac51aeb5cdcb41f60c0a29a0a720 [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,
Jason Chang87058c62023-09-27 11:34:43 -0700289 log_as_error=True,
Gavin Makea2e3302023-03-11 06:46:20 +0000290 ):
291 if project:
292 if not cwd:
293 cwd = project.worktree
294 if not gitdir:
295 gitdir = project.gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700296
Jason Changa6413f52023-07-26 13:23:40 -0700297 self.project = project
298 self.cmdv = cmdv
299 self.verify_command = verify_command
300
Gavin Makea2e3302023-03-11 06:46:20 +0000301 # Git on Windows wants its paths only using / for reliability.
302 if platform_utils.isWindows():
303 if objdir:
304 objdir = objdir.replace("\\", "/")
305 if gitdir:
306 gitdir = gitdir.replace("\\", "/")
Sam Sacconed6863652022-11-15 23:57:22 +0000307
Gavin Makea2e3302023-03-11 06:46:20 +0000308 env = _build_env(
309 disable_editor=disable_editor,
310 ssh_proxy=ssh_proxy,
311 objdir=objdir,
312 gitdir=gitdir,
313 bare=bare,
314 )
Mike Frysinger67d6cdf2021-12-23 17:36:09 -0500315
Gavin Makea2e3302023-03-11 06:46:20 +0000316 command = [GIT]
317 if bare:
318 cwd = None
Jason Changf19b3102023-09-01 16:07:34 -0700319 command_name = cmdv[0]
320 command.append(command_name)
Gavin Makea2e3302023-03-11 06:46:20 +0000321 # Need to use the --progress flag for fetch/clone so output will be
322 # displayed as by default git only does progress output if stderr is a
323 # TTY.
Jason Changf19b3102023-09-01 16:07:34 -0700324 if sys.stderr.isatty() and command_name in ("fetch", "clone"):
Gavin Makea2e3302023-03-11 06:46:20 +0000325 if "--progress" not in cmdv and "--quiet" not in cmdv:
326 command.append("--progress")
327 command.extend(cmdv[1:])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700328
Gavin Makea2e3302023-03-11 06:46:20 +0000329 stdin = subprocess.PIPE if input else None
330 stdout = subprocess.PIPE if capture_stdout else None
331 stderr = (
332 subprocess.STDOUT
333 if merge_output
334 else (subprocess.PIPE if capture_stderr else None)
335 )
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700336
Jason Changf19b3102023-09-01 16:07:34 -0700337 event_log = (
338 BaseEventLog(env=env, add_init_count=True)
339 if add_event_log
340 else None
341 )
342
343 try:
344 self._RunCommand(
345 command,
346 env,
347 stdin=stdin,
348 stdout=stdout,
349 stderr=stderr,
350 ssh_proxy=ssh_proxy,
351 cwd=cwd,
352 input=input,
353 )
354 self.VerifyCommand()
355 except GitCommandError as e:
356 if event_log is not None:
357 error_info = json.dumps(
358 {
359 "ErrorType": type(e).__name__,
360 "Project": e.project,
361 "CommandName": command_name,
362 "Message": str(e),
363 "ReturnCode": str(e.git_rc)
364 if e.git_rc is not None
365 else None,
Jason Chang87058c62023-09-27 11:34:43 -0700366 "IsError": log_as_error,
Jason Changf19b3102023-09-01 16:07:34 -0700367 }
368 )
369 event_log.ErrorEvent(
370 f"{ERROR_EVENT_LOGGING_PREFIX}:{error_info}"
371 )
372 event_log.Write(GetEventTargetPath())
373 if isinstance(e, GitPopenCommandError):
374 raise
375
376 def _RunCommand(
377 self,
378 command,
379 env,
380 stdin=None,
381 stdout=None,
382 stderr=None,
383 ssh_proxy=None,
384 cwd=None,
385 input=None,
386 ):
Gavin Makea2e3302023-03-11 06:46:20 +0000387 dbg = ""
388 if IsTrace():
389 global LAST_CWD
390 global LAST_GITDIR
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700391
Gavin Makea2e3302023-03-11 06:46:20 +0000392 if cwd and LAST_CWD != cwd:
393 if LAST_GITDIR or LAST_CWD:
394 dbg += "\n"
395 dbg += ": cd %s\n" % cwd
396 LAST_CWD = cwd
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700397
Gavin Makea2e3302023-03-11 06:46:20 +0000398 if GIT_DIR in env and LAST_GITDIR != env[GIT_DIR]:
399 if LAST_GITDIR or LAST_CWD:
400 dbg += "\n"
401 dbg += ": export GIT_DIR=%s\n" % env[GIT_DIR]
402 LAST_GITDIR = env[GIT_DIR]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700403
Gavin Makea2e3302023-03-11 06:46:20 +0000404 if "GIT_OBJECT_DIRECTORY" in env:
405 dbg += (
406 ": export GIT_OBJECT_DIRECTORY=%s\n"
407 % env["GIT_OBJECT_DIRECTORY"]
408 )
409 if "GIT_ALTERNATE_OBJECT_DIRECTORIES" in env:
410 dbg += ": export GIT_ALTERNATE_OBJECT_DIRECTORIES=%s\n" % (
411 env["GIT_ALTERNATE_OBJECT_DIRECTORIES"]
412 )
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700413
Gavin Makea2e3302023-03-11 06:46:20 +0000414 dbg += ": "
415 dbg += " ".join(command)
416 if stdin == subprocess.PIPE:
417 dbg += " 0<|"
418 if stdout == subprocess.PIPE:
419 dbg += " 1>|"
420 if stderr == subprocess.PIPE:
421 dbg += " 2>|"
422 elif stderr == subprocess.STDOUT:
423 dbg += " 2>&1"
Mike Frysinger67d6cdf2021-12-23 17:36:09 -0500424
Gavin Makea2e3302023-03-11 06:46:20 +0000425 with Trace(
426 "git command %s %s with debug: %s", LAST_GITDIR, command, dbg
427 ):
428 try:
429 p = subprocess.Popen(
430 command,
431 cwd=cwd,
432 env=env,
433 encoding="utf-8",
434 errors="backslashreplace",
435 stdin=stdin,
436 stdout=stdout,
437 stderr=stderr,
438 )
439 except Exception as e:
Jason Changf19b3102023-09-01 16:07:34 -0700440 raise GitPopenCommandError(
Jason Changa6413f52023-07-26 13:23:40 -0700441 message="%s: %s" % (command[1], e),
Jason Changf19b3102023-09-01 16:07:34 -0700442 project=self.project.name if self.project else None,
443 command_args=self.cmdv,
Jason Changa6413f52023-07-26 13:23:40 -0700444 )
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700445
Gavin Makea2e3302023-03-11 06:46:20 +0000446 if ssh_proxy:
447 ssh_proxy.add_client(p)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700448
Gavin Makea2e3302023-03-11 06:46:20 +0000449 self.process = p
Joanna Wanga6c52f52022-11-03 16:51:19 -0400450
Gavin Makea2e3302023-03-11 06:46:20 +0000451 try:
452 self.stdout, self.stderr = p.communicate(input=input)
453 finally:
454 if ssh_proxy:
455 ssh_proxy.remove_client(p)
456 self.rc = p.wait()
Joanna Wanga6c52f52022-11-03 16:51:19 -0400457
Gavin Makea2e3302023-03-11 06:46:20 +0000458 @staticmethod
459 def _GetBasicEnv():
460 """Return a basic env for running git under.
Mike Frysingerc5bbea82021-02-16 15:45:19 -0500461
Gavin Makea2e3302023-03-11 06:46:20 +0000462 This is guaranteed to be side-effect free.
463 """
464 env = os.environ.copy()
465 for key in (
466 REPO_TRACE,
467 GIT_DIR,
468 "GIT_ALTERNATE_OBJECT_DIRECTORIES",
469 "GIT_OBJECT_DIRECTORY",
470 "GIT_WORK_TREE",
471 "GIT_GRAFT_FILE",
472 "GIT_INDEX_FILE",
473 ):
474 env.pop(key, None)
475 return env
Mike Frysinger71b0f312019-09-30 22:39:49 -0400476
Jason Changf19b3102023-09-01 16:07:34 -0700477 def VerifyCommand(self):
478 if self.rc == 0:
479 return None
Jason Changa6413f52023-07-26 13:23:40 -0700480 stdout = (
481 "\n".join(self.stdout.split("\n")[:GIT_ERROR_STDOUT_LINES])
482 if self.stdout
483 else None
484 )
Jason Changa6413f52023-07-26 13:23:40 -0700485 stderr = (
486 "\n".join(self.stderr.split("\n")[:GIT_ERROR_STDERR_LINES])
487 if self.stderr
488 else None
489 )
490 project = self.project.name if self.project else None
491 raise GitCommandError(
492 project=project,
493 command_args=self.cmdv,
494 git_rc=self.rc,
495 git_stdout=stdout,
496 git_stderr=stderr,
497 )
498
Jason Changf19b3102023-09-01 16:07:34 -0700499 def Wait(self):
500 if self.verify_command:
501 self.VerifyCommand()
502 return self.rc
503
Jason Changa6413f52023-07-26 13:23:40 -0700504
Jason Changf9aacd42023-08-03 14:38:00 -0700505class GitRequireError(RepoExitError):
506 """Error raised when git version is unavailable or invalid."""
507
508 def __init__(self, message, exit_code: int = INVALID_GIT_EXIT_CODE):
509 super().__init__(message, exit_code=exit_code)
510
511
Jason Changa6413f52023-07-26 13:23:40 -0700512class GitCommandError(GitError):
513 """
514 Error raised from a failed git command.
515 Note that GitError can refer to any Git related error (e.g. branch not
516 specified for project.py 'UploadForReview'), while GitCommandError is
517 raised exclusively from non-zero exit codes returned from git commands.
518 """
519
520 def __init__(
521 self,
522 message: str = DEFAULT_GIT_FAIL_MESSAGE,
523 git_rc: int = None,
524 git_stdout: str = None,
525 git_stderr: str = None,
526 **kwargs,
527 ):
528 super().__init__(
529 message,
530 **kwargs,
531 )
532 self.git_rc = git_rc
533 self.git_stdout = git_stdout
534 self.git_stderr = git_stderr
535
536 def __str__(self):
537 args = "[]" if not self.command_args else " ".join(self.command_args)
538 error_type = type(self).__name__
539 return f"""{error_type}: {self.message}
540 Project: {self.project}
541 Args: {args}
542 Stdout:
543{self.git_stdout}
544 Stderr:
545{self.git_stderr}"""
Jason Changf19b3102023-09-01 16:07:34 -0700546
547
548class GitPopenCommandError(GitError):
549 """
550 Error raised when subprocess.Popen fails for a GitCommand
551 """