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

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 

8 

9 

10@dataclass 

11class UrlParts: 

12 url_start: str 

13 commit_hash: str 

14 path_html: str 

15 

16 

17class NotInsideRepositoryException(Exception): 

18 """Exception raised when a path is not inside a Git repository.""" 

19 

20 

21class PathToUrlConverter: 

22 

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) 

30 

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()) 

35 

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('/')}" 

39 

40 def path_to_url(self, path: Path, commit_id: str) -> UrlParts: 

41 """Convert a path to a URL based on the submodule configuration. 

42 

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 

61 

62 url_start = self._url_base.rstrip("/") 

63 

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) 

67 

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. 

71 

72 Example: 

73 path = "/home/<user>/git/swh/repo/domains/driving/folder1/folder2/file.cpp" 

74 submodule.path = "domains/driving" 

75 

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 

88 

89 raise KeyError(f"No submodule found for path: {path}")