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 |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 16 | import os |
| 17 | import sys |
| 18 | import subprocess |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 19 | from typing import Any, Optional |
Renaud Paquay | 2e70291 | 2016-11-01 11:23:38 -0700 | [diff] [blame] | 20 | |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 21 | from error import GitError |
Jason Chang | f9aacd4 | 2023-08-03 14:38:00 -0700 | [diff] [blame^] | 22 | from error import RepoExitError |
Mike Frysinger | 71b0f31 | 2019-09-30 22:39:49 -0400 | [diff] [blame] | 23 | from git_refs import HEAD |
Renaud Paquay | 2e70291 | 2016-11-01 11:23:38 -0700 | [diff] [blame] | 24 | import platform_utils |
Mike Frysinger | 8a11f6f | 2019-08-27 00:26:15 -0400 | [diff] [blame] | 25 | from repo_trace import REPO_TRACE, IsTrace, Trace |
Conley Owens | ff0a3c8 | 2014-01-30 14:46:03 -0800 | [diff] [blame] | 26 | from wrapper import Wrapper |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 27 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 28 | GIT = "git" |
Mike Frysinger | 82caef6 | 2020-02-11 18:51:08 -0500 | [diff] [blame] | 29 | # 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. |
| 38 | MIN_GIT_VERSION_SOFT = (1, 9, 1) |
| 39 | MIN_GIT_VERSION_HARD = (1, 7, 2) |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 40 | GIT_DIR = "GIT_DIR" |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 41 | |
| 42 | LAST_GITDIR = None |
| 43 | LAST_CWD = None |
Jason Chang | a6413f5 | 2023-07-26 13:23:40 -0700 | [diff] [blame] | 44 | DEFAULT_GIT_FAIL_MESSAGE = "git command failure" |
| 45 | # Common line length limit |
| 46 | GIT_ERROR_STDOUT_LINES = 1 |
| 47 | GIT_ERROR_STDERR_LINES = 1 |
Jason Chang | f9aacd4 | 2023-08-03 14:38:00 -0700 | [diff] [blame^] | 48 | INVALID_GIT_EXIT_CODE = 126 |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 49 | |
David Pursehouse | 819827a | 2020-02-12 15:20:19 +0900 | [diff] [blame] | 50 | |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 51 | class _GitCall(object): |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 52 | @functools.lru_cache(maxsize=None) |
| 53 | def version_tuple(self): |
| 54 | ret = Wrapper().ParseGitVersion() |
| 55 | if ret is None: |
Jason Chang | f9aacd4 | 2023-08-03 14:38:00 -0700 | [diff] [blame^] | 56 | msg = "fatal: unable to detect git version" |
| 57 | print(msg, file=sys.stderr) |
| 58 | raise GitRequireError(msg) |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 59 | return ret |
Shawn O. Pearce | 334851e | 2011-09-19 08:05:31 -0700 | [diff] [blame] | 60 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 61 | def __getattr__(self, name): |
| 62 | name = name.replace("_", "-") |
David Pursehouse | 819827a | 2020-02-12 15:20:19 +0900 | [diff] [blame] | 63 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 64 | def fun(*cmdv): |
| 65 | command = [name] |
| 66 | command.extend(cmdv) |
| 67 | return GitCommand(None, command).Wait() == 0 |
| 68 | |
| 69 | return fun |
David Pursehouse | 819827a | 2020-02-12 15:20:19 +0900 | [diff] [blame] | 70 | |
| 71 | |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 72 | git = _GitCall() |
| 73 | |
Mike Frysinger | 369814b | 2019-07-10 17:10:07 -0400 | [diff] [blame] | 74 | |
Mike Frysinger | 71b0f31 | 2019-09-30 22:39:49 -0400 | [diff] [blame] | 75 | def RepoSourceVersion(): |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 76 | """Return the version of the repo.git tree.""" |
| 77 | ver = getattr(RepoSourceVersion, "version", None) |
Mike Frysinger | 369814b | 2019-07-10 17:10:07 -0400 | [diff] [blame] | 78 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 79 | # We avoid GitCommand so we don't run into circular deps -- GitCommand needs |
| 80 | # to initialize version info we provide. |
| 81 | if ver is None: |
| 82 | env = GitCommand._GetBasicEnv() |
Mike Frysinger | 71b0f31 | 2019-09-30 22:39:49 -0400 | [diff] [blame] | 83 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 84 | proj = os.path.dirname(os.path.abspath(__file__)) |
| 85 | env[GIT_DIR] = os.path.join(proj, ".git") |
| 86 | result = subprocess.run( |
| 87 | [GIT, "describe", HEAD], |
| 88 | stdout=subprocess.PIPE, |
| 89 | stderr=subprocess.DEVNULL, |
| 90 | encoding="utf-8", |
| 91 | env=env, |
| 92 | check=False, |
| 93 | ) |
| 94 | if result.returncode == 0: |
| 95 | ver = result.stdout.strip() |
| 96 | if ver.startswith("v"): |
| 97 | ver = ver[1:] |
| 98 | else: |
| 99 | ver = "unknown" |
| 100 | setattr(RepoSourceVersion, "version", ver) |
Mike Frysinger | 71b0f31 | 2019-09-30 22:39:49 -0400 | [diff] [blame] | 101 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 102 | return ver |
Mike Frysinger | 71b0f31 | 2019-09-30 22:39:49 -0400 | [diff] [blame] | 103 | |
| 104 | |
| 105 | class UserAgent(object): |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 106 | """Mange User-Agent settings when talking to external services |
Mike Frysinger | 369814b | 2019-07-10 17:10:07 -0400 | [diff] [blame] | 107 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 108 | We follow the style as documented here: |
| 109 | https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent |
| 110 | """ |
Mike Frysinger | 369814b | 2019-07-10 17:10:07 -0400 | [diff] [blame] | 111 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 112 | _os = None |
| 113 | _repo_ua = None |
| 114 | _git_ua = None |
Mike Frysinger | 369814b | 2019-07-10 17:10:07 -0400 | [diff] [blame] | 115 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 116 | @property |
| 117 | def os(self): |
| 118 | """The operating system name.""" |
| 119 | if self._os is None: |
| 120 | os_name = sys.platform |
| 121 | if os_name.lower().startswith("linux"): |
| 122 | os_name = "Linux" |
| 123 | elif os_name == "win32": |
| 124 | os_name = "Win32" |
| 125 | elif os_name == "cygwin": |
| 126 | os_name = "Cygwin" |
| 127 | elif os_name == "darwin": |
| 128 | os_name = "Darwin" |
| 129 | self._os = os_name |
Mike Frysinger | 369814b | 2019-07-10 17:10:07 -0400 | [diff] [blame] | 130 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 131 | return self._os |
Mike Frysinger | 369814b | 2019-07-10 17:10:07 -0400 | [diff] [blame] | 132 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 133 | @property |
| 134 | def repo(self): |
| 135 | """The UA when connecting directly from repo.""" |
| 136 | if self._repo_ua is None: |
| 137 | py_version = sys.version_info |
| 138 | self._repo_ua = "git-repo/%s (%s) git/%s Python/%d.%d.%d" % ( |
| 139 | RepoSourceVersion(), |
| 140 | self.os, |
| 141 | git.version_tuple().full, |
| 142 | py_version.major, |
| 143 | py_version.minor, |
| 144 | py_version.micro, |
| 145 | ) |
Mike Frysinger | 369814b | 2019-07-10 17:10:07 -0400 | [diff] [blame] | 146 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 147 | return self._repo_ua |
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 | @property |
| 150 | def git(self): |
| 151 | """The UA when running git.""" |
| 152 | if self._git_ua is None: |
| 153 | self._git_ua = "git/%s (%s) git-repo/%s" % ( |
| 154 | git.version_tuple().full, |
| 155 | self.os, |
| 156 | RepoSourceVersion(), |
| 157 | ) |
Mike Frysinger | 2f0951b | 2019-07-10 17:13:46 -0400 | [diff] [blame] | 158 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 159 | return self._git_ua |
Mike Frysinger | 2f0951b | 2019-07-10 17:13:46 -0400 | [diff] [blame] | 160 | |
David Pursehouse | 819827a | 2020-02-12 15:20:19 +0900 | [diff] [blame] | 161 | |
Mike Frysinger | 71b0f31 | 2019-09-30 22:39:49 -0400 | [diff] [blame] | 162 | user_agent = UserAgent() |
Mike Frysinger | 369814b | 2019-07-10 17:10:07 -0400 | [diff] [blame] | 163 | |
David Pursehouse | 819827a | 2020-02-12 15:20:19 +0900 | [diff] [blame] | 164 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 165 | def git_require(min_version, fail=False, msg=""): |
| 166 | git_version = git.version_tuple() |
| 167 | if min_version <= git_version: |
| 168 | return True |
| 169 | if fail: |
| 170 | need = ".".join(map(str, min_version)) |
| 171 | if msg: |
| 172 | msg = " for " + msg |
Jason Chang | f9aacd4 | 2023-08-03 14:38:00 -0700 | [diff] [blame^] | 173 | error_msg = "fatal: git %s or later required%s" % (need, msg) |
| 174 | print(error_msg, file=sys.stderr) |
| 175 | raise GitRequireError(error_msg) |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 176 | return False |
Shawn O. Pearce | 2ec00b9 | 2009-06-12 09:32:50 -0700 | [diff] [blame] | 177 | |
David Pursehouse | 819827a | 2020-02-12 15:20:19 +0900 | [diff] [blame] | 178 | |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 179 | def _build_env( |
Sergiy Belozorov | 78e82ec | 2023-01-05 18:57:31 +0100 | [diff] [blame] | 180 | _kwargs_only=(), |
| 181 | bare: Optional[bool] = False, |
| 182 | disable_editor: Optional[bool] = False, |
| 183 | ssh_proxy: Optional[Any] = None, |
| 184 | gitdir: Optional[str] = None, |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 185 | objdir: Optional[str] = None, |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 186 | ): |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 187 | """Constucts an env dict for command execution.""" |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 188 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 189 | assert _kwargs_only == (), "_build_env only accepts keyword arguments." |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 190 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 191 | env = GitCommand._GetBasicEnv() |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 192 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 193 | if disable_editor: |
| 194 | env["GIT_EDITOR"] = ":" |
| 195 | if ssh_proxy: |
| 196 | env["REPO_SSH_SOCK"] = ssh_proxy.sock() |
| 197 | env["GIT_SSH"] = ssh_proxy.proxy |
| 198 | env["GIT_SSH_VARIANT"] = "ssh" |
| 199 | if "http_proxy" in env and "darwin" == sys.platform: |
| 200 | s = "'http.proxy=%s'" % (env["http_proxy"],) |
| 201 | p = env.get("GIT_CONFIG_PARAMETERS") |
| 202 | if p is not None: |
| 203 | s = p + " " + s |
| 204 | env["GIT_CONFIG_PARAMETERS"] = s |
| 205 | if "GIT_ALLOW_PROTOCOL" not in env: |
| 206 | env[ |
| 207 | "GIT_ALLOW_PROTOCOL" |
| 208 | ] = "file:git:http:https:ssh:persistent-http:persistent-https:sso:rpc" |
| 209 | env["GIT_HTTP_USER_AGENT"] = user_agent.git |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 210 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 211 | if objdir: |
| 212 | # Set to the place we want to save the objects. |
| 213 | env["GIT_OBJECT_DIRECTORY"] = objdir |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 214 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 215 | alt_objects = os.path.join(gitdir, "objects") if gitdir else None |
| 216 | if alt_objects and os.path.realpath(alt_objects) != os.path.realpath( |
| 217 | objdir |
| 218 | ): |
| 219 | # Allow git to search the original place in case of local or unique |
| 220 | # refs that git will attempt to resolve even if we aren't fetching |
| 221 | # them. |
| 222 | env["GIT_ALTERNATE_OBJECT_DIRECTORIES"] = alt_objects |
| 223 | if bare and gitdir is not None: |
| 224 | env[GIT_DIR] = gitdir |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 225 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 226 | return env |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 227 | |
| 228 | |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 229 | class GitCommand(object): |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 230 | """Wrapper around a single git invocation.""" |
Mike Frysinger | 790f4ce | 2020-12-07 22:04:55 -0500 | [diff] [blame] | 231 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 232 | def __init__( |
| 233 | self, |
| 234 | project, |
| 235 | cmdv, |
| 236 | bare=False, |
| 237 | input=None, |
| 238 | capture_stdout=False, |
| 239 | capture_stderr=False, |
| 240 | merge_output=False, |
| 241 | disable_editor=False, |
| 242 | ssh_proxy=None, |
| 243 | cwd=None, |
| 244 | gitdir=None, |
| 245 | objdir=None, |
Jason Chang | a6413f5 | 2023-07-26 13:23:40 -0700 | [diff] [blame] | 246 | verify_command=False, |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 247 | ): |
| 248 | if project: |
| 249 | if not cwd: |
| 250 | cwd = project.worktree |
| 251 | if not gitdir: |
| 252 | gitdir = project.gitdir |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 253 | |
Jason Chang | a6413f5 | 2023-07-26 13:23:40 -0700 | [diff] [blame] | 254 | self.project = project |
| 255 | self.cmdv = cmdv |
| 256 | self.verify_command = verify_command |
| 257 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 258 | # Git on Windows wants its paths only using / for reliability. |
| 259 | if platform_utils.isWindows(): |
| 260 | if objdir: |
| 261 | objdir = objdir.replace("\\", "/") |
| 262 | if gitdir: |
| 263 | gitdir = gitdir.replace("\\", "/") |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 264 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 265 | env = _build_env( |
| 266 | disable_editor=disable_editor, |
| 267 | ssh_proxy=ssh_proxy, |
| 268 | objdir=objdir, |
| 269 | gitdir=gitdir, |
| 270 | bare=bare, |
| 271 | ) |
Mike Frysinger | 67d6cdf | 2021-12-23 17:36:09 -0500 | [diff] [blame] | 272 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 273 | command = [GIT] |
| 274 | if bare: |
| 275 | cwd = None |
| 276 | command.append(cmdv[0]) |
| 277 | # Need to use the --progress flag for fetch/clone so output will be |
| 278 | # displayed as by default git only does progress output if stderr is a |
| 279 | # TTY. |
| 280 | if sys.stderr.isatty() and cmdv[0] in ("fetch", "clone"): |
| 281 | if "--progress" not in cmdv and "--quiet" not in cmdv: |
| 282 | command.append("--progress") |
| 283 | command.extend(cmdv[1:]) |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 284 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 285 | stdin = subprocess.PIPE if input else None |
| 286 | stdout = subprocess.PIPE if capture_stdout else None |
| 287 | stderr = ( |
| 288 | subprocess.STDOUT |
| 289 | if merge_output |
| 290 | else (subprocess.PIPE if capture_stderr else None) |
| 291 | ) |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 292 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 293 | dbg = "" |
| 294 | if IsTrace(): |
| 295 | global LAST_CWD |
| 296 | global LAST_GITDIR |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 297 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 298 | if cwd and LAST_CWD != cwd: |
| 299 | if LAST_GITDIR or LAST_CWD: |
| 300 | dbg += "\n" |
| 301 | dbg += ": cd %s\n" % cwd |
| 302 | LAST_CWD = cwd |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 303 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 304 | if GIT_DIR in env and LAST_GITDIR != env[GIT_DIR]: |
| 305 | if LAST_GITDIR or LAST_CWD: |
| 306 | dbg += "\n" |
| 307 | dbg += ": export GIT_DIR=%s\n" % env[GIT_DIR] |
| 308 | LAST_GITDIR = env[GIT_DIR] |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 309 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 310 | if "GIT_OBJECT_DIRECTORY" in env: |
| 311 | dbg += ( |
| 312 | ": export GIT_OBJECT_DIRECTORY=%s\n" |
| 313 | % env["GIT_OBJECT_DIRECTORY"] |
| 314 | ) |
| 315 | if "GIT_ALTERNATE_OBJECT_DIRECTORIES" in env: |
| 316 | dbg += ": export GIT_ALTERNATE_OBJECT_DIRECTORIES=%s\n" % ( |
| 317 | env["GIT_ALTERNATE_OBJECT_DIRECTORIES"] |
| 318 | ) |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 319 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 320 | dbg += ": " |
| 321 | dbg += " ".join(command) |
| 322 | if stdin == subprocess.PIPE: |
| 323 | dbg += " 0<|" |
| 324 | if stdout == subprocess.PIPE: |
| 325 | dbg += " 1>|" |
| 326 | if stderr == subprocess.PIPE: |
| 327 | dbg += " 2>|" |
| 328 | elif stderr == subprocess.STDOUT: |
| 329 | dbg += " 2>&1" |
Mike Frysinger | 67d6cdf | 2021-12-23 17:36:09 -0500 | [diff] [blame] | 330 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 331 | with Trace( |
| 332 | "git command %s %s with debug: %s", LAST_GITDIR, command, dbg |
| 333 | ): |
| 334 | try: |
| 335 | p = subprocess.Popen( |
| 336 | command, |
| 337 | cwd=cwd, |
| 338 | env=env, |
| 339 | encoding="utf-8", |
| 340 | errors="backslashreplace", |
| 341 | stdin=stdin, |
| 342 | stdout=stdout, |
| 343 | stderr=stderr, |
| 344 | ) |
| 345 | except Exception as e: |
Jason Chang | a6413f5 | 2023-07-26 13:23:40 -0700 | [diff] [blame] | 346 | raise GitCommandError( |
| 347 | message="%s: %s" % (command[1], e), |
| 348 | project=project.name if project else None, |
| 349 | command_args=cmdv, |
| 350 | ) |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 351 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 352 | if ssh_proxy: |
| 353 | ssh_proxy.add_client(p) |
The Android Open Source Project | cf31fe9 | 2008-10-21 07:00:00 -0700 | [diff] [blame] | 354 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 355 | self.process = p |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 356 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 357 | try: |
| 358 | self.stdout, self.stderr = p.communicate(input=input) |
| 359 | finally: |
| 360 | if ssh_proxy: |
| 361 | ssh_proxy.remove_client(p) |
| 362 | self.rc = p.wait() |
Joanna Wang | a6c52f5 | 2022-11-03 16:51:19 -0400 | [diff] [blame] | 363 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 364 | @staticmethod |
| 365 | def _GetBasicEnv(): |
| 366 | """Return a basic env for running git under. |
Mike Frysinger | c5bbea8 | 2021-02-16 15:45:19 -0500 | [diff] [blame] | 367 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 368 | This is guaranteed to be side-effect free. |
| 369 | """ |
| 370 | env = os.environ.copy() |
| 371 | for key in ( |
| 372 | REPO_TRACE, |
| 373 | GIT_DIR, |
| 374 | "GIT_ALTERNATE_OBJECT_DIRECTORIES", |
| 375 | "GIT_OBJECT_DIRECTORY", |
| 376 | "GIT_WORK_TREE", |
| 377 | "GIT_GRAFT_FILE", |
| 378 | "GIT_INDEX_FILE", |
| 379 | ): |
| 380 | env.pop(key, None) |
| 381 | return env |
Mike Frysinger | 71b0f31 | 2019-09-30 22:39:49 -0400 | [diff] [blame] | 382 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 383 | def Wait(self): |
Jason Chang | a6413f5 | 2023-07-26 13:23:40 -0700 | [diff] [blame] | 384 | if not self.verify_command or self.rc == 0: |
| 385 | return self.rc |
| 386 | |
| 387 | stdout = ( |
| 388 | "\n".join(self.stdout.split("\n")[:GIT_ERROR_STDOUT_LINES]) |
| 389 | if self.stdout |
| 390 | else None |
| 391 | ) |
| 392 | |
| 393 | stderr = ( |
| 394 | "\n".join(self.stderr.split("\n")[:GIT_ERROR_STDERR_LINES]) |
| 395 | if self.stderr |
| 396 | else None |
| 397 | ) |
| 398 | project = self.project.name if self.project else None |
| 399 | raise GitCommandError( |
| 400 | project=project, |
| 401 | command_args=self.cmdv, |
| 402 | git_rc=self.rc, |
| 403 | git_stdout=stdout, |
| 404 | git_stderr=stderr, |
| 405 | ) |
| 406 | |
| 407 | |
Jason Chang | f9aacd4 | 2023-08-03 14:38:00 -0700 | [diff] [blame^] | 408 | class GitRequireError(RepoExitError): |
| 409 | """Error raised when git version is unavailable or invalid.""" |
| 410 | |
| 411 | def __init__(self, message, exit_code: int = INVALID_GIT_EXIT_CODE): |
| 412 | super().__init__(message, exit_code=exit_code) |
| 413 | |
| 414 | |
Jason Chang | a6413f5 | 2023-07-26 13:23:40 -0700 | [diff] [blame] | 415 | class GitCommandError(GitError): |
| 416 | """ |
| 417 | Error raised from a failed git command. |
| 418 | Note that GitError can refer to any Git related error (e.g. branch not |
| 419 | specified for project.py 'UploadForReview'), while GitCommandError is |
| 420 | raised exclusively from non-zero exit codes returned from git commands. |
| 421 | """ |
| 422 | |
| 423 | def __init__( |
| 424 | self, |
| 425 | message: str = DEFAULT_GIT_FAIL_MESSAGE, |
| 426 | git_rc: int = None, |
| 427 | git_stdout: str = None, |
| 428 | git_stderr: str = None, |
| 429 | **kwargs, |
| 430 | ): |
| 431 | super().__init__( |
| 432 | message, |
| 433 | **kwargs, |
| 434 | ) |
| 435 | self.git_rc = git_rc |
| 436 | self.git_stdout = git_stdout |
| 437 | self.git_stderr = git_stderr |
| 438 | |
| 439 | def __str__(self): |
| 440 | args = "[]" if not self.command_args else " ".join(self.command_args) |
| 441 | error_type = type(self).__name__ |
| 442 | return f"""{error_type}: {self.message} |
| 443 | Project: {self.project} |
| 444 | Args: {args} |
| 445 | Stdout: |
| 446 | {self.git_stdout} |
| 447 | Stderr: |
| 448 | {self.git_stderr}""" |