feat: parameterize create_pr.py and add PRGS remote
- argparse: --remote {dadeschools,prgs}, --title/--head/--base/--body,
--body-file (or '-' for stdin), and --host/--org/--repo overrides.
- REMOTES table: dadeschools (gitea.dadeschools.net/Contractor) and
prgs (gitea.prgs.cc/Scaled-Tech-Consulting).
- Print 'PR #N: <url>' on success; surface API error body on failure.
- Fix credential parsing to split('=', 1) so tokens containing '=' work.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+105
-37
@@ -1,44 +1,112 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Create a Gitea pull request.
|
||||||
|
|
||||||
|
Parameterized over title/body/head/base and the target Gitea instance.
|
||||||
|
Two instances are known out of the box:
|
||||||
|
|
||||||
|
dadeschools -> gitea.dadeschools.net / Contractor / Timesheet
|
||||||
|
prgs -> gitea.prgs.cc / Scaled-Tech-Consulting / Timesheet
|
||||||
|
|
||||||
|
Auth is pulled from the macOS keychain via `git credential fill` for the
|
||||||
|
chosen host -- no tokens on the command line.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
create_pr.py --remote dadeschools \\
|
||||||
|
--title "Open generated PDF after creation (#7)" \\
|
||||||
|
--head feat/7-open-pdf-2234ddf5 --base feat/4-5-24-validations \\
|
||||||
|
--body "Closes #7"
|
||||||
|
|
||||||
|
create_pr.py --remote prgs --title "Fix X" --head fix/x --body-file body.md
|
||||||
|
|
||||||
|
# override any field of a known remote, or point at an arbitrary repo:
|
||||||
|
create_pr.py --host gitea.example.com --org Foo --repo Bar \\
|
||||||
|
--title "..." --head topic
|
||||||
|
"""
|
||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
import urllib.request
|
|
||||||
import subprocess
|
|
||||||
import base64
|
import base64
|
||||||
|
import argparse
|
||||||
|
import subprocess
|
||||||
|
import urllib.request
|
||||||
|
import urllib.error
|
||||||
|
|
||||||
host = "gitea.dadeschools.net"
|
REMOTES = {
|
||||||
org = "Contractor"
|
"dadeschools": {"host": "gitea.dadeschools.net", "org": "Contractor",
|
||||||
repo = "Timesheet"
|
"repo": "Timesheet"},
|
||||||
|
"prgs": {"host": "gitea.prgs.cc", "org": "Scaled-Tech-Consulting",
|
||||||
p = subprocess.Popen(["git", "credential", "fill"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True)
|
"repo": "Timesheet"},
|
||||||
out, _ = p.communicate(f"protocol=https\nhost=gitea.dadeschools.net\n\n")
|
|
||||||
|
|
||||||
user = ""
|
|
||||||
password = ""
|
|
||||||
for line in out.splitlines():
|
|
||||||
if line.startswith("username="):
|
|
||||||
user = line.split("=")[1]
|
|
||||||
if line.startswith("password="):
|
|
||||||
password = line.split("=")[1]
|
|
||||||
|
|
||||||
if not user or not password:
|
|
||||||
print("Could not get credentials")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
url = f"https://{host}/api/v1/repos/{org}/{repo}/pulls"
|
|
||||||
data = {
|
|
||||||
"title": "feat: Support PTO, Sick, Holiday, and Unpaid days",
|
|
||||||
"body": "Closes #6",
|
|
||||||
"head": "feat/6-absence-categories",
|
|
||||||
"base": "main"
|
|
||||||
}
|
}
|
||||||
req = urllib.request.Request(url, data=json.dumps(data).encode("utf-8"), headers={
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
})
|
|
||||||
|
|
||||||
auth_b64 = base64.b64encode(f"{user}:{password}".encode("utf-8")).decode("utf-8")
|
|
||||||
req.add_header("Authorization", f"Basic {auth_b64}")
|
|
||||||
|
|
||||||
try:
|
def get_credentials(host):
|
||||||
with urllib.request.urlopen(req) as response:
|
"""Return (user, password) for `host` via `git credential fill`."""
|
||||||
print(response.read().decode())
|
p = subprocess.Popen(
|
||||||
except urllib.error.HTTPError as e:
|
["git", "credential", "fill"],
|
||||||
print("Error:", e.read().decode())
|
stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True,
|
||||||
|
)
|
||||||
|
out, _ = p.communicate(f"protocol=https\nhost={host}\n\n")
|
||||||
|
user = password = ""
|
||||||
|
for line in out.splitlines():
|
||||||
|
# split(maxsplit=1): tokens/passwords can themselves contain '='.
|
||||||
|
if line.startswith("username="):
|
||||||
|
user = line.split("=", 1)[1]
|
||||||
|
elif line.startswith("password="):
|
||||||
|
password = line.split("=", 1)[1]
|
||||||
|
return user, password
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None):
|
||||||
|
parser = argparse.ArgumentParser(description="Create a Gitea pull request.")
|
||||||
|
parser.add_argument("--remote", choices=sorted(REMOTES), default="dadeschools",
|
||||||
|
help="Known Gitea instance (default: dadeschools).")
|
||||||
|
parser.add_argument("--host", help="Override the Gitea host.")
|
||||||
|
parser.add_argument("--org", help="Override the owner/org.")
|
||||||
|
parser.add_argument("--repo", help="Override the repository.")
|
||||||
|
parser.add_argument("--title", required=True, help="PR title.")
|
||||||
|
parser.add_argument("--head", required=True, help="Source branch.")
|
||||||
|
parser.add_argument("--base", default="main", help="Target branch (default: main).")
|
||||||
|
parser.add_argument("--body", default="", help="PR body text.")
|
||||||
|
parser.add_argument("--body-file", help="Read PR body from this file ('-' for stdin).")
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
|
profile = REMOTES[args.remote]
|
||||||
|
host = args.host or profile["host"]
|
||||||
|
org = args.org or profile["org"]
|
||||||
|
repo = args.repo or profile["repo"]
|
||||||
|
|
||||||
|
body = args.body
|
||||||
|
if args.body_file:
|
||||||
|
if args.body_file == "-":
|
||||||
|
body = sys.stdin.read()
|
||||||
|
else:
|
||||||
|
with open(args.body_file, "r", encoding="utf-8") as fh:
|
||||||
|
body = fh.read()
|
||||||
|
|
||||||
|
user, password = get_credentials(host)
|
||||||
|
if not user or not password:
|
||||||
|
print(f"Could not get credentials for {host} "
|
||||||
|
f"(no keychain entry? try a manual `git credential fill`).",
|
||||||
|
file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
url = f"https://{host}/api/v1/repos/{org}/{repo}/pulls"
|
||||||
|
payload = {"title": args.title, "body": body, "head": args.head, "base": args.base}
|
||||||
|
req = urllib.request.Request(
|
||||||
|
url, data=json.dumps(payload).encode("utf-8"),
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
)
|
||||||
|
auth_b64 = base64.b64encode(f"{user}:{password}".encode("utf-8")).decode("utf-8")
|
||||||
|
req.add_header("Authorization", f"Basic {auth_b64}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req) as response:
|
||||||
|
data = json.load(response)
|
||||||
|
print(f"PR #{data.get('number')}: {data.get('html_url')}")
|
||||||
|
return 0
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
print(f"Error {e.code}: {e.read().decode()}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
|
|||||||
Reference in New Issue
Block a user