This repository has been archived by the owner on Oct 4, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
165 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
#!/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() |