-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathutil.py
More file actions
154 lines (121 loc) · 5.56 KB
/
util.py
File metadata and controls
154 lines (121 loc) · 5.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
"""Module containing util."""
import argparse
import stat
import subprocess
from pathlib import Path
from typing import Any
from typing import Callable
from typing import Optional
REPO_FOLDER: Path = Path(__file__).resolve().parent.parent
MAIN_BRANCH: str = "main"
DEVELOP_BRANCH: str = "develop"
class MissingDependencyError(Exception):
"""Exception raised when a depedency is missing from the system running setup-repo."""
def __init__(self, project: Path, dependency: str):
"""Initializes MisssingDependencyError."""
message_lines: list[str] = [
f"Unable to find {dependency=}.",
f"Please ensure that {dependency} is installed before setting up the repo at {project.absolute()}",
]
message: str = "\n".join(message_lines)
super().__init__(message)
def check_dependencies(path: Path, dependencies: list[str]) -> None:
"""Checks for any passed dependencies."""
for dependency in dependencies:
try:
subprocess.check_call([dependency, "--version"], cwd=path)
except subprocess.CalledProcessError as e:
raise MissingDependencyError(path, dependency) from e
def require_clean_and_up_to_date_repo() -> None:
"""Checks if the repo is clean and up to date with any important branches."""
commands: list[list[str]] = [
["git", "fetch"],
["git", "merge-base", "--is-ancestor", MAIN_BRANCH, f"origin/{MAIN_BRANCH}"],
["git", "merge-base", "--is-ancestor", f"origin/{MAIN_BRANCH}", MAIN_BRANCH],
["git", "merge-base", "--is-ancestor", DEVELOP_BRANCH, f"origin/{DEVELOP_BRANCH}"],
["git", "merge-base", "--is-ancestor", f"origin/{DEVELOP_BRANCH}", DEVELOP_BRANCH],
["git", "merge-base", "--is-ancestor", MAIN_BRANCH, DEVELOP_BRANCH],
["git", "status", "--porcelain"],
]
for command in commands:
subprocess.run(command, cwd=REPO_FOLDER, check=True)
def existing_dir(value: str) -> Path:
"""Responsible for validating argparse inputs and returning them as pathlib Path's if they meet criteria."""
path = Path(value).expanduser().resolve()
if not path.exists():
raise argparse.ArgumentTypeError(f"{path} does not exist.")
if not path.is_dir():
raise argparse.ArgumentTypeError(f"{path} is not a directory.")
return path
def remove_readonly(func: Callable[[str], Any], path: str, _: Any) -> None:
"""Clears the readonly bit and attempts to call the provided function.
This is passed to shutil.rmtree as the onerror kwarg.
"""
Path(path).chmod(stat.S_IWRITE)
func(path)
def get_package_version() -> str:
"""Gets the package version."""
result: subprocess.CompletedProcess = subprocess.run(
["uvx", "--from", "commitizen", "cz", "version", "-p"], cwd=REPO_FOLDER, capture_output=True
)
return result.stdout.decode("utf-8").strip()
def get_bumped_package_version(increment: Optional[str] = None) -> str:
"""Gets the bumped package version."""
args: list[str] = ["uvx", "--from", "commitizen", "cz", "bump", "--get-next", "--yes", "--dry-run"]
if increment is not None:
args.extend(["--increment", increment])
result: subprocess.CompletedProcess = subprocess.run(args, cwd=REPO_FOLDER, capture_output=True)
return result.stdout.decode("utf-8").strip()
def create_release_branch(new_version: str) -> None:
"""Creates a release branch."""
commands: list[list[str]] = [
["git", "status", "--porcelain"],
["git", "checkout", "-b", f"release/{new_version}", "develop"],
]
for command in commands:
subprocess.run(command, cwd=REPO_FOLDER, capture_output=True, check=True)
def bump_version(increment: Optional[str] = None) -> None:
"""Bumps the package version."""
bump_cmd: list[str] = ["uvx", "--from", "commitizen", "cz", "bump", "--yes", "--files-only", "--changelog"]
if increment is not None:
bump_cmd.extend(["--increment", increment])
subprocess.run(bump_cmd, cwd=REPO_FOLDER, check=True)
def get_latest_tag() -> Optional[str]:
"""Gets the latest git tag."""
sort_tags: list[str] = ["git", "tag", "--sort=-creatordate"]
find_last: list[str] = ["grep", "-v", '"${GITHUB_REF_NAME}"']
echo_none: list[str] = ["echo", "''"]
result: subprocess.CompletedProcess = subprocess.run(
[*sort_tags, "|", *find_last, "|", "tail", "-n1", "||", *echo_none], cwd=REPO_FOLDER, capture_output=True
)
tag: str = result.stdout.decode("utf-8").strip()
if tag == "":
return None
return tag
def get_latest_release_notes() -> str:
"""Gets the release notes.
Assumes the latest_tag hasn't been applied yet.
"""
latest_tag: Optional[str] = get_latest_tag()
latest_version: str = get_package_version()
if latest_tag == latest_version:
raise ValueError(
"The latest tag and version are the same. Please ensure the release notes are taken before tagging."
)
rev_range: str = "" if latest_tag is None else f"{latest_tag}..{latest_version}"
command: list[str] = [
"uvx",
"--from",
"commitizen",
"cz",
"changelog",
rev_range,
"--dry-run",
"--unreleased-version",
latest_version,
]
result: subprocess.CompletedProcess = subprocess.run(command, cwd=REPO_FOLDER, capture_output=True, check=True)
return result.stdout.decode("utf-8")
def tag_release() -> None:
"""Tags the release using commitizen bump with tag only."""
subprocess.run(["uvx", "--from", "commitizen", "cz", "bump", "--tag-only", "--yes"], cwd=REPO_FOLDER, check=True)