diff options
| -rw-r--r-- | git_superproject.py | 16 | ||||
| -rw-r--r-- | man/repo-smartsync.1 | 6 | ||||
| -rw-r--r-- | man/repo-sync.1 | 6 | ||||
| -rw-r--r-- | subcmds/sync.py | 85 | ||||
| -rw-r--r-- | tests/test_subcmds_sync.py | 168 |
5 files changed, 271 insertions, 10 deletions
diff --git a/git_superproject.py b/git_superproject.py index 81a6b2e59..27bc10e10 100644 --- a/git_superproject.py +++ b/git_superproject.py | |||
| @@ -34,6 +34,7 @@ import urllib.parse | |||
| 34 | 34 | ||
| 35 | from git_command import git_require | 35 | from git_command import git_require |
| 36 | from git_command import GitCommand | 36 | from git_command import GitCommand |
| 37 | from git_config import IsId | ||
| 37 | from git_config import RepoConfig | 38 | from git_config import RepoConfig |
| 38 | from git_refs import GitRefs | 39 | from git_refs import GitRefs |
| 39 | import platform_utils | 40 | import platform_utils |
| @@ -132,6 +133,10 @@ class Superproject: | |||
| 132 | """Set the _print_messages attribute.""" | 133 | """Set the _print_messages attribute.""" |
| 133 | self._print_messages = value | 134 | self._print_messages = value |
| 134 | 135 | ||
| 136 | def SetRevisionId(self, revision_id: str) -> None: | ||
| 137 | """Set the revisionId of the superproject to sync to.""" | ||
| 138 | self.revision = revision_id | ||
| 139 | |||
| 135 | @property | 140 | @property |
| 136 | def commit_id(self): | 141 | def commit_id(self): |
| 137 | """Returns the commit ID of the superproject checkout.""" | 142 | """Returns the commit ID of the superproject checkout.""" |
| @@ -314,7 +319,14 @@ class Superproject: | |||
| 314 | cmd.extend(["--negotiation-tip", rev_commit]) | 319 | cmd.extend(["--negotiation-tip", rev_commit]) |
| 315 | 320 | ||
| 316 | if self.revision: | 321 | if self.revision: |
| 317 | cmd += [self.revision + ":" + self.revision] | 322 | # If revision is a commit hash, fetch it directly to avoid |
| 323 | # creating a local branch of the same name. | ||
| 324 | refspec = ( | ||
| 325 | self.revision | ||
| 326 | if IsId(self.revision) | ||
| 327 | else f"{self.revision}:{self.revision}" | ||
| 328 | ) | ||
| 329 | cmd.append(refspec) | ||
| 318 | p = GitCommand( | 330 | p = GitCommand( |
| 319 | None, | 331 | None, |
| 320 | cmd, | 332 | cmd, |
| @@ -401,6 +413,8 @@ class Superproject: | |||
| 401 | 413 | ||
| 402 | if not self._Init(): | 414 | if not self._Init(): |
| 403 | return SyncResult(False, should_exit) | 415 | return SyncResult(False, should_exit) |
| 416 | if IsId(self.revision) and self.commit_id: | ||
| 417 | return SyncResult(True, False) | ||
| 404 | if not self._Fetch(): | 418 | if not self._Fetch(): |
| 405 | return SyncResult(False, should_exit) | 419 | return SyncResult(False, should_exit) |
| 406 | if not self._quiet: | 420 | if not self._quiet: |
diff --git a/man/repo-smartsync.1 b/man/repo-smartsync.1 index 5ea063e6b..25b85f126 100644 --- a/man/repo-smartsync.1 +++ b/man/repo-smartsync.1 | |||
| @@ -1,5 +1,5 @@ | |||
| 1 | .\" DO NOT MODIFY THIS FILE! It was generated by help2man. | 1 | .\" DO NOT MODIFY THIS FILE! It was generated by help2man. |
| 2 | .TH REPO "1" "August 2025" "repo smartsync" "Repo Manual" | 2 | .TH REPO "1" "May 2026" "repo smartsync" "Repo Manual" |
| 3 | .SH NAME | 3 | .SH NAME |
| 4 | repo \- repo smartsync - manual page for repo smartsync | 4 | repo \- repo smartsync - manual page for repo smartsync |
| 5 | .SH SYNOPSIS | 5 | .SH SYNOPSIS |
| @@ -101,6 +101,10 @@ implies \fB\-c\fR | |||
| 101 | \fB\-\-no\-use\-superproject\fR | 101 | \fB\-\-no\-use\-superproject\fR |
| 102 | disable use of manifest superprojects | 102 | disable use of manifest superprojects |
| 103 | .TP | 103 | .TP |
| 104 | \fB\-\-superproject\-revision\fR=\fI\,SUPERPROJECT_REVISION\/\fR | ||
| 105 | sync to superproject revision (applies to outer | ||
| 106 | manifest) | ||
| 107 | .TP | ||
| 104 | \fB\-\-tags\fR | 108 | \fB\-\-tags\fR |
| 105 | fetch tags | 109 | fetch tags |
| 106 | .TP | 110 | .TP |
diff --git a/man/repo-sync.1 b/man/repo-sync.1 index 8f145a09e..4e115e200 100644 --- a/man/repo-sync.1 +++ b/man/repo-sync.1 | |||
| @@ -1,5 +1,5 @@ | |||
| 1 | .\" DO NOT MODIFY THIS FILE! It was generated by help2man. | 1 | .\" DO NOT MODIFY THIS FILE! It was generated by help2man. |
| 2 | .TH REPO "1" "August 2025" "repo sync" "Repo Manual" | 2 | .TH REPO "1" "May 2026" "repo sync" "Repo Manual" |
| 3 | .SH NAME | 3 | .SH NAME |
| 4 | repo \- repo sync - manual page for repo sync | 4 | repo \- repo sync - manual page for repo sync |
| 5 | .SH SYNOPSIS | 5 | .SH SYNOPSIS |
| @@ -101,6 +101,10 @@ implies \fB\-c\fR | |||
| 101 | \fB\-\-no\-use\-superproject\fR | 101 | \fB\-\-no\-use\-superproject\fR |
| 102 | disable use of manifest superprojects | 102 | disable use of manifest superprojects |
| 103 | .TP | 103 | .TP |
| 104 | \fB\-\-superproject\-revision\fR=\fI\,SUPERPROJECT_REVISION\/\fR | ||
| 105 | sync to superproject revision (applies to outer | ||
| 106 | manifest) | ||
| 107 | .TP | ||
| 104 | \fB\-\-tags\fR | 108 | \fB\-\-tags\fR |
| 105 | fetch tags | 109 | fetch tags |
| 106 | .TP | 110 | .TP |
diff --git a/subcmds/sync.py b/subcmds/sync.py index 8c2591180..7e0e7418c 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py | |||
| @@ -64,6 +64,7 @@ from error import SyncError | |||
| 64 | from error import UpdateManifestError | 64 | from error import UpdateManifestError |
| 65 | import event_log | 65 | import event_log |
| 66 | from git_command import git_require | 66 | from git_command import git_require |
| 67 | from git_command import GitCommand | ||
| 67 | from git_config import GetUrlCookieFile | 68 | from git_config import GetUrlCookieFile |
| 68 | from git_refs import HEAD | 69 | from git_refs import HEAD |
| 69 | from git_refs import R_HEADS | 70 | from git_refs import R_HEADS |
| @@ -565,6 +566,11 @@ later is required to fix a server side protocol bug. | |||
| 565 | dest="use_superproject", | 566 | dest="use_superproject", |
| 566 | help="disable use of manifest superprojects", | 567 | help="disable use of manifest superprojects", |
| 567 | ) | 568 | ) |
| 569 | p.add_option( | ||
| 570 | "--superproject-revision", | ||
| 571 | action="store", | ||
| 572 | help="sync to superproject revision (applies to outer manifest)", | ||
| 573 | ) | ||
| 568 | p.add_option("--tags", action="store_true", help="fetch tags") | 574 | p.add_option("--tags", action="store_true", help="fetch tags") |
| 569 | p.add_option( | 575 | p.add_option( |
| 570 | "--no-tags", | 576 | "--no-tags", |
| @@ -668,6 +674,24 @@ later is required to fix a server side protocol bug. | |||
| 668 | or opt.current_branch_only | 674 | or opt.current_branch_only |
| 669 | ) | 675 | ) |
| 670 | 676 | ||
| 677 | def _ConfigureSuperproject( | ||
| 678 | self, | ||
| 679 | opt: optparse.Values, | ||
| 680 | manifest, | ||
| 681 | revision: Optional[str] = None, | ||
| 682 | ) -> bool: | ||
| 683 | """Configure superproject with options.""" | ||
| 684 | if not manifest.superproject: | ||
| 685 | return False | ||
| 686 | manifest.superproject.SetQuiet(not opt.verbose) | ||
| 687 | print_messages = git_superproject.PrintMessages( | ||
| 688 | opt.use_superproject, manifest | ||
| 689 | ) | ||
| 690 | manifest.superproject.SetPrintMessages(print_messages) | ||
| 691 | if revision: | ||
| 692 | manifest.superproject.SetRevisionId(revision) | ||
| 693 | return print_messages | ||
| 694 | |||
| 671 | def _UpdateProjectsRevisionId( | 695 | def _UpdateProjectsRevisionId( |
| 672 | self, opt, args, superproject_logging_data, manifest | 696 | self, opt, args, superproject_logging_data, manifest |
| 673 | ): | 697 | ): |
| @@ -741,11 +765,7 @@ later is required to fix a server side protocol bug. | |||
| 741 | 765 | ||
| 742 | if not use_super: | 766 | if not use_super: |
| 743 | continue | 767 | continue |
| 744 | m.superproject.SetQuiet(not opt.verbose) | 768 | print_messages = self._ConfigureSuperproject(opt, m) |
| 745 | print_messages = git_superproject.PrintMessages( | ||
| 746 | opt.use_superproject, m | ||
| 747 | ) | ||
| 748 | m.superproject.SetPrintMessages(print_messages) | ||
| 749 | update_result = m.superproject.UpdateProjectsRevisionId( | 769 | update_result = m.superproject.UpdateProjectsRevisionId( |
| 750 | per_manifest[m.path_prefix], git_event_log=self.git_event_log | 770 | per_manifest[m.path_prefix], git_event_log=self.git_event_log |
| 751 | ) | 771 | ) |
| @@ -1832,7 +1852,11 @@ later is required to fix a server side protocol bug. | |||
| 1832 | mp: the manifestProject to query. | 1852 | mp: the manifestProject to query. |
| 1833 | manifest_name: Manifest file to be reloaded. | 1853 | manifest_name: Manifest file to be reloaded. |
| 1834 | """ | 1854 | """ |
| 1835 | if not mp.standalone_manifest_url: | 1855 | if opt.superproject_revision and mp.manifest == self.outer_manifest: |
| 1856 | self._SyncToSuperprojectRev( | ||
| 1857 | opt, mp.manifest, mp, manifest_name, errors | ||
| 1858 | ) | ||
| 1859 | elif not mp.standalone_manifest_url: | ||
| 1836 | self._UpdateManifestProject(opt, mp, manifest_name, errors) | 1860 | self._UpdateManifestProject(opt, mp, manifest_name, errors) |
| 1837 | 1861 | ||
| 1838 | if mp.manifest.submanifests: | 1862 | if mp.manifest.submanifests: |
| @@ -2041,6 +2065,53 @@ later is required to fix a server side protocol bug. | |||
| 2041 | if not success: | 2065 | if not success: |
| 2042 | print("Warning: post-sync hook reported failure.") | 2066 | print("Warning: post-sync hook reported failure.") |
| 2043 | 2067 | ||
| 2068 | def _SyncToSuperprojectRev( | ||
| 2069 | self, | ||
| 2070 | opt: optparse.Values, | ||
| 2071 | manifest, | ||
| 2072 | mp: Project, | ||
| 2073 | manifest_name: Optional[str], | ||
| 2074 | errors: List[Exception], | ||
| 2075 | ) -> None: | ||
| 2076 | """Sync to a specific superproject commit.""" | ||
| 2077 | if not manifest.superproject: | ||
| 2078 | raise SyncError("superproject not defined in manifest") | ||
| 2079 | |||
| 2080 | self._ConfigureSuperproject( | ||
| 2081 | opt, manifest, revision=opt.superproject_revision | ||
| 2082 | ) | ||
| 2083 | |||
| 2084 | sync_result = manifest.superproject.Sync(self.git_event_log) | ||
| 2085 | if not sync_result.success: | ||
| 2086 | raise SyncError("failed to sync superproject") | ||
| 2087 | |||
| 2088 | cmd = ["show", f"{opt.superproject_revision}:.supermanifest"] | ||
| 2089 | p = GitCommand( | ||
| 2090 | None, | ||
| 2091 | cmd, | ||
| 2092 | gitdir=manifest.superproject._work_git, | ||
| 2093 | bare=True, | ||
| 2094 | capture_stdout=True, | ||
| 2095 | capture_stderr=True, | ||
| 2096 | ) | ||
| 2097 | if p.Wait() != 0: | ||
| 2098 | raise SyncError( | ||
| 2099 | f"failed to read .supermanifest from superproject: {p.stderr}" | ||
| 2100 | ) | ||
| 2101 | |||
| 2102 | try: | ||
| 2103 | _, _, manifest_commit = p.stdout.strip().split() | ||
| 2104 | except ValueError: | ||
| 2105 | raise SyncError("could not parse .supermanifest") | ||
| 2106 | |||
| 2107 | mp.SetRevision(manifest_commit) | ||
| 2108 | try: | ||
| 2109 | self._UpdateManifestProject(opt, mp, manifest_name, errors) | ||
| 2110 | except UpdateManifestError as e: | ||
| 2111 | raise SyncError( | ||
| 2112 | "failed to sync manifest project", aggregate_errors=[e] | ||
| 2113 | ) | ||
| 2114 | |||
| 2044 | def _ExecuteHelper(self, opt, args, errors): | 2115 | def _ExecuteHelper(self, opt, args, errors): |
| 2045 | manifest = self.outer_manifest | 2116 | manifest = self.outer_manifest |
| 2046 | if not opt.outer_manifest: | 2117 | if not opt.outer_manifest: |
| @@ -2099,7 +2170,7 @@ later is required to fix a server side protocol bug. | |||
| 2099 | ): | 2170 | ): |
| 2100 | mp.ConfigureCloneFilterForDepth("blob:none") | 2171 | mp.ConfigureCloneFilterForDepth("blob:none") |
| 2101 | 2172 | ||
| 2102 | if opt.mp_update: | 2173 | if opt.mp_update or opt.superproject_revision: |
| 2103 | self._UpdateAllManifestProjects(opt, mp, manifest_name, errors) | 2174 | self._UpdateAllManifestProjects(opt, mp, manifest_name, errors) |
| 2104 | else: | 2175 | else: |
| 2105 | print("Skipping update of local manifest project.") | 2176 | print("Skipping update of local manifest project.") |
diff --git a/tests/test_subcmds_sync.py b/tests/test_subcmds_sync.py index ef162392b..785c0e629 100644 --- a/tests/test_subcmds_sync.py +++ b/tests/test_subcmds_sync.py | |||
| @@ -1286,3 +1286,171 @@ class UpdateCopyLinkfileListTest(unittest.TestCase): | |||
| 1286 | self.assertFalse(os.path.lexists(os.path.join(llms_dir, "rules"))) | 1286 | self.assertFalse(os.path.lexists(os.path.join(llms_dir, "rules"))) |
| 1287 | self.assertTrue(os.path.exists(os.path.join(llms_dir, "my-notes.txt"))) | 1287 | self.assertTrue(os.path.exists(os.path.join(llms_dir, "my-notes.txt"))) |
| 1288 | self.assertTrue(os.path.isdir(llms_dir)) | 1288 | self.assertTrue(os.path.isdir(llms_dir)) |
| 1289 | |||
| 1290 | |||
| 1291 | class SyncToSuperprojectRevTests(unittest.TestCase): | ||
| 1292 | """Tests for Sync._SyncToSuperprojectRev.""" | ||
| 1293 | |||
| 1294 | def setUp(self): | ||
| 1295 | self.repodir = tempfile.mkdtemp(".repo") | ||
| 1296 | self.manifest = mock.MagicMock(repodir=self.repodir) | ||
| 1297 | self.manifest.superproject = mock.MagicMock() | ||
| 1298 | self.manifest.path_prefix = "" | ||
| 1299 | |||
| 1300 | self.mp = mock.MagicMock() | ||
| 1301 | self.cmd = sync.Sync(manifest=self.manifest) | ||
| 1302 | self.cmd.outer_manifest = self.manifest | ||
| 1303 | |||
| 1304 | self.opt = mock.Mock() | ||
| 1305 | self.opt.verbose = False | ||
| 1306 | self.opt.superproject_revision = "deadbeef" | ||
| 1307 | self.opt.mp_update = True | ||
| 1308 | |||
| 1309 | self.errors = [] | ||
| 1310 | |||
| 1311 | def tearDown(self): | ||
| 1312 | shutil.rmtree(self.repodir) | ||
| 1313 | |||
| 1314 | @mock.patch("subcmds.sync.GitCommand") | ||
| 1315 | def test_successful_sync(self, mock_git_command): | ||
| 1316 | """Test successful sync to superproject rev.""" | ||
| 1317 | mock_superproject = self.manifest.superproject | ||
| 1318 | mock_superproject.Sync.return_value = mock.Mock(success=True) | ||
| 1319 | |||
| 1320 | mock_git = mock.Mock() | ||
| 1321 | mock_git.Wait.return_value = 0 | ||
| 1322 | mock_git.stdout = "proj branch manifest_commit_hash\n" | ||
| 1323 | mock_git_command.return_value = mock_git | ||
| 1324 | |||
| 1325 | with mock.patch.object( | ||
| 1326 | self.cmd, "_UpdateManifestProject" | ||
| 1327 | ) as mock_update: | ||
| 1328 | self.cmd._SyncToSuperprojectRev( | ||
| 1329 | self.opt, self.manifest, self.mp, "name", self.errors | ||
| 1330 | ) | ||
| 1331 | |||
| 1332 | mock_superproject.SetRevisionId.assert_called_with("deadbeef") | ||
| 1333 | mock_superproject.Sync.assert_called_once() | ||
| 1334 | mock_git_command.assert_called_once() | ||
| 1335 | self.mp.SetRevision.assert_called_with("manifest_commit_hash") | ||
| 1336 | mock_update.assert_called_once() | ||
| 1337 | self.assertEqual(self.errors, []) | ||
| 1338 | |||
| 1339 | @mock.patch("subcmds.sync.GitCommand") | ||
| 1340 | def test_parse_error(self, mock_git_command): | ||
| 1341 | """Test error when .supermanifest cannot be parsed.""" | ||
| 1342 | mock_superproject = self.manifest.superproject | ||
| 1343 | mock_superproject.Sync.return_value = mock.Mock(success=True) | ||
| 1344 | |||
| 1345 | mock_git = mock.Mock() | ||
| 1346 | mock_git.Wait.return_value = 0 | ||
| 1347 | # Invalid format (not 3 parts) | ||
| 1348 | mock_git.stdout = "invalid_content\n" | ||
| 1349 | mock_git_command.return_value = mock_git | ||
| 1350 | |||
| 1351 | with self.assertRaises(sync.SyncError) as e: | ||
| 1352 | self.cmd._SyncToSuperprojectRev( | ||
| 1353 | self.opt, self.manifest, self.mp, "name", self.errors | ||
| 1354 | ) | ||
| 1355 | self.assertIn("could not parse .supermanifest", str(e.exception)) | ||
| 1356 | |||
| 1357 | @mock.patch("subcmds.sync.GitCommand") | ||
| 1358 | def test_read_error(self, mock_git_command): | ||
| 1359 | """Test error when reading .supermanifest fails.""" | ||
| 1360 | mock_superproject = self.manifest.superproject | ||
| 1361 | mock_superproject.Sync.return_value = mock.Mock(success=True) | ||
| 1362 | |||
| 1363 | mock_git = mock.Mock() | ||
| 1364 | mock_git.Wait.return_value = 1 | ||
| 1365 | mock_git.stderr = "git error" | ||
| 1366 | mock_git_command.return_value = mock_git | ||
| 1367 | |||
| 1368 | with self.assertRaises(sync.SyncError) as e: | ||
| 1369 | self.cmd._SyncToSuperprojectRev( | ||
| 1370 | self.opt, self.manifest, self.mp, "name", self.errors | ||
| 1371 | ) | ||
| 1372 | self.assertIn("failed to read .supermanifest", str(e.exception)) | ||
| 1373 | |||
| 1374 | def test_no_superproject(self): | ||
| 1375 | """Test error when superproject is not defined.""" | ||
| 1376 | self.manifest.superproject = None | ||
| 1377 | |||
| 1378 | with self.assertRaises(sync.SyncError) as e: | ||
| 1379 | self.cmd._SyncToSuperprojectRev( | ||
| 1380 | self.opt, self.manifest, self.mp, "name", self.errors | ||
| 1381 | ) | ||
| 1382 | self.assertIn("superproject not defined", str(e.exception)) | ||
| 1383 | |||
| 1384 | @mock.patch("subcmds.sync.GitCommand") | ||
| 1385 | def test_sync_failure(self, mock_git_command): | ||
| 1386 | """Test error when superproject sync fails.""" | ||
| 1387 | mock_superproject = self.manifest.superproject | ||
| 1388 | mock_superproject.Sync.return_value = mock.Mock(success=False) | ||
| 1389 | |||
| 1390 | with self.assertRaises(sync.SyncError) as e: | ||
| 1391 | self.cmd._SyncToSuperprojectRev( | ||
| 1392 | self.opt, self.manifest, self.mp, "name", self.errors | ||
| 1393 | ) | ||
| 1394 | self.assertIn("failed to sync superproject", str(e.exception)) | ||
| 1395 | |||
| 1396 | |||
| 1397 | class UpdateAllManifestProjectsTests(unittest.TestCase): | ||
| 1398 | """Tests for Sync._UpdateAllManifestProjects.""" | ||
| 1399 | |||
| 1400 | def setUp(self): | ||
| 1401 | self.repodir = tempfile.mkdtemp(".repo") | ||
| 1402 | self.manifest = mock.MagicMock(repodir=self.repodir) | ||
| 1403 | self.manifest.superproject = mock.MagicMock() | ||
| 1404 | self.manifest.path_prefix = "" | ||
| 1405 | self.manifest.standalone_manifest_url = None | ||
| 1406 | self.manifest.submanifests = {} | ||
| 1407 | |||
| 1408 | self.mp = mock.MagicMock() | ||
| 1409 | self.mp.manifest = self.manifest | ||
| 1410 | self.mp.standalone_manifest_url = None | ||
| 1411 | self.cmd = sync.Sync(manifest=self.manifest) | ||
| 1412 | self.cmd.outer_manifest = self.manifest | ||
| 1413 | |||
| 1414 | self.opt = mock.Mock() | ||
| 1415 | self.opt.verbose = False | ||
| 1416 | self.opt.superproject_revision = None | ||
| 1417 | self.opt.mp_update = True | ||
| 1418 | |||
| 1419 | self.errors = [] | ||
| 1420 | |||
| 1421 | def tearDown(self): | ||
| 1422 | shutil.rmtree(self.repodir) | ||
| 1423 | |||
| 1424 | def test_superproject_revision_outer_manifest(self): | ||
| 1425 | """Test that _SyncToSuperprojectRev is called for outer manifest.""" | ||
| 1426 | self.opt.superproject_revision = "deadbeef" | ||
| 1427 | |||
| 1428 | with mock.patch.object( | ||
| 1429 | self.cmd, "_SyncToSuperprojectRev" | ||
| 1430 | ) as mock_sync_to_rev: | ||
| 1431 | self.cmd._UpdateAllManifestProjects( | ||
| 1432 | self.opt, self.mp, "name", self.errors | ||
| 1433 | ) | ||
| 1434 | mock_sync_to_rev.assert_called_once_with( | ||
| 1435 | self.opt, self.manifest, self.mp, "name", self.errors | ||
| 1436 | ) | ||
| 1437 | |||
| 1438 | def test_superproject_revision_submanifest(self): | ||
| 1439 | """Test that _SyncToSuperprojectRev is NOT called for submanifest.""" | ||
| 1440 | self.opt.superproject_revision = "deadbeef" | ||
| 1441 | submanifest = mock.MagicMock() | ||
| 1442 | submanifest.path_prefix = "sub/" | ||
| 1443 | submanifest.standalone_manifest_url = None | ||
| 1444 | self.mp.manifest = submanifest | ||
| 1445 | |||
| 1446 | with mock.patch.object( | ||
| 1447 | self.cmd, "_SyncToSuperprojectRev" | ||
| 1448 | ) as mock_sync_to_rev: | ||
| 1449 | with mock.patch.object( | ||
| 1450 | self.cmd, "_UpdateManifestProject" | ||
| 1451 | ) as mock_update_manifest: | ||
| 1452 | self.cmd._UpdateAllManifestProjects( | ||
| 1453 | self.opt, self.mp, "name", self.errors | ||
| 1454 | ) | ||
| 1455 | mock_sync_to_rev.assert_not_called() | ||
| 1456 | mock_update_manifest.assert_called_once() | ||
