summaryrefslogtreecommitdiffstats
path: root/project.py
diff options
context:
space:
mode:
authorJason Chang <jasonnc@google.com>2023-07-14 16:45:35 -0700
committerLUCI <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com>2023-08-02 18:29:05 +0000
commit32b59565b7bd41ec1a121869823557f0b2b022d7 (patch)
tree0cd0fe644ecc6e319df96861f26b77a55c9969eb /project.py
parenta6413f5d88f12466b3daa833668d0f59fc65ece4 (diff)
downloadgit-repo-32b59565b7bd41ec1a121869823557f0b2b022d7.tar.gz
Refactor errors for sync command
Per discussion in go/repo-error-update updated aggregated and exit errors for sync command. Aggregated errors are errors that result in eventual command failure. Exit errors are errors that result in immediate command failure. Also updated main.py to log aggregated and exit errors to git sessions log Bug: b/293344017 Change-Id: I77a21f14da32fe2e68c16841feb22de72e86a251 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/379614 Reviewed-by: Aravind Vasudevan <aravindvasudev@google.com> Tested-by: Jason Chang <jasonnc@google.com> Commit-Queue: Jason Chang <jasonnc@google.com>
Diffstat (limited to 'project.py')
-rw-r--r--project.py283
1 files changed, 196 insertions, 87 deletions
diff --git a/project.py b/project.py
index 83f3eff9..b268007d 100644
--- a/project.py
+++ b/project.py
@@ -26,7 +26,7 @@ import sys
26import tarfile 26import tarfile
27import tempfile 27import tempfile
28import time 28import time
29from typing import NamedTuple 29from typing import NamedTuple, List
30import urllib.parse 30import urllib.parse
31 31
32from color import Coloring 32from color import Coloring
@@ -41,7 +41,12 @@ from git_config import (
41) 41)
42import git_superproject 42import git_superproject
43from git_trace2_event_log import EventLog 43from git_trace2_event_log import EventLog
44from error import GitError, UploadError, DownloadError 44from error import (
45 GitError,
46 UploadError,
47 DownloadError,
48 RepoError,
49)
45from error import ManifestInvalidRevisionError, ManifestInvalidPathError 50from error import ManifestInvalidRevisionError, ManifestInvalidPathError
46from error import NoManifestException, ManifestParseError 51from error import NoManifestException, ManifestParseError
47import platform_utils 52import platform_utils
@@ -54,11 +59,33 @@ from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M, R_WORKTREE_M
54class SyncNetworkHalfResult(NamedTuple): 59class SyncNetworkHalfResult(NamedTuple):
55 """Sync_NetworkHalf return value.""" 60 """Sync_NetworkHalf return value."""
56 61
57 # True if successful.
58 success: bool
59 # Did we query the remote? False when optimized_fetch is True and we have 62 # Did we query the remote? False when optimized_fetch is True and we have
60 # the commit already present. 63 # the commit already present.
61 remote_fetched: bool 64 remote_fetched: bool
65 # Error from SyncNetworkHalf
66 error: Exception = None
67
68 @property
69 def success(self) -> bool:
70 return not self.error
71
72
73class SyncNetworkHalfError(RepoError):
74 """Failure trying to sync."""
75
76
77class DeleteWorktreeError(RepoError):
78 """Failure to delete worktree."""
79
80 def __init__(
81 self, *args, aggregate_errors: List[Exception] = None, **kwargs
82 ) -> None:
83 super().__init__(*args, **kwargs)
84 self.aggregate_errors = aggregate_errors or []
85
86
87class DeleteDirtyWorktreeError(DeleteWorktreeError):
88 """Failure to delete worktree due to uncommitted changes."""
62 89
63 90
64# Maximum sleep time allowed during retries. 91# Maximum sleep time allowed during retries.
@@ -1070,13 +1097,19 @@ class Project(object):
1070 if branch is None: 1097 if branch is None:
1071 branch = self.CurrentBranch 1098 branch = self.CurrentBranch
1072 if branch is None: 1099 if branch is None:
1073 raise GitError("not currently on a branch") 1100 raise GitError("not currently on a branch", project=self.name)
1074 1101
1075 branch = self.GetBranch(branch) 1102 branch = self.GetBranch(branch)
1076 if not branch.LocalMerge: 1103 if not branch.LocalMerge:
1077 raise GitError("branch %s does not track a remote" % branch.name) 1104 raise GitError(
1105 "branch %s does not track a remote" % branch.name,
1106 project=self.name,
1107 )
1078 if not branch.remote.review: 1108 if not branch.remote.review:
1079 raise GitError("remote %s has no review url" % branch.remote.name) 1109 raise GitError(
1110 "remote %s has no review url" % branch.remote.name,
1111 project=self.name,
1112 )
1080 1113
1081 # Basic validity check on label syntax. 1114 # Basic validity check on label syntax.
1082 for label in labels: 1115 for label in labels:
@@ -1193,11 +1226,18 @@ class Project(object):
1193 """ 1226 """
1194 if archive and not isinstance(self, MetaProject): 1227 if archive and not isinstance(self, MetaProject):
1195 if self.remote.url.startswith(("http://", "https://")): 1228 if self.remote.url.startswith(("http://", "https://")):
1229 msg_template = (
1230 "%s: Cannot fetch archives from http/https remotes."
1231 )
1232 msg_args = self.name
1233 msg = msg_template % msg_args
1196 _error( 1234 _error(
1197 "%s: Cannot fetch archives from http/https remotes.", 1235 msg_template,
1198 self.name, 1236 msg_args,
1237 )
1238 return SyncNetworkHalfResult(
1239 False, SyncNetworkHalfError(msg, project=self.name)
1199 ) 1240 )
1200 return SyncNetworkHalfResult(False, False)
1201 1241
1202 name = self.relpath.replace("\\", "/") 1242 name = self.relpath.replace("\\", "/")
1203 name = name.replace("/", "_") 1243 name = name.replace("/", "_")
@@ -1208,19 +1248,25 @@ class Project(object):
1208 self._FetchArchive(tarpath, cwd=topdir) 1248 self._FetchArchive(tarpath, cwd=topdir)
1209 except GitError as e: 1249 except GitError as e:
1210 _error("%s", e) 1250 _error("%s", e)
1211 return SyncNetworkHalfResult(False, False) 1251 return SyncNetworkHalfResult(False, e)
1212 1252
1213 # From now on, we only need absolute tarpath. 1253 # From now on, we only need absolute tarpath.
1214 tarpath = os.path.join(topdir, tarpath) 1254 tarpath = os.path.join(topdir, tarpath)
1215 1255
1216 if not self._ExtractArchive(tarpath, path=topdir): 1256 if not self._ExtractArchive(tarpath, path=topdir):
1217 return SyncNetworkHalfResult(False, True) 1257 return SyncNetworkHalfResult(
1258 True,
1259 SyncNetworkHalfError(
1260 f"Unable to Extract Archive {tarpath}",
1261 project=self.name,
1262 ),
1263 )
1218 try: 1264 try:
1219 platform_utils.remove(tarpath) 1265 platform_utils.remove(tarpath)
1220 except OSError as e: 1266 except OSError as e:
1221 _warn("Cannot remove archive %s: %s", tarpath, str(e)) 1267 _warn("Cannot remove archive %s: %s", tarpath, str(e))
1222 self._CopyAndLinkFiles() 1268 self._CopyAndLinkFiles()
1223 return SyncNetworkHalfResult(True, True) 1269 return SyncNetworkHalfResult(True)
1224 1270
1225 # If the shared object dir already exists, don't try to rebootstrap with 1271 # If the shared object dir already exists, don't try to rebootstrap with
1226 # a clone bundle download. We should have the majority of objects 1272 # a clone bundle download. We should have the majority of objects
@@ -1310,23 +1356,35 @@ class Project(object):
1310 ) 1356 )
1311 ): 1357 ):
1312 remote_fetched = True 1358 remote_fetched = True
1313 if not self._RemoteFetch( 1359 try:
1314 initial=is_new, 1360 if not self._RemoteFetch(
1315 quiet=quiet, 1361 initial=is_new,
1316 verbose=verbose, 1362 quiet=quiet,
1317 output_redir=output_redir, 1363 verbose=verbose,
1318 alt_dir=alt_dir, 1364 output_redir=output_redir,
1319 current_branch_only=current_branch_only, 1365 alt_dir=alt_dir,
1320 tags=tags, 1366 current_branch_only=current_branch_only,
1321 prune=prune, 1367 tags=tags,
1322 depth=depth, 1368 prune=prune,
1323 submodules=submodules, 1369 depth=depth,
1324 force_sync=force_sync, 1370 submodules=submodules,
1325 ssh_proxy=ssh_proxy, 1371 force_sync=force_sync,
1326 clone_filter=clone_filter, 1372 ssh_proxy=ssh_proxy,
1327 retry_fetches=retry_fetches, 1373 clone_filter=clone_filter,
1328 ): 1374 retry_fetches=retry_fetches,
1329 return SyncNetworkHalfResult(False, remote_fetched) 1375 ):
1376 return SyncNetworkHalfResult(
1377 remote_fetched,
1378 SyncNetworkHalfError(
1379 f"Unable to remote fetch project {self.name}",
1380 project=self.name,
1381 ),
1382 )
1383 except RepoError as e:
1384 return SyncNetworkHalfResult(
1385 remote_fetched,
1386 e,
1387 )
1330 1388
1331 mp = self.manifest.manifestProject 1389 mp = self.manifest.manifestProject
1332 dissociate = mp.dissociate 1390 dissociate = mp.dissociate
@@ -1346,7 +1404,12 @@ class Project(object):
1346 if p.stdout and output_redir: 1404 if p.stdout and output_redir:
1347 output_redir.write(p.stdout) 1405 output_redir.write(p.stdout)
1348 if p.Wait() != 0: 1406 if p.Wait() != 0:
1349 return SyncNetworkHalfResult(False, remote_fetched) 1407 return SyncNetworkHalfResult(
1408 remote_fetched,
1409 GitError(
1410 "Unable to repack alternates", project=self.name
1411 ),
1412 )
1350 platform_utils.remove(alternates_file) 1413 platform_utils.remove(alternates_file)
1351 1414
1352 if self.worktree: 1415 if self.worktree:
@@ -1356,7 +1419,7 @@ class Project(object):
1356 platform_utils.remove( 1419 platform_utils.remove(
1357 os.path.join(self.gitdir, "FETCH_HEAD"), missing_ok=True 1420 os.path.join(self.gitdir, "FETCH_HEAD"), missing_ok=True
1358 ) 1421 )
1359 return SyncNetworkHalfResult(True, remote_fetched) 1422 return SyncNetworkHalfResult(remote_fetched)
1360 1423
1361 def PostRepoUpgrade(self): 1424 def PostRepoUpgrade(self):
1362 self._InitHooks() 1425 self._InitHooks()
@@ -1409,16 +1472,27 @@ class Project(object):
1409 1472
1410 self.revisionId = revisionId 1473 self.revisionId = revisionId
1411 1474
1412 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False): 1475 def Sync_LocalHalf(
1476 self, syncbuf, force_sync=False, submodules=False, errors=None
1477 ):
1413 """Perform only the local IO portion of the sync process. 1478 """Perform only the local IO portion of the sync process.
1414 1479
1415 Network access is not required. 1480 Network access is not required.
1416 """ 1481 """
1482 if errors is None:
1483 errors = []
1484
1485 def fail(error: Exception):
1486 errors.append(error)
1487 syncbuf.fail(self, error)
1488
1417 if not os.path.exists(self.gitdir): 1489 if not os.path.exists(self.gitdir):
1418 syncbuf.fail( 1490 fail(
1419 self, 1491 LocalSyncFail(
1420 "Cannot checkout %s due to missing network sync; Run " 1492 "Cannot checkout %s due to missing network sync; Run "
1421 "`repo sync -n %s` first." % (self.name, self.name), 1493 "`repo sync -n %s` first." % (self.name, self.name),
1494 project=self.name,
1495 )
1422 ) 1496 )
1423 return 1497 return
1424 1498
@@ -1438,10 +1512,12 @@ class Project(object):
1438 ) 1512 )
1439 bad_paths = paths & PROTECTED_PATHS 1513 bad_paths = paths & PROTECTED_PATHS
1440 if bad_paths: 1514 if bad_paths:
1441 syncbuf.fail( 1515 fail(
1442 self, 1516 LocalSyncFail(
1443 "Refusing to checkout project that writes to protected " 1517 "Refusing to checkout project that writes to protected "
1444 "paths: %s" % (", ".join(bad_paths),), 1518 "paths: %s" % (", ".join(bad_paths),),
1519 project=self.name,
1520 )
1445 ) 1521 )
1446 return 1522 return
1447 1523
@@ -1466,7 +1542,7 @@ class Project(object):
1466 # Currently on a detached HEAD. The user is assumed to 1542 # Currently on a detached HEAD. The user is assumed to
1467 # not have any local modifications worth worrying about. 1543 # not have any local modifications worth worrying about.
1468 if self.IsRebaseInProgress(): 1544 if self.IsRebaseInProgress():
1469 syncbuf.fail(self, _PriorSyncFailedError()) 1545 fail(_PriorSyncFailedError(project=self.name))
1470 return 1546 return
1471 1547
1472 if head == revid: 1548 if head == revid:
@@ -1486,7 +1562,7 @@ class Project(object):
1486 if submodules: 1562 if submodules:
1487 self._SyncSubmodules(quiet=True) 1563 self._SyncSubmodules(quiet=True)
1488 except GitError as e: 1564 except GitError as e:
1489 syncbuf.fail(self, e) 1565 fail(e)
1490 return 1566 return
1491 self._CopyAndLinkFiles() 1567 self._CopyAndLinkFiles()
1492 return 1568 return
@@ -1511,7 +1587,7 @@ class Project(object):
1511 if submodules: 1587 if submodules:
1512 self._SyncSubmodules(quiet=True) 1588 self._SyncSubmodules(quiet=True)
1513 except GitError as e: 1589 except GitError as e:
1514 syncbuf.fail(self, e) 1590 fail(e)
1515 return 1591 return
1516 self._CopyAndLinkFiles() 1592 self._CopyAndLinkFiles()
1517 return 1593 return
@@ -1534,10 +1610,13 @@ class Project(object):
1534 # The user has published this branch and some of those 1610 # The user has published this branch and some of those
1535 # commits are not yet merged upstream. We do not want 1611 # commits are not yet merged upstream. We do not want
1536 # to rewrite the published commits so we punt. 1612 # to rewrite the published commits so we punt.
1537 syncbuf.fail( 1613 fail(
1538 self, 1614 LocalSyncFail(
1539 "branch %s is published (but not merged) and is now " 1615 "branch %s is published (but not merged) and is "
1540 "%d commits behind" % (branch.name, len(upstream_gain)), 1616 "now %d commits behind"
1617 % (branch.name, len(upstream_gain)),
1618 project=self.name,
1619 )
1541 ) 1620 )
1542 return 1621 return
1543 elif pub == head: 1622 elif pub == head:
@@ -1565,7 +1644,7 @@ class Project(object):
1565 return 1644 return
1566 1645
1567 if self.IsDirty(consider_untracked=False): 1646 if self.IsDirty(consider_untracked=False):
1568 syncbuf.fail(self, _DirtyError()) 1647 fail(_DirtyError(project=self.name))
1569 return 1648 return
1570 1649
1571 # If the upstream switched on us, warn the user. 1650 # If the upstream switched on us, warn the user.
@@ -1615,7 +1694,7 @@ class Project(object):
1615 self._SyncSubmodules(quiet=True) 1694 self._SyncSubmodules(quiet=True)
1616 self._CopyAndLinkFiles() 1695 self._CopyAndLinkFiles()
1617 except GitError as e: 1696 except GitError as e:
1618 syncbuf.fail(self, e) 1697 fail(e)
1619 return 1698 return
1620 else: 1699 else:
1621 syncbuf.later1(self, _doff) 1700 syncbuf.later1(self, _doff)
@@ -1687,12 +1766,12 @@ class Project(object):
1687 file=sys.stderr, 1766 file=sys.stderr,
1688 ) 1767 )
1689 else: 1768 else:
1690 print( 1769 msg = (
1691 "error: %s: Cannot remove project: uncommitted changes are " 1770 "error: %s: Cannot remove project: uncommitted"
1692 "present.\n" % (self.RelPath(local=False),), 1771 "changes are present.\n" % self.RelPath(local=False)
1693 file=sys.stderr,
1694 ) 1772 )
1695 return False 1773 print(msg, file=sys.stderr)
1774 raise DeleteDirtyWorktreeError(msg, project=self)
1696 1775
1697 if not quiet: 1776 if not quiet:
1698 print( 1777 print(
@@ -1745,12 +1824,13 @@ class Project(object):
1745 % (self.RelPath(local=False),), 1824 % (self.RelPath(local=False),),
1746 file=sys.stderr, 1825 file=sys.stderr,
1747 ) 1826 )
1748 return False 1827 raise DeleteWorktreeError(aggregate_errors=[e])
1749 1828
1750 # Delete everything under the worktree, except for directories that 1829 # Delete everything under the worktree, except for directories that
1751 # contain another git project. 1830 # contain another git project.
1752 dirs_to_remove = [] 1831 dirs_to_remove = []
1753 failed = False 1832 failed = False
1833 errors = []
1754 for root, dirs, files in platform_utils.walk(self.worktree): 1834 for root, dirs, files in platform_utils.walk(self.worktree):
1755 for f in files: 1835 for f in files:
1756 path = os.path.join(root, f) 1836 path = os.path.join(root, f)
@@ -1763,6 +1843,7 @@ class Project(object):
1763 file=sys.stderr, 1843 file=sys.stderr,
1764 ) 1844 )
1765 failed = True 1845 failed = True
1846 errors.append(e)
1766 dirs[:] = [ 1847 dirs[:] = [
1767 d 1848 d
1768 for d in dirs 1849 for d in dirs
@@ -1784,6 +1865,7 @@ class Project(object):
1784 file=sys.stderr, 1865 file=sys.stderr,
1785 ) 1866 )
1786 failed = True 1867 failed = True
1868 errors.append(e)
1787 elif not platform_utils.listdir(d): 1869 elif not platform_utils.listdir(d):
1788 try: 1870 try:
1789 platform_utils.rmdir(d) 1871 platform_utils.rmdir(d)
@@ -1794,6 +1876,7 @@ class Project(object):
1794 file=sys.stderr, 1876 file=sys.stderr,
1795 ) 1877 )
1796 failed = True 1878 failed = True
1879 errors.append(e)
1797 if failed: 1880 if failed:
1798 print( 1881 print(
1799 "error: %s: Failed to delete obsolete checkout." 1882 "error: %s: Failed to delete obsolete checkout."
@@ -1804,7 +1887,7 @@ class Project(object):
1804 " Remove manually, then run `repo sync -l`.", 1887 " Remove manually, then run `repo sync -l`.",
1805 file=sys.stderr, 1888 file=sys.stderr,
1806 ) 1889 )
1807 return False 1890 raise DeleteWorktreeError(aggregate_errors=errors)
1808 1891
1809 # Try deleting parent dirs if they are empty. 1892 # Try deleting parent dirs if they are empty.
1810 path = self.worktree 1893 path = self.worktree
@@ -2264,11 +2347,14 @@ class Project(object):
2264 cmd.append(self.revisionExpr) 2347 cmd.append(self.revisionExpr)
2265 2348
2266 command = GitCommand( 2349 command = GitCommand(
2267 self, cmd, cwd=cwd, capture_stdout=True, capture_stderr=True 2350 self,
2351 cmd,
2352 cwd=cwd,
2353 capture_stdout=True,
2354 capture_stderr=True,
2355 verify_command=True,
2268 ) 2356 )
2269 2357 command.Wait()
2270 if command.Wait() != 0:
2271 raise GitError("git archive %s: %s" % (self.name, command.stderr))
2272 2358
2273 def _RemoteFetch( 2359 def _RemoteFetch(
2274 self, 2360 self,
@@ -2289,7 +2375,7 @@ class Project(object):
2289 retry_fetches=2, 2375 retry_fetches=2,
2290 retry_sleep_initial_sec=4.0, 2376 retry_sleep_initial_sec=4.0,
2291 retry_exp_factor=2.0, 2377 retry_exp_factor=2.0,
2292 ): 2378 ) -> bool:
2293 is_sha1 = False 2379 is_sha1 = False
2294 tag_name = None 2380 tag_name = None
2295 # The depth should not be used when fetching to a mirror because 2381 # The depth should not be used when fetching to a mirror because
@@ -2473,6 +2559,7 @@ class Project(object):
2473 retry_cur_sleep = retry_sleep_initial_sec 2559 retry_cur_sleep = retry_sleep_initial_sec
2474 ok = prune_tried = False 2560 ok = prune_tried = False
2475 for try_n in range(retry_fetches): 2561 for try_n in range(retry_fetches):
2562 verify_command = try_n == retry_fetches - 1
2476 gitcmd = GitCommand( 2563 gitcmd = GitCommand(
2477 self, 2564 self,
2478 cmd, 2565 cmd,
@@ -2481,6 +2568,7 @@ class Project(object):
2481 ssh_proxy=ssh_proxy, 2568 ssh_proxy=ssh_proxy,
2482 merge_output=True, 2569 merge_output=True,
2483 capture_stdout=quiet or bool(output_redir), 2570 capture_stdout=quiet or bool(output_redir),
2571 verify_command=verify_command,
2484 ) 2572 )
2485 if gitcmd.stdout and not quiet and output_redir: 2573 if gitcmd.stdout and not quiet and output_redir:
2486 output_redir.write(gitcmd.stdout) 2574 output_redir.write(gitcmd.stdout)
@@ -2732,7 +2820,9 @@ class Project(object):
2732 cmd.append("--") 2820 cmd.append("--")
2733 if GitCommand(self, cmd).Wait() != 0: 2821 if GitCommand(self, cmd).Wait() != 0:
2734 if self._allrefs: 2822 if self._allrefs:
2735 raise GitError("%s checkout %s " % (self.name, rev)) 2823 raise GitError(
2824 "%s checkout %s " % (self.name, rev), project=self.name
2825 )
2736 2826
2737 def _CherryPick(self, rev, ffonly=False, record_origin=False): 2827 def _CherryPick(self, rev, ffonly=False, record_origin=False):
2738 cmd = ["cherry-pick"] 2828 cmd = ["cherry-pick"]
@@ -2744,7 +2834,9 @@ class Project(object):
2744 cmd.append("--") 2834 cmd.append("--")
2745 if GitCommand(self, cmd).Wait() != 0: 2835 if GitCommand(self, cmd).Wait() != 0:
2746 if self._allrefs: 2836 if self._allrefs:
2747 raise GitError("%s cherry-pick %s " % (self.name, rev)) 2837 raise GitError(
2838 "%s cherry-pick %s " % (self.name, rev), project=self.name
2839 )
2748 2840
2749 def _LsRemote(self, refs): 2841 def _LsRemote(self, refs):
2750 cmd = ["ls-remote", self.remote.name, refs] 2842 cmd = ["ls-remote", self.remote.name, refs]
@@ -2760,7 +2852,9 @@ class Project(object):
2760 cmd.append("--") 2852 cmd.append("--")
2761 if GitCommand(self, cmd).Wait() != 0: 2853 if GitCommand(self, cmd).Wait() != 0:
2762 if self._allrefs: 2854 if self._allrefs:
2763 raise GitError("%s revert %s " % (self.name, rev)) 2855 raise GitError(
2856 "%s revert %s " % (self.name, rev), project=self.name
2857 )
2764 2858
2765 def _ResetHard(self, rev, quiet=True): 2859 def _ResetHard(self, rev, quiet=True):
2766 cmd = ["reset", "--hard"] 2860 cmd = ["reset", "--hard"]
@@ -2768,7 +2862,9 @@ class Project(object):
2768 cmd.append("-q") 2862 cmd.append("-q")
2769 cmd.append(rev) 2863 cmd.append(rev)
2770 if GitCommand(self, cmd).Wait() != 0: 2864 if GitCommand(self, cmd).Wait() != 0:
2771 raise GitError("%s reset --hard %s " % (self.name, rev)) 2865 raise GitError(
2866 "%s reset --hard %s " % (self.name, rev), project=self.name
2867 )
2772 2868
2773 def _SyncSubmodules(self, quiet=True): 2869 def _SyncSubmodules(self, quiet=True):
2774 cmd = ["submodule", "update", "--init", "--recursive"] 2870 cmd = ["submodule", "update", "--init", "--recursive"]
@@ -2776,7 +2872,8 @@ class Project(object):
2776 cmd.append("-q") 2872 cmd.append("-q")
2777 if GitCommand(self, cmd).Wait() != 0: 2873 if GitCommand(self, cmd).Wait() != 0:
2778 raise GitError( 2874 raise GitError(
2779 "%s submodule update --init --recursive " % self.name 2875 "%s submodule update --init --recursive " % self.name,
2876 project=self.name,
2780 ) 2877 )
2781 2878
2782 def _Rebase(self, upstream, onto=None): 2879 def _Rebase(self, upstream, onto=None):
@@ -2785,14 +2882,18 @@ class Project(object):
2785 cmd.extend(["--onto", onto]) 2882 cmd.extend(["--onto", onto])
2786 cmd.append(upstream) 2883 cmd.append(upstream)
2787 if GitCommand(self, cmd).Wait() != 0: 2884 if GitCommand(self, cmd).Wait() != 0:
2788 raise GitError("%s rebase %s " % (self.name, upstream)) 2885 raise GitError(
2886 "%s rebase %s " % (self.name, upstream), project=self.name
2887 )
2789 2888
2790 def _FastForward(self, head, ffonly=False): 2889 def _FastForward(self, head, ffonly=False):
2791 cmd = ["merge", "--no-stat", head] 2890 cmd = ["merge", "--no-stat", head]
2792 if ffonly: 2891 if ffonly:
2793 cmd.append("--ff-only") 2892 cmd.append("--ff-only")
2794 if GitCommand(self, cmd).Wait() != 0: 2893 if GitCommand(self, cmd).Wait() != 0:
2795 raise GitError("%s merge %s " % (self.name, head)) 2894 raise GitError(
2895 "%s merge %s " % (self.name, head), project=self.name
2896 )
2796 2897
2797 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False): 2898 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
2798 init_git_dir = not os.path.exists(self.gitdir) 2899 init_git_dir = not os.path.exists(self.gitdir)
@@ -2964,7 +3065,9 @@ class Project(object):
2964 try: 3065 try:
2965 os.link(stock_hook, dst) 3066 os.link(stock_hook, dst)
2966 except OSError: 3067 except OSError:
2967 raise GitError(self._get_symlink_error_message()) 3068 raise GitError(
3069 self._get_symlink_error_message(), project=self.name
3070 )
2968 else: 3071 else:
2969 raise 3072 raise
2970 3073
@@ -3065,7 +3168,8 @@ class Project(object):
3065 "work tree. If you're comfortable with the " 3168 "work tree. If you're comfortable with the "
3066 "possibility of losing the work tree's git metadata," 3169 "possibility of losing the work tree's git metadata,"
3067 " use `repo sync --force-sync {0}` to " 3170 " use `repo sync --force-sync {0}` to "
3068 "proceed.".format(self.RelPath(local=False)) 3171 "proceed.".format(self.RelPath(local=False)),
3172 project=self.name,
3069 ) 3173 )
3070 3174
3071 def _ReferenceGitDir(self, gitdir, dotgit, copy_all): 3175 def _ReferenceGitDir(self, gitdir, dotgit, copy_all):
@@ -3175,7 +3279,7 @@ class Project(object):
3175 3279
3176 # If using an old layout style (a directory), migrate it. 3280 # If using an old layout style (a directory), migrate it.
3177 if not platform_utils.islink(dotgit) and platform_utils.isdir(dotgit): 3281 if not platform_utils.islink(dotgit) and platform_utils.isdir(dotgit):
3178 self._MigrateOldWorkTreeGitDir(dotgit) 3282 self._MigrateOldWorkTreeGitDir(dotgit, project=self.name)
3179 3283
3180 init_dotgit = not os.path.exists(dotgit) 3284 init_dotgit = not os.path.exists(dotgit)
3181 if self.use_git_worktrees: 3285 if self.use_git_worktrees:
@@ -3205,7 +3309,8 @@ class Project(object):
3205 cmd = ["read-tree", "--reset", "-u", "-v", HEAD] 3309 cmd = ["read-tree", "--reset", "-u", "-v", HEAD]
3206 if GitCommand(self, cmd).Wait() != 0: 3310 if GitCommand(self, cmd).Wait() != 0:
3207 raise GitError( 3311 raise GitError(
3208 "Cannot initialize work tree for " + self.name 3312 "Cannot initialize work tree for " + self.name,
3313 project=self.name,
3209 ) 3314 )
3210 3315
3211 if submodules: 3316 if submodules:
@@ -3213,7 +3318,7 @@ class Project(object):
3213 self._CopyAndLinkFiles() 3318 self._CopyAndLinkFiles()
3214 3319
3215 @classmethod 3320 @classmethod
3216 def _MigrateOldWorkTreeGitDir(cls, dotgit): 3321 def _MigrateOldWorkTreeGitDir(cls, dotgit, project=None):
3217 """Migrate the old worktree .git/ dir style to a symlink. 3322 """Migrate the old worktree .git/ dir style to a symlink.
3218 3323
3219 This logic specifically only uses state from |dotgit| to figure out 3324 This logic specifically only uses state from |dotgit| to figure out
@@ -3223,7 +3328,9 @@ class Project(object):
3223 """ 3328 """
3224 # Figure out where in .repo/projects/ it's pointing to. 3329 # Figure out where in .repo/projects/ it's pointing to.
3225 if not os.path.islink(os.path.join(dotgit, "refs")): 3330 if not os.path.islink(os.path.join(dotgit, "refs")):
3226 raise GitError(f"{dotgit}: unsupported checkout state") 3331 raise GitError(
3332 f"{dotgit}: unsupported checkout state", project=project
3333 )
3227 gitdir = os.path.dirname(os.path.realpath(os.path.join(dotgit, "refs"))) 3334 gitdir = os.path.dirname(os.path.realpath(os.path.join(dotgit, "refs")))
3228 3335
3229 # Remove known symlink paths that exist in .repo/projects/. 3336 # Remove known symlink paths that exist in .repo/projects/.
@@ -3271,7 +3378,10 @@ class Project(object):
3271 f"{dotgit_path}: unknown file; please file a bug" 3378 f"{dotgit_path}: unknown file; please file a bug"
3272 ) 3379 )
3273 if unknown_paths: 3380 if unknown_paths:
3274 raise GitError("Aborting migration: " + "\n".join(unknown_paths)) 3381 raise GitError(
3382 "Aborting migration: " + "\n".join(unknown_paths),
3383 project=project,
3384 )
3275 3385
3276 # Now walk the paths and sync the .git/ to .repo/projects/. 3386 # Now walk the paths and sync the .git/ to .repo/projects/.
3277 for name in platform_utils.listdir(dotgit): 3387 for name in platform_utils.listdir(dotgit):
@@ -3537,12 +3647,9 @@ class Project(object):
3537 gitdir=self._gitdir, 3647 gitdir=self._gitdir,
3538 capture_stdout=True, 3648 capture_stdout=True,
3539 capture_stderr=True, 3649 capture_stderr=True,
3650 verify_command=True,
3540 ) 3651 )
3541 if p.Wait() != 0: 3652 p.Wait()
3542 raise GitError(
3543 "%s rev-list %s: %s"
3544 % (self._project.name, str(args), p.stderr)
3545 )
3546 return p.stdout.splitlines() 3653 return p.stdout.splitlines()
3547 3654
3548 def __getattr__(self, name): 3655 def __getattr__(self, name):
@@ -3588,11 +3695,9 @@ class Project(object):
3588 gitdir=self._gitdir, 3695 gitdir=self._gitdir,
3589 capture_stdout=True, 3696 capture_stdout=True,
3590 capture_stderr=True, 3697 capture_stderr=True,
3698 verify_command=True,
3591 ) 3699 )
3592 if p.Wait() != 0: 3700 p.Wait()
3593 raise GitError(
3594 "%s %s: %s" % (self._project.name, name, p.stderr)
3595 )
3596 r = p.stdout 3701 r = p.stdout
3597 if r.endswith("\n") and r.index("\n") == len(r) - 1: 3702 if r.endswith("\n") and r.index("\n") == len(r) - 1:
3598 return r[:-1] 3703 return r[:-1]
@@ -3601,12 +3706,16 @@ class Project(object):
3601 return runner 3706 return runner
3602 3707
3603 3708
3604class _PriorSyncFailedError(Exception): 3709class LocalSyncFail(RepoError):
3710 """Default error when there is an Sync_LocalHalf error."""
3711
3712
3713class _PriorSyncFailedError(LocalSyncFail):
3605 def __str__(self): 3714 def __str__(self):
3606 return "prior sync failed; rebase still in progress" 3715 return "prior sync failed; rebase still in progress"
3607 3716
3608 3717
3609class _DirtyError(Exception): 3718class _DirtyError(LocalSyncFail):
3610 def __str__(self): 3719 def __str__(self):
3611 return "contains uncommitted changes" 3720 return "contains uncommitted changes"
3612 3721