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