Coverage for tsfpga/svn_utils.py: 74%
57 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-01 20:51 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-01 20:51 +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# --------------------------------------------------------------------------------------------------
9from __future__ import annotations
11import re
12import subprocess
13from typing import TYPE_CHECKING
15from .system_utils import file_is_in_directory, run_command
17if TYPE_CHECKING:
18 from collections.abc import Iterable
19 from pathlib import Path
22def get_svn_revision_information(cwd: Path | None = None, use_rst_annotation: bool = False) -> str:
23 """
24 Get a string describing the current SVN commit.
25 E.g. ``"r1234"`` or ``"r1234 (local changes present)"``.
27 Arguments:
28 cwd: The directory where SVN commands will be run.
29 use_rst_annotation: Use reStructuredText literal annotation for the revision value.
30 """
31 check_that_svn_commands_are_available(cwd=cwd)
33 annotation = "``" if use_rst_annotation else ""
34 revision = f"r{get_svn_revision(cwd=cwd)}"
35 result = f"{annotation}{revision}{annotation}"
37 if svn_local_changes_are_present(cwd=cwd):
38 result += " (local changes present)"
40 return result
43def svn_commands_are_available(cwd: Path | None = None) -> bool:
44 """
45 True if "svn" command executable is available and ``cwd`` is in a valid SVN repo.
47 Arguments:
48 cwd: The directory where SVN commands will be run.
49 """
50 try:
51 get_svn_revision(cwd=cwd)
52 except (subprocess.CalledProcessError, FileNotFoundError):
53 return False
54 return True
57def check_that_svn_commands_are_available(cwd: Path | None = None) -> None:
58 """
59 Raise an exception unless "svn" command executable is available and ``cwd`` is in a valid
60 SVN repo.
62 Arguments:
63 cwd: The directory where SVN commands will be run.
64 """
65 if not svn_commands_are_available(cwd):
66 message = (
67 "Could not run SVN. Is the command available on PATH? Is the script called from a repo?"
68 )
69 raise RuntimeError(message)
72def get_svn_revision(cwd: Path | None = None) -> int:
73 """
74 Get the current SVN revision number.
76 Arguments:
77 cwd: The directory where SVN commands will be run.
78 """
79 command = ["svn", "info", "--show-item", "revision"]
80 stdout: str = run_command(cmd=command, cwd=cwd, capture_output=True).stdout
82 # Remove trailing newline
83 return int(stdout.strip())
86def svn_local_changes_are_present(cwd: Path | None = None) -> bool:
87 """
88 Return true if the repo contains changes that have been made after the last commit.
89 Info from here: https://rubyinrails.com/2014/01/11/svn-command-to-check-modified-files/
91 Arguments:
92 cwd: The directory where SVN commands will be run.
93 """
94 command = ["svn", "status"]
95 stdout: str = run_command(cmd=command, cwd=cwd, capture_output=True).stdout
97 # Status code for file Added, Deleted, Modified, in Conflict or missing
98 regexp = re.compile(r"\n[ADMC!] ")
99 return regexp.search(stdout) is not None
102RE_SVN_STATUS_LINE = re.compile(r"^.+\d+\s+\d+\s+\S+\s+(\S+)$")
105def find_svn_files(
106 directory: Path,
107 excludes: list[Path] | None = None,
108 file_endings_include: str | tuple[str, ...] | None = None,
109 file_endings_avoid: str | tuple[str, ...] | None = None,
110) -> Iterable[Path]:
111 """
112 Find files that are checked in to SVN. It runs "svn status" rather than "svn ls". This means
113 that it is a local operation, that does not require credentials or any connection with
114 an SVN server.
116 Arguments:
117 directory: Search in this directory.
118 excludes: These files and folders will not be included.
119 file_endings_include: Only files with these endings will be included.
120 file_endings_avoid: Files with these endings will not be included.
122 Return:
123 The files that are available in SVN.
124 """
125 excludes = [] if excludes is None else [exclude.resolve() for exclude in excludes]
127 command = ["svn", "status", "-v"]
128 stdout = run_command(cmd=command, cwd=directory, capture_output=True).stdout
130 for line in stdout.split("\n"):
131 match = RE_SVN_STATUS_LINE.match(line)
132 if not match:
133 continue
135 svn_file = match.group(1)
136 file_path = directory / svn_file
138 if not file_path.exists():
139 raise FileNotFoundError(f"Error when concatenating relative paths: {file_path}")
141 if file_path.is_dir():
142 continue
144 if file_endings_include is not None and not file_path.name.endswith(file_endings_include):
145 continue
147 if file_endings_avoid is not None and file_path.name.endswith(file_endings_avoid):
148 continue
150 if file_is_in_directory(file_path, excludes):
151 continue
153 yield file_path