diff --git a/swh/web/browse/snapshot_context.py b/swh/web/browse/snapshot_context.py --- a/swh/web/browse/snapshot_context.py +++ b/swh/web/browse/snapshot_context.py @@ -485,7 +485,13 @@ releases = list(reversed(releases)) - snapshot_sizes = archive.lookup_snapshot_sizes(snapshot_id) + snapshot_sizes_cache_id = f"swh_snapshot_{snapshot_id}_sizes" + snapshot_sizes = cache.get(snapshot_sizes_cache_id) + if snapshot_sizes is None: + snapshot_sizes = archive.lookup_snapshot_sizes( + snapshot_id, resolve_aliases=True + ) + cache.set(snapshot_sizes_cache_id, snapshot_sizes) is_empty = sum(snapshot_sizes.values()) == 0 diff --git a/swh/web/common/archive.py b/swh/web/common/archive.py --- a/swh/web/common/archive.py +++ b/swh/web/common/archive.py @@ -14,7 +14,7 @@ from swh.model.model import OriginVisit, Revision from swh.storage.algos import diff, revisions_walker from swh.storage.algos.origin import origin_get_latest_visit_status -from swh.storage.algos.snapshot import snapshot_get_latest +from swh.storage.algos.snapshot import snapshot_get_latest, snapshot_resolve_aliases from swh.vault.exc import NotFoundExc as VaultNotFoundExc from swh.web import config from swh.web.common import converters, query @@ -992,11 +992,15 @@ return converters.from_origin_visit({**visit_status.to_dict(), "type": visit.type}) -def lookup_snapshot_sizes(snapshot_id): +def lookup_snapshot_sizes( + snapshot_id: str, resolve_aliases: bool = False +) -> Dict[str, int]: """Count the number of branches in the snapshot with the given id Args: snapshot_id (str): sha1 identifier of the snapshot + resolve_aliases: if True, resolve branch aliases to their real targets + and update target types count Returns: dict: A dict whose keys are the target types of branches and @@ -1010,27 +1014,17 @@ snapshot_sizes["release"] = 0 # adjust revision / release count for display if aliases are defined if "alias" in snapshot_sizes: - aliases = lookup_snapshot( - snapshot_id, branches_count=snapshot_sizes["alias"], target_types=["alias"] - ) - for alias in aliases["branches"].values(): - try: - for target_type in ("revision", "release"): - snapshot = lookup_snapshot( - snapshot_id, - branches_from=alias["target"], - branches_count=1, - target_types=[target_type], - ) - if snapshot and alias["target"] in snapshot["branches"]: - snapshot_sizes[target_type] += 1 - except NotFoundExc: - # aliased revision or release is missing in the snapshot - pass - del snapshot_sizes["alias"] - # remove possible None key returned by snapshot_count_branches - # when null branches are present in the snapshot - snapshot_sizes.pop(None, None) + if resolve_aliases: + aliases = lookup_snapshot_aliases(snapshot_id, snapshot_sizes["alias"]) + for branch_info in aliases["branches"].values(): + if branch_info is not None: + snapshot_sizes[branch_info["target_type"]] += 1 + del snapshot_sizes["alias"] + else: + snapshot_sizes["alias"] = 0 + # remove possible None key returned by snapshot_count_branches + # when null branches are present in the snapshot + snapshot_sizes.pop(None, None) return snapshot_sizes @@ -1063,6 +1057,32 @@ return converters.from_partial_branches(partial_branches) +def lookup_snapshot_aliases( + snapshot_id: str, nb_aliases: Optional[int] = None +) -> Dict[str, Any]: + """ + Return snapshot branch aliases (possibly chained) resolved to their real targets. + + Args: + snapshot_id: snapshot identifier + nb_aliases: optional parameter to restrict the number of aliases to resolve + (total number of aliases will be considered otherwise) + + Returns: + A dict with the following keys: + * **id**: identifier of the snapshot + * **branches**: a dict of resolved aliased branches contained in the + snapshot whose keys are the alias names and values the real targets. + """ + snapshot_id_bin = _to_sha1_bin(snapshot_id) + partial_branches = snapshot_resolve_aliases(storage, snapshot_id_bin, nb_aliases) + if partial_branches is None: + raise NotFoundExc(f"Snapshot with id {snapshot_id} not found!") + aliases = converters.from_partial_branches(partial_branches) + aliases.pop("next_branch") + return aliases + + def lookup_latest_origin_snapshot( origin: str, allowed_statuses: List[str] = None ) -> Optional[Dict[str, Any]]: diff --git a/swh/web/tests/common/test_archive.py b/swh/web/tests/common/test_archive.py --- a/swh/web/tests/common/test_archive.py +++ b/swh/web/tests/common/test_archive.py @@ -1013,3 +1013,59 @@ origins = list(archive.lookup_origins_by_sha1s([origin_sha1, unknown_origin_sha1])) assert origins == [origin_info, None] + + +@given(snapshot()) +def test_lookup_snapshot_aliases(archive_data, snapshot): + branches = archive_data.snapshot_get(snapshot)["branches"] + + expected_aliases = { + "id": snapshot, + "branches": {}, + } + + for branch_name, branch_info in branches.items(): + if branch_info is not None and branch_info["target_type"] == "alias": + expected_aliases["branches"][branch_name] = branches[branch_info["target"]] + + assert archive.lookup_snapshot_aliases(snapshot) == expected_aliases + + +@given(snapshot()) +def test_lookup_snapshot_sizes(archive_data, snapshot): + branches = archive_data.snapshot_get(snapshot)["branches"] + + expected_sizes = { + "alias": 0, + "release": 0, + "revision": 0, + } + + for branch_name, branch_info in branches.items(): + if branch_info is not None: + expected_sizes[branch_info["target_type"]] += 1 + + assert archive.lookup_snapshot_sizes(snapshot) == expected_sizes + + +@given(snapshot()) +def test_lookup_snapshot_sizes_resolve_aliases(archive_data, snapshot): + branches = archive_data.snapshot_get(snapshot)["branches"] + + expected_sizes = { + "release": 0, + "revision": 0, + } + + for branch_name, branch_info in branches.items(): + if branch_info is None: + continue + if branch_info["target_type"] != "alias": + expected_sizes[branch_info["target_type"]] += 1 + else: + target = branches[branch_info["target"]] + expected_sizes[target["target_type"]] += 1 + + assert ( + archive.lookup_snapshot_sizes(snapshot, resolve_aliases=True) == expected_sizes + )