The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 1 | # 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 Frysinger | 8e768ea | 2021-05-06 00:28:32 -0400 | [diff] [blame] | 15 | import functools |
Jason Chang | f19b310 | 2023-09-01 16:07:34 -0700 | [diff] [blame] | 16 | import json |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 17 | import os |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 18 | import subprocess |
Mike Frysinger | 6447733 | 2023-08-21 21:20:32 -0400 | [diff] [blame] | 19 | import sys |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 20 | from typing import Any, Optional |
Renaud Paquay | 2e70291 | 2016-11-01 11:23:38 -0700 | [diff] [blame] | 21 | |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 22 | from error import GitError |
Jason Chang | f9aacd4 | 2023-08-03 14:38:00 -0700 | [diff] [blame] | 23 | from error import RepoExitError |
Mike Frysinger | 71b0f31 | 2019-09-30 22:39:49 -0400 | [diff] [blame] | 24 | from git_refs import HEAD |
Jason Chang | f19b310 | 2023-09-01 16:07:34 -0700 | [diff] [blame] | 25 | from git_trace2_event_log_base import BaseEventLog |
Renaud Paquay | 2e70291 | 2016-11-01 11:23:38 -0700 | [diff] [blame] | 26 | import platform_utils |
Mike Frysinger | 6447733 | 2023-08-21 21:20:32 -0400 | [diff] [blame] | 27 | from repo_trace import IsTrace |
| 28 | from repo_trace import REPO_TRACE |
| 29 | from repo_trace import Trace |
Conley Owens | ff0a3c8 | 2014-01-30 14:46:03 -0800 | [diff] [blame] | 30 | from wrapper import Wrapper |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 31 | |
Mike Frysinger | 6447733 | 2023-08-21 21:20:32 -0400 | [diff] [blame] | 32 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 33 | GIT = "git" |
Mike Frysinger | 82caef6 | 2020-02-11 18:51:08 -0500 | [diff] [blame] | 34 | # 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. |
| 43 | MIN_GIT_VERSION_SOFT = (1, 9, 1) |
| 44 | MIN_GIT_VERSION_HARD = (1, 7, 2) |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 45 | GIT_DIR = "GIT_DIR" |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 46 | |
| 47 | LAST_GITDIR = None |
| 48 | LAST_CWD = None |
Jason Chang | a6413f5 | 2023-07-26 13:23:40 -0700 | [diff] [blame] | 49 | DEFAULT_GIT_FAIL_MESSAGE = "git command failure" |
Jason Chang | f19b310 | 2023-09-01 16:07:34 -0700 | [diff] [blame] | 50 | ERROR_EVENT_LOGGING_PREFIX = "RepoGitCommandError" |
Jason Chang | a6413f5 | 2023-07-26 13:23:40 -0700 | [diff] [blame] | 51 | # Common line length limit |
| 52 | GIT_ERROR_STDOUT_LINES = 1 |
| 53 | GIT_ERROR_STDERR_LINES = 1 |
Jason Chang | f9aacd4 | 2023-08-03 14:38:00 -0700 | [diff] [blame] | 54 | INVALID_GIT_EXIT_CODE = 126 |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 55 | |
David Pursehouse | 819827a | 2020-02-12 15:20:19 +0900 | [diff] [blame] | 56 | |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 57 | class _GitCall(object): |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 58 | @functools.lru_cache(maxsize=None) |
| 59 | def version_tuple(self): |
| 60 | ret = Wrapper().ParseGitVersion() |
| 61 | if ret is None: |
Jason Chang | f9aacd4 | 2023-08-03 14:38:00 -0700 | [diff] [blame] | 62 | msg = "fatal: unable to detect git version" |
| 63 | print(msg, file=sys.stderr) |
| 64 | raise GitRequireError(msg) |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 65 | return ret |
Shawn O. Pearce | 334851e | 2011-09-19 08:05:31 -0700 | [diff] [blame] | 66 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 67 | def __getattr__(self, name): |
| 68 | name = name.replace("_", "-") |
David Pursehouse | 819827a | 2020-02-12 15:20:19 +0900 | [diff] [blame] | 69 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 70 | def fun(*cmdv): |
| 71 | command = [name] |
| 72 | command.extend(cmdv) |
Jason Chang | f19b310 | 2023-09-01 16:07:34 -0700 | [diff] [blame] | 73 | return GitCommand(None, command, add_event_log=False).Wait() == 0 |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 74 | |
| 75 | return fun |
David Pursehouse | 819827a | 2020-02-12 15:20:19 +0900 | [diff] [blame] | 76 | |
| 77 | |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 78 | git = _GitCall() |
| 79 | |
Mike Frysinger | 369814b | 2019-07-10 17:10:07 -0400 | [diff] [blame] | 80 | |
Mike Frysinger | 71b0f31 | 2019-09-30 22:39:49 -0400 | [diff] [blame] | 81 | def RepoSourceVersion(): |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 82 | """Return the version of the repo.git tree.""" |
| 83 | ver = getattr(RepoSourceVersion, "version", None) |
Mike Frysinger | 369814b | 2019-07-10 17:10:07 -0400 | [diff] [blame] | 84 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 85 | # 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 Frysinger | 71b0f31 | 2019-09-30 22:39:49 -0400 | [diff] [blame] | 89 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 90 | 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 Frysinger | 71b0f31 | 2019-09-30 22:39:49 -0400 | [diff] [blame] | 107 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 108 | return ver |
Mike Frysinger | 71b0f31 | 2019-09-30 22:39:49 -0400 | [diff] [blame] | 109 | |
| 110 | |
Jason Chang | f19b310 | 2023-09-01 16:07:34 -0700 | [diff] [blame] | 111 | @functools.lru_cache(maxsize=None) |
| 112 | def 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 Frysinger | 71b0f31 | 2019-09-30 22:39:49 -0400 | [diff] [blame] | 146 | class UserAgent(object): |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 147 | """Mange User-Agent settings when talking to external services |
Mike Frysinger | 369814b | 2019-07-10 17:10:07 -0400 | [diff] [blame] | 148 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 149 | We follow the style as documented here: |
| 150 | https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent |
| 151 | """ |
Mike Frysinger | 369814b | 2019-07-10 17:10:07 -0400 | [diff] [blame] | 152 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 153 | _os = None |
| 154 | _repo_ua = None |
| 155 | _git_ua = None |
Mike Frysinger | 369814b | 2019-07-10 17:10:07 -0400 | [diff] [blame] | 156 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 157 | @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 Frysinger | 369814b | 2019-07-10 17:10:07 -0400 | [diff] [blame] | 171 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 172 | return self._os |
Mike Frysinger | 369814b | 2019-07-10 17:10:07 -0400 | [diff] [blame] | 173 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 174 | @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 Frysinger | 369814b | 2019-07-10 17:10:07 -0400 | [diff] [blame] | 187 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 188 | return self._repo_ua |
Mike Frysinger | 369814b | 2019-07-10 17:10:07 -0400 | [diff] [blame] | 189 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 190 | @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 Frysinger | 2f0951b | 2019-07-10 17:13:46 -0400 | [diff] [blame] | 199 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 200 | return self._git_ua |
Mike Frysinger | 2f0951b | 2019-07-10 17:13:46 -0400 | [diff] [blame] | 201 | |
David Pursehouse | 819827a | 2020-02-12 15:20:19 +0900 | [diff] [blame] | 202 | |
Mike Frysinger | 71b0f31 | 2019-09-30 22:39:49 -0400 | [diff] [blame] | 203 | user_agent = UserAgent() |
Mike Frysinger | 369814b | 2019-07-10 17:10:07 -0400 | [diff] [blame] | 204 | |
David Pursehouse | 819827a | 2020-02-12 15:20:19 +0900 | [diff] [blame] | 205 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 206 | def 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 Chang | f9aacd4 | 2023-08-03 14:38:00 -0700 | [diff] [blame] | 214 | error_msg = "fatal: git %s or later required%s" % (need, msg) |
| 215 | print(error_msg, file=sys.stderr) |
| 216 | raise GitRequireError(error_msg) |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 217 | return False |
Shawn O. Pearce | 2ec00b9 | 2009-06-12 09:32:50 -0700 | [diff] [blame] | 218 | |
David Pursehouse | 819827a | 2020-02-12 15:20:19 +0900 | [diff] [blame] | 219 | |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 220 | def _build_env( |
Sergiy Belozorov | 78e82ec | 2023-01-05 18:57:31 +0100 | [diff] [blame] | 221 | _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 Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 226 | objdir: Optional[str] = None, |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 227 | ): |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 228 | """Constucts an env dict for command execution.""" |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 229 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 230 | assert _kwargs_only == (), "_build_env only accepts keyword arguments." |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 231 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 232 | env = GitCommand._GetBasicEnv() |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 233 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 234 | 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 Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 251 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 252 | if objdir: |
| 253 | # Set to the place we want to save the objects. |
| 254 | env["GIT_OBJECT_DIRECTORY"] = objdir |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 255 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 256 | 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 Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 266 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 267 | return env |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 268 | |
| 269 | |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 270 | class GitCommand(object): |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 271 | """Wrapper around a single git invocation.""" |
Mike Frysinger | 790f4ce | 2020-12-07 22:04:55 -0500 | [diff] [blame] | 272 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 273 | 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 Chang | a6413f5 | 2023-07-26 13:23:40 -0700 | [diff] [blame] | 287 | verify_command=False, |
Jason Chang | f19b310 | 2023-09-01 16:07:34 -0700 | [diff] [blame] | 288 | add_event_log=True, |
Jason Chang | 87058c6 | 2023-09-27 11:34:43 -0700 | [diff] [blame] | 289 | log_as_error=True, |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 290 | ): |
| 291 | if project: |
| 292 | if not cwd: |
| 293 | cwd = project.worktree |
| 294 | if not gitdir: |
| 295 | gitdir = project.gitdir |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 296 | |
Jason Chang | a6413f5 | 2023-07-26 13:23:40 -0700 | [diff] [blame] | 297 | self.project = project |
| 298 | self.cmdv = cmdv |
| 299 | self.verify_command = verify_command |
| 300 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 301 | # 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 Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 307 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 308 | env = _build_env( |
| 309 | disable_editor=disable_editor, |
| 310 | ssh_proxy=ssh_proxy, |
| 311 | objdir=objdir, |
| 312 | gitdir=gitdir, |
| 313 | bare=bare, |
| 314 | ) |
Mike Frysinger | 67d6cdf | 2021-12-23 17:36:09 -0500 | [diff] [blame] | 315 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 316 | command = [GIT] |
| 317 | if bare: |
| 318 | cwd = None |
Jason Chang | f19b310 | 2023-09-01 16:07:34 -0700 | [diff] [blame] | 319 | command_name = cmdv[0] |
| 320 | command.append(command_name) |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 321 | # 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 Chang | f19b310 | 2023-09-01 16:07:34 -0700 | [diff] [blame] | 324 | if sys.stderr.isatty() and command_name in ("fetch", "clone"): |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 325 | if "--progress" not in cmdv and "--quiet" not in cmdv: |
| 326 | command.append("--progress") |
| 327 | command.extend(cmdv[1:]) |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 328 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 329 | 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 Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 336 | |
Jason Chang | f19b310 | 2023-09-01 16:07:34 -0700 | [diff] [blame] | 337 | 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 Chang | 87058c6 | 2023-09-27 11:34:43 -0700 | [diff] [blame] | 366 | "IsError": log_as_error, |
Jason Chang | f19b310 | 2023-09-01 16:07:34 -0700 | [diff] [blame] | 367 | } |
| 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 Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 387 | dbg = "" |
| 388 | if IsTrace(): |
| 389 | global LAST_CWD |
| 390 | global LAST_GITDIR |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 391 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 392 | 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 Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 397 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 398 | 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 Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 403 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 404 | 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 Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 413 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 414 | 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 Frysinger | 67d6cdf | 2021-12-23 17:36:09 -0500 | [diff] [blame] | 424 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 425 | 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 Chang | f19b310 | 2023-09-01 16:07:34 -0700 | [diff] [blame] | 440 | raise GitPopenCommandError( |
Jason Chang | a6413f5 | 2023-07-26 13:23:40 -0700 | [diff] [blame] | 441 | message="%s: %s" % (command[1], e), |
Jason Chang | f19b310 | 2023-09-01 16:07:34 -0700 | [diff] [blame] | 442 | project=self.project.name if self.project else None, |
| 443 | command_args=self.cmdv, |
Jason Chang | a6413f5 | 2023-07-26 13:23:40 -0700 | [diff] [blame] | 444 | ) |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 445 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 446 | if ssh_proxy: |
| 447 | ssh_proxy.add_client(p) |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 448 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 449 | self.process = p |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 450 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 451 | 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 Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 457 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 458 | @staticmethod |
| 459 | def _GetBasicEnv(): |
| 460 | """Return a basic env for running git under. |
Mike Frysinger | c5bbea8 | 2021-02-16 15:45:19 -0500 | [diff] [blame] | 461 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 462 | 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 Frysinger | 71b0f31 | 2019-09-30 22:39:49 -0400 | [diff] [blame] | 476 | |
Jason Chang | f19b310 | 2023-09-01 16:07:34 -0700 | [diff] [blame] | 477 | def VerifyCommand(self): |
| 478 | if self.rc == 0: |
| 479 | return None |
Jason Chang | a6413f5 | 2023-07-26 13:23:40 -0700 | [diff] [blame] | 480 | stdout = ( |
| 481 | "\n".join(self.stdout.split("\n")[:GIT_ERROR_STDOUT_LINES]) |
| 482 | if self.stdout |
| 483 | else None |
| 484 | ) |
Jason Chang | a6413f5 | 2023-07-26 13:23:40 -0700 | [diff] [blame] | 485 | 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 Chang | f19b310 | 2023-09-01 16:07:34 -0700 | [diff] [blame] | 499 | def Wait(self): |
| 500 | if self.verify_command: |
| 501 | self.VerifyCommand() |
| 502 | return self.rc |
| 503 | |
Jason Chang | a6413f5 | 2023-07-26 13:23:40 -0700 | [diff] [blame] | 504 | |
Jason Chang | f9aacd4 | 2023-08-03 14:38:00 -0700 | [diff] [blame] | 505 | class 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 Chang | a6413f5 | 2023-07-26 13:23:40 -0700 | [diff] [blame] | 512 | class 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 Chang | f19b310 | 2023-09-01 16:07:34 -0700 | [diff] [blame] | 546 | |
| 547 | |
| 548 | class GitPopenCommandError(GitError): |
| 549 | """ |
| 550 | Error raised when subprocess.Popen fails for a GitCommand |
| 551 | """ |