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>
diff --git a/git_command.py b/git_command.py
index a5cf514..71b464c 100644
--- a/git_command.py
+++ b/git_command.py
@@ -13,6 +13,7 @@
# limitations under the License.
import functools
+import json
import os
import subprocess
import sys
@@ -21,6 +22,7 @@
from error import GitError
from error import RepoExitError
from git_refs import HEAD
+from git_trace2_event_log_base import BaseEventLog
import platform_utils
from repo_trace import IsTrace
from repo_trace import REPO_TRACE
@@ -45,6 +47,7 @@
LAST_GITDIR = None
LAST_CWD = None
DEFAULT_GIT_FAIL_MESSAGE = "git command failure"
+ERROR_EVENT_LOGGING_PREFIX = "RepoGitCommandError"
# Common line length limit
GIT_ERROR_STDOUT_LINES = 1
GIT_ERROR_STDERR_LINES = 1
@@ -67,7 +70,7 @@
def fun(*cmdv):
command = [name]
command.extend(cmdv)
- return GitCommand(None, command).Wait() == 0
+ return GitCommand(None, command, add_event_log=False).Wait() == 0
return fun
@@ -105,6 +108,41 @@
return ver
+@functools.lru_cache(maxsize=None)
+def GetEventTargetPath():
+ """Get the 'trace2.eventtarget' path from git configuration.
+
+ Returns:
+ path: git config's 'trace2.eventtarget' path if it exists, or None
+ """
+ path = None
+ cmd = ["config", "--get", "trace2.eventtarget"]
+ # TODO(https://crbug.com/gerrit/13706): Use GitConfig when it supports
+ # system git config variables.
+ p = GitCommand(
+ None,
+ cmd,
+ capture_stdout=True,
+ capture_stderr=True,
+ bare=True,
+ add_event_log=False,
+ )
+ retval = p.Wait()
+ if retval == 0:
+ # Strip trailing carriage-return in path.
+ path = p.stdout.rstrip("\n")
+ elif retval != 1:
+ # `git config --get` is documented to produce an exit status of `1`
+ # if the requested variable is not present in the configuration.
+ # Report any other return value as an error.
+ print(
+ "repo: error: 'git config --get' call failed with return code: "
+ "%r, stderr: %r" % (retval, p.stderr),
+ file=sys.stderr,
+ )
+ return path
+
+
class UserAgent(object):
"""Mange User-Agent settings when talking to external services
@@ -247,6 +285,7 @@
gitdir=None,
objdir=None,
verify_command=False,
+ add_event_log=True,
):
if project:
if not cwd:
@@ -276,11 +315,12 @@
command = [GIT]
if bare:
cwd = None
- command.append(cmdv[0])
+ command_name = cmdv[0]
+ command.append(command_name)
# Need to use the --progress flag for fetch/clone so output will be
# displayed as by default git only does progress output if stderr is a
# TTY.
- if sys.stderr.isatty() and cmdv[0] in ("fetch", "clone"):
+ if sys.stderr.isatty() and command_name in ("fetch", "clone"):
if "--progress" not in cmdv and "--quiet" not in cmdv:
command.append("--progress")
command.extend(cmdv[1:])
@@ -293,6 +333,55 @@
else (subprocess.PIPE if capture_stderr else None)
)
+ event_log = (
+ BaseEventLog(env=env, add_init_count=True)
+ if add_event_log
+ else None
+ )
+
+ try:
+ self._RunCommand(
+ command,
+ env,
+ stdin=stdin,
+ stdout=stdout,
+ stderr=stderr,
+ ssh_proxy=ssh_proxy,
+ cwd=cwd,
+ input=input,
+ )
+ self.VerifyCommand()
+ except GitCommandError as e:
+ if event_log is not None:
+ error_info = json.dumps(
+ {
+ "ErrorType": type(e).__name__,
+ "Project": e.project,
+ "CommandName": command_name,
+ "Message": str(e),
+ "ReturnCode": str(e.git_rc)
+ if e.git_rc is not None
+ else None,
+ }
+ )
+ event_log.ErrorEvent(
+ f"{ERROR_EVENT_LOGGING_PREFIX}:{error_info}"
+ )
+ event_log.Write(GetEventTargetPath())
+ if isinstance(e, GitPopenCommandError):
+ raise
+
+ def _RunCommand(
+ self,
+ command,
+ env,
+ stdin=None,
+ stdout=None,
+ stderr=None,
+ ssh_proxy=None,
+ cwd=None,
+ input=None,
+ ):
dbg = ""
if IsTrace():
global LAST_CWD
@@ -346,10 +435,10 @@
stderr=stderr,
)
except Exception as e:
- raise GitCommandError(
+ raise GitPopenCommandError(
message="%s: %s" % (command[1], e),
- project=project.name if project else None,
- command_args=cmdv,
+ project=self.project.name if self.project else None,
+ command_args=self.cmdv,
)
if ssh_proxy:
@@ -383,16 +472,14 @@
env.pop(key, None)
return env
- def Wait(self):
- if not self.verify_command or self.rc == 0:
- return self.rc
-
+ def VerifyCommand(self):
+ if self.rc == 0:
+ return None
stdout = (
"\n".join(self.stdout.split("\n")[:GIT_ERROR_STDOUT_LINES])
if self.stdout
else None
)
-
stderr = (
"\n".join(self.stderr.split("\n")[:GIT_ERROR_STDERR_LINES])
if self.stderr
@@ -407,6 +494,11 @@
git_stderr=stderr,
)
+ def Wait(self):
+ if self.verify_command:
+ self.VerifyCommand()
+ return self.rc
+
class GitRequireError(RepoExitError):
"""Error raised when git version is unavailable or invalid."""
@@ -449,3 +541,9 @@
{self.git_stdout}
Stderr:
{self.git_stderr}"""
+
+
+class GitPopenCommandError(GitError):
+ """
+ Error raised when subprocess.Popen fails for a GitCommand
+ """