Merge pull request 'agit: init agit helper' (#3938) from kenji/agit: init agit helper into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3938
This commit is contained in:
@@ -33,6 +33,7 @@
|
|||||||
self'.packages.tea-create-pr
|
self'.packages.tea-create-pr
|
||||||
self'.packages.merge-after-ci
|
self'.packages.merge-after-ci
|
||||||
self'.packages.pending-reviews
|
self'.packages.pending-reviews
|
||||||
|
self'.packages.agit
|
||||||
# treefmt with config defined in ./flake-parts/formatting.nix
|
# treefmt with config defined in ./flake-parts/formatting.nix
|
||||||
config.treefmt.build.wrapper
|
config.treefmt.build.wrapper
|
||||||
];
|
];
|
||||||
|
|||||||
37
pkgs/agit/README.md
Normal file
37
pkgs/agit/README.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# agit
|
||||||
|
|
||||||
|
A helper script for the AGit workflow with a gitea instance.
|
||||||
|
|
||||||
|
<!-- `$ agit --help` -->
|
||||||
|
|
||||||
|
```
|
||||||
|
usage: agit [-h] {create,c} ...
|
||||||
|
|
||||||
|
AGit utility for creating and pulling PRs
|
||||||
|
|
||||||
|
positional arguments:
|
||||||
|
{create,c} Commands
|
||||||
|
create (c) Create an AGit PR
|
||||||
|
|
||||||
|
options:
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
|
||||||
|
The defaults that are assumed are:
|
||||||
|
TARGET_REMOTE_REPOSITORY = origin
|
||||||
|
DEFAULT_TARGET_BRANCH = main
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
$ agit create
|
||||||
|
Will create an AGit Pr with the latest commit message title as it's topic.
|
||||||
|
|
||||||
|
$ agit create --topic "my-feature"
|
||||||
|
Set a custom topic.
|
||||||
|
|
||||||
|
$ agit create --force
|
||||||
|
Force push to a certain topic
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
References:
|
||||||
|
- https://docs.gitea.com/usage/agit
|
||||||
|
- https://git-repo.info/en/2020/03/agit-flow-and-git-repo/
|
||||||
205
pkgs/agit/agit.py
Normal file
205
pkgs/agit/agit.py
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
import argparse
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# push origin HEAD:refs/for/main
|
||||||
|
# HEAD: The target branch
|
||||||
|
# origin: The target repository (not a fork!)
|
||||||
|
# HEAD: The local branch containing the changes you are proposing
|
||||||
|
TARGET_REMOTE_REPOSITORY = "origin"
|
||||||
|
DEFAULT_TARGET_BRANCH = "main"
|
||||||
|
|
||||||
|
|
||||||
|
def run_git_command(command: list) -> tuple[int, str, str]:
|
||||||
|
"""Run a git command and return exit code, stdout, and stderr."""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(command, capture_output=True, text=True, check=False)
|
||||||
|
return result.returncode, result.stdout.strip(), result.stderr.strip()
|
||||||
|
except Exception as e:
|
||||||
|
return 1, "", str(e)
|
||||||
|
|
||||||
|
|
||||||
|
def get_latest_commit_info() -> tuple[str, str]:
|
||||||
|
"""Get the title and body of the latest commit."""
|
||||||
|
exit_code, commit_msg, error = run_git_command(
|
||||||
|
["git", "log", "-1", "--pretty=format:%B"]
|
||||||
|
)
|
||||||
|
|
||||||
|
if exit_code != 0:
|
||||||
|
print(f"Error getting commit info: {error}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
lines = commit_msg.strip().split("\n")
|
||||||
|
title = lines[0].strip() if lines else ""
|
||||||
|
|
||||||
|
body_lines = []
|
||||||
|
for line in lines[1:]:
|
||||||
|
if body_lines or line.strip():
|
||||||
|
body_lines.append(line)
|
||||||
|
|
||||||
|
body = "\n".join(body_lines).strip()
|
||||||
|
|
||||||
|
return title, body
|
||||||
|
|
||||||
|
|
||||||
|
def create_agit_push(
|
||||||
|
remote: str = "origin",
|
||||||
|
branch: str = "main",
|
||||||
|
topic: str | None = None,
|
||||||
|
title: str | None = None,
|
||||||
|
description: str | None = None,
|
||||||
|
force_push: bool = False,
|
||||||
|
local_branch: str = "HEAD",
|
||||||
|
) -> None:
|
||||||
|
if topic is None or title is None:
|
||||||
|
commit_title, _ = get_latest_commit_info()
|
||||||
|
|
||||||
|
if topic is None:
|
||||||
|
topic = commit_title
|
||||||
|
if title is None:
|
||||||
|
title = commit_title
|
||||||
|
|
||||||
|
refspec = f"{local_branch}:refs/for/{branch}"
|
||||||
|
push_cmd = ["git", "push", remote, refspec]
|
||||||
|
|
||||||
|
push_cmd.extend(["-o", f"topic={topic}"])
|
||||||
|
|
||||||
|
if title:
|
||||||
|
push_cmd.extend(["-o", f"title={title}"])
|
||||||
|
|
||||||
|
if description:
|
||||||
|
escaped_desc = description.replace('"', '\\"')
|
||||||
|
push_cmd.extend(["-o", f"description={escaped_desc}"])
|
||||||
|
|
||||||
|
if force_push:
|
||||||
|
push_cmd.extend(["-o", "force-push"])
|
||||||
|
|
||||||
|
if description:
|
||||||
|
print(
|
||||||
|
f" Description: {description[:50]}..."
|
||||||
|
if len(description) > 50
|
||||||
|
else f" Description: {description}"
|
||||||
|
)
|
||||||
|
print()
|
||||||
|
|
||||||
|
exit_code, stdout, stderr = run_git_command(push_cmd)
|
||||||
|
|
||||||
|
if stdout:
|
||||||
|
print(stdout)
|
||||||
|
if stderr:
|
||||||
|
print(stderr, file=sys.stderr)
|
||||||
|
|
||||||
|
if exit_code != 0:
|
||||||
|
print("\nPush failed!")
|
||||||
|
sys.exit(exit_code)
|
||||||
|
else:
|
||||||
|
print("\nPush successful!")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_create(args: argparse.Namespace) -> None:
|
||||||
|
"""Handle the create subcommand."""
|
||||||
|
create_agit_push(
|
||||||
|
remote=args.remote,
|
||||||
|
branch=args.branch,
|
||||||
|
topic=args.topic,
|
||||||
|
title=args.title,
|
||||||
|
description=args.description,
|
||||||
|
force_push=args.force,
|
||||||
|
local_branch=args.local_branch,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_parser() -> argparse.ArgumentParser:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog="agit",
|
||||||
|
description="AGit utility for creating and pulling PRs",
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
epilog=f"""
|
||||||
|
The defaults that are assumed are:
|
||||||
|
TARGET_REMOTE_REPOSITORY = {TARGET_REMOTE_REPOSITORY}
|
||||||
|
DEFAULT_TARGET_BRANCH = {DEFAULT_TARGET_BRANCH}
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
$ agit create
|
||||||
|
Will create an AGit Pr with the latest commit message title as it's topic.
|
||||||
|
|
||||||
|
$ agit create --topic "my-feature"
|
||||||
|
Set a custom topic.
|
||||||
|
|
||||||
|
$ agit create --force
|
||||||
|
Force push to a certain topic
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
subparsers = parser.add_subparsers(dest="subcommand", help="Commands")
|
||||||
|
|
||||||
|
create_parser = subparsers.add_parser(
|
||||||
|
"create",
|
||||||
|
aliases=["c"],
|
||||||
|
help="Create an AGit PR",
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
epilog="""
|
||||||
|
Examples:
|
||||||
|
$ agit create
|
||||||
|
Will create an AGit Pr with the latest commit message title as it's topic.
|
||||||
|
|
||||||
|
$ agit create --topic "my-feature"
|
||||||
|
Set a custom topic.
|
||||||
|
|
||||||
|
$ agit create --force
|
||||||
|
Force push to a certain topic
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
create_parser.add_argument(
|
||||||
|
"-r",
|
||||||
|
"--remote",
|
||||||
|
default=TARGET_REMOTE_REPOSITORY,
|
||||||
|
help=f"Git remote to push to (default: {TARGET_REMOTE_REPOSITORY})",
|
||||||
|
)
|
||||||
|
|
||||||
|
create_parser.add_argument(
|
||||||
|
"-b",
|
||||||
|
"--branch",
|
||||||
|
default=DEFAULT_TARGET_BRANCH,
|
||||||
|
help=f"Target branch for the PR (default: {DEFAULT_TARGET_BRANCH})",
|
||||||
|
)
|
||||||
|
|
||||||
|
create_parser.add_argument(
|
||||||
|
"-l",
|
||||||
|
"--local-branch",
|
||||||
|
default="HEAD",
|
||||||
|
help="Local branch to push (default: HEAD)",
|
||||||
|
)
|
||||||
|
|
||||||
|
create_parser.add_argument(
|
||||||
|
"-t", "--topic", help="Set PR topic (default: last commit title)"
|
||||||
|
)
|
||||||
|
|
||||||
|
create_parser.add_argument(
|
||||||
|
"--title", help="Set the PR title (default: last commit title)"
|
||||||
|
)
|
||||||
|
|
||||||
|
create_parser.add_argument(
|
||||||
|
"--description", help="Override the PR description (default: commit body)"
|
||||||
|
)
|
||||||
|
|
||||||
|
create_parser.add_argument(
|
||||||
|
"-f", "--force", action="store_true", help="Force push the changes"
|
||||||
|
)
|
||||||
|
|
||||||
|
create_parser.set_defaults(func=cmd_create)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = create_parser()
|
||||||
|
args = parser.parse_args()
|
||||||
|
if args.subcommand is None:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(0)
|
||||||
|
args.func(args)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
26
pkgs/agit/default.nix
Normal file
26
pkgs/agit/default.nix
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
bash,
|
||||||
|
callPackage,
|
||||||
|
git,
|
||||||
|
lib,
|
||||||
|
openssh,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
writers = callPackage ../builders/script-writers.nix { };
|
||||||
|
in
|
||||||
|
writers.writePython3Bin "agit" {
|
||||||
|
flakeIgnore = [
|
||||||
|
"E501"
|
||||||
|
];
|
||||||
|
makeWrapperArgs = [
|
||||||
|
"--prefix"
|
||||||
|
"PATH"
|
||||||
|
":"
|
||||||
|
(lib.makeBinPath [
|
||||||
|
bash
|
||||||
|
git
|
||||||
|
openssh
|
||||||
|
])
|
||||||
|
];
|
||||||
|
} ./agit.py
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
{ config, pkgs, ... }:
|
{ config, pkgs, ... }:
|
||||||
{
|
{
|
||||||
packages = {
|
packages = {
|
||||||
|
agit = pkgs.callPackage ./agit { };
|
||||||
tea-create-pr = pkgs.callPackage ./tea-create-pr { };
|
tea-create-pr = pkgs.callPackage ./tea-create-pr { };
|
||||||
zerotier-members = pkgs.callPackage ./zerotier-members { };
|
zerotier-members = pkgs.callPackage ./zerotier-members { };
|
||||||
zt-tcp-relay = pkgs.callPackage ./zt-tcp-relay { };
|
zt-tcp-relay = pkgs.callPackage ./zt-tcp-relay { };
|
||||||
|
|||||||
Reference in New Issue
Block a user