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 @@ -7,6 +7,7 @@ from dataclasses import dataclass, field import json import logging +import os from pathlib import Path from typing import Any, AsyncIterator, Dict, List @@ -482,10 +483,19 @@ next_prefix = next_subdirs[0] if len(next_subdirs) == 1: + # Non-alias targets are symlinks to their corresponding archived + # artifact, whereas alias targets are relative symlinks to the + # corresponding snapshot directory entry. + target_type = branch_meta["target_type"] + target_raw = branch_meta["target"] + if target_type == "alias": + prefix = Path(branch_name).parent + target = os.path.relpath(target_raw, prefix) + else: + target = f"{root_path}/archive/{target_raw}" + yield self.create_child( - FuseSymlinkEntry, - name=next_prefix, - target=Path(root_path, f"archive/{branch_meta['target']}"), + FuseSymlinkEntry, name=next_prefix, target=Path(target), ) else: subdirs.add(next_prefix) 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 @@ -2817,6 +2817,45 @@ "directory_url": "https://archive.softwareheritage.org/api/1/directory/1ac29db0e7280af41064676569a96d1f88ccfa96/", }, "graph/visit/edges/swh:1:rev:430a9fd4c797c50cea26157141b2408073b2ed91": "", + "snapshot/0000000000000000000000000000000000000000/": { + "branches": { + "mycnt": { + "target_type": "content", + "target": "61d3c9e1157203f0c4ed5165608d92294eaca808", + }, + "mydir": { + "target_type": "directory", + "target": "c6dcbe9711ea6d5a31429a833a3d0c59cbbb2578", + }, + "myrev": { + "target_type": "revision", + "target": "b8cedc00407a4c56a3bda1ed605c6fc166655447", + }, + "myrel": { + "target_type": "release", + "target": "874f7cbe352033cac5a8bc889847da2fe1d13e9f", + }, + "refs/heads/master": { + "target_type": "revision", + "target": "430a9fd4c797c50cea26157141b2408073b2ed91", + }, + "alias-rootdir": { + "target_type": "alias", + "target": "refs/heads/master", + "expected_symlink": "refs/heads/master", + }, + "refs/heads/alias-subdir": { + "target_type": "alias", + "target": "refs/heads/master", + "expected_symlink": "master", + }, + "refs/tags/alias-different-subdir": { + "target_type": "alias", + "target": "refs/heads/master", + "expected_symlink": "../heads/master", + }, + } + }, "origin/https://github.com/rust-lang/rust/visits/": [ { "origin": "https://github.com/rust-lang/rust", 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 @@ -5,6 +5,12 @@ """ Use the Rust compiler (v1.42.0) as a testing repository """ + +def remove_swhid_prefix(swhid_str: str) -> str: + prefix = "swh:1:XXX:" + return swhid_str[len(prefix) :] + + # Content REGULAR_FILE = "swh:1:cnt:61d3c9e1157203f0c4ed5165608d92294eaca808" # Directory @@ -32,6 +38,34 @@ # problem, only the mock offline one. ROOT_SNP = "swh:1:snp:02db117fef22434f1658b833a756775ca6effed0" ROOT_SNP_MASTER_BRANCH = "swh:1:rev:430a9fd4c797c50cea26157141b2408073b2ed91" +FAKE_SNP_SPECIAL_CASES_SWHID = "swh:1:snp:0000000000000000000000000000000000000000" +FAKE_SNP_SPECIAL_CASES = { + # All possible target types + "mycnt": {"target_type": "content", "target": remove_swhid_prefix(REGULAR_FILE),}, + "mydir": {"target_type": "directory", "target": remove_swhid_prefix(ROOT_DIR),}, + "myrev": {"target_type": "revision", "target": remove_swhid_prefix(ROOT_REV),}, + "myrel": {"target_type": "release", "target": remove_swhid_prefix(ROOT_REL),}, + "refs/heads/master": { + "target_type": "revision", + "target": remove_swhid_prefix(ROOT_SNP_MASTER_BRANCH), + }, + # Alias with different target paths + "alias-rootdir": { + "target_type": "alias", + "target": "refs/heads/master", + "expected_symlink": "refs/heads/master", + }, + "refs/heads/alias-subdir": { + "target_type": "alias", + "target": "refs/heads/master", + "expected_symlink": "master", + }, + "refs/tags/alias-different-subdir": { + "target_type": "alias", + "target": "refs/heads/master", + "expected_symlink": "../heads/master", + }, +} # Origin ORIGIN_URL = "https://github.com/rust-lang/rust" ORIGIN_URL_ENCODED = "https%3A%2F%2Fgithub.com%2Frust-lang%2Frust" 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 @@ -16,7 +16,13 @@ swhid_to_graph_url, swhid_to_web_url, ) -from swh.fuse.tests.data.config import ALL_ENTRIES, ORIGIN_URL, REV_SMALL_HISTORY +from swh.fuse.tests.data.config import ( + ALL_ENTRIES, + FAKE_SNP_SPECIAL_CASES, + FAKE_SNP_SPECIAL_CASES_SWHID, + ORIGIN_URL, + REV_SMALL_HISTORY, +) from swh.model.identifiers import ( CONTENT, DIRECTORY, @@ -153,6 +159,11 @@ generate_archive_web_api(swhid, recursive=True) generate_archive_graph_api(swhid) +# Custom fake snapshot to handle most special cases +MOCK_ARCHIVE[swhid_to_web_url(FAKE_SNP_SPECIAL_CASES_SWHID)] = { + "branches": FAKE_SNP_SPECIAL_CASES +} + # Origin artifacts are not identified by SWHID but using an URL generate_origin_archive_web_api(ORIGIN_URL) diff --git a/swh/fuse/tests/test_snapshot.py b/swh/fuse/tests/test_snapshot.py --- a/swh/fuse/tests/test_snapshot.py +++ b/swh/fuse/tests/test_snapshot.py @@ -2,7 +2,7 @@ from pathlib import Path from swh.fuse.tests.common import get_data_from_web_archive -from swh.fuse.tests.data.config import ROOT_SNP +from swh.fuse.tests.data.config import FAKE_SNP_SPECIAL_CASES_SWHID, ROOT_SNP def test_list_branches(fuse_mntdir): @@ -12,6 +12,16 @@ assert (snp_dir / branch_name).is_symlink() +def test_special_cases(fuse_mntdir): + snp_dir = Path(fuse_mntdir / "archive" / FAKE_SNP_SPECIAL_CASES_SWHID) + snp_meta = get_data_from_web_archive(FAKE_SNP_SPECIAL_CASES_SWHID) + for branch_name, branch_meta in snp_meta.items(): + curr = snp_dir / branch_name + assert curr.is_symlink() + if "expected_symlink" in branch_meta: + assert os.readlink(curr) == branch_meta["expected_symlink"] + + def test_access_rev_target(fuse_mntdir): dir_path = fuse_mntdir / "archive" / ROOT_SNP / "refs/heads/master" expected = set(["meta.json", "root", "parent", "parents", "history"])