diff options
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 | """ | ||