Coverage for tsfpga/svn_utils.py: 76%
55 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-21 20:51 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-21 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) -> 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 """
30 check_that_svn_commands_are_available(cwd=cwd)
32 result = f"r{get_svn_revision(cwd=cwd)}"
34 if svn_local_changes_are_present(cwd=cwd):
35 result += " (local changes present)"
37 return result
40def svn_commands_are_available(cwd: Path | None = None) -> bool:
41 """
42 True if "svn" command executable is available and ``cwd`` is in a valid SVN repo.
44 Arguments:
45 cwd: The directory where SVN commands will be run.
46 """
47 try:
48 get_svn_revision(cwd=cwd)
49 except (subprocess.CalledProcessError, FileNotFoundError):
50 return False
51 return True
54def check_that_svn_commands_are_available(cwd: Path | None = None) -> None:
55 """
56 Raise an exception unless "svn" command executable is available and ``cwd`` is in a valid
57 SVN repo.
59 Arguments:
60 cwd: The directory where SVN commands will be run.
61 """
62 if not svn_commands_are_available(cwd):
63 message = (
64 "Could not run SVN. Is the command available on PATH? Is the script called from a repo?"
65 )
66 raise RuntimeError(message)
69def get_svn_revision(cwd: Path | None = None) -> int:
70 """
71 Get the current SVN revision number.
73 Arguments:
74 cwd: The directory where SVN commands will be run.
75 """
76 command = ["svn", "info", "--show-item", "revision"]
77 stdout: str = run_command(cmd=command, cwd=cwd, capture_output=True).stdout
79 # Remove trailing newline
80 return int(stdout.strip())
83def svn_local_changes_are_present(cwd: Path | None = None) -> bool:
84 """
85 Return true if the repo contains changes that have been made after the last commit.
86 Info from here: https://rubyinrails.com/2014/01/11/svn-command-to-check-modified-files/
88 Arguments:
89 cwd: The directory where SVN commands will be run.
90 """
91 command = ["svn", "status"]
92 stdout: str = run_command(cmd=command, cwd=cwd, capture_output=True).stdout
94 # Status code for file Added, Deleted, Modified, in Conflict or missing
95 regexp = re.compile(r"\n[ADMC!] ")
96 return regexp.search(stdout) is not None
99RE_SVN_STATUS_LINE = re.compile(r"^.+\d+\s+\d+\s+\S+\s+(\S+)$")
102def find_svn_files(
103 directory: Path,
104 excludes: list[Path] | None = None,
105 file_endings_include: str | tuple[str, ...] | None = None,
106 file_endings_avoid: str | tuple[str, ...] | None = None,
107) -> Iterable[Path]:
108 """
109 Find files that are checked in to SVN. It runs "svn status" rather than "svn ls". This means
110 that it is a local operation, that does not require credentials or any connection with
111 an SVN server.
113 Arguments:
114 directory: Search in this directory.
115 excludes: These files and folders will not be included.
116 file_endings_include: Only files with these endings will be included.
117 file_endings_avoid: Files with these endings will not be included.
119 Return:
120 The files that are available in SVN.
121 """
122 excludes = [] if excludes is None else [exclude.resolve() for exclude in excludes]
124 command = ["svn", "status", "-v"]
125 stdout = run_command(cmd=command, cwd=directory, capture_output=True).stdout
127 for line in stdout.split("\n"):
128 match = RE_SVN_STATUS_LINE.match(line)
129 if not match:
130 continue
132 svn_file = match.group(1)
133 file_path = directory / svn_file
135 if not file_path.exists():
136 raise FileNotFoundError(f"Error when concatenating relative paths: {file_path}")
138 if file_path.is_dir():
139 continue
141 if file_endings_include is not None and not file_path.name.endswith(file_endings_include):
142 continue
144 if file_endings_avoid is not None and file_path.name.endswith(file_endings_avoid):
145 continue
147 if file_is_in_directory(file_path, excludes):
148 continue
150 yield file_path