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