Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed #55 unstable merge status bypass #56

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 48 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,9 +239,56 @@ stack-pr land -B HEAD~5 -H HEAD~2
```

## Command Line Options Reference
The section is not added yet, contributions are welcome!

### Common Arguments

These arguments can be used with any subcommand:

- `-R, --remote`: Remote name (default: "origin")
- `-B, --base`: Local base branch
- `-H, --head`: Local head branch (default: "HEAD")
- `-T, --target`: Remote target branch (default: "main")
- `--hyperlinks/--no-hyperlinks`: Enable/disable hyperlink support (default: enabled)
- `-V, --verbose`: Enable verbose output from Git subcommands (default: false)
- `--branch-name-template`: Template for generated branch names (default: "$USERNAME/stack")
- `--merge-status-mode`: Mode for checking merge status when verifying PRs in GitHub (default: clean)
- `clean`: Only allow clean merge state
- `unstable`: Allow unstable and clean merge states. See [GitHub's documentation on MergeStateStatus](https://docs.github.com/en/enterprise-cloud@latest/graphql/reference/enums#mergestatestatus) for details
- `bypass`: Skip merge status checks completely. This is risky

### Subcommands

#### submit (alias: export)

Submit a stack of PRs

Options:

- `--keep-body`: Keep current PR body, only update cross-links (default: false)
- `-d, --draft`: Submit PRs in draft mode (default: false)
- `--draft-bitmask`: Bitmask for setting draft status per PR
- `--reviewer`: List of reviewers for the PRs (default: from $STACK_PR_DEFAULT_REVIEWER or config)

#### land

Land the current stack

Takes no additional arguments beyond common ones.

#### abandon

Abandon the current stack

Takes no additional arguments beyond common ones.

#### view

Inspect the current stack

Takes no additional arguments beyond common ones.

### Config files

Default values for command line options can be specified via a config file.
Path to the config file can be specified via `STACKPR_CONFIG` envvar, and by
default it's assumed to be `.stack-pr.cfg` in the current folder.
Expand Down
41 changes: 35 additions & 6 deletions src/stack_pr/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,13 @@
If you use the default commit message filled by the web UI, links to other PRs from the stack will be included in the commit message.
"""

from enum import Enum, auto

class MergeStatusMode(str, Enum):
CLEAN = "clean"
UNSTABLE = "unstable"
BYPASS = "bypass"


# ===----------------------------------------------------------------------=== #
# Class to work with git commit contents
Expand Down Expand Up @@ -458,7 +465,7 @@ def set_base_branches(st: List[StackEntry], target: str):
e.base, prev_branch = prev_branch, e._head


def verify(st: List[StackEntry], check_base: bool = False):
def verify(st: List[StackEntry], merge_status_mode: MergeStatusMode, check_base: bool = False):
log(h("Verifying stack info"), level=1)
for index, e in enumerate(st):
if e.has_missing_info():
Expand Down Expand Up @@ -506,9 +513,20 @@ def verify(st: List[StackEntry], check_base: bool = False):
raise RuntimeError

# The first entry on the stack needs to be actually mergeable on GitHub.
if check_base and index == 0 and d["mergeStateStatus"] != "CLEAN" and d["mergeStateStatus"] != "UNKNOWN":
error(ERROR_STACKINFO_PR_NOT_MERGEABLE.format(**locals()))
raise RuntimeError
match merge_status_mode:
case MergeStatusMode.CLEAN:
if check_base and index == 0 and d["mergeStateStatus"] != "CLEAN" and d["mergeStateStatus"] != "UNKNOWN":
error(ERROR_STACKINFO_PR_NOT_MERGEABLE.format(**locals()))
raise RuntimeError
case MergeStatusMode.UNSTABLE:
log("Checking merge status and allowing UNSTABLE status", level=1)
if check_base and index == 0 and d["mergeStateStatus"] != "CLEAN" and d["mergeStateStatus"] != "UNKNOWN" and d["mergeStateStatus"] != "UNSTABLE":
error(ERROR_STACKINFO_PR_NOT_MERGEABLE.format(**locals()))
raise RuntimeError
case MergeStatusMode.BYPASS | _:
log("Bypassing merge status check", level=1)
raise NotImplementedError("Not implemented yet") # I don't want to test this locally, so not implementing for now.



def print_stack(st: List[StackEntry], links: bool, level=1):
Expand Down Expand Up @@ -799,6 +817,7 @@ def update_local_base(base: str, remote: str, target: str, verbose: bool):
)



class CommonArgs(NamedTuple):
"""Class to help type checkers and separate implementation for CLI args."""

Expand All @@ -809,6 +828,7 @@ class CommonArgs(NamedTuple):
hyperlinks: bool
verbose: bool
branch_name_template: str
merge_status_mode: MergeStatusMode

@classmethod
def from_args(cls, args: argparse.Namespace) -> "CommonArgs":
Expand All @@ -820,6 +840,7 @@ def from_args(cls, args: argparse.Namespace) -> "CommonArgs":
args.hyperlinks,
args.verbose,
args.branch_name_template,
args.merge_status_mode,
)


Expand Down Expand Up @@ -848,6 +869,7 @@ def deduce_base(args: CommonArgs) -> CommonArgs:
args.hyperlinks,
args.verbose,
args.branch_name_template,
args.merge_status_mode,
)


Expand Down Expand Up @@ -929,7 +951,7 @@ def command_submit(
create_pr(e, is_pr_draft, reviewer)

# Verify consistency in everything we have so far
verify(st)
verify(st, args.merge_status_mode)

# Embed stack-info into commit messages
log(h("Updating commit messages with stack metadata"), level=1)
Expand Down Expand Up @@ -1095,7 +1117,7 @@ def command_land(args: CommonArgs):
print_stack(st, args.hyperlinks)

# Verify that the stack is correct before trying to land it.
verify(st, check_base=True)
verify(st, args.merge_status_mode, check_base=True)

# All good, land the bottommost PR!
land_pr(st[0], args.remote, args.target, args.verbose)
Expand Down Expand Up @@ -1308,6 +1330,13 @@ def create_argparser(
),
help="A template for names of the branches stack-pr would use.",
)
common_parser.add_argument(
"--merge-status-mode",
type=MergeStatusMode,
choices=[x.value for x in MergeStatusMode],
default=MergeStatusMode.CLEAN,
help="Mode for checking merge status (clean, unstable, bypass). Default: clean. Ustable allows landing stacks where PRs still have failing checks, but are mergable (use with caution!). Bypass allows landing stacks where PRs are not mergable (do not use if you're not certain that's what you want).",
)

parser_submit = subparsers.add_parser(
"submit",
Expand Down