diff --git a/swh/fuse/fs/artifact.py b/swh/fuse/fs/artifact.py --- a/swh/fuse/fs/artifact.py +++ b/swh/fuse/fs/artifact.py @@ -10,7 +10,7 @@ from swh.fuse.fs.entry import EntryMode, FuseEntry from swh.fuse.fs.symlink import SymlinkEntry from swh.model.from_disk import DentryPerms -from swh.model.identifiers import CONTENT, DIRECTORY, REVISION, SWHID +from swh.model.identifiers import CONTENT, DIRECTORY, RELEASE, REVISION, SWHID @dataclass @@ -176,4 +176,78 @@ ) -OBJTYPE_GETTERS = {CONTENT: Content, DIRECTORY: Directory, REVISION: Revision} +class Release(ArtifactEntry): + """ Software Heritage release artifact. + + Release nodes are represented on the file-system as directories with the + following entries: + + - `target`: target node, as a symlink to `archive/` + - `target_type`: regular file containing the type of the target SWHID + - `root`: present if and only if the release points to something that + (transitively) resolves to a directory. When present it is a symlink + pointing into `archive/` to the SWHID of the given directory + - `meta.json`: metadata for the current node, as a symlink pointing to the + relevant `meta/.json` file """ + + async def find_root_directory(self, swhid: SWHID) -> SWHID: + if swhid.object_type == RELEASE: + metadata = await self.fuse.get_metadata(swhid) + return await self.find_root_directory(metadata["target"]) + elif swhid.object_type == REVISION: + metadata = await self.fuse.get_metadata(swhid) + return metadata["directory"] + elif swhid.object_type == DIRECTORY: + return swhid + else: + return None + + async def __aiter__(self) -> AsyncIterator[FuseEntry]: + metadata = await self.fuse.get_metadata(self.swhid) + root_path = self.get_relative_root_path() + + yield self.create_child( + SymlinkEntry, + name="meta.json", + target=Path(root_path, f"meta/{self.swhid}.json"), + ) + + target = metadata["target"] + yield self.create_child( + SymlinkEntry, name="target", target=Path(root_path, f"archive/{target}") + ) + yield self.create_child( + ReleaseType, + name="target_type", + mode=int(EntryMode.RDONLY_FILE), + target_type=target.object_type, + ) + + target_dir = await self.find_root_directory(target) + if target_dir is not None: + yield self.create_child( + SymlinkEntry, + name="root", + target=Path(root_path, f"archive/{target_dir}"), + ) + + +@dataclass +class ReleaseType(FuseEntry): + """ Release type virtual file """ + + target_type: str + + async def get_content(self) -> bytes: + return str.encode(self.target_type + "\n") + + async def size(self) -> int: + return len(await self.get_content()) + + +OBJTYPE_GETTERS = { + CONTENT: Content, + DIRECTORY: Directory, + REVISION: Revision, + RELEASE: Release, +} diff --git a/swh/fuse/tests/common.py b/swh/fuse/tests/common.py --- a/swh/fuse/tests/common.py +++ b/swh/fuse/tests/common.py @@ -3,7 +3,9 @@ # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information -from typing import Any +import os +from pathlib import Path +from typing import Any, List from swh.fuse.tests.data.api_data import MOCK_ARCHIVE, SWHID2URL @@ -13,3 +15,14 @@ if raw: url += "raw/" return MOCK_ARCHIVE[url] + + +def get_dir_name_entries(swhid: str) -> List[str]: + dir_meta = get_data_from_archive(swhid) + return [x["name"] for x in dir_meta] + + +def check_dir_name_entries(dir_path: Path, dir_swhid: str) -> None: + expected = get_dir_name_entries(dir_swhid) + actual = os.listdir(dir_path) + assert set(actual) == set(expected) diff --git a/swh/fuse/tests/data/api_data.py b/swh/fuse/tests/data/api_data.py --- a/swh/fuse/tests/data/api_data.py +++ b/swh/fuse/tests/data/api_data.py @@ -31,6 +31,11 @@ "swh:1:rev:2d39e2894830331fb02b77980a6190e972ad3d68": "revision/2d39e2894830331fb02b77980a6190e972ad3d68/", "swh:1:rev:92baf7293dd2d418d2ac4b141b0faa822075d9f7": "revision/92baf7293dd2d418d2ac4b141b0faa822075d9f7/", "swh:1:rev:cf6447aff01e4bcb1fdbc89d6f754451a157589e": "revision/cf6447aff01e4bcb1fdbc89d6f754451a157589e/", + "swh:1:rel:874f7cbe352033cac5a8bc889847da2fe1d13e9f": "release/874f7cbe352033cac5a8bc889847da2fe1d13e9f/", + "swh:1:rel:da5f9898d6248ab26277116f54aca855338401d2": "release/da5f9898d6248ab26277116f54aca855338401d2/", + "swh:1:cnt:be5effea679c057aec2bb020f0241b1d1d660840": "content/sha1_git:be5effea679c057aec2bb020f0241b1d1d660840/", + "swh:1:rel:3a7b2dfffed2945d2933ba4ebc063adba35ddb2e": "release/3a7b2dfffed2945d2933ba4ebc063adba35ddb2e/", + "swh:1:dir:b24d39c928b9c3f440f8e2ec06c78f43d28d87d6": "directory/b24d39c928b9c3f440f8e2ec06c78f43d28d87d6/", } MOCK_ARCHIVE = { @@ -1737,4 +1742,129 @@ "history_url": "https://archive.softwareheritage.org/api/1/revision/cf6447aff01e4bcb1fdbc89d6f754451a157589e/log/", "directory_url": "https://archive.softwareheritage.org/api/1/directory/59263209d6c932eefc716826aa0d0df60e540cbe/", }, + "release/874f7cbe352033cac5a8bc889847da2fe1d13e9f/": { + "name": "1.42.0", + "message": "1.42.0 release\n", + "target": "b8cedc00407a4c56a3bda1ed605c6fc166655447", + "target_type": "revision", + "synthetic": False, + "author": { + "fullname": "Pietro Albini ", + "name": "Pietro Albini", + "email": "pietro@pietroalbini.org", + }, + "date": "2020-03-12T15:05:24+01:00", + "id": "874f7cbe352033cac5a8bc889847da2fe1d13e9f", + "target_url": "https://archive.softwareheritage.org/api/1/revision/b8cedc00407a4c56a3bda1ed605c6fc166655447/", + }, + "release/da5f9898d6248ab26277116f54aca855338401d2/": { + "name": "maintainer-gpg-key", + "message": "GPG public key of SimCav\n-----BEGIN PGP SIGNATURE-----\n\niQIcBAABAgAGBQJaPZzzAAoJEBXKttsl/fq6/y4P+wVtI9WpXeR5E1OSdJtXiomY\nh1Htc+d0mRS5PDT6h9R80VdfAl9Bvts+xiHqy1kqptAfxRqFZJIorbwq6MGFn42i\nSEVA/Y6yWvgxNUhMdJAywlzJ6ql4D2Awa3AqM+nwtHtvDJ0FQe9tE+mYjah1fL51\nf41HJi9iaFbfEmMMwENPsbbOZtQDRsMimPCQnlQU0O+DTrhvQA/1dpVdhWg0azC6\nc3NPoEbq8dzYPbYUPJeotb9wIxPxeX+XFCwtc9aIoNP+LLtXwztYQTt5AqBhSf4T\nFYZmYkT9X+0uBru4AyJbeiHBt1ssh9ri3e6kfxcjE49btCQz5HoLPUnRxUWr0FFW\nyxEdyljt4Tzl7DcImkI4crQmMzym5c4h1KkK+O9dv205kCwya8aLQyRLEzMcGFBp\n7SsbHdVMf3K6nXBIDnf/AxErO76/PbjvYtCRVlfMRlMKXLciJu0N4/GTEYrK7qxc\nU3UFvdmxq33VE0YjcorzwSSkb5GTqwc6qwjsnnYl3tO35Ev/1+c+uryEwlk+P00n\neVnyq8zzgEANcxAyTxchFbd73sJ2JsWrBLsDBOQRk5Qo5tXTw+pDJzl3dmOytv4S\nAcFlryzMt4ShKdLUUUN6tvmuziCGkfiwWWj1LG+G+DEr5bGQH49Q1l9IMxgAvgPP\ncJPmfbi315UGs8k48xfT\n=JiPC\n-----END PGP SIGNATURE-----\n", + "target": "be5effea679c057aec2bb020f0241b1d1d660840", + "target_type": "content", + "synthetic": False, + "author": { + "fullname": "SimCav ", + "name": "SimCav", + "email": "simcav@protonmail.com", + }, + "date": "2017-12-23T01:01:25+01:00", + "id": "da5f9898d6248ab26277116f54aca855338401d2", + "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:be5effea679c057aec2bb020f0241b1d1d660840/", + }, + "content/sha1_git:be5effea679c057aec2bb020f0241b1d1d660840/": { + "length": 5279, + "status": "visible", + "checksums": { + "sha256": "d3923bc07a944321af5eb781c1ae7b86b1f8c07385dce3adad1eee052f2cda47", + "sha1": "c640e23feb6f93b02878de5b02d70e87388a2bd2", + "blake2s256": "6f515bb07318b5730f7c2d0aa4dbe24fe1b65ed4f38cf3500a8ffbdbb1ea3cfe", + "sha1_git": "be5effea679c057aec2bb020f0241b1d1d660840", + }, + "data_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:be5effea679c057aec2bb020f0241b1d1d660840/raw/", + "filetype_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:be5effea679c057aec2bb020f0241b1d1d660840/filetype/", + "language_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:be5effea679c057aec2bb020f0241b1d1d660840/language/", + "license_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:be5effea679c057aec2bb020f0241b1d1d660840/license/", + }, + "content/sha1_git:be5effea679c057aec2bb020f0241b1d1d660840/raw/": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\n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\n=kZK9\n-----END PGP PUBLIC KEY BLOCK-----\n", + "release/3a7b2dfffed2945d2933ba4ebc063adba35ddb2e/": { + "name": "luvit/lit/v1.2.10", + "message": '{"license":"MIT","version":"1.2.10","luvi":{"version":"2.0.9","flavor":"regular"},"homepage":"https://github.com/luvit/lit","author":{"name":"Tim Caswell"},"dependencies":["luvit/require@1.2.0","luvit/pretty-print@1.0.2","luvit/http-codec@1.0.0","luvit/json@2.5.0","creationix/coro-fs@1.2.3","creationix/coro-tcp@1.0.5","creationix/coro-http@1.0.7","creationix/coro-tls@1.2.0","creationix/coro-wrapper@1.0.0","creationix/hex-bin@1.0.0","creationix/semver@1.0.2","creationix/git@1.0.1","creationix/prompt@1.0.3","creationix/ssh-rsa@1.0.0","creationix/websocket-codec@1.0.2"],"tags":["lit","meta"],"name":"luvit/lit","description":"The Luvit Invention Toolkit is a luvi app that handles dependencies and luvi builds."}\n-----BEGIN RSA SIGNATURE-----\nFormat: sha256-ssh-rsa\nFingerprint: 89:08:c2:48:49:f6:f8:dd:df:e3:fc:1c:a0:7f:3a:ec\n\nIwalmldr0L9ZBHcKMhq9bgwFnCIjQoAhXjZd+hPOcrzTNNI50LWJxwr2d4yOEckC\nk0n1HcYDMhJpOEOo91A61Trts6hu27VW3FaQMzqtlqlwlL5X/px92RLb06CsBaOR\n4B+x0FXnL9RB4Cdw0JZFNWcC/jkMoKXS+tiCwYueIAb8VIH1CXcKUkJgmWPldkxX\nOPm1u6ITcJ/eBU8EzCfI+03beIqIbDA1FBhPegsyGLY/uaCOQKx/Ofqfuxxiz/kf\nsRKIQuGZzQYXgOdU9rjg7gkEw0FV8rqAavD1crWahlx+RohmUceISnKvC8cd0fn8\nI6rcW6jxHIOjqA/m7wwHbA==\n-----END RSA SIGNATURE-----\n', + "target": "b24d39c928b9c3f440f8e2ec06c78f43d28d87d6", + "target_type": "directory", + "synthetic": False, + "author": { + "fullname": "Tim Caswell ", + "name": "Tim Caswell", + "email": "tim@creationix.com", + }, + "date": "2015-05-27T12:03:12-05:00", + "id": "3a7b2dfffed2945d2933ba4ebc063adba35ddb2e", + "target_url": "https://archive.softwareheritage.org/api/1/directory/b24d39c928b9c3f440f8e2ec06c78f43d28d87d6/", + }, + "directory/b24d39c928b9c3f440f8e2ec06c78f43d28d87d6/": [ + { + "dir_id": "b24d39c928b9c3f440f8e2ec06c78f43d28d87d6", + "type": "dir", + "target": "7aac5c4e25bd03690b47db72425ada565c58cac6", + "name": "commands", + "perms": 16384, + "length": None, + "target_url": "https://archive.softwareheritage.org/api/1/directory/7aac5c4e25bd03690b47db72425ada565c58cac6/", + }, + { + "dir_id": "b24d39c928b9c3f440f8e2ec06c78f43d28d87d6", + "type": "file", + "target": "53ea710b37aef348b3e09478b18e2bfd180efb43", + "name": "init.lua", + "perms": 33188, + "status": "visible", + "length": 27, + "checksums": { + "sha256": "28d6e007e8ba8de537247c2e4dce5ea081919da9eabd2a1cd580afd02425275b", + "sha1": "e757103bdac5b2be6e8f28b47595862dd3d36b2b", + "sha1_git": "53ea710b37aef348b3e09478b18e2bfd180efb43", + }, + "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:53ea710b37aef348b3e09478b18e2bfd180efb43/", + }, + { + "dir_id": "b24d39c928b9c3f440f8e2ec06c78f43d28d87d6", + "type": "dir", + "target": "8535df65f0a259879040f7922438de2c4272e48f", + "name": "libs", + "perms": 16384, + "length": None, + "target_url": "https://archive.softwareheritage.org/api/1/directory/8535df65f0a259879040f7922438de2c4272e48f/", + }, + { + "dir_id": "b24d39c928b9c3f440f8e2ec06c78f43d28d87d6", + "type": "file", + "target": "5ddf82d3f5330bad8c830ac6b21bab2e912bee6e", + "name": "main.lua", + "perms": 33188, + "status": "visible", + "length": 1216, + "checksums": { + "sha256": "e6ab5dc18e4ca7612439c28a991d5a5c09ed2006a5efa2e9034ced6ee995cf1e", + "sha1": "37a14e4c123ae1d5006665ef867f84bc23ca2fe8", + "sha1_git": "5ddf82d3f5330bad8c830ac6b21bab2e912bee6e", + }, + "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:5ddf82d3f5330bad8c830ac6b21bab2e912bee6e/", + }, + { + "dir_id": "b24d39c928b9c3f440f8e2ec06c78f43d28d87d6", + "type": "file", + "target": "d8d2804032211fec42ec197827b049d5dea40ea7", + "name": "package.lua", + "perms": 33188, + "status": "visible", + "length": 915, + "checksums": { + "sha256": "c63c6cbe41d8fc6fcc3401f0d4d993e42a7ae873dd97fda9dc4cfc2132d61c03", + "sha1": "bf83eda0827a970c3cccc9d3ba681c497b1108e9", + "sha1_git": "d8d2804032211fec42ec197827b049d5dea40ea7", + }, + "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:d8d2804032211fec42ec197827b049d5dea40ea7/", + }, + ], } diff --git a/swh/fuse/tests/data/config.py b/swh/fuse/tests/data/config.py --- a/swh/fuse/tests/data/config.py +++ b/swh/fuse/tests/data/config.py @@ -22,8 +22,24 @@ "swh:1:rev:92baf7293dd2d418d2ac4b141b0faa822075d9f7", ] # Release +ROOT_REL = "swh:1:rel:874f7cbe352033cac5a8bc889847da2fe1d13e9f" # TODO # Snapshot # TODO -ALL_ENTRIES = [REGULAR_FILE, ROOT_DIR, DIR_WITH_SUBMODULES, ROOT_REV, *SUBMODULES] +# Special corner cases (not from Rust compiler) +REL_TARGET_CNT = "swh:1:rel:da5f9898d6248ab26277116f54aca855338401d2" +TARGET_CNT = "swh:1:cnt:be5effea679c057aec2bb020f0241b1d1d660840" +REL_TARGET_DIR = "swh:1:rel:3a7b2dfffed2945d2933ba4ebc063adba35ddb2e" +TARGET_DIR = "swh:1:dir:b24d39c928b9c3f440f8e2ec06c78f43d28d87d6" + +ALL_ENTRIES = [ + REGULAR_FILE, + ROOT_DIR, + DIR_WITH_SUBMODULES, + ROOT_REV, + *SUBMODULES, + ROOT_REL, + REL_TARGET_CNT, + REL_TARGET_DIR, +] diff --git a/swh/fuse/tests/data/gen-api-data.py b/swh/fuse/tests/data/gen-api-data.py --- a/swh/fuse/tests/data/gen-api-data.py +++ b/swh/fuse/tests/data/gen-api-data.py @@ -12,7 +12,14 @@ import requests from swh.fuse.tests.data.config import ALL_ENTRIES -from swh.model.identifiers import CONTENT, DIRECTORY, REVISION, SWHID, parse_swhid +from swh.model.identifiers import ( + CONTENT, + DIRECTORY, + RELEASE, + REVISION, + SWHID, + parse_swhid, +) API_URL_real = "https://archive.softwareheritage.org/api/1" API_URL_test = "https://invalid-test-only.archive.softwareheritage.org/api/1" @@ -28,12 +35,29 @@ CONTENT: "content/sha1_git:", DIRECTORY: "directory/", REVISION: "revision/", + RELEASE: "release/", } return f"{prefix[swhid.object_type]}{swhid.object_id}/" -def generate_archive_data(swhid: SWHID, raw: bool = False) -> None: +def get_short_type(object_type: str) -> str: + short_type = { + CONTENT: "cnt", + DIRECTORY: "dir", + REVISION: "rev", + RELEASE: "rel", + } + return short_type[object_type] + + +def generate_archive_data( + swhid: SWHID, raw: bool = False, recursive: bool = False +) -> None: + # Already in mock archive + if swhid in METADATA and not raw: + return + url = swhid2url(swhid) SWHID2URL[str(swhid)] = url @@ -47,19 +71,26 @@ MOCK_ARCHIVE[url] = data METADATA[swhid] = data + # Retrieve additional needed data for different artifacts (eg: content's + # blob data, revision parents, etc.) + if recursive: + if swhid.object_type == CONTENT: + generate_archive_data(swhid, raw=True) + elif swhid.object_type == REVISION: + for parent in METADATA[swhid]["parents"]: + parent_swhid = parse_swhid(f"swh:1:rev:{parent['id']}") + # Only retrieve one-level of parent (disable recursivity) + generate_archive_data(parent_swhid) + elif swhid.object_type == RELEASE: + target_type = METADATA[swhid]["target_type"] + target_id = METADATA[swhid]["target"] + target = parse_swhid(f"swh:1:{get_short_type(target_type)}:{target_id}") + generate_archive_data(target, recursive=True) + for entry in ALL_ENTRIES: swhid = parse_swhid(entry) - generate_archive_data(swhid) - - # Retrieve raw blob data for content artifact - if swhid.object_type == CONTENT: - generate_archive_data(swhid, raw=True) - # Retrieve parent commits for revision artifact - elif swhid.object_type == REVISION: - for parent in METADATA[swhid]["parents"]: - parent_swhid = parse_swhid(f"swh:1:rev:{parent['id']}") - generate_archive_data(parent_swhid) + generate_archive_data(swhid, recursive=True) print("# GENERATED FILE, DO NOT EDIT.") print("# Run './gen-api-data.py > api_data.py' instead.") diff --git a/swh/fuse/tests/test_directory.py b/swh/fuse/tests/test_directory.py --- a/swh/fuse/tests/test_directory.py +++ b/swh/fuse/tests/test_directory.py @@ -1,15 +1,12 @@ import os -from swh.fuse.tests.common import get_data_from_archive +from swh.fuse.tests.common import check_dir_name_entries from swh.fuse.tests.data.config import DIR_WITH_SUBMODULES, ROOT_DIR def test_list_dir(fuse_mntdir): dir_path = fuse_mntdir / "archive" / ROOT_DIR - dir_meta = get_data_from_archive(ROOT_DIR) - expected = [x["name"] for x in dir_meta] - actual = os.listdir(dir_path) - assert set(actual) == set(expected) + check_dir_name_entries(dir_path, ROOT_DIR) def test_access_file(fuse_mntdir): diff --git a/swh/fuse/tests/test_release.py b/swh/fuse/tests/test_release.py new file mode 100644 --- /dev/null +++ b/swh/fuse/tests/test_release.py @@ -0,0 +1,46 @@ +import json +import os + +from swh.fuse.tests.common import check_dir_name_entries, get_data_from_archive +from swh.fuse.tests.data.config import ( + REL_TARGET_CNT, + REL_TARGET_DIR, + ROOT_DIR, + ROOT_REL, + TARGET_CNT, + TARGET_DIR, +) + + +def test_access_meta(fuse_mntdir): + file_path = fuse_mntdir / "archive" / ROOT_REL / "meta.json" + expected = json.dumps(get_data_from_archive(ROOT_REL)) + assert file_path.read_text() == expected + + +def test_access_rev_target(fuse_mntdir): + target_path = fuse_mntdir / "archive" / ROOT_REL / "target" + expected = ["meta.json", "root", "parent", "parents"] + actual = os.listdir(target_path) + assert set(actual) == set(expected) + + +def test_access_dir_target(fuse_mntdir): + target_path = fuse_mntdir / "archive" / REL_TARGET_DIR / "target" + check_dir_name_entries(target_path, TARGET_DIR) + + +def test_access_cnt_target(fuse_mntdir): + target_path = fuse_mntdir / "archive" / REL_TARGET_CNT / "target" + expected = get_data_from_archive(TARGET_CNT, raw=True) + assert target_path.read_text() == expected + + +def test_target_type(fuse_mntdir): + file_path = fuse_mntdir / "archive" / ROOT_REL / "target_type" + assert file_path.read_text() == "revision\n" + + +def test_access_root(fuse_mntdir): + dir_path = fuse_mntdir / "archive" / ROOT_REL / "root" + check_dir_name_entries(dir_path, ROOT_DIR) diff --git a/swh/fuse/tests/test_revision.py b/swh/fuse/tests/test_revision.py --- a/swh/fuse/tests/test_revision.py +++ b/swh/fuse/tests/test_revision.py @@ -1,7 +1,7 @@ import json import os -from swh.fuse.tests.common import get_data_from_archive +from swh.fuse.tests.common import check_dir_name_entries, get_data_from_archive from swh.fuse.tests.data.config import ROOT_DIR, ROOT_REV @@ -13,10 +13,7 @@ def test_list_root(fuse_mntdir): dir_path = fuse_mntdir / "archive" / ROOT_REV / "root" - dir_meta = get_data_from_archive(ROOT_DIR) - expected = [x["name"] for x in dir_meta] - actual = os.listdir(dir_path) - assert set(actual) == set(expected) + check_dir_name_entries(dir_path, ROOT_DIR) def test_list_parents(fuse_mntdir):