diff options
| -rw-r--r-- | docs/manifest-format.md | 13 | ||||
| -rw-r--r-- | man/repo-manifest.1 | 13 | ||||
| -rw-r--r-- | project.py | 55 | ||||
| -rw-r--r-- | tests/test_manifest_xml.py | 90 |
4 files changed, 131 insertions, 40 deletions
diff --git a/docs/manifest-format.md b/docs/manifest-format.md index d1a11cc9..42e1ab18 100644 --- a/docs/manifest-format.md +++ b/docs/manifest-format.md | |||
| @@ -453,10 +453,14 @@ Intermediate paths must not be symlinks either. | |||
| 453 | 453 | ||
| 454 | Parent directories of "dest" will be automatically created if missing. | 454 | Parent directories of "dest" will be automatically created if missing. |
| 455 | 455 | ||
| 456 | The files are copied in the order they are specified in the manifests. | ||
| 457 | If multiple elements specify the same source and destination, they will | ||
| 458 | only be applied as one, based on the first occurence. Files are copied | ||
| 459 | before any links specified via linkfile elements are created. | ||
| 460 | |||
| 456 | ### Element linkfile | 461 | ### Element linkfile |
| 457 | 462 | ||
| 458 | It's just like copyfile and runs at the same time as copyfile but | 463 | It's just like copyfile, but instead of copying it creates a symlink. |
| 459 | instead of copying it creates a symlink. | ||
| 460 | 464 | ||
| 461 | The symlink is created at "dest" (relative to the top of the tree) and | 465 | The symlink is created at "dest" (relative to the top of the tree) and |
| 462 | points to the path specified by "src" which is a path in the project. | 466 | points to the path specified by "src" which is a path in the project. |
| @@ -466,6 +470,11 @@ Parent directories of "dest" will be automatically created if missing. | |||
| 466 | The symlink target may be a file or directory, but it may not point outside | 470 | The symlink target may be a file or directory, but it may not point outside |
| 467 | of the repo client. | 471 | of the repo client. |
| 468 | 472 | ||
| 473 | The links are created in the order they are specified in the manifests. | ||
| 474 | If multiple elements specify the same source and destination, they will | ||
| 475 | only be applied as one, based on the first occurence. Links are created | ||
| 476 | after any files specified via copyfile elements are copied. | ||
| 477 | |||
| 469 | ### Element remove-project | 478 | ### Element remove-project |
| 470 | 479 | ||
| 471 | Deletes a project from the internal manifest table, possibly | 480 | Deletes a project from the internal manifest table, possibly |
diff --git a/man/repo-manifest.1 b/man/repo-manifest.1 index df3943ce..1a97ff7d 100644 --- a/man/repo-manifest.1 +++ b/man/repo-manifest.1 | |||
| @@ -521,10 +521,14 @@ Intermediate paths must not be symlinks either. | |||
| 521 | .PP | 521 | .PP |
| 522 | Parent directories of "dest" will be automatically created if missing. | 522 | Parent directories of "dest" will be automatically created if missing. |
| 523 | .PP | 523 | .PP |
| 524 | The files are copied in the order they are specified in the manifests. If | ||
| 525 | multiple elements specify the same source and destination, they will only be | ||
| 526 | applied as one, based on the first occurence. Files are copied before any links | ||
| 527 | specified via linkfile elements are created. | ||
| 528 | .PP | ||
| 524 | Element linkfile | 529 | Element linkfile |
| 525 | .PP | 530 | .PP |
| 526 | It's just like copyfile and runs at the same time as copyfile but instead of | 531 | It's just like copyfile, but instead of copying it creates a symlink. |
| 527 | copying it creates a symlink. | ||
| 528 | .PP | 532 | .PP |
| 529 | The symlink is created at "dest" (relative to the top of the tree) and points to | 533 | The symlink is created at "dest" (relative to the top of the tree) and points to |
| 530 | the path specified by "src" which is a path in the project. | 534 | the path specified by "src" which is a path in the project. |
| @@ -534,6 +538,11 @@ Parent directories of "dest" will be automatically created if missing. | |||
| 534 | The symlink target may be a file or directory, but it may not point outside of | 538 | The symlink target may be a file or directory, but it may not point outside of |
| 535 | the repo client. | 539 | the repo client. |
| 536 | .PP | 540 | .PP |
| 541 | The links are created in the order they are specified in the manifests. If | ||
| 542 | multiple elements specify the same source and destination, they will only be | ||
| 543 | applied as one, based on the first occurence. Links are created after any files | ||
| 544 | specified via copyfile elements are copied. | ||
| 545 | .PP | ||
| 537 | Element remove\-project | 546 | Element remove\-project |
| 538 | .PP | 547 | .PP |
| 539 | Deletes a project from the internal manifest table, possibly allowing a | 548 | Deletes a project from the internal manifest table, possibly allowing a |
| @@ -390,22 +390,17 @@ def _SafeExpandPath(base, subpath, skipfinal=False): | |||
| 390 | return path | 390 | return path |
| 391 | 391 | ||
| 392 | 392 | ||
| 393 | class _CopyFile: | 393 | class _CopyFile(NamedTuple): |
| 394 | """Container for <copyfile> manifest element.""" | 394 | """Container for <copyfile> manifest element.""" |
| 395 | 395 | ||
| 396 | def __init__(self, git_worktree, src, topdir, dest): | 396 | # Absolute path to the git project checkout. |
| 397 | """Register a <copyfile> request. | 397 | git_worktree: str |
| 398 | 398 | # Relative path under |git_worktree| of file to read. | |
| 399 | Args: | 399 | src: str |
| 400 | git_worktree: Absolute path to the git project checkout. | 400 | # Absolute path to the top of the repo client checkout. |
| 401 | src: Relative path under |git_worktree| of file to read. | 401 | topdir: str |
| 402 | topdir: Absolute path to the top of the repo client checkout. | 402 | # Relative path under |topdir| of file to write. |
| 403 | dest: Relative path under |topdir| of file to write. | 403 | dest: str |
| 404 | """ | ||
| 405 | self.git_worktree = git_worktree | ||
| 406 | self.topdir = topdir | ||
| 407 | self.src = src | ||
| 408 | self.dest = dest | ||
| 409 | 404 | ||
| 410 | def _Copy(self): | 405 | def _Copy(self): |
| 411 | src = _SafeExpandPath(self.git_worktree, self.src) | 406 | src = _SafeExpandPath(self.git_worktree, self.src) |
| @@ -439,22 +434,17 @@ class _CopyFile: | |||
| 439 | logger.error("error: Cannot copy file %s to %s", src, dest) | 434 | logger.error("error: Cannot copy file %s to %s", src, dest) |
| 440 | 435 | ||
| 441 | 436 | ||
| 442 | class _LinkFile: | 437 | class _LinkFile(NamedTuple): |
| 443 | """Container for <linkfile> manifest element.""" | 438 | """Container for <linkfile> manifest element.""" |
| 444 | 439 | ||
| 445 | def __init__(self, git_worktree, src, topdir, dest): | 440 | # Absolute path to the git project checkout. |
| 446 | """Register a <linkfile> request. | 441 | git_worktree: str |
| 447 | 442 | # Target of symlink relative to path under |git_worktree|. | |
| 448 | Args: | 443 | src: str |
| 449 | git_worktree: Absolute path to the git project checkout. | 444 | # Absolute path to the top of the repo client checkout. |
| 450 | src: Target of symlink relative to path under |git_worktree|. | 445 | topdir: str |
| 451 | topdir: Absolute path to the top of the repo client checkout. | 446 | # Relative path under |topdir| of symlink to create. |
| 452 | dest: Relative path under |topdir| of symlink to create. | 447 | dest: str |
| 453 | """ | ||
| 454 | self.git_worktree = git_worktree | ||
| 455 | self.topdir = topdir | ||
| 456 | self.src = src | ||
| 457 | self.dest = dest | ||
| 458 | 448 | ||
| 459 | def __linkIt(self, relSrc, absDest): | 449 | def __linkIt(self, relSrc, absDest): |
| 460 | # Link file if it does not exist or is out of date. | 450 | # Link file if it does not exist or is out of date. |
| @@ -633,8 +623,9 @@ class Project: | |||
| 633 | self.subprojects = [] | 623 | self.subprojects = [] |
| 634 | 624 | ||
| 635 | self.snapshots = {} | 625 | self.snapshots = {} |
| 636 | self.copyfiles = [] | 626 | # Use dicts to dedupe while maintaining declared order. |
| 637 | self.linkfiles = [] | 627 | self.copyfiles = {} |
| 628 | self.linkfiles = {} | ||
| 638 | self.annotations = [] | 629 | self.annotations = [] |
| 639 | self.dest_branch = dest_branch | 630 | self.dest_branch = dest_branch |
| 640 | 631 | ||
| @@ -1794,7 +1785,7 @@ class Project: | |||
| 1794 | Paths should have basic validation run on them before being queued. | 1785 | Paths should have basic validation run on them before being queued. |
| 1795 | Further checking will be handled when the actual copy happens. | 1786 | Further checking will be handled when the actual copy happens. |
| 1796 | """ | 1787 | """ |
| 1797 | self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest)) | 1788 | self.copyfiles[_CopyFile(self.worktree, src, topdir, dest)] = True |
| 1798 | 1789 | ||
| 1799 | def AddLinkFile(self, src, dest, topdir): | 1790 | def AddLinkFile(self, src, dest, topdir): |
| 1800 | """Mark |dest| to create a symlink (relative to |topdir|) pointing to | 1791 | """Mark |dest| to create a symlink (relative to |topdir|) pointing to |
| @@ -1805,7 +1796,7 @@ class Project: | |||
| 1805 | Paths should have basic validation run on them before being queued. | 1796 | Paths should have basic validation run on them before being queued. |
| 1806 | Further checking will be handled when the actual link happens. | 1797 | Further checking will be handled when the actual link happens. |
| 1807 | """ | 1798 | """ |
| 1808 | self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest)) | 1799 | self.linkfiles[_LinkFile(self.worktree, src, topdir, dest)] = True |
| 1809 | 1800 | ||
| 1810 | def AddAnnotation(self, name, value, keep): | 1801 | def AddAnnotation(self, name, value, keep): |
| 1811 | self.annotations.append(Annotation(name, value, keep)) | 1802 | self.annotations.append(Annotation(name, value, keep)) |
diff --git a/tests/test_manifest_xml.py b/tests/test_manifest_xml.py index d4bf76a9..f5991515 100644 --- a/tests/test_manifest_xml.py +++ b/tests/test_manifest_xml.py | |||
| @@ -1254,8 +1254,8 @@ class ExtendProjectElementTests(ManifestParseTestCase): | |||
| 1254 | </manifest> | 1254 | </manifest> |
| 1255 | """ | 1255 | """ |
| 1256 | ) | 1256 | ) |
| 1257 | self.assertEqual(manifest.projects[0].copyfiles[0].src, "foo") | 1257 | self.assertEqual(list(manifest.projects[0].copyfiles)[0].src, "foo") |
| 1258 | self.assertEqual(manifest.projects[0].copyfiles[0].dest, "bar") | 1258 | self.assertEqual(list(manifest.projects[0].copyfiles)[0].dest, "bar") |
| 1259 | self.assertEqual( | 1259 | self.assertEqual( |
| 1260 | sort_attributes(manifest.ToXml().toxml()), | 1260 | sort_attributes(manifest.ToXml().toxml()), |
| 1261 | '<?xml version="1.0" ?><manifest>' | 1261 | '<?xml version="1.0" ?><manifest>' |
| @@ -1267,6 +1267,47 @@ class ExtendProjectElementTests(ManifestParseTestCase): | |||
| 1267 | "</manifest>", | 1267 | "</manifest>", |
| 1268 | ) | 1268 | ) |
| 1269 | 1269 | ||
| 1270 | def test_extend_project_duplicate_copyfiles(self): | ||
| 1271 | root_m = self.manifest_dir / "root.xml" | ||
| 1272 | root_m.write_text( | ||
| 1273 | """ | ||
| 1274 | <manifest> | ||
| 1275 | <remote name="test-remote" fetch="http://localhost" /> | ||
| 1276 | <default remote="test-remote" revision="refs/heads/main" /> | ||
| 1277 | <project name="myproject" /> | ||
| 1278 | <include name="man1.xml" /> | ||
| 1279 | <include name="man2.xml" /> | ||
| 1280 | </manifest> | ||
| 1281 | """ | ||
| 1282 | ) | ||
| 1283 | (self.manifest_dir / "man1.xml").write_text( | ||
| 1284 | """ | ||
| 1285 | <manifest> | ||
| 1286 | <include name="common.xml" /> | ||
| 1287 | </manifest> | ||
| 1288 | """ | ||
| 1289 | ) | ||
| 1290 | (self.manifest_dir / "man2.xml").write_text( | ||
| 1291 | """ | ||
| 1292 | <manifest> | ||
| 1293 | <include name="common.xml" /> | ||
| 1294 | </manifest> | ||
| 1295 | """ | ||
| 1296 | ) | ||
| 1297 | (self.manifest_dir / "common.xml").write_text( | ||
| 1298 | """ | ||
| 1299 | <manifest> | ||
| 1300 | <extend-project name="myproject"> | ||
| 1301 | <copyfile dest="bar" src="foo"/> | ||
| 1302 | </extend-project> | ||
| 1303 | </manifest> | ||
| 1304 | """ | ||
| 1305 | ) | ||
| 1306 | manifest = manifest_xml.XmlManifest(str(self.repodir), str(root_m)) | ||
| 1307 | self.assertEqual(len(manifest.projects[0].copyfiles), 1) | ||
| 1308 | self.assertEqual(list(manifest.projects[0].copyfiles)[0].src, "foo") | ||
| 1309 | self.assertEqual(list(manifest.projects[0].copyfiles)[0].dest, "bar") | ||
| 1310 | |||
| 1270 | def test_extend_project_linkfiles(self): | 1311 | def test_extend_project_linkfiles(self): |
| 1271 | manifest = self.getXmlManifest( | 1312 | manifest = self.getXmlManifest( |
| 1272 | """ | 1313 | """ |
| @@ -1280,8 +1321,8 @@ class ExtendProjectElementTests(ManifestParseTestCase): | |||
| 1280 | </manifest> | 1321 | </manifest> |
| 1281 | """ | 1322 | """ |
| 1282 | ) | 1323 | ) |
| 1283 | self.assertEqual(manifest.projects[0].linkfiles[0].src, "foo") | 1324 | self.assertEqual(list(manifest.projects[0].linkfiles)[0].src, "foo") |
| 1284 | self.assertEqual(manifest.projects[0].linkfiles[0].dest, "bar") | 1325 | self.assertEqual(list(manifest.projects[0].linkfiles)[0].dest, "bar") |
| 1285 | self.assertEqual( | 1326 | self.assertEqual( |
| 1286 | sort_attributes(manifest.ToXml().toxml()), | 1327 | sort_attributes(manifest.ToXml().toxml()), |
| 1287 | '<?xml version="1.0" ?><manifest>' | 1328 | '<?xml version="1.0" ?><manifest>' |
| @@ -1293,6 +1334,47 @@ class ExtendProjectElementTests(ManifestParseTestCase): | |||
| 1293 | "</manifest>", | 1334 | "</manifest>", |
| 1294 | ) | 1335 | ) |
| 1295 | 1336 | ||
| 1337 | def test_extend_project_duplicate_linkfiles(self): | ||
| 1338 | root_m = self.manifest_dir / "root.xml" | ||
| 1339 | root_m.write_text( | ||
| 1340 | """ | ||
| 1341 | <manifest> | ||
| 1342 | <remote name="test-remote" fetch="http://localhost" /> | ||
| 1343 | <default remote="test-remote" revision="refs/heads/main" /> | ||
| 1344 | <project name="myproject" /> | ||
| 1345 | <include name="man1.xml" /> | ||
| 1346 | <include name="man2.xml" /> | ||
| 1347 | </manifest> | ||
| 1348 | """ | ||
| 1349 | ) | ||
| 1350 | (self.manifest_dir / "man1.xml").write_text( | ||
| 1351 | """ | ||
| 1352 | <manifest> | ||
| 1353 | <include name="common.xml" /> | ||
| 1354 | </manifest> | ||
| 1355 | """ | ||
| 1356 | ) | ||
| 1357 | (self.manifest_dir / "man2.xml").write_text( | ||
| 1358 | """ | ||
| 1359 | <manifest> | ||
| 1360 | <include name="common.xml" /> | ||
| 1361 | </manifest> | ||
| 1362 | """ | ||
| 1363 | ) | ||
| 1364 | (self.manifest_dir / "common.xml").write_text( | ||
| 1365 | """ | ||
| 1366 | <manifest> | ||
| 1367 | <extend-project name="myproject"> | ||
| 1368 | <linkfile dest="bar" src="foo"/> | ||
| 1369 | </extend-project> | ||
| 1370 | </manifest> | ||
| 1371 | """ | ||
| 1372 | ) | ||
| 1373 | manifest = manifest_xml.XmlManifest(str(self.repodir), str(root_m)) | ||
| 1374 | self.assertEqual(len(manifest.projects[0].linkfiles), 1) | ||
| 1375 | self.assertEqual(list(manifest.projects[0].linkfiles)[0].src, "foo") | ||
| 1376 | self.assertEqual(list(manifest.projects[0].linkfiles)[0].dest, "bar") | ||
| 1377 | |||
| 1296 | def test_extend_project_annotations(self): | 1378 | def test_extend_project_annotations(self): |
| 1297 | manifest = self.getXmlManifest( | 1379 | manifest = self.getXmlManifest( |
| 1298 | """ | 1380 | """ |
