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
« 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# --------------------------------------------------------------------------------------------------
9# Standard libraries
10import re
11import subprocess
12from pathlib import Path
13from typing import Iterable, Optional, Union
15# Local folder libraries
16from .system_utils import file_is_in_directory, run_command
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)"``.
24 Arguments:
25 cwd: The directory where SVN commands will be run.
26 """
27 check_that_svn_commands_are_available(cwd=cwd)
29 result = f"r{get_svn_revision(cwd=cwd)}"
31 if svn_local_changes_are_present(cwd=cwd):
32 result += " (local changes present)"
34 return result
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.
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
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.
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)
66def get_svn_revision(cwd: Optional[Path] = None) -> int:
67 """
68 Get the current SVN revision number.
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
76 # Remove trailing newline
77 return int(stdout.strip())
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/
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
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
96RE_SVN_STATUS_LINE = re.compile(r"^.+\d+\s+\d+\s+\S+\s+(\S+)$")
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.
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.
116 Return:
117 The files that are available in SVN.
118 """
119 excludes = [] if excludes is None else [exclude.resolve() for exclude in excludes]
121 command = ["svn", "status", "-v"]
122 stdout = run_command(cmd=command, cwd=directory, capture_output=True).stdout
124 for line in stdout.split("\n"):
125 match = RE_SVN_STATUS_LINE.match(line)
126 if not match:
127 continue
129 svn_file = match.group(1)
130 file_path = directory / svn_file
132 # Make sure concatenation of relative paths worked
133 assert file_path.exists(), file_path
135 if file_path.is_dir():
136 continue
138 if file_endings_include is not None and not file_path.name.endswith(file_endings_include):
139 continue
141 if file_endings_avoid is not None and file_path.name.endswith(file_endings_avoid):
142 continue
144 if file_is_in_directory(file_path, excludes):
145 continue
147 yield file_path