Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion scripts/bump_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import shutil
import subprocess
import sys
import urllib.parse
from datetime import datetime
from pathlib import Path

Expand Down Expand Up @@ -153,12 +154,36 @@ def git_commit(version: str, files: list[str] | None = None, cwd: Path | None =
print(f"✅ Git commit on {release_branch(version)}: chore: bump version to {version}")


def _github_repo(url: str) -> str | None:
"""owner/name when the remote URL points at github.com, otherwise None.

The host and the path are read apart and compared whole: a substring test lets
any host that mentions github.com in its path or its own name through (CodeQL
py/incomplete-url-substring-sanitization, alert #60).
"""
url = url.strip()
if "://" in url:
parts = urllib.parse.urlsplit(url)
host, path = parts.hostname or "", parts.path
else:
# scp-like syntax: [user@]host:path
scp = re.fullmatch(r"(?:[^@/:]+@)?([^@/:]+):(.+)", url)
if not scp:
return None
host, path = scp.group(1).lower(), scp.group(2)
if host != "github.com":
return None
path = path.strip("/")
return path.removesuffix(".git") or None


def _origin_is_github(cwd: Path | None) -> bool:
url = subprocess.run(
["git", "remote", "get-url", "origin"], capture_output=True, text=True, encoding="utf-8", check=True,
cwd=cwd,
).stdout
return "github.com" in url and RELEASE_REPO in url
repo = _github_repo(url)
return repo is not None and repo.lower() == RELEASE_REPO.lower()


def git_push(version: str, cwd: Path | None = None) -> None:
Expand Down
44 changes: 44 additions & 0 deletions tests/test_release_through_pr.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,47 @@ def test_the_bump_is_committed_on_a_release_branch_and_main_is_left_alone(repo):
assert _git(remote, "log", "-1", "--format=%s", "release/2026.47") == "chore: bump version to 2026.47"
assert _git(remote, "log", "-1", "--format=%s", "main") == "initial", "main was pushed"
assert _git(work, "rev-parse", "--abbrev-ref", "HEAD") == "main"


# --- who opens the pull request ----------------------------------------------------
#
# The pull request is opened with gh, with auto-merge, only when origin is this
# repository on GitHub. CodeQL alert #60 (py/incomplete-url-substring-sanitization)
# was right about the first version: it asked whether "github.com" and the repository
# name occurred anywhere in the URL, which a host of anyone's choosing satisfies.
# The URLs below are set on a real clone with `git remote set-url`; git does not
# contact them to answer `git remote get-url`.


@pytest.mark.parametrize("url", [
"https://github.com/maksimtech/patchradar.git",
"https://github.com/maksimtech/patchradar",
"https://GitHub.com/maksimtech/patchradar.git",
"git@github.com:maksimtech/patchradar.git",
"ssh://git@github.com/maksimtech/patchradar.git",
])
def test_origin_on_github_is_recognised(repo, url):
work, _ = repo
_git(work, "remote", "set-url", "origin", url)
assert bump._origin_is_github(work)


@pytest.mark.parametrize("url", [
"https://evil.example/github.com/maksimtech/patchradar.git",
"https://github.com.evil.example/maksimtech/patchradar.git",
"https://evil.example/?github.com/maksimtech/patchradar",
"git@evil.example:github.com/maksimtech/patchradar.git",
"https://github.com/maksimtech/patchradar-fork.git",
"https://github.com/someone/maksimtech/patchradar.git",
"https://github.com/someone-else/patchradar.git",
])
def test_origin_that_only_mentions_github_is_not_trusted(repo, url):
work, _ = repo
_git(work, "remote", "set-url", "origin", url)
assert not bump._origin_is_github(work)


def test_a_local_origin_is_not_github(repo):
work, remote = repo
assert _git(work, "remote", "get-url", "origin") == str(remote)
assert not bump._origin_is_github(work)
Loading