Coverage for src / rhiza / models / _git / helpers.py: 100%

24 statements  

« prev     ^ index     » next       coverage.py v7.14.0, created at 2026-07-16 12:51 +0000

1"""Module-level git and text helpers shared across the git engine.""" 

2 

3import shutil 

4from typing import Any 

5 

6from loguru import logger 

7 

8 

9def _normalize_to_list(value: Any | list[Any] | None) -> list[Any]: 

10 r"""Convert a value to a list of strings. 

11 

12 Handles the case where YAML multi-line strings (using |) are parsed as 

13 a single string instead of a list. Splits the string by newlines and 

14 strips whitespace from each item. 

15 

16 Args: 

17 value: A string, list of strings, or None. 

18 

19 Returns: 

20 A list of strings. Empty list if value is None or empty. 

21 

22 Examples: 

23 >>> _normalize_to_list(None) 

24 [] 

25 >>> _normalize_to_list([]) 

26 [] 

27 >>> _normalize_to_list(['a', 'b', 'c']) 

28 ['a', 'b', 'c'] 

29 >>> _normalize_to_list('single line') 

30 ['single line'] 

31 >>> _normalize_to_list('line1\\n' + 'line2\\n' + 'line3') 

32 ['line1', 'line2', 'line3'] 

33 >>> _normalize_to_list(' item1 \\n' + ' item2 ') 

34 ['item1', 'item2'] 

35 """ 

36 if value is None: 

37 return [] 

38 if isinstance(value, list): 

39 return value 

40 if isinstance(value, str): 

41 # Split by newlines and filter out empty strings 

42 # Handle both actual newlines (\n) and literal backslash-n (\\n) 

43 items = value.split("\\n") if "\\n" in value and "\n" not in value else value.split("\n") 

44 return [item.strip() for item in items if item.strip()] 

45 return [] 

46 

47 

48def get_git_executable() -> str: 

49 """Get the absolute path to the git executable. 

50 

51 This function ensures we use the full path to git to prevent 

52 security issues related to PATH manipulation. 

53 

54 Returns: 

55 str: Absolute path to the git executable. 

56 

57 Raises: 

58 RuntimeError: If git executable is not found in PATH. 

59 """ 

60 git_path = shutil.which("git") 

61 if git_path is None: 

62 msg = "git executable not found in PATH. Please ensure git is installed and available." 

63 raise RuntimeError(msg) 

64 return git_path 

65 

66 

67def _log_git_stderr_errors(stderr: str | None) -> None: 

68 """Extract and log only relevant error messages from git stderr. 

69 

70 Args: 

71 stderr: Git command stderr output. 

72 """ 

73 if stderr: 

74 for line in stderr.strip().split("\n"): 

75 line = line.strip() 

76 if line and (line.startswith(("fatal:", "error:"))): 

77 logger.error(line)