diff options
Diffstat (limited to 'project.py')
| -rw-r--r-- | project.py | 283 |
1 files changed, 196 insertions, 87 deletions
| @@ -26,7 +26,7 @@ import sys | |||
| 26 | import tarfile | 26 | import tarfile |
| 27 | import tempfile | 27 | import tempfile |
| 28 | import time | 28 | import time |
| 29 | from typing import NamedTuple | 29 | from typing import NamedTuple, List |
| 30 | import urllib.parse | 30 | import urllib.parse |
| 31 | 31 | ||
| 32 | from color import Coloring | 32 | from color import Coloring |
| @@ -41,7 +41,12 @@ from git_config import ( | |||
| 41 | ) | 41 | ) |
| 42 | import git_superproject | 42 | import git_superproject |
| 43 | from git_trace2_event_log import EventLog | 43 | from git_trace2_event_log import EventLog |
| 44 | from error import GitError, UploadError, DownloadError | 44 | from error import ( |
| 45 | GitError, | ||
| 46 | UploadError, | ||
| 47 | DownloadError, | ||
| 48 | RepoError, | ||
| 49 | ) | ||
| 45 | from error import ManifestInvalidRevisionError, ManifestInvalidPathError | 50 | from error import ManifestInvalidRevisionError, ManifestInvalidPathError |
| 46 | from error import NoManifestException, ManifestParseError | 51 | from error import NoManifestException, ManifestParseError |
| 47 | import platform_utils | 52 | import platform_utils |
| @@ -54,11 +59,33 @@ from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M, R_WORKTREE_M | |||
| 54 | class SyncNetworkHalfResult(NamedTuple): | 59 | class 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 | |||
| 73 | class SyncNetworkHalfError(RepoError): | ||
| 74 | """Failure trying to sync.""" | ||
| 75 | |||
| 76 | |||
| 77 | class 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 | |||
| 87 | class 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 | ||
| 3604 | class _PriorSyncFailedError(Exception): | 3709 | class LocalSyncFail(RepoError): |
| 3710 | """Default error when there is an Sync_LocalHalf error.""" | ||
| 3711 | |||
| 3712 | |||
| 3713 | class _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 | ||
| 3609 | class _DirtyError(Exception): | 3718 | class _DirtyError(LocalSyncFail): |
| 3610 | def __str__(self): | 3719 | def __str__(self): |
| 3611 | return "contains uncommitted changes" | 3720 | return "contains uncommitted changes" |
| 3612 | 3721 | ||
