Skip to content

Commit d2b60bb

Browse files
committed
Update CI files
1 parent 4d6c935 commit d2b60bb

File tree

9 files changed

+193
-151
lines changed

9 files changed

+193
-151
lines changed

.ci/scripts/check_release.py

Lines changed: 105 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import tomllib
77
import yaml
88
from pathlib import Path
9-
from tempfile import TemporaryDirectory
109
from packaging.version import Version
1110
from git import Repo
1211

@@ -25,6 +24,12 @@ def options():
2524
"'supported'. Defaults to 'supported', see `supported_release_branches` in "
2625
"`plugin_template.yml`.",
2726
)
27+
parser.add_argument(
28+
"--no-fetch",
29+
default=False,
30+
action="store_true",
31+
help="Don't fetch remote. Run faster at the expense of maybe being outdated.",
32+
)
2833
return parser.parse_args()
2934

3035

@@ -50,10 +55,15 @@ def current_version(repo, commitish):
5055

5156
def check_pyproject_dependencies(repo, from_commit, to_commit):
5257
try:
53-
old_pyproject = tomllib.load(repo.show("{from_commit}:pyproject.toml"))
58+
new_pyproject = tomllib.loads(repo.git.show(f"{to_commit}:pyproject.toml"))
59+
try:
60+
new_dependencies = set(new_pyproject["project"]["dependencies"])
61+
except KeyError:
62+
# New branch does not declare dependencies in pyproject.toml.
63+
# Assume no release needed for this reason.
64+
return []
65+
old_pyproject = tomllib.loads(repo.git.show(f"{from_commit}:pyproject.toml"))
5466
old_dependencies = set(old_pyproject["project"]["dependencies"])
55-
new_pyproject = tomllib.load(repo.show("{to_commit}:pyproject.toml"))
56-
new_dependencies = set(new_pyproject["project"]["dependencies"])
5767
if old_dependencies != new_dependencies:
5868
return ["dependencies"]
5969
else:
@@ -65,97 +75,100 @@ def check_pyproject_dependencies(repo, from_commit, to_commit):
6575

6676

6777
def main(options, template_config):
68-
with TemporaryDirectory() as d:
69-
# Clone from upstream to ensure we have updated branches & main
70-
GITHUB_ORG = template_config["github_org"]
71-
PLUGIN_NAME = template_config["plugin_name"]
72-
UPSTREAM_REMOTE = f"https://github.com/{GITHUB_ORG}/{PLUGIN_NAME}.git"
73-
DEFAULT_BRANCH = template_config["plugin_default_branch"]
74-
75-
repo = Repo.clone_from(UPSTREAM_REMOTE, d, filter="blob:none")
76-
heads = [h.split("/")[-1] for h in repo.git.ls_remote("--heads").split("\n")]
77-
available_branches = [h for h in heads if re.search(RELEASE_BRANCH_REGEX, h)]
78-
available_branches.sort(key=lambda ver: Version(ver))
79-
available_branches.append(DEFAULT_BRANCH)
80-
81-
branches = options.branches
82-
if branches == "supported":
83-
with open(f"{d}/template_config.yml", mode="r") as f:
84-
tc = yaml.safe_load(f)
85-
branches = set(tc["supported_release_branches"])
86-
latest_release_branch = tc["latest_release_branch"]
87-
if latest_release_branch is not None:
88-
branches.add(latest_release_branch)
89-
branches.add(DEFAULT_BRANCH)
90-
else:
91-
branches = set(branches.split(","))
92-
93-
if diff := branches - set(available_branches):
94-
print(f"Supplied branches contains non-existent branches! {diff}")
95-
exit(1)
96-
97-
print(f"Checking for releases on branches: {branches}")
98-
99-
releases = []
100-
for branch in branches:
101-
if branch != DEFAULT_BRANCH:
102-
# Check if a Z release is needed
103-
reasons = []
104-
changes = repo.git.ls_tree("-r", "--name-only", f"origin/{branch}", "CHANGES/")
105-
z_changelog = False
106-
for change in changes.split("\n"):
107-
# Check each changelog file to make sure everything checks out
108-
_, ext = os.path.splitext(change)
109-
if ext in Y_CHANGELOG_EXTS:
110-
print(
111-
f"Warning: A non-backported changelog ({change}) is present in the "
112-
f"{branch} release branch!"
113-
)
114-
elif ext in Z_CHANGELOG_EXTS:
115-
z_changelog = True
116-
if z_changelog:
117-
reasons.append("Backports")
118-
119-
last_tag = repo.git.describe("--tags", "--abbrev=0", f"origin/{branch}")
120-
req_txt_diff = repo.git.diff(
121-
f"{last_tag}", f"origin/{branch}", "--name-only", "--", "requirements.txt"
122-
)
123-
if req_txt_diff:
124-
reasons.append("requirements.txt")
125-
pyproject_diff = repo.git.diff(
126-
f"{last_tag}", f"origin/{branch}", "--name-only", "--", "pyproject.toml"
127-
)
128-
if pyproject_diff:
129-
reasons.extend(check_pyproject_dependencies(repo, last_tag, f"origin/{branch}"))
130-
131-
if reasons:
132-
curr_version = Version(last_tag)
133-
assert curr_version.base_version.startswith(
134-
branch
135-
), "Current-version has to belong to the current branch!"
136-
next_version = Version(f"{branch}.{curr_version.micro + 1}")
78+
DEFAULT_BRANCH = template_config["plugin_default_branch"]
79+
80+
repo = Repo()
81+
82+
upstream_default_branch = next(
83+
(branch for branch in repo.branches if branch.name == DEFAULT_BRANCH)
84+
).tracking_branch()
85+
remote = upstream_default_branch.remote_name
86+
if not options.no_fetch:
87+
repo.remote(remote).fetch()
88+
89+
# Warning: This will not work if branch names contain "/" but we don't really care here.
90+
heads = [h.split("/")[-1] for h in repo.git.branch("--remote").split("\n")]
91+
available_branches = [h for h in heads if re.search(RELEASE_BRANCH_REGEX, h)]
92+
available_branches.sort(key=lambda ver: Version(ver))
93+
available_branches.append(DEFAULT_BRANCH)
94+
95+
branches = options.branches
96+
if branches == "supported":
97+
tc = yaml.safe_load(repo.git.show(f"{upstream_default_branch}:template_config.yml"))
98+
branches = set(tc["supported_release_branches"])
99+
latest_release_branch = tc["latest_release_branch"]
100+
if latest_release_branch is not None:
101+
branches.add(latest_release_branch)
102+
branches.add(DEFAULT_BRANCH)
103+
else:
104+
branches = set(branches.split(","))
105+
106+
if diff := branches - set(available_branches):
107+
print(f"Supplied branches contains non-existent branches! {diff}")
108+
exit(1)
109+
110+
print(f"Checking for releases on branches: {branches}")
111+
112+
releases = []
113+
for branch in branches:
114+
if branch != DEFAULT_BRANCH:
115+
# Check if a Z release is needed
116+
reasons = []
117+
changes = repo.git.ls_tree("-r", "--name-only", f"{remote}/{branch}", "CHANGES/")
118+
z_changelog = False
119+
for change in changes.split("\n"):
120+
# Check each changelog file to make sure everything checks out
121+
_, ext = os.path.splitext(change)
122+
if ext in Y_CHANGELOG_EXTS:
137123
print(
138-
f"A Z-release is needed for {branch}, "
139-
f"Prev: {last_tag}, "
140-
f"Next: {next_version.base_version}, "
141-
f"Reason: {','.join(reasons)}"
124+
f"Warning: A non-backported changelog ({change}) is present in the "
125+
f"{branch} release branch!"
142126
)
127+
elif ext in Z_CHANGELOG_EXTS:
128+
z_changelog = True
129+
if z_changelog:
130+
reasons.append("Backports")
131+
132+
last_tag = repo.git.describe("--tags", "--abbrev=0", f"{remote}/{branch}")
133+
req_txt_diff = repo.git.diff(
134+
f"{last_tag}", f"{remote}/{branch}", "--name-only", "--", "requirements.txt"
135+
)
136+
if req_txt_diff:
137+
reasons.append("requirements.txt")
138+
pyproject_diff = repo.git.diff(
139+
f"{last_tag}", f"{remote}/{branch}", "--name-only", "--", "pyproject.toml"
140+
)
141+
if pyproject_diff:
142+
reasons.extend(check_pyproject_dependencies(repo, last_tag, f"{remote}/{branch}"))
143+
144+
if reasons:
145+
curr_version = Version(last_tag)
146+
assert curr_version.base_version.startswith(
147+
branch
148+
), "Current-version has to belong to the current branch!"
149+
next_version = Version(f"{branch}.{curr_version.micro + 1}")
150+
print(
151+
f"A Z-release is needed for {branch}, "
152+
f"Prev: {last_tag}, "
153+
f"Next: {next_version.base_version}, "
154+
f"Reason: {','.join(reasons)}"
155+
)
156+
releases.append(next_version)
157+
else:
158+
# Check if a Y release is needed
159+
changes = repo.git.ls_tree("-r", "--name-only", DEFAULT_BRANCH, "CHANGES/")
160+
for change in changes.split("\n"):
161+
_, ext = os.path.splitext(change)
162+
if ext in Y_CHANGELOG_EXTS:
163+
# We don't put Y release bumps in the commit message, check file instead.
164+
# The 'current_version' is always the dev of the next version to release.
165+
next_version = current_version(repo, DEFAULT_BRANCH).base_version
166+
print(f"A new Y-release is needed! New Version: {next_version}")
143167
releases.append(next_version)
144-
else:
145-
# Check if a Y release is needed
146-
changes = repo.git.ls_tree("-r", "--name-only", DEFAULT_BRANCH, "CHANGES/")
147-
for change in changes.split("\n"):
148-
_, ext = os.path.splitext(change)
149-
if ext in Y_CHANGELOG_EXTS:
150-
# We don't put Y release bumps in the commit message, check file instead.
151-
# The 'current_version' is always the dev of the next version to release.
152-
next_version = current_version(repo, DEFAULT_BRANCH).base_version
153-
print(f"A new Y-release is needed! New Version: {next_version}")
154-
releases.append(next_version)
155-
break
156-
157-
if len(releases) == 0:
158-
print("No new releases to perform.")
168+
break
169+
170+
if len(releases) == 0:
171+
print("No new releases to perform.")
159172

160173

161174
if __name__ == "__main__":

.ci/scripts/validate_commit_message.py

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,59 +5,78 @@
55
#
66
# For more info visit https://github.com/pulp/plugin_template
77

8+
import os
89
import re
10+
import subprocess
911
import sys
12+
import tomllib
1013
from pathlib import Path
11-
import subprocess
12-
import os
13-
import warnings
14+
1415
from github import Github
1516

16-
CHANGELOG_EXTS = [".feature", ".bugfix", ".doc", ".removal", ".misc", ".deprecation"]
17+
with open("pyproject.toml", "rb") as fp:
18+
PYPROJECT_TOML = tomllib.load(fp)
1719
KEYWORDS = ["fixes", "closes"]
20+
BLOCKING_REGEX = [
21+
r"^DRAFT",
22+
r"^WIP",
23+
r"^NOMERGE",
24+
r"^DO\s*NOT\s*MERGE",
25+
r"^EXPERIMENT",
26+
r"^FIXUP",
27+
r"Apply suggestions from code review",
28+
]
29+
try:
30+
CHANGELOG_EXTS = [
31+
f".{item['directory']}" for item in PYPROJECT_TOML["tool"]["towncrier"]["type"]
32+
]
33+
except KeyError:
34+
CHANGELOG_EXTS = [".feature", ".bugfix", ".doc", ".removal", ".misc"]
35+
NOISSUE_MARKER = "[noissue]"
1836

1937
sha = sys.argv[1]
2038
message = subprocess.check_output(["git", "log", "--format=%B", "-n 1", sha]).decode("utf-8")
2139

40+
if NOISSUE_MARKER in message:
41+
sys.exit(f"Do not add '{NOISSUE_MARKER}' in the commit message.")
42+
43+
if any((re.match(pattern, message, re.IGNORECASE) for pattern in BLOCKING_REGEX)):
44+
sys.exit("This PR is not ready for consumption.")
45+
2246
g = Github(os.environ.get("GITHUB_TOKEN"))
2347
repo = g.get_repo("pulp/pulp_python")
2448

2549

26-
def __check_status(issue):
50+
def check_status(issue):
2751
gi = repo.get_issue(int(issue))
2852
if gi.pull_request:
2953
sys.exit(f"Error: issue #{issue} is a pull request.")
30-
if gi.closed_at and "cherry picked from commit" not in message:
31-
warnings.warn(
32-
"When backporting, use the -x flag to append a line that says "
33-
"'(cherry picked from commit ...)' to the original commit message."
34-
)
54+
if gi.closed_at:
3555
sys.exit(f"Error: issue #{issue} is closed.")
3656

3757

38-
def __check_changelog(issue):
58+
def check_changelog(issue):
3959
matches = list(Path("CHANGES").rglob(f"{issue}.*"))
4060

4161
if len(matches) < 1:
4262
sys.exit(f"Could not find changelog entry in CHANGES/ for {issue}.")
4363
for match in matches:
4464
if match.suffix not in CHANGELOG_EXTS:
4565
sys.exit(f"Invalid extension for changelog entry '{match}'.")
46-
if match.suffix == ".feature" and "cherry picked from commit" in message:
47-
sys.exit(f"Can not backport '{match}' as it is a feature.")
4866

4967

5068
print("Checking commit message for {sha}.".format(sha=sha[0:7]))
5169

5270
# validate the issue attached to the commit
53-
regex = r"(?:{keywords})[\s:]+#(\d+)".format(keywords=("|").join(KEYWORDS))
54-
pattern = re.compile(regex, re.IGNORECASE)
55-
56-
issues = pattern.findall(message)
71+
issue_regex = r"(?:{keywords})[\s:]+#(\d+)".format(keywords=("|").join(KEYWORDS))
72+
issues = re.findall(issue_regex, message, re.IGNORECASE)
73+
cherry_pick_regex = r"^\s*\(cherry picked from commit [0-9a-f]*\)\s*$"
74+
cherry_pick = re.search(cherry_pick_regex, message, re.MULTILINE)
5775

5876
if issues:
59-
for issue in pattern.findall(message):
60-
__check_status(issue)
61-
__check_changelog(issue)
77+
for issue in issues:
78+
if not cherry_pick:
79+
check_status(issue)
80+
check_changelog(issue)
6281

6382
print("Commit message for {sha} passed.".format(sha=sha[0:7]))

.github/template_gitref

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2021.08.26-406-g5f397e3
1+
2021.08.26-417-gcec8e48

.github/workflows/pr_checks.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@
99
name: "Python PR static checks"
1010
on:
1111
pull_request_target:
12-
types: ["opened", "synchronize", "reopened"]
12+
types:
13+
- "opened"
14+
- "synchronize"
15+
- "reopened"
16+
branches:
17+
- "main"
18+
- "[0-9]+.[0-9]+"
1319

1420
# This workflow runs with elevated permissions.
1521
# Do not even think about running a single bit of code from the PR.

.github/workflows/scripts/check_commit.sh

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,4 @@ set -euv
1515
for SHA in $(curl -H "Authorization: token $GITHUB_TOKEN" "$GITHUB_CONTEXT" | jq -r '.[].sha')
1616
do
1717
python3 .ci/scripts/validate_commit_message.py "$SHA"
18-
VALUE=$?
19-
if [ "$VALUE" -gt 0 ]; then
20-
exit $VALUE
21-
fi
2218
done

.github/workflows/scripts/publish_client_gem.sh

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,15 @@ cd "$(dirname "$(realpath -e "$0")")"/../../..
1414

1515
VERSION="$1"
1616

17-
if [[ -z "$VERSION" ]]; then
17+
if [[ -z "${VERSION}" ]]
18+
then
1819
echo "No version specified."
1920
exit 1
2021
fi
2122

22-
RESPONSE="$(curl --write-out '%{http_code}' --silent --output /dev/null "https://rubygems.org/gems/pulp_python_client/versions/$VERSION")"
23-
24-
if [ "$RESPONSE" == "200" ];
25-
then
26-
echo "pulp_python client $VERSION has already been released. Skipping."
27-
exit
28-
fi
29-
3023
mkdir -p ~/.gem
3124
touch ~/.gem/credentials
3225
echo "---
33-
:rubygems_api_key: $RUBYGEMS_API_KEY" > ~/.gem/credentials
26+
:rubygems_api_key: ${RUBYGEMS_API_KEY}" > ~/.gem/credentials
3427
sudo chmod 600 ~/.gem/credentials
3528
gem push "pulp_python_client-${VERSION}.gem"

0 commit comments

Comments
 (0)