Coverage for tsfpga/svn_utils.py: 80%

51 statements  

« prev     ^ index     » next       coverage.py v7.6.9, created at 2024-12-20 20:52 +0000

1# -------------------------------------------------------------------------------------------------- 

2# Copyright (c) Lukas Vik. All rights reserved. 

3# 

4# This file is part of the tsfpga project, a project platform for modern FPGA development. 

5# https://tsfpga.com 

6# https://github.com/tsfpga/tsfpga 

7# -------------------------------------------------------------------------------------------------- 

8 

9# Standard libraries 

10import re 

11import subprocess 

12from pathlib import Path 

13from typing import Iterable, Optional, Union 

14 

15# Local folder libraries 

16from .system_utils import file_is_in_directory, run_command 

17 

18 

19def get_svn_revision_information(cwd: Optional[Path] = None) -> str: 

20 """ 

21 Get a string describing the current SVN commit. 

22 E.g. ``"r1234"`` or ``"r1234 (local changes present)"``. 

23 

24 Arguments: 

25 cwd: The directory where SVN commands will be run. 

26 """ 

27 check_that_svn_commands_are_available(cwd=cwd) 

28 

29 result = f"r{get_svn_revision(cwd=cwd)}" 

30 

31 if svn_local_changes_are_present(cwd=cwd): 

32 result += " (local changes present)" 

33 

34 return result 

35 

36 

37def svn_commands_are_available(cwd: Optional[Path] = None) -> bool: 

38 """ 

39 True if "svn" command executable is available and ``cwd`` is in a valid SVN repo. 

40 

41 Arguments: 

42 cwd: The directory where SVN commands will be run. 

43 """ 

44 try: 

45 get_svn_revision(cwd=cwd) 

46 except (subprocess.CalledProcessError, FileNotFoundError): 

47 return False 

48 return True 

49 

50 

51def check_that_svn_commands_are_available(cwd: Optional[Path] = None) -> None: 

52 """ 

53 Raise an exception unless "svn" command executable is available and ``cwd`` is in a valid 

54 SVN repo. 

55 

56 Arguments: 

57 cwd: The directory where SVN commands will be run. 

58 """ 

59 if not svn_commands_are_available(cwd): 

60 message = ( 

61 "Could not run SVN. Is the command available on PATH? Is the script called from a repo?" 

62 ) 

63 raise RuntimeError(message) 

64 

65 

66def get_svn_revision(cwd: Optional[Path] = None) -> int: 

67 """ 

68 Get the current SVN revision number. 

69 

70 Arguments: 

71 cwd: The directory where SVN commands will be run. 

72 """ 

73 command = ["svn", "info", "--show-item", "revision"] 

74 stdout: str = run_command(cmd=command, cwd=cwd, capture_output=True).stdout 

75 

76 # Remove trailing newline 

77 return int(stdout.strip()) 

78 

79 

80def svn_local_changes_are_present(cwd: Optional[Path] = None) -> bool: 

81 """ 

82 Return true if the repo contains changes that have been made after the last commit. 

83 Info from here: https://rubyinrails.com/2014/01/11/svn-command-to-check-modified-files/ 

84 

85 Arguments: 

86 cwd: The directory where SVN commands will be run. 

87 """ 

88 command = ["svn", "status"] 

89 stdout: str = run_command(cmd=command, cwd=cwd, capture_output=True).stdout 

90 

91 # Status code for file Added, Deleted, Modified, in Conflict or missing 

92 regexp = re.compile(r"\n[ADMC!] ") 

93 return regexp.search(stdout) is not None 

94 

95 

96RE_SVN_STATUS_LINE = re.compile(r"^.+\d+\s+\d+\s+\S+\s+(\S+)$") 

97 

98 

99def find_svn_files( 

100 directory: Path, 

101 excludes: Optional[list[Path]] = None, 

102 file_endings_include: Optional[Union[str, tuple[str, ...]]] = None, 

103 file_endings_avoid: Optional[Union[str, tuple[str, ...]]] = None, 

104) -> Iterable[Path]: 

105 """ 

106 Find files that are checked in to SVN. It runs "svn status" rather than "svn ls". This means 

107 that it is a local operation, that does not require credentials or any connection with 

108 an SVN server. 

109 

110 Arguments: 

111 directory: Search in this directory. 

112 excludes: These files and folders will not be included. 

113 file_endings_include: Only files with these endings will be included. 

114 file_endings_avoid: Files with these endings will not be included. 

115 

116 Return: 

117 The files that are available in SVN. 

118 """ 

119 excludes = [] if excludes is None else [exclude.resolve() for exclude in excludes] 

120 

121 command = ["svn", "status", "-v"] 

122 stdout = run_command(cmd=command, cwd=directory, capture_output=True).stdout 

123 

124 for line in stdout.split("\n"): 

125 match = RE_SVN_STATUS_LINE.match(line) 

126 if not match: 

127 continue 

128 

129 svn_file = match.group(1) 

130 file_path = directory / svn_file 

131 

132 # Make sure concatenation of relative paths worked 

133 assert file_path.exists(), file_path 

134 

135 if file_path.is_dir(): 

136 continue 

137 

138 if file_endings_include is not None and not file_path.name.endswith(file_endings_include): 

139 continue 

140 

141 if file_endings_avoid is not None and file_path.name.endswith(file_endings_avoid): 

142 continue 

143 

144 if file_is_in_directory(file_path, excludes): 

145 continue 

146 

147 yield file_path