diff options
| author | Aravind Vasudevan <aravindvasudev@google.com> | 2023-09-14 22:54:04 +0000 |
|---|---|---|
| committer | LUCI <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com> | 2023-09-18 20:06:30 +0000 |
| commit | b8fd19215f59f7f8dbe69528aefca700a2190ecd (patch) | |
| tree | 6f6ac455d16b0e58fd6cf16475d11ba7407883ca | |
| parent | 7a1f1f70f0587795e2b6979adf7eac389037de57 (diff) | |
| download | git-repo-b8fd19215f59f7f8dbe69528aefca700a2190ecd.tar.gz | |
main: Use repo logger
Bug: b/292704435
Change-Id: Ica02e4c00994a2f64083bb36e8f4ee8aa45d76bd
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/386454
Reviewed-by: Jason Chang <jasonnc@google.com>
Commit-Queue: Aravind Vasudevan <aravindvasudev@google.com>
Tested-by: Aravind Vasudevan <aravindvasudev@google.com>
| -rwxr-xr-x | main.py | 120 | ||||
| -rw-r--r-- | repo_logging.py | 25 | ||||
| -rw-r--r-- | tests/test_repo_logging.py | 66 |
3 files changed, 104 insertions, 107 deletions
| @@ -32,6 +32,8 @@ import textwrap | |||
| 32 | import time | 32 | import time |
| 33 | import urllib.request | 33 | import urllib.request |
| 34 | 34 | ||
| 35 | from repo_logging import RepoLogger | ||
| 36 | |||
| 35 | 37 | ||
| 36 | try: | 38 | try: |
| 37 | import kerberos | 39 | import kerberos |
| @@ -69,6 +71,9 @@ from wrapper import Wrapper | |||
| 69 | from wrapper import WrapperPath | 71 | from wrapper import WrapperPath |
| 70 | 72 | ||
| 71 | 73 | ||
| 74 | logger = RepoLogger(__file__) | ||
| 75 | |||
| 76 | |||
| 72 | # NB: These do not need to be kept in sync with the repo launcher script. | 77 | # NB: These do not need to be kept in sync with the repo launcher script. |
| 73 | # These may be much newer as it allows the repo launcher to roll between | 78 | # These may be much newer as it allows the repo launcher to roll between |
| 74 | # different repo releases while source versions might require a newer python. | 79 | # different repo releases while source versions might require a newer python. |
| @@ -82,25 +87,25 @@ MIN_PYTHON_VERSION_SOFT = (3, 6) | |||
| 82 | MIN_PYTHON_VERSION_HARD = (3, 6) | 87 | MIN_PYTHON_VERSION_HARD = (3, 6) |
| 83 | 88 | ||
| 84 | if sys.version_info.major < 3: | 89 | if sys.version_info.major < 3: |
| 85 | print( | 90 | logger.error( |
| 86 | "repo: error: Python 2 is no longer supported; " | 91 | "repo: error: Python 2 is no longer supported; " |
| 87 | "Please upgrade to Python {}.{}+.".format(*MIN_PYTHON_VERSION_SOFT), | 92 | "Please upgrade to Python %d.%d+.", |
| 88 | file=sys.stderr, | 93 | *MIN_PYTHON_VERSION_SOFT, |
| 89 | ) | 94 | ) |
| 90 | sys.exit(1) | 95 | sys.exit(1) |
| 91 | else: | 96 | else: |
| 92 | if sys.version_info < MIN_PYTHON_VERSION_HARD: | 97 | if sys.version_info < MIN_PYTHON_VERSION_HARD: |
| 93 | print( | 98 | logger.error( |
| 94 | "repo: error: Python 3 version is too old; " | 99 | "repo: error: Python 3 version is too old; " |
| 95 | "Please upgrade to Python {}.{}+.".format(*MIN_PYTHON_VERSION_SOFT), | 100 | "Please upgrade to Python %d.%d+.", |
| 96 | file=sys.stderr, | 101 | *MIN_PYTHON_VERSION_SOFT, |
| 97 | ) | 102 | ) |
| 98 | sys.exit(1) | 103 | sys.exit(1) |
| 99 | elif sys.version_info < MIN_PYTHON_VERSION_SOFT: | 104 | elif sys.version_info < MIN_PYTHON_VERSION_SOFT: |
| 100 | print( | 105 | logger.error( |
| 101 | "repo: warning: your Python 3 version is no longer supported; " | 106 | "repo: warning: your Python 3 version is no longer supported; " |
| 102 | "Please upgrade to Python {}.{}+.".format(*MIN_PYTHON_VERSION_SOFT), | 107 | "Please upgrade to Python %d.%d+.", |
| 103 | file=sys.stderr, | 108 | *MIN_PYTHON_VERSION_SOFT, |
| 104 | ) | 109 | ) |
| 105 | 110 | ||
| 106 | KEYBOARD_INTERRUPT_EXIT = 128 + signal.SIGINT | 111 | KEYBOARD_INTERRUPT_EXIT = 128 + signal.SIGINT |
| @@ -309,7 +314,7 @@ class _Repo(object): | |||
| 309 | ) | 314 | ) |
| 310 | 315 | ||
| 311 | if Wrapper().gitc_parse_clientdir(os.getcwd()): | 316 | if Wrapper().gitc_parse_clientdir(os.getcwd()): |
| 312 | print("GITC is not supported.", file=sys.stderr) | 317 | logger.error("GITC is not supported.") |
| 313 | raise GitcUnsupportedError() | 318 | raise GitcUnsupportedError() |
| 314 | 319 | ||
| 315 | try: | 320 | try: |
| @@ -322,32 +327,24 @@ class _Repo(object): | |||
| 322 | git_event_log=git_trace2_event_log, | 327 | git_event_log=git_trace2_event_log, |
| 323 | ) | 328 | ) |
| 324 | except KeyError: | 329 | except KeyError: |
| 325 | print( | 330 | logger.error( |
| 326 | "repo: '%s' is not a repo command. See 'repo help'." % name, | 331 | "repo: '%s' is not a repo command. See 'repo help'.", name |
| 327 | file=sys.stderr, | ||
| 328 | ) | 332 | ) |
| 329 | return 1 | 333 | return 1 |
| 330 | 334 | ||
| 331 | Editor.globalConfig = cmd.client.globalConfig | 335 | Editor.globalConfig = cmd.client.globalConfig |
| 332 | 336 | ||
| 333 | if not isinstance(cmd, MirrorSafeCommand) and cmd.manifest.IsMirror: | 337 | if not isinstance(cmd, MirrorSafeCommand) and cmd.manifest.IsMirror: |
| 334 | print( | 338 | logger.error("fatal: '%s' requires a working directory", name) |
| 335 | "fatal: '%s' requires a working directory" % name, | ||
| 336 | file=sys.stderr, | ||
| 337 | ) | ||
| 338 | return 1 | 339 | return 1 |
| 339 | 340 | ||
| 340 | try: | 341 | try: |
| 341 | copts, cargs = cmd.OptionParser.parse_args(argv) | 342 | copts, cargs = cmd.OptionParser.parse_args(argv) |
| 342 | copts = cmd.ReadEnvironmentOptions(copts) | 343 | copts = cmd.ReadEnvironmentOptions(copts) |
| 343 | except NoManifestException as e: | 344 | except NoManifestException as e: |
| 344 | print( | 345 | logger.error("error: in `%s`: %s", " ".join([name] + argv), e) |
| 345 | "error: in `%s`: %s" % (" ".join([name] + argv), str(e)), | 346 | logger.error( |
| 346 | file=sys.stderr, | 347 | "error: manifest missing or unreadable -- please run init" |
| 347 | ) | ||
| 348 | print( | ||
| 349 | "error: manifest missing or unreadable -- please run init", | ||
| 350 | file=sys.stderr, | ||
| 351 | ) | 348 | ) |
| 352 | return 1 | 349 | return 1 |
| 353 | 350 | ||
| @@ -453,34 +450,28 @@ class _Repo(object): | |||
| 453 | ManifestInvalidRevisionError, | 450 | ManifestInvalidRevisionError, |
| 454 | NoManifestException, | 451 | NoManifestException, |
| 455 | ) as e: | 452 | ) as e: |
| 456 | print( | 453 | logger.error("error: in `%s`: %s", " ".join([name] + argv), e) |
| 457 | "error: in `%s`: %s" % (" ".join([name] + argv), str(e)), | ||
| 458 | file=sys.stderr, | ||
| 459 | ) | ||
| 460 | if isinstance(e, NoManifestException): | 454 | if isinstance(e, NoManifestException): |
| 461 | print( | 455 | logger.error( |
| 462 | "error: manifest missing or unreadable -- please run init", | 456 | "error: manifest missing or unreadable -- please run init" |
| 463 | file=sys.stderr, | ||
| 464 | ) | 457 | ) |
| 465 | result = e.exit_code | 458 | result = e.exit_code |
| 466 | except NoSuchProjectError as e: | 459 | except NoSuchProjectError as e: |
| 467 | if e.name: | 460 | if e.name: |
| 468 | print("error: project %s not found" % e.name, file=sys.stderr) | 461 | logger.error("error: project %s not found", e.name) |
| 469 | else: | 462 | else: |
| 470 | print("error: no project in current directory", file=sys.stderr) | 463 | logger.error("error: no project in current directory") |
| 471 | result = e.exit_code | 464 | result = e.exit_code |
| 472 | except InvalidProjectGroupsError as e: | 465 | except InvalidProjectGroupsError as e: |
| 473 | if e.name: | 466 | if e.name: |
| 474 | print( | 467 | logger.error( |
| 475 | "error: project group must be enabled for project %s" | 468 | "error: project group must be enabled for project %s", |
| 476 | % e.name, | 469 | e.name, |
| 477 | file=sys.stderr, | ||
| 478 | ) | 470 | ) |
| 479 | else: | 471 | else: |
| 480 | print( | 472 | logger.error( |
| 481 | "error: project group must be enabled for the project in " | 473 | "error: project group must be enabled for the project in " |
| 482 | "the current directory", | 474 | "the current directory" |
| 483 | file=sys.stderr, | ||
| 484 | ) | 475 | ) |
| 485 | result = e.exit_code | 476 | result = e.exit_code |
| 486 | except SystemExit as e: | 477 | except SystemExit as e: |
| @@ -547,7 +538,7 @@ def _CheckWrapperVersion(ver_str, repo_path): | |||
| 547 | repo_path = "~/bin/repo" | 538 | repo_path = "~/bin/repo" |
| 548 | 539 | ||
| 549 | if not ver_str: | 540 | if not ver_str: |
| 550 | print("no --wrapper-version argument", file=sys.stderr) | 541 | logger.error("no --wrapper-version argument") |
| 551 | sys.exit(1) | 542 | sys.exit(1) |
| 552 | 543 | ||
| 553 | # Pull out the version of the repo launcher we know about to compare. | 544 | # Pull out the version of the repo launcher we know about to compare. |
| @@ -556,7 +547,7 @@ def _CheckWrapperVersion(ver_str, repo_path): | |||
| 556 | 547 | ||
| 557 | exp_str = ".".join(map(str, exp)) | 548 | exp_str = ".".join(map(str, exp)) |
| 558 | if ver < MIN_REPO_VERSION: | 549 | if ver < MIN_REPO_VERSION: |
| 559 | print( | 550 | logger.error( |
| 560 | """ | 551 | """ |
| 561 | repo: error: | 552 | repo: error: |
| 562 | !!! Your version of repo %s is too old. | 553 | !!! Your version of repo %s is too old. |
| @@ -565,42 +556,42 @@ repo: error: | |||
| 565 | !!! You must upgrade before you can continue: | 556 | !!! You must upgrade before you can continue: |
| 566 | 557 | ||
| 567 | cp %s %s | 558 | cp %s %s |
| 568 | """ | 559 | """, |
| 569 | % (ver_str, min_str, exp_str, WrapperPath(), repo_path), | 560 | ver_str, |
| 570 | file=sys.stderr, | 561 | min_str, |
| 562 | exp_str, | ||
| 563 | WrapperPath(), | ||
| 564 | repo_path, | ||
| 571 | ) | 565 | ) |
| 572 | sys.exit(1) | 566 | sys.exit(1) |
| 573 | 567 | ||
| 574 | if exp > ver: | 568 | if exp > ver: |
| 575 | print( | 569 | logger.warn("\n... A new version of repo (%s) is available.", exp_str) |
| 576 | "\n... A new version of repo (%s) is available." % (exp_str,), | ||
| 577 | file=sys.stderr, | ||
| 578 | ) | ||
| 579 | if os.access(repo_path, os.W_OK): | 570 | if os.access(repo_path, os.W_OK): |
| 580 | print( | 571 | logger.warn( |
| 581 | """\ | 572 | """\ |
| 582 | ... You should upgrade soon: | 573 | ... You should upgrade soon: |
| 583 | cp %s %s | 574 | cp %s %s |
| 584 | """ | 575 | """, |
| 585 | % (WrapperPath(), repo_path), | 576 | WrapperPath(), |
| 586 | file=sys.stderr, | 577 | repo_path, |
| 587 | ) | 578 | ) |
| 588 | else: | 579 | else: |
| 589 | print( | 580 | logger.warn( |
| 590 | """\ | 581 | """\ |
| 591 | ... New version is available at: %s | 582 | ... New version is available at: %s |
| 592 | ... The launcher is run from: %s | 583 | ... The launcher is run from: %s |
| 593 | !!! The launcher is not writable. Please talk to your sysadmin or distro | 584 | !!! The launcher is not writable. Please talk to your sysadmin or distro |
| 594 | !!! to get an update installed. | 585 | !!! to get an update installed. |
| 595 | """ | 586 | """, |
| 596 | % (WrapperPath(), repo_path), | 587 | WrapperPath(), |
| 597 | file=sys.stderr, | 588 | repo_path, |
| 598 | ) | 589 | ) |
| 599 | 590 | ||
| 600 | 591 | ||
| 601 | def _CheckRepoDir(repo_dir): | 592 | def _CheckRepoDir(repo_dir): |
| 602 | if not repo_dir: | 593 | if not repo_dir: |
| 603 | print("no --repo-dir argument", file=sys.stderr) | 594 | logger.error("no --repo-dir argument") |
| 604 | sys.exit(1) | 595 | sys.exit(1) |
| 605 | 596 | ||
| 606 | 597 | ||
| @@ -861,18 +852,7 @@ def _Main(argv): | |||
| 861 | result = repo._Run(name, gopts, argv) or 0 | 852 | result = repo._Run(name, gopts, argv) or 0 |
| 862 | except RepoExitError as e: | 853 | except RepoExitError as e: |
| 863 | if not isinstance(e, SilentRepoExitError): | 854 | if not isinstance(e, SilentRepoExitError): |
| 864 | exception_name = type(e).__name__ | 855 | logger.log_aggregated_errors(e) |
| 865 | print("fatal: %s" % e, file=sys.stderr) | ||
| 866 | if e.aggregate_errors: | ||
| 867 | print(f"{exception_name} Aggregate Errors") | ||
| 868 | for err in e.aggregate_errors[:MAX_PRINT_ERRORS]: | ||
| 869 | print(err) | ||
| 870 | if ( | ||
| 871 | e.aggregate_errors | ||
| 872 | and len(e.aggregate_errors) > MAX_PRINT_ERRORS | ||
| 873 | ): | ||
| 874 | diff = len(e.aggregate_errors) - MAX_PRINT_ERRORS | ||
| 875 | print(f"+{diff} additional errors ...") | ||
| 876 | result = e.exit_code | 856 | result = e.exit_code |
| 877 | except KeyboardInterrupt: | 857 | except KeyboardInterrupt: |
| 878 | print("aborted by user", file=sys.stderr) | 858 | print("aborted by user", file=sys.stderr) |
diff --git a/repo_logging.py b/repo_logging.py index 7d050555..58625351 100644 --- a/repo_logging.py +++ b/repo_logging.py | |||
| @@ -15,12 +15,13 @@ | |||
| 15 | """Logic for printing user-friendly logs in repo.""" | 15 | """Logic for printing user-friendly logs in repo.""" |
| 16 | 16 | ||
| 17 | import logging | 17 | import logging |
| 18 | from typing import List | ||
| 19 | 18 | ||
| 20 | from color import Coloring | 19 | from color import Coloring |
| 20 | from error import RepoExitError | ||
| 21 | 21 | ||
| 22 | 22 | ||
| 23 | SEPARATOR = "=" * 80 | 23 | SEPARATOR = "=" * 80 |
| 24 | MAX_PRINT_ERRORS = 5 | ||
| 24 | 25 | ||
| 25 | 26 | ||
| 26 | class _ConfigMock: | 27 | class _ConfigMock: |
| @@ -70,8 +71,22 @@ class RepoLogger(logging.Logger): | |||
| 70 | handler.setFormatter(_LogColoringFormatter(config)) | 71 | handler.setFormatter(_LogColoringFormatter(config)) |
| 71 | self.addHandler(handler) | 72 | self.addHandler(handler) |
| 72 | 73 | ||
| 73 | def log_aggregated_errors(self, errors: List[Exception]): | 74 | def log_aggregated_errors(self, err: RepoExitError): |
| 74 | """Print all aggregated logs.""" | 75 | """Print all aggregated logs.""" |
| 75 | super().error(SEPARATOR) | 76 | self.error(SEPARATOR) |
| 76 | super().error("Repo command failed due to following errors:") | 77 | |
| 77 | super().error("\n".join(str(e) for e in errors)) | 78 | if not err.aggregate_errors: |
| 79 | self.error("Repo command failed: %s", type(err).__name__) | ||
| 80 | return | ||
| 81 | |||
| 82 | self.error( | ||
| 83 | "Repo command failed due to the following `%s` errors:", | ||
| 84 | type(err).__name__, | ||
| 85 | ) | ||
| 86 | self.error( | ||
| 87 | "\n".join(str(e) for e in err.aggregate_errors[:MAX_PRINT_ERRORS]) | ||
| 88 | ) | ||
| 89 | |||
| 90 | diff = len(err.aggregate_errors) - MAX_PRINT_ERRORS | ||
| 91 | if diff: | ||
| 92 | self.error("+%d additional errors...", diff) | ||
diff --git a/tests/test_repo_logging.py b/tests/test_repo_logging.py index 52f251a7..0f6a3355 100644 --- a/tests/test_repo_logging.py +++ b/tests/test_repo_logging.py | |||
| @@ -16,47 +16,49 @@ | |||
| 16 | import unittest | 16 | import unittest |
| 17 | from unittest import mock | 17 | from unittest import mock |
| 18 | 18 | ||
| 19 | from error import RepoExitError | ||
| 19 | from repo_logging import RepoLogger | 20 | from repo_logging import RepoLogger |
| 20 | 21 | ||
| 21 | 22 | ||
| 22 | class TestRepoLogger(unittest.TestCase): | 23 | class TestRepoLogger(unittest.TestCase): |
| 23 | def test_log_aggregated_errors_logs_aggregated_errors(self): | 24 | @mock.patch.object(RepoLogger, "error") |
| 24 | """Test if log_aggregated_errors outputs aggregated errors.""" | 25 | def test_log_aggregated_errors_logs_aggregated_errors(self, mock_error): |
| 26 | """Test if log_aggregated_errors logs a list of aggregated errors.""" | ||
| 25 | logger = RepoLogger(__name__) | 27 | logger = RepoLogger(__name__) |
| 26 | result = [] | ||
| 27 | |||
| 28 | def mock_handler(log): | ||
| 29 | nonlocal result | ||
| 30 | result.append(log.getMessage()) | ||
| 31 | |||
| 32 | mock_out = mock.MagicMock() | ||
| 33 | mock_out.level = 0 | ||
| 34 | mock_out.handle = mock_handler | ||
| 35 | logger.addHandler(mock_out) | ||
| 36 | |||
| 37 | logger.error("Never gonna give you up") | ||
| 38 | logger.error("Never gonna let you down") | ||
| 39 | logger.error("Never gonna run around and desert you") | ||
| 40 | logger.log_aggregated_errors( | 28 | logger.log_aggregated_errors( |
| 29 | RepoExitError( | ||
| 30 | aggregate_errors=[ | ||
| 31 | Exception("foo"), | ||
| 32 | Exception("bar"), | ||
| 33 | Exception("baz"), | ||
| 34 | Exception("hello"), | ||
| 35 | Exception("world"), | ||
| 36 | Exception("test"), | ||
| 37 | ] | ||
| 38 | ) | ||
| 39 | ) | ||
| 40 | |||
| 41 | mock_error.assert_has_calls( | ||
| 41 | [ | 42 | [ |
| 42 | "Never gonna give you up", | 43 | mock.call("=" * 80), |
| 43 | "Never gonna let you down", | 44 | mock.call( |
| 44 | "Never gonna run around and desert you", | 45 | "Repo command failed due to the following `%s` errors:", |
| 46 | "RepoExitError", | ||
| 47 | ), | ||
| 48 | mock.call("foo\nbar\nbaz\nhello\nworld"), | ||
| 49 | mock.call("+%d additional errors...", 1), | ||
| 45 | ] | 50 | ] |
| 46 | ) | 51 | ) |
| 47 | 52 | ||
| 48 | self.assertEqual( | 53 | @mock.patch.object(RepoLogger, "error") |
| 49 | result, | 54 | def test_log_aggregated_errors_logs_single_error(self, mock_error): |
| 55 | """Test if log_aggregated_errors logs empty aggregated_errors.""" | ||
| 56 | logger = RepoLogger(__name__) | ||
| 57 | logger.log_aggregated_errors(RepoExitError()) | ||
| 58 | |||
| 59 | mock_error.assert_has_calls( | ||
| 50 | [ | 60 | [ |
| 51 | "Never gonna give you up", | 61 | mock.call("=" * 80), |
| 52 | "Never gonna let you down", | 62 | mock.call("Repo command failed: %s", "RepoExitError"), |
| 53 | "Never gonna run around and desert you", | 63 | ] |
| 54 | "=" * 80, | ||
| 55 | "Repo command failed due to following errors:", | ||
| 56 | ( | ||
| 57 | "Never gonna give you up\n" | ||
| 58 | "Never gonna let you down\n" | ||
| 59 | "Never gonna run around and desert you" | ||
| 60 | ), | ||
| 61 | ], | ||
| 62 | ) | 64 | ) |
