mirror of
https://github.com/LostArtefacts/TR2X.git
synced 2024-11-23 13:59:45 +00:00
166 lines
4.8 KiB
Python
Executable File
166 lines
4.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
import argparse
|
||
import re
|
||
import sys
|
||
from datetime import datetime
|
||
from pathlib import Path
|
||
from subprocess import check_output, run
|
||
|
||
from shared.common import REPO_DIR
|
||
from shared.versioning import get_branch_version
|
||
|
||
HEADER = "## [Unreleased](https://github.com/LostArtefacts/TR2X/compare/stable...develop) - ××××-××-××"
|
||
CHANGELOG_PATH = REPO_DIR / "CHANGELOG.md"
|
||
|
||
|
||
def update_changelog(
|
||
changelog: str, old_version: str, new_version: str
|
||
) -> str:
|
||
if f"[{new_version}]" in changelog:
|
||
return changelog
|
||
changelog = re.sub("Unreleased", new_version, changelog, count=1)
|
||
changelog = re.sub("stable", old_version, changelog, count=1)
|
||
changelog = re.sub("develop", new_version, changelog, count=1)
|
||
changelog = re.sub(
|
||
"××××-××-××", datetime.now().strftime("%Y-%m-%d"), changelog
|
||
)
|
||
changelog = HEADER + "\n\n" + changelog
|
||
return changelog
|
||
|
||
|
||
class Git:
|
||
def checkout_branch(self, branch_name: str) -> None:
|
||
if check_output(["git", "diff", "--cached", "--name-only"]):
|
||
raise RuntimeError("Staged files")
|
||
check_output(["git", "checkout", branch_name])
|
||
|
||
def reset(self, target: str, hard: bool = False) -> None:
|
||
check_output(
|
||
["git", "reset", "develop", *(["--hard"] if hard else [])]
|
||
)
|
||
|
||
def delete_tag(self, tag_name: str) -> None:
|
||
run(["git", "tag", "-d", tag_name])
|
||
|
||
def create_tag(self, tag_name: str) -> None:
|
||
check_output(["git", "tag", tag_name])
|
||
|
||
def add(self, target: str) -> None:
|
||
check_output(["git", "add", target])
|
||
|
||
def commit(self, message: str) -> None:
|
||
check_output(["git", "commit", "-m", message])
|
||
|
||
def push(
|
||
self, upstream: str, targets: list[str], force: bool = False
|
||
) -> None:
|
||
check_output(
|
||
[
|
||
"git",
|
||
"push",
|
||
upstream,
|
||
*targets,
|
||
*(["--force-with-lease"] if force else []),
|
||
]
|
||
)
|
||
|
||
|
||
class BaseCommand:
|
||
name: str = NotImplemented
|
||
help: str = NotImplemented
|
||
|
||
def __init__(self, git: Git) -> None:
|
||
self.git = git
|
||
|
||
def decorate_parser(self, parser: argparse.ArgumentParser) -> None:
|
||
parser.add_argument("version")
|
||
|
||
def run(self, args: argparse.Namespace) -> None:
|
||
raise NotImplementedError("not implemented")
|
||
|
||
|
||
class CommitCommand(BaseCommand):
|
||
name = "commit"
|
||
help = "Create and tag a commit with the release information"
|
||
|
||
def decorate_parser(self, parser: argparse.ArgumentParser) -> None:
|
||
super().decorate_parser(parser)
|
||
parser.add_argument(
|
||
"-d",
|
||
"--dry-run",
|
||
action='store_true',
|
||
help="only output the changelog to stdout, do not commit anything",
|
||
)
|
||
|
||
def run(self, args: argparse.Namespace) -> None:
|
||
self.git.checkout_branch("develop")
|
||
old_version = get_branch_version("origin/stable")
|
||
new_version = args.version
|
||
|
||
old_changelog = CHANGELOG_PATH.read_text()
|
||
new_changelog = update_changelog(
|
||
old_changelog, old_version, args.version
|
||
)
|
||
if old_changelog == new_changelog:
|
||
return
|
||
|
||
if args.dry_run:
|
||
print(new_changelog)
|
||
return
|
||
|
||
CHANGELOG_PATH.write_text(new_changelog)
|
||
self.git.add(CHANGELOG_PATH)
|
||
self.git.commit(f"docs: release {args.version}")
|
||
self.git.delete_tag(args.version)
|
||
self.git.create_tag(args.version)
|
||
|
||
|
||
class BranchCommand(BaseCommand):
|
||
name = "branch"
|
||
help = "Merge branch to the specified tag"
|
||
|
||
def run(self, args: argparse.Namespace) -> None:
|
||
self.git.checkout_branch("stable")
|
||
self.git.reset(args.version, hard=True)
|
||
self.git.checkout_branch("develop")
|
||
|
||
|
||
class PushCommand(BaseCommand):
|
||
name = "push"
|
||
help = (
|
||
"Push the develop and stable branches, and the version tag to GitHub"
|
||
)
|
||
|
||
def run(self, args) -> None:
|
||
self.git.push(
|
||
"origin", ["develop", "stable", args.version], force=True
|
||
)
|
||
|
||
|
||
def parse_args(commands: list[BaseCommand]) -> None:
|
||
parser = argparse.ArgumentParser(
|
||
description="Argument parser with subcommands"
|
||
)
|
||
subparsers = parser.add_subparsers(title="subcommands", dest="subcommand")
|
||
for command in commands:
|
||
subparser = subparsers.add_parser(command.name, help=command.help)
|
||
command.decorate_parser(subparser)
|
||
subparser.set_defaults(command=command)
|
||
|
||
result = parser.parse_args()
|
||
if not hasattr(result, "command"):
|
||
parser.error("missing command")
|
||
return result
|
||
|
||
|
||
def main() -> None:
|
||
git = Git()
|
||
commands = [
|
||
command_cls(git) for command_cls in BaseCommand.__subclasses__()
|
||
]
|
||
args = parse_args(commands)
|
||
args.command.run(args)
|
||
|
||
|
||
main()
|