Skip to content

Commit

Permalink
feat: add stack checkout
Browse files Browse the repository at this point in the history
This new command allow to download a stack.

Change-Id: I7f1fa09eaed82fd2519997f4743f8d6654f85900
  • Loading branch information
sileht committed Oct 25, 2024
1 parent 00a9f0b commit d116a55
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 14 deletions.
151 changes: 139 additions & 12 deletions mergify_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

import argparse
import asyncio
import contextlib
import dataclasses
import importlib.metadata
import os
Expand Down Expand Up @@ -645,6 +644,28 @@ async def get_remote_changes(
return remote_changes


def get_github_http_client(github_server: str, token: str) -> httpx.AsyncClient:
event_hooks: typing.Mapping[str, list[typing.Callable[..., typing.Any]]] = {
"request": [],
"response": [check_for_status],
}
if DEBUG:
event_hooks["request"].insert(0, log_httpx_request)
event_hooks["response"].insert(0, log_httpx_response)

return httpx.AsyncClient(
base_url=github_server,
headers={
"Accept": "application/vnd.github.v3+json",
"User-Agent": f"mergify_cli/{VERSION}",
"Authorization": f"token {token}",
},
event_hooks=event_hooks,
follow_redirects=True,
timeout=5.0,
)


# TODO(charly): fix code to conform to linter (number of arguments, local
# variables, statements, positional arguments, branches)
async def stack_push( # noqa: PLR0913, PLR0915, PLR0917
Expand Down Expand Up @@ -681,6 +702,7 @@ async def stack_push( # noqa: PLR0913, PLR0915, PLR0917
sys.exit(1)

stack_prefix = f"{branch_prefix}/{dest_branch}"
stack_prefix = stack_prefix.removeprefix("/")

if not dry_run:
if skip_rebase:
Expand Down Expand Up @@ -708,17 +730,7 @@ async def stack_push( # noqa: PLR0913, PLR0915, PLR0917
event_hooks["request"].insert(0, log_httpx_request)
event_hooks["response"].insert(0, log_httpx_response)

async with httpx.AsyncClient(
base_url=github_server,
headers={
"Accept": "application/vnd.github.v3+json",
"User-Agent": f"mergify_cli/{VERSION}",
"Authorization": f"token {token}",
},
event_hooks=event_hooks,
follow_redirects=True,
timeout=5.0,
) as client:
async with get_github_http_client(github_server, token) as client:
if author is None:
r_author = await client.get("/user")
author = r_author.json()["login"]
Expand Down Expand Up @@ -890,6 +902,77 @@ async def _stack_push(args: argparse.Namespace) -> None:
)


@dataclasses.dataclass
class ChangeNode:
pull: github_types.PullRequest
up: ChangeNode | None = None


async def _stack_checkout(args: argparse.Namespace) -> None:
user, repo = args.repository.split("/")

stack_branch = f"{args.branch_prefix}/{args.branch}"
stack_branch = stack_branch.removeprefix("/")

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

root_node: ChangeNode | None = None

nodes = {
pull["base"]["ref"]: ChangeNode(pull)
for pull in remote_changes.values()
if pull["state"] == "open"
}

# Linking nodes and finding the base
for node in nodes.values():
node.up = nodes.get(node.pull["head"]["ref"])

if not node.pull["base"]["ref"].startswith(stack_branch):
if root_node is not None:
console.print(
"Unexpected stack layout, two root commits found",
style="red",
)
sys.exit(1)
root_node = node

if root_node is None:
console.print("No stacked pull requests found")
sys.exit(0)

console.log("Stacked pull requests:")
node = root_node
while True:
pull = node.pull
console.log(
f"* [b][white]#{pull['number']}[/] {pull['title']}[/] {pull['html_url']}",
)
console.log(f" [grey42]{pull['base']['ref']} -> {pull['head']['ref']}[/]")

if node.up is None:
break
node = node.up

if args.dry_run:
return

remote = args.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("branch", f"--set-upstream-to={upstream}")


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


async def register_stack_checkout_parser(
sub_parsers: argparse._SubParsersAction[typing.Any],
) -> None:
parser = sub_parsers.add_parser(
"checkout",
description="Checkout a pull requests stack",
help="Checkout a pull requests stack",
)
parser.set_defaults(func=_stack_checkout)
parser.add_argument(
"--author",
help="Set the author of the stack (default: the author of the token)",
)
parser.add_argument(
"--repository",
"--repo",
help="Set the repository where the stack is located (eg: owner/repo)",
)
parser.add_argument(
"--branch",
help="Branch used to create stacked PR.",
)
parser.add_argument(
"--branch-prefix",
default=await get_default_branch_prefix(),
help="Branch prefix used to create stacked PR. "
"Default fetched from git config if added with `git config --add mergify-cli.stack-branch-prefix some-prefix`",
)
parser.add_argument(
"--dry-run",
"-n",
action="store_true",
help="Only show what is going to be done",
)
parser.add_argument(
"--trunk",
"-t",
type=trunk_type,
default=await get_trunk(),
help="Change the target branch of the stack.",
)


async def register_stack_push_parser(
sub_parsers: argparse._SubParsersAction[typing.Any],
) -> None:
Expand Down Expand Up @@ -1019,6 +1145,7 @@ async def parse_args(args: typing.MutableSequence[str]) -> argparse.Namespace:
)
stack_sub_parsers = stack_parser.add_subparsers(dest="stack_action")
await register_stack_push_parser(stack_sub_parsers)
await register_stack_checkout_parser(stack_sub_parsers)
register_stack_edit_parser(stack_sub_parsers)
register_stack_setup_parser(stack_sub_parsers)

Expand Down
5 changes: 3 additions & 2 deletions mergify_cli/github_types.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import typing


class HeadRef(typing.TypedDict):
class PullRequestRef(typing.TypedDict):
sha: str
ref: str

Expand All @@ -11,7 +11,8 @@ class PullRequest(typing.TypedDict):
number: str
title: str
body: str | None
head: HeadRef
base: PullRequestRef
head: PullRequestRef
state: str
draft: bool
node_id: str
Expand Down

0 comments on commit d116a55

Please sign in to comment.