Skip to content

Commit

Permalink
feat: add stack github-action-auto-rebase command
Browse files Browse the repository at this point in the history
Change-Id: I419a57f50240d69a92f56f2355ad2b0b5d2a8d8c
  • Loading branch information
sileht committed Oct 28, 2024
1 parent cc50e18 commit 4dd4575
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 6 deletions.
14 changes: 14 additions & 0 deletions github-workflow-example.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: Mergify Stack Autorebase
on:
issue_comment:
types: [created, edited, deleted]

jobs:
mergify-stack-auto-rebase:
if: github.event.issue.pull_request && startsWith(github.event.comment.body, '@mergify-cli stack rebase')
runs-on: ubuntu-22.04
steps:
- name: Run mergify stack github-action-auto-rebase
uses: Mergifyio/mergify-cli@main
with:
GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
130 changes: 124 additions & 6 deletions mergify_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import asyncio
import dataclasses
import importlib.metadata
import json
import os
import pathlib
import re
Expand Down Expand Up @@ -913,17 +914,41 @@ class ChangeNode:
async def _stack_checkout(args: argparse.Namespace) -> None:
user, repo = args.repository.split("/")

stack_branch = f"{args.branch_prefix}/{args.branch}"
await stack_checkout(
args.github_server,
args.token,
user,
repo,
args.branch_prefix,
args.branch,
args.author,
args.trunk,
args.dry_run,
)


async def stack_checkout(
github_server: str,
token: str,
user: str,
repo: str,
branch_prefix: str,
branch: str,
author: str,
trunk: tuple[str, str],
dry_run: bool,
) -> None:
stack_branch = f"{branch_prefix}/{branch}"
stack_branch = stack_branch.removeprefix("/")

async with get_github_http_client(args.github_server, args.token) as client:
async with get_github_http_client(github_server, token) as client:
with console.status("Retrieving latest pushed stacks"):
remote_changes = await get_remote_changes(
client,
user,
repo,
stack_branch,
args.author,
author,
)

root_node: ChangeNode | None = None
Expand Down Expand Up @@ -964,17 +989,98 @@ async def _stack_checkout(args: argparse.Namespace) -> None:
break
node = node.up

if args.dry_run:
if dry_run:
return

remote = args.trunk[0]
remote = trunk[0]
upstream = f"{remote}/{root_node.pull['base']['ref']}"
head_ref = f"{remote}/{node.pull['head']['ref']}"
await git("fetch", remote, node.pull["head"]["ref"])
await git("checkout", "-b", args.branch, head_ref)
await git("checkout", "-b", branch, head_ref)
await git("branch", f"--set-upstream-to={upstream}")


async def _stack_github_action_auto_rebase(args: argparse.Namespace) -> None:
for env in ("GITHUB_EVENT_NAME", "GITHUB_EVENT_PATH", "GITHUB_REPOSITORY"):
if env not in os.environ:
console.log("This action only works in a GitHub Action", style="red")
sys.exit(1)

event_name = os.environ["GITHUB_EVENT_NAME"]
event_path = os.environ["GITHUB_EVENT_PATH"]
user, repo = os.environ["GITHUB_REPOSITORY"].split("/")

async with aiofiles.open(event_path) as f:
event = json.loads(await f.read())

if event_name != "issue_comment" or not event["issue"]["pull_request"]:
console.log(
"This action only works with `issue_comment` event for pull request",
style="red",
)
sys.exit(1)

async with get_github_http_client(args.github_server, args.token) as client:
await client.post(
f"/repos/{user}/{repo}/issues/comments/{event['comment']['id']}/reactions",
json={"content": "+1"},
)
resp = await client.get(event["issue"]["pull_request"]["url"])
pull = resp.json()

author = pull["user"]["login"]
base = pull["base"]["ref"]
head = pull["head"]["ref"]

head_changeid = head.split("/")[-1]
if not head_changeid.startswith("I") or len(head_changeid) != 41:
console.log("This pull request is not part of a stack", style="red")
sys.exit(1)

base_changeid = base.split("/")[-1]
if base_changeid.startswith("I") and len(base_changeid) == 41:
console.log("This pull request is not the bottom of the stack", style="red")
sys.exit(1)

stack_branch = head.removesuffix(f"/{head_changeid}")

await git("config", "--global", "user.name", f"{author}")
await git("config", "--global", "user.email", f"{author}@users.noreply.github.com")
await git("branch", "--set-upstream-to", f"origin/{base}")

await stack_checkout(
args.github_server,
args.token,
user=user,
repo=repo,
branch_prefix="",
branch=stack_branch,
author=author,
trunk=("origin", base),
dry_run=False,
)
await stack_push(
args.github_server,
args.token,
skip_rebase=False,
next_only=False,
branch_prefix="",
dry_run=False,
trunk=("origin", base),
create_as_draft=False,
keep_pull_request_title_and_body=True,
only_update_existing_pulls=False,
author=author,
)

async with get_github_http_client(args.github_server, args.token) as client:
body_quote = "> " + "\n> ".join(event["comment"]["body"].split("\n"))
await client.post(
f"/repos/{user}/{repo}/issues/{pull['number']}/comments",
json={"body": f"{body_quote}\n\nThe stack has been rebased"},
)


def register_stack_setup_parser(
sub_parsers: argparse._SubParsersAction[typing.Any],
) -> None:
Expand All @@ -997,6 +1103,17 @@ def register_stack_edit_parser(
parser.set_defaults(func=stack_edit)


def register_stack_github_action_autorebase(
sub_parsers: argparse._SubParsersAction[typing.Any],
) -> None:
parser = sub_parsers.add_parser(
"github-action-auto-rebase",
description="Autorebase a pull requests stack",
help="Checkout a pull requests stack",
)
parser.set_defaults(func=_stack_github_action_auto_rebase)


async def register_stack_checkout_parser(
sub_parsers: argparse._SubParsersAction[typing.Any],
) -> None:
Expand Down Expand Up @@ -1150,6 +1267,7 @@ async def parse_args(args: typing.MutableSequence[str]) -> argparse.Namespace:
await register_stack_checkout_parser(stack_sub_parsers)
register_stack_edit_parser(stack_sub_parsers)
register_stack_setup_parser(stack_sub_parsers)
register_stack_github_action_autorebase(stack_sub_parsers)

known_args, _ = parser.parse_known_args(args)

Expand Down

0 comments on commit 4dd4575

Please sign in to comment.