summaryrefslogtreecommitdiffstats
path: root/git_command.py
diff options
context:
space:
mode:
Diffstat (limited to 'git_command.py')
-rw-r--r--git_command.py120
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
15import functools 15import functools
16import json
16import os 17import os
17import subprocess 18import subprocess
18import sys 19import sys
@@ -21,6 +22,7 @@ from typing import Any, Optional
21from error import GitError 22from error import GitError
22from error import RepoExitError 23from error import RepoExitError
23from git_refs import HEAD 24from git_refs import HEAD
25from git_trace2_event_log_base import BaseEventLog
24import platform_utils 26import platform_utils
25from repo_trace import IsTrace 27from repo_trace import IsTrace
26from repo_trace import REPO_TRACE 28from repo_trace import REPO_TRACE
@@ -45,6 +47,7 @@ GIT_DIR = "GIT_DIR"
45LAST_GITDIR = None 47LAST_GITDIR = None
46LAST_CWD = None 48LAST_CWD = None
47DEFAULT_GIT_FAIL_MESSAGE = "git command failure" 49DEFAULT_GIT_FAIL_MESSAGE = "git command failure"
50ERROR_EVENT_LOGGING_PREFIX = "RepoGitCommandError"
48# Common line length limit 51# Common line length limit
49GIT_ERROR_STDOUT_LINES = 1 52GIT_ERROR_STDOUT_LINES = 1
50GIT_ERROR_STDERR_LINES = 1 53GIT_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)
112def 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
108class UserAgent(object): 146class 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
411class GitRequireError(RepoExitError): 503class 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
546class GitPopenCommandError(GitError):
547 """
548 Error raised when subprocess.Popen fails for a GitCommand
549 """