Coverage for lobster/tools/core/online_report/path_to_url_converter.py: 36%
51 statements
« prev ^ index » next coverage.py v7.10.5, created at 2025-08-27 13:02 +0000
« prev ^ index » next coverage.py v7.10.5, created at 2025-08-27 13:02 +0000
1from dataclasses import dataclass
2import logging
3import os
4from pathlib import Path
5from typing import Tuple
6from urllib.parse import quote
7from git import Repo, Submodule
10@dataclass
11class UrlParts:
12 url_start: str
13 commit_hash: str
14 path_html: str
17class NotInsideRepositoryException(Exception):
18 """Exception raised when a path is not inside a Git repository."""
21class PathToUrlConverter:
23 def __init__(self, repo_path, url_base: str):
24 self._logger = logging.getLogger(self.__class__.__name__)
25 self._repo = Repo(repo_path)
26 self._url_base = url_base
27 for submodule in self._repo.submodules:
28 self._logger.info("Name: %s, Path: %s, URL: %s",
29 submodule.name, submodule.path, submodule.url)
31 @staticmethod
32 def _path_to_html_format(path: Path) -> str:
33 """Convert a file path to a URL format suitable for HTML links."""
34 return quote(path.as_posix())
36 def _get_submodule_full_url(self, submodule_url: str) -> str:
37 """Get the full URL for a submodule."""
38 return f"{self._url_base.rstrip('/')}/{submodule_url.lstrip('/')}"
40 def path_to_url(self, path: Path, commit_id: str) -> UrlParts:
41 """Convert a path to a URL based on the submodule configuration.
43 The path must be nested inside a submodule of the repository.
44 """
45 path = path.resolve()
46 try:
47 submodule, relative_path = self._get_submodule_and_relative_path(path)
48 url_start = self._get_submodule_full_url(submodule.url)
49 commit_hash = submodule.hexsha
50 except KeyError as e:
51 self._logger.error("Path '%s' is not inside a submodule: %s", path, e)
52 # Path is not inside a submodule — use main repo
53 commit_hash = commit_id
54 try:
55 relative_path = path.resolve().relative_to(self._repo.working_tree_dir)
56 except ValueError as value_error:
57 raise NotInsideRepositoryException(
58 f"Path '{path}' is not inside the repository '"
59 f"{self._repo.working_tree_dir}'!",
60 ) from value_error
62 url_start = self._url_base.rstrip("/")
64 path_html = self._path_to_html_format(relative_path)
65 return UrlParts(url_start=url_start, commit_hash=commit_hash,
66 path_html=path_html)
68 def _get_submodule_and_relative_path(self, path: Path) -> Tuple[Submodule, Path]:
69 """Get the submodule and the relative path (relative to the submodule folder,
70 not to repo root) for a given path.
72 Example:
73 path = "/home/<user>/git/swh/repo/domains/driving/folder1/folder2/file.cpp"
74 submodule.path = "domains/driving"
76 return Submodule instance of "domains/driving", Path of "folder1/folder2/
77 file.cpp"
78 """
79 path = path.resolve()
80 for submodule in self._repo.submodules:
81 submodule_path = (Path(str(self._repo.working_tree_dir)) /
82 str(submodule.path).replace("/", os.sep))
83 try:
84 rel_path = path.relative_to(submodule_path)
85 return submodule, rel_path
86 except ValueError:
87 continue
89 raise KeyError(f"No submodule found for path: {path}")