summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJason Chang <jasonnc@google.com>2023-08-31 17:06:36 -0700
committerLUCI <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com>2023-09-06 17:36:31 +0000
commitdaf2ad38eb62fb990fadc3fc872f303113ac4768 (patch)
tree433961d2f498093386ed651c0dff2c523a67e1fd
parentb861511db919b93483b69136fd0f2c6ddab6b4ea (diff)
downloadgit-repo-daf2ad38eb62fb990fadc3fc872f303113ac4768.tar.gz
sync: Preserve errors on KeyboardInterrupt
If a KeyboardInterrupt is encountered before an error is aggregated then the context surrounding the interrupt is lost. This change aggregates errors as soon as possible for the sync command Bug: b/293344017 Change-Id: Iac14f9d59723cc9dedbb960f14fdc1fa5b348ea3 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/384974 Tested-by: Jason Chang <jasonnc@google.com> Commit-Queue: Jason Chang <jasonnc@google.com> Reviewed-by: Gavin Mak <gavinmak@google.com>
-rw-r--r--subcmds/sync.py119
1 files changed, 78 insertions, 41 deletions
diff --git a/subcmds/sync.py b/subcmds/sync.py
index 7ccd680f..bdb5616c 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -22,9 +22,10 @@ import netrc
22import optparse 22import optparse
23import os 23import os
24import socket 24import socket
25import sys
25import tempfile 26import tempfile
26import time 27import time
27from typing import List, NamedTuple, Set 28from typing import List, NamedTuple, Set, Union
28import urllib.error 29import urllib.error
29import urllib.parse 30import urllib.parse
30import urllib.request 31import urllib.request
@@ -55,6 +56,7 @@ from command import MirrorSafeCommand
55from command import WORKER_BATCH_SIZE 56from command import WORKER_BATCH_SIZE
56from error import GitError 57from error import GitError
57from error import RepoChangedException 58from error import RepoChangedException
59from error import RepoError
58from error import RepoExitError 60from error import RepoExitError
59from error import RepoUnhandledExceptionError 61from error import RepoUnhandledExceptionError
60from error import SyncError 62from error import SyncError
@@ -120,7 +122,6 @@ class _FetchResult(NamedTuple):
120 122
121 success: bool 123 success: bool
122 projects: Set[str] 124 projects: Set[str]
123 errors: List[Exception]
124 125
125 126
126class _FetchMainResult(NamedTuple): 127class _FetchMainResult(NamedTuple):
@@ -131,7 +132,6 @@ class _FetchMainResult(NamedTuple):
131 """ 132 """
132 133
133 all_projects: List[Project] 134 all_projects: List[Project]
134 errors: List[Exception]
135 135
136 136
137class _CheckoutOneResult(NamedTuple): 137class _CheckoutOneResult(NamedTuple):
@@ -163,6 +163,34 @@ class SmartSyncError(SyncError):
163 """Smart sync exit error.""" 163 """Smart sync exit error."""
164 164
165 165
166class ManifestInterruptError(RepoError):
167 """Aggregate Error to be logged when a user interrupts a manifest update."""
168
169 def __init__(self, output, **kwargs):
170 super().__init__(output, **kwargs)
171 self.output = output
172
173 def __str__(self):
174 error_type = type(self).__name__
175 return f"{error_type}:{self.output}"
176
177
178class TeeStringIO(io.StringIO):
179 """StringIO class that can write to an additional destination."""
180
181 def __init__(
182 self, io: Union[io.TextIOWrapper, None], *args, **kwargs
183 ) -> None:
184 super().__init__(*args, **kwargs)
185 self.io = io
186
187 def write(self, s: str) -> int:
188 """Write to additional destination."""
189 super().write(s)
190 if self.io is not None:
191 self.io.write(s)
192
193
166class Sync(Command, MirrorSafeCommand): 194class Sync(Command, MirrorSafeCommand):
167 COMMON = True 195 COMMON = True
168 MULTI_MANIFEST_SUPPORT = True 196 MULTI_MANIFEST_SUPPORT = True
@@ -648,7 +676,7 @@ later is required to fix a server side protocol bug.
648 success = False 676 success = False
649 remote_fetched = False 677 remote_fetched = False
650 errors = [] 678 errors = []
651 buf = io.StringIO() 679 buf = TeeStringIO(sys.stdout if opt.verbose else None)
652 try: 680 try:
653 sync_result = project.Sync_NetworkHalf( 681 sync_result = project.Sync_NetworkHalf(
654 quiet=opt.quiet, 682 quiet=opt.quiet,
@@ -675,7 +703,7 @@ later is required to fix a server side protocol bug.
675 errors.append(sync_result.error) 703 errors.append(sync_result.error)
676 704
677 output = buf.getvalue() 705 output = buf.getvalue()
678 if (opt.verbose or not success) and output: 706 if output and buf.io is None and not success:
679 print("\n" + output.rstrip()) 707 print("\n" + output.rstrip())
680 708
681 if not success: 709 if not success:
@@ -729,13 +757,12 @@ later is required to fix a server side protocol bug.
729 jobs = jobs_str(len(items)) 757 jobs = jobs_str(len(items))
730 return f"{jobs} | {elapsed_str(elapsed)} {earliest_proj}" 758 return f"{jobs} | {elapsed_str(elapsed)} {earliest_proj}"
731 759
732 def _Fetch(self, projects, opt, err_event, ssh_proxy): 760 def _Fetch(self, projects, opt, err_event, ssh_proxy, errors):
733 ret = True 761 ret = True
734 762
735 jobs = opt.jobs_network 763 jobs = opt.jobs_network
736 fetched = set() 764 fetched = set()
737 remote_fetched = set() 765 remote_fetched = set()
738 errors = []
739 pm = Progress( 766 pm = Progress(
740 "Fetching", 767 "Fetching",
741 len(projects), 768 len(projects),
@@ -850,10 +877,10 @@ later is required to fix a server side protocol bug.
850 if not self.outer_client.manifest.IsArchive: 877 if not self.outer_client.manifest.IsArchive:
851 self._GCProjects(projects, opt, err_event) 878 self._GCProjects(projects, opt, err_event)
852 879
853 return _FetchResult(ret, fetched, errors) 880 return _FetchResult(ret, fetched)
854 881
855 def _FetchMain( 882 def _FetchMain(
856 self, opt, args, all_projects, err_event, ssh_proxy, manifest 883 self, opt, args, all_projects, err_event, ssh_proxy, manifest, errors
857 ): 884 ):
858 """The main network fetch loop. 885 """The main network fetch loop.
859 886
@@ -869,7 +896,6 @@ later is required to fix a server side protocol bug.
869 List of all projects that should be checked out. 896 List of all projects that should be checked out.
870 """ 897 """
871 rp = manifest.repoProject 898 rp = manifest.repoProject
872 errors = []
873 899
874 to_fetch = [] 900 to_fetch = []
875 now = time.time() 901 now = time.time()
@@ -878,11 +904,9 @@ later is required to fix a server side protocol bug.
878 to_fetch.extend(all_projects) 904 to_fetch.extend(all_projects)
879 to_fetch.sort(key=self._fetch_times.Get, reverse=True) 905 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
880 906
881 result = self._Fetch(to_fetch, opt, err_event, ssh_proxy) 907 result = self._Fetch(to_fetch, opt, err_event, ssh_proxy, errors)
882 success = result.success 908 success = result.success
883 fetched = result.projects 909 fetched = result.projects
884 if result.errors:
885 errors.extend(result.errors)
886 910
887 if not success: 911 if not success:
888 err_event.set() 912 err_event.set()
@@ -898,7 +922,7 @@ later is required to fix a server side protocol bug.
898 922
899 logger.error(e) 923 logger.error(e)
900 raise e 924 raise e
901 return _FetchMainResult([], errors) 925 return _FetchMainResult([])
902 926
903 # Iteratively fetch missing and/or nested unregistered submodules. 927 # Iteratively fetch missing and/or nested unregistered submodules.
904 previously_missing_set = set() 928 previously_missing_set = set()
@@ -923,16 +947,14 @@ later is required to fix a server side protocol bug.
923 if previously_missing_set == missing_set: 947 if previously_missing_set == missing_set:
924 break 948 break
925 previously_missing_set = missing_set 949 previously_missing_set = missing_set
926 result = self._Fetch(missing, opt, err_event, ssh_proxy) 950 result = self._Fetch(missing, opt, err_event, ssh_proxy, errors)
927 success = result.success 951 success = result.success
928 new_fetched = result.projects 952 new_fetched = result.projects
929 if result.errors:
930 errors.extend(result.errors)
931 if not success: 953 if not success:
932 err_event.set() 954 err_event.set()
933 fetched.update(new_fetched) 955 fetched.update(new_fetched)
934 956
935 return _FetchMainResult(all_projects, errors) 957 return _FetchMainResult(all_projects)
936 958
937 def _CheckoutOne(self, detach_head, force_sync, project): 959 def _CheckoutOne(self, detach_head, force_sync, project):
938 """Checkout work tree for one project 960 """Checkout work tree for one project
@@ -1440,7 +1462,7 @@ later is required to fix a server side protocol bug.
1440 1462
1441 return manifest_name 1463 return manifest_name
1442 1464
1443 def _UpdateAllManifestProjects(self, opt, mp, manifest_name): 1465 def _UpdateAllManifestProjects(self, opt, mp, manifest_name, errors):
1444 """Fetch & update the local manifest project. 1466 """Fetch & update the local manifest project.
1445 1467
1446 After syncing the manifest project, if the manifest has any sub 1468 After syncing the manifest project, if the manifest has any sub
@@ -1452,7 +1474,7 @@ later is required to fix a server side protocol bug.
1452 manifest_name: Manifest file to be reloaded. 1474 manifest_name: Manifest file to be reloaded.
1453 """ 1475 """
1454 if not mp.standalone_manifest_url: 1476 if not mp.standalone_manifest_url:
1455 self._UpdateManifestProject(opt, mp, manifest_name) 1477 self._UpdateManifestProject(opt, mp, manifest_name, errors)
1456 1478
1457 if mp.manifest.submanifests: 1479 if mp.manifest.submanifests:
1458 for submanifest in mp.manifest.submanifests.values(): 1480 for submanifest in mp.manifest.submanifests.values():
@@ -1465,10 +1487,10 @@ later is required to fix a server side protocol bug.
1465 git_event_log=self.git_event_log, 1487 git_event_log=self.git_event_log,
1466 ) 1488 )
1467 self._UpdateAllManifestProjects( 1489 self._UpdateAllManifestProjects(
1468 opt, child.manifestProject, None 1490 opt, child.manifestProject, None, errors
1469 ) 1491 )
1470 1492
1471 def _UpdateManifestProject(self, opt, mp, manifest_name): 1493 def _UpdateManifestProject(self, opt, mp, manifest_name, errors):
1472 """Fetch & update the local manifest project. 1494 """Fetch & update the local manifest project.
1473 1495
1474 Args: 1496 Args:
@@ -1478,21 +1500,32 @@ later is required to fix a server side protocol bug.
1478 """ 1500 """
1479 if not opt.local_only: 1501 if not opt.local_only:
1480 start = time.time() 1502 start = time.time()
1481 result = mp.Sync_NetworkHalf( 1503 buf = TeeStringIO(sys.stdout)
1482 quiet=opt.quiet, 1504 try:
1483 verbose=opt.verbose, 1505 result = mp.Sync_NetworkHalf(
1484 current_branch_only=self._GetCurrentBranchOnly( 1506 quiet=opt.quiet,
1485 opt, mp.manifest 1507 output_redir=buf,
1486 ), 1508 verbose=opt.verbose,
1487 force_sync=opt.force_sync, 1509 current_branch_only=self._GetCurrentBranchOnly(
1488 tags=opt.tags, 1510 opt, mp.manifest
1489 optimized_fetch=opt.optimized_fetch, 1511 ),
1490 retry_fetches=opt.retry_fetches, 1512 force_sync=opt.force_sync,
1491 submodules=mp.manifest.HasSubmodules, 1513 tags=opt.tags,
1492 clone_filter=mp.manifest.CloneFilter, 1514 optimized_fetch=opt.optimized_fetch,
1493 partial_clone_exclude=mp.manifest.PartialCloneExclude, 1515 retry_fetches=opt.retry_fetches,
1494 clone_filter_for_depth=mp.manifest.CloneFilterForDepth, 1516 submodules=mp.manifest.HasSubmodules,
1495 ) 1517 clone_filter=mp.manifest.CloneFilter,
1518 partial_clone_exclude=mp.manifest.PartialCloneExclude,
1519 clone_filter_for_depth=mp.manifest.CloneFilterForDepth,
1520 )
1521 if result.error:
1522 errors.append(result.error)
1523 except KeyboardInterrupt:
1524 errors.append(
1525 ManifestInterruptError(buf.getvalue(), project=mp.name)
1526 )
1527 raise
1528
1496 finish = time.time() 1529 finish = time.time()
1497 self.event_log.AddSync( 1530 self.event_log.AddSync(
1498 mp, event_log.TASK_SYNC_NETWORK, start, finish, result.success 1531 mp, event_log.TASK_SYNC_NETWORK, start, finish, result.success
@@ -1664,7 +1697,7 @@ later is required to fix a server side protocol bug.
1664 mp.ConfigureCloneFilterForDepth("blob:none") 1697 mp.ConfigureCloneFilterForDepth("blob:none")
1665 1698
1666 if opt.mp_update: 1699 if opt.mp_update:
1667 self._UpdateAllManifestProjects(opt, mp, manifest_name) 1700 self._UpdateAllManifestProjects(opt, mp, manifest_name, errors)
1668 else: 1701 else:
1669 logger.info("Skipping update of local manifest project.") 1702 logger.info("Skipping update of local manifest project.")
1670 1703
@@ -1704,10 +1737,14 @@ later is required to fix a server side protocol bug.
1704 # Initialize the socket dir once in the parent. 1737 # Initialize the socket dir once in the parent.
1705 ssh_proxy.sock() 1738 ssh_proxy.sock()
1706 result = self._FetchMain( 1739 result = self._FetchMain(
1707 opt, args, all_projects, err_event, ssh_proxy, manifest 1740 opt,
1741 args,
1742 all_projects,
1743 err_event,
1744 ssh_proxy,
1745 manifest,
1746 errors,
1708 ) 1747 )
1709 if result.errors:
1710 errors.extend(result.errors)
1711 all_projects = result.all_projects 1748 all_projects = result.all_projects
1712 1749
1713 if opt.network_only: 1750 if opt.network_only: