diff options
| author | Jason Chang <jasonnc@google.com> | 2023-09-01 16:07:34 -0700 |
|---|---|---|
| committer | LUCI <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com> | 2023-09-06 18:22:33 +0000 |
| commit | f19b310f15e03e92075e7409c9d7f0956acc007d (patch) | |
| tree | 554ef8c4dcf5aab828a9f74e3568e6dffee17eab /git_command.py | |
| parent | 712e62b9b07f690abbb40e089a17f4ddec6ba952 (diff) | |
| download | git-repo-f19b310f15e03e92075e7409c9d7f0956acc007d.tar.gz | |
Log ErrorEvent for failing GitCommands
Change-Id: I270af7401cff310349e736bef87e9b381cc4d016
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/385054
Reviewed-by: Gavin Mak <gavinmak@google.com>
Commit-Queue: Jason Chang <jasonnc@google.com>
Tested-by: Jason Chang <jasonnc@google.com>
Diffstat (limited to 'git_command.py')
| -rw-r--r-- | git_command.py | 120 |
1 files changed, 109 insertions, 11 deletions
diff --git a/git_command.py b/git_command.py index a5cf514b..71b464c6 100644 --- a/git_command.py +++ b/git_command.py | |||
| @@ -13,6 +13,7 @@ | |||
| 13 | # limitations under the License. | 13 | # limitations under the License. |
| 14 | 14 | ||
| 15 | import functools | 15 | import functools |
| 16 | import json | ||
| 16 | import os | 17 | import os |
| 17 | import subprocess | 18 | import subprocess |
| 18 | import sys | 19 | import sys |
| @@ -21,6 +22,7 @@ from typing import Any, Optional | |||
| 21 | from error import GitError | 22 | from error import GitError |
| 22 | from error import RepoExitError | 23 | from error import RepoExitError |
| 23 | from git_refs import HEAD | 24 | from git_refs import HEAD |
| 25 | from git_trace2_event_log_base import BaseEventLog | ||
| 24 | import platform_utils | 26 | import platform_utils |
| 25 | from repo_trace import IsTrace | 27 | from repo_trace import IsTrace |
| 26 | from repo_trace import REPO_TRACE | 28 | from repo_trace import REPO_TRACE |
| @@ -45,6 +47,7 @@ GIT_DIR = "GIT_DIR" | |||
| 45 | LAST_GITDIR = None | 47 | LAST_GITDIR = None |
| 46 | LAST_CWD = None | 48 | LAST_CWD = None |
| 47 | DEFAULT_GIT_FAIL_MESSAGE = "git command failure" | 49 | DEFAULT_GIT_FAIL_MESSAGE = "git command failure" |
| 50 | ERROR_EVENT_LOGGING_PREFIX = "RepoGitCommandError" | ||
| 48 | # Common line length limit | 51 | # Common line length limit |
| 49 | GIT_ERROR_STDOUT_LINES = 1 | 52 | GIT_ERROR_STDOUT_LINES = 1 |
| 50 | GIT_ERROR_STDERR_LINES = 1 | 53 | GIT_ERROR_STDERR_LINES = 1 |
| @@ -67,7 +70,7 @@ class _GitCall(object): | |||
| 67 | def fun(*cmdv): | 70 | def fun(*cmdv): |
| 68 | command = [name] | 71 | command = [name] |
| 69 | command.extend(cmdv) | 72 | command.extend(cmdv) |
| 70 | return GitCommand(None, command).Wait() == 0 | 73 | return GitCommand(None, command, add_event_log=False).Wait() == 0 |
| 71 | 74 | ||
| 72 | return fun | 75 | return fun |
| 73 | 76 | ||
| @@ -105,6 +108,41 @@ def RepoSourceVersion(): | |||
| 105 | return ver | 108 | return ver |
| 106 | 109 | ||
| 107 | 110 | ||
| 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 | |||
| 108 | class UserAgent(object): | 146 | class UserAgent(object): |
| 109 | """Mange User-Agent settings when talking to external services | 147 | """Mange User-Agent settings when talking to external services |
| 110 | 148 | ||
| @@ -247,6 +285,7 @@ class GitCommand(object): | |||
| 247 | gitdir=None, | 285 | gitdir=None, |
| 248 | objdir=None, | 286 | objdir=None, |
| 249 | verify_command=False, | 287 | verify_command=False, |
| 288 | add_event_log=True, | ||
| 250 | ): | 289 | ): |
| 251 | if project: | 290 | if project: |
| 252 | if not cwd: | 291 | if not cwd: |
| @@ -276,11 +315,12 @@ class GitCommand(object): | |||
| 276 | command = [GIT] | 315 | command = [GIT] |
| 277 | if bare: | 316 | if bare: |
| 278 | cwd = None | 317 | cwd = None |
| 279 | command.append(cmdv[0]) | 318 | command_name = cmdv[0] |
| 319 | command.append(command_name) | ||
| 280 | # Need to use the --progress flag for fetch/clone so output will be | 320 | # 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 | 321 | # displayed as by default git only does progress output if stderr is a |
| 282 | # TTY. | 322 | # TTY. |
| 283 | if sys.stderr.isatty() and cmdv[0] in ("fetch", "clone"): | 323 | if sys.stderr.isatty() and command_name in ("fetch", "clone"): |
| 284 | if "--progress" not in cmdv and "--quiet" not in cmdv: | 324 | if "--progress" not in cmdv and "--quiet" not in cmdv: |
| 285 | command.append("--progress") | 325 | command.append("--progress") |
| 286 | command.extend(cmdv[1:]) | 326 | command.extend(cmdv[1:]) |
| @@ -293,6 +333,55 @@ class GitCommand(object): | |||
| 293 | else (subprocess.PIPE if capture_stderr else None) | 333 | else (subprocess.PIPE if capture_stderr else None) |
| 294 | ) | 334 | ) |
| 295 | 335 | ||
| 336 | event_log = ( | ||
| 337 | BaseEventLog(env=env, add_init_count=True) | ||
| 338 | if add_event_log | ||
| 339 | else None | ||
| 340 | ) | ||
| 341 | |||
| 342 | try: | ||
| 343 | self._RunCommand( | ||
| 344 | command, | ||
| 345 | env, | ||
| 346 | stdin=stdin, | ||
| 347 | stdout=stdout, | ||
| 348 | stderr=stderr, | ||
| 349 | ssh_proxy=ssh_proxy, | ||
| 350 | cwd=cwd, | ||
| 351 | input=input, | ||
| 352 | ) | ||
| 353 | self.VerifyCommand() | ||
| 354 | except GitCommandError as e: | ||
| 355 | if event_log is not None: | ||
| 356 | error_info = json.dumps( | ||
| 357 | { | ||
| 358 | "ErrorType": type(e).__name__, | ||
| 359 | "Project": e.project, | ||
| 360 | "CommandName": command_name, | ||
| 361 | "Message": str(e), | ||
| 362 | "ReturnCode": str(e.git_rc) | ||
| 363 | if e.git_rc is not None | ||
| 364 | else None, | ||
| 365 | } | ||
| 366 | ) | ||
| 367 | event_log.ErrorEvent( | ||
| 368 | f"{ERROR_EVENT_LOGGING_PREFIX}:{error_info}" | ||
| 369 | ) | ||
| 370 | event_log.Write(GetEventTargetPath()) | ||
| 371 | if isinstance(e, GitPopenCommandError): | ||
| 372 | raise | ||
| 373 | |||
| 374 | def _RunCommand( | ||
| 375 | self, | ||
| 376 | command, | ||
| 377 | env, | ||
| 378 | stdin=None, | ||
| 379 | stdout=None, | ||
| 380 | stderr=None, | ||
| 381 | ssh_proxy=None, | ||
| 382 | cwd=None, | ||
| 383 | input=None, | ||
| 384 | ): | ||
| 296 | dbg = "" | 385 | dbg = "" |
| 297 | if IsTrace(): | 386 | if IsTrace(): |
| 298 | global LAST_CWD | 387 | global LAST_CWD |
| @@ -346,10 +435,10 @@ class GitCommand(object): | |||
| 346 | stderr=stderr, | 435 | stderr=stderr, |
| 347 | ) | 436 | ) |
| 348 | except Exception as e: | 437 | except Exception as e: |
| 349 | raise GitCommandError( | 438 | raise GitPopenCommandError( |
| 350 | message="%s: %s" % (command[1], e), | 439 | message="%s: %s" % (command[1], e), |
| 351 | project=project.name if project else None, | 440 | project=self.project.name if self.project else None, |
| 352 | command_args=cmdv, | 441 | command_args=self.cmdv, |
| 353 | ) | 442 | ) |
| 354 | 443 | ||
| 355 | if ssh_proxy: | 444 | if ssh_proxy: |
| @@ -383,16 +472,14 @@ class GitCommand(object): | |||
| 383 | env.pop(key, None) | 472 | env.pop(key, None) |
| 384 | return env | 473 | return env |
| 385 | 474 | ||
| 386 | def Wait(self): | 475 | def VerifyCommand(self): |
| 387 | if not self.verify_command or self.rc == 0: | 476 | if self.rc == 0: |
| 388 | return self.rc | 477 | return None |
| 389 | |||
| 390 | stdout = ( | 478 | stdout = ( |
| 391 | "\n".join(self.stdout.split("\n")[:GIT_ERROR_STDOUT_LINES]) | 479 | "\n".join(self.stdout.split("\n")[:GIT_ERROR_STDOUT_LINES]) |
| 392 | if self.stdout | 480 | if self.stdout |
| 393 | else None | 481 | else None |
| 394 | ) | 482 | ) |
| 395 | |||
| 396 | stderr = ( | 483 | stderr = ( |
| 397 | "\n".join(self.stderr.split("\n")[:GIT_ERROR_STDERR_LINES]) | 484 | "\n".join(self.stderr.split("\n")[:GIT_ERROR_STDERR_LINES]) |
| 398 | if self.stderr | 485 | if self.stderr |
| @@ -407,6 +494,11 @@ class GitCommand(object): | |||
| 407 | git_stderr=stderr, | 494 | git_stderr=stderr, |
| 408 | ) | 495 | ) |
| 409 | 496 | ||
| 497 | def Wait(self): | ||
| 498 | if self.verify_command: | ||
| 499 | self.VerifyCommand() | ||
| 500 | return self.rc | ||
| 501 | |||
| 410 | 502 | ||
| 411 | class GitRequireError(RepoExitError): | 503 | class GitRequireError(RepoExitError): |
| 412 | """Error raised when git version is unavailable or invalid.""" | 504 | """Error raised when git version is unavailable or invalid.""" |
| @@ -449,3 +541,9 @@ class GitCommandError(GitError): | |||
| 449 | {self.git_stdout} | 541 | {self.git_stdout} |
| 450 | Stderr: | 542 | Stderr: |
| 451 | {self.git_stderr}""" | 543 | {self.git_stderr}""" |
| 544 | |||
| 545 | |||
| 546 | class GitPopenCommandError(GitError): | ||
| 547 | """ | ||
| 548 | Error raised when subprocess.Popen fails for a GitCommand | ||
| 549 | """ | ||
