home/git: add git-post-pr script

This commit is contained in:
Oystein Kristoffer Tveit 2024-11-13 16:34:30 +01:00
parent 49f0a457e5
commit a5cad921ff
Signed by: oysteikt
GPG Key ID: 9F2F7D8250F35146
2 changed files with 146 additions and 0 deletions

View File

@ -54,6 +54,7 @@ in
authors = "shortlog --summary --numbered --email"; authors = "shortlog --summary --numbered --email";
si = "switch-interactive"; si = "switch-interactive";
ff = "fixup-fixup"; ff = "fixup-fixup";
pp = "post-pr";
subs = "submodule update --init --recursive"; subs = "submodule update --init --recursive";
rebase-author = "rebase -i -x \"git commit --amend --reset-author -CHEAD\""; rebase-author = "rebase -i -x \"git commit --amend --reset-author -CHEAD\"";
git = "!git"; git = "!git";
@ -284,6 +285,21 @@ in
"SC2001" # (style): See if you can use ${variable//search/replace} instead. (sed invocation) "SC2001" # (style): See if you can use ${variable//search/replace} instead. (sed invocation)
]; ];
}) })
((pkgs.writers.writePython3Bin "git-post-pr" {
libraries = with pkgs.python3Packages; [
tkinter
];
flakeIgnore = [
"E501" # I like long lines grr
];
} (lib.fileContents ./scripts/git-post-pr.py)).overrideAttrs (_: {
postFixup = ''
wrapProgram $out/bin/git-post-pr \
--prefix PATH : ${lib.makeBinPath [
pkgs.github-cli
]}
'';
}))
pkgs.git-absorb pkgs.git-absorb
]; ];

View File

@ -0,0 +1,130 @@
import argparse
import json
import subprocess
import tkinter
# TODO: add support for gitea, and maybe other git hosting options.
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
prog="post-pr",
description="Post links to PRs",
)
parser.add_argument("-n", "--no-clipboard", action="store_true", help="do not copy the message to the clipboard")
pr_id = parser.add_mutually_exclusive_group()
pr_id.add_argument("-c", "--current-branch", action="store_true", help="generate post for the PR for the current branch")
pr_id.add_argument("-l", "--latest", action="store_true", help="generate post for the latest PR for the current user")
pr_id.add_argument("pr_id", nargs="?", default=None, help="generate post for the PR with the given ID")
args = parser.parse_args()
if not any([args.current_branch, args.latest, args.pr_id,]):
args.current_branch = True
return args
def _gh(args: list[str]) -> str:
try:
return subprocess.check_output(["gh"] + args).decode("utf8")
except subprocess.CalledProcessError as e:
raise RuntimeError(f"GitHub CLI command failed: 'gh {' '.join(args)}'") from e
def _gh_retcode(args: list[str]) -> int:
return subprocess.run(["gh"] + args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode
def ensure_gh_installed():
try:
if _gh_retcode(["--version"]) != 0:
raise RuntimeError("GitHub CLI (gh) is not installed, please install it")
except FileNotFoundError:
raise RuntimeError("GitHub CLI (gh) is not installed, please install it")
def ensure_gh_authenticated():
if _gh_retcode(["auth", "status"]) != 0:
raise RuntimeError("Failed to authenticate with GitHub, please run 'gh auth login'")
GH_PR_JSON_FIELDS = ",".join([
"additions",
"deletions",
"state",
"title",
"url",
])
def fetch_pr_data(current_branch: bool, latest: bool, pr_id: str | None) -> dict[str, any]:
if pr_id:
pr_data = _gh(["pr", "view", pr_id, "--json", GH_PR_JSON_FIELDS])
pr_data = json.loads(pr_data)
elif latest:
pr_list = _gh(["pr", "list", "--author", "@me", "--limit", "1", "--json", GH_PR_JSON_FIELDS])
pr_list = json.loads(pr_list)
if len(pr_list) == 0:
raise RuntimeError("Failed to find PR, are you sure you have any open PRs?")
pr_data = pr_list[0]
elif current_branch:
pr_data = _gh(["pr", "view", "--json", GH_PR_JSON_FIELDS])
pr_data = json.loads(pr_data)
return pr_data
def format_message(pr_data: dict[str, any]) -> str:
additions = pr_data["additions"]
deletions = pr_data["deletions"]
title = pr_data["title"]
pr_url = pr_data["url"]
pr_state = pr_data["state"]
state_html = f"({pr_state.lower()}) " if pr_state != "OPEN" else ""
additions_html = f"+{additions}" if additions > 0 else str(additions)
deletions_html = f"-{deletions}" if deletions > 0 else str(deletions)
return f"""{state_html}{pr_url} {title} [diff: {additions_html}/{deletions_html}]"""
def copy_to_clipboard(message: str):
r = tkinter.Tk()
r.withdraw()
r.clipboard_clear()
r.clipboard_append(message)
r.update()
r.destroy()
def main():
args = parse_args()
ensure_gh_installed()
ensure_gh_authenticated()
pr_data = fetch_pr_data(args.current_branch, args.latest, args.pr_id)
message = format_message(pr_data)
print("Message:\n")
print(f" {message}\n")
if not args.no_clipboard:
copy_to_clipboard(message)
print("Copied to clipboard")
if __name__ == "__main__":
try:
main()
except Exception as e:
print(f"Error: {e}")
exit(1)