From 5150f69622c304044d9270b5b0bc23e6db5e90ed Mon Sep 17 00:00:00 2001 From: Russell Mora Date: Thu, 19 Feb 2026 17:52:15 -0500 Subject: [PATCH] Github has an [option](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/managing-repository-settings/managing-the-push-policy-for-your-repository) to limit the number of branches that can be pushed to it in one push. With this option enabled you get an error something like ``` ... Updating remote branches Enumerating objects: 33, done. Counting objects: 100% (33/33), done. Delta compression using up to 16 threads Compressing objects: 100% (21/21), done. Writing objects: 100% (21/21), 15.04 KiB | 342.00 KiB/s, done. Total 21 (delta 15), reused 3 (delta 0), pack-reused 0 remote: Resolving deltas: 100% (15/15), completed with 11 local objects. remote: error: GH013: Repository rule violations found for refs/heads/Apertyx0/stack/1. remote: Review all repository rules at https://github.com/OWNER/REPO/rules?ref=refs%2Fheads%2FApteryx0%2Fstack%2F1 remote: remote: - Pushes can not update more than 2 branches or tags. remote: remote: error: GH013: Repository rule violations found for refs/heads/Apteryx0/stack/2. remote: Review all repository rules at https://github.com/OWNER/REPO/rules?ref=refs%2Fheads%2FApteryx0%2Fstack%2F2 remote: remote: - Pushes can not update more than 2 branches or tags. remote: remote: error: GH013: Repository rule violations found for refs/heads/Apteryx0/stack/3. remote: Review all repository rules at https://github.com/OWNER/REPO/rules?ref=refs%2Fheads%2FApteryx0%2Fstack%2F3 remote: remote: - Pushes can not update more than 2 branches or tags. remote: To github.com:OWNER/REPO.git ! [remote rejected] Apertyx0/stack/1 -> Apertyx0/stack/1 (push declined due to repository rule violations) ! [remote rejected] Apertyx0/stack/2 -> Apertyx0/stack/2 (push declined due to repository rule violations) ! [remote rejected] Apertyx0/stack/3 -> Apertyx0/stack/3 (push declined due to repository rule violations) error: failed to push some refs to 'github.com:OWNER/REPO.git' Switched to branch 'Apteryx0' Exitcode: 1 Stdout: None Stderr: None Traceback (most recent call last): File "/home/russellm/.local/bin/stack-pr", line 6, in sys.exit(main()) File "/home/russellm/.local/pipx/venvs/stack-pr/lib/python3.10/site-packages/stack_pr/cli.py", line 1711, in main command_submit( File "/home/russellm/.local/pipx/venvs/stack-pr/lib/python3.10/site-packages/stack_pr/cli.py", line 1106, in command_submit push_branches(st, remote=args.remote, verbose=args.verbose) File "/home/russellm/.local/pipx/venvs/stack-pr/lib/python3.10/site-packages/stack_pr/cli.py", line 712, in push_branches run_shell_command(cmd, quiet=not verbose) File "/home/russellm/.local/pipx/venvs/stack-pr/lib/python3.10/site-packages/stack_pr/shell_commands.py", line 55, in run_shell_command return subprocess.run(list(map(str, cmd)), **kwargs, check=check) File "/usr/lib/python3.10/subprocess.py", line 526, in run raise CalledProcessError(retcode, process.args, subprocess.CalledProcessError: Command '['git', 'push', '-f', 'origin', 'Apertyx0/stack/1:Apertyx0/stack/1', 'Apertyx0/stack/2:Apertyx0/stack/2', 'Apertyx0/stack/3:Apertyx0/stack/3']' returned non-zero exit status 1. ``` The change adds a config option and CLI option to specify chunks of PR to push to GitHub. I set the default to 5 since this is the value GitHub recommends so hopefully users generally won't run into this unless they or their org set the limit lower (2 in my case) With this in place I get success ``` Updating remote branches Enumerating objects: 26, done. Counting objects: 100% (26/26), done. Delta compression using up to 16 threads Compressing objects: 100% (16/16), done. Writing objects: 100% (16/16), 9.57 KiB | 257.00 KiB/s, done. Total 16 (delta 13), reused 1 (delta 0), pack-reused 0 remote: Resolving deltas: 100% (13/13), completed with 10 local objects. remote: To github.com:OWNER/REPO.git + 4b688d63a46...4e452e9ad05 Apertyx0/stack/1 -> Apertyx0/stack/1 (forced update) + 855768e034f...301af1f1b9c Apertyx0/stack/2 -> Apertyx0/stack/2 (forced update) Updating remote branches Enumerating objects: 8, done. Counting objects: 100% (8/8), done. Delta compression using up to 16 threads Compressing objects: 100% (5/5), done. Writing objects: 100% (5/5), 5.54 KiB | 166.00 KiB/s, done. Total 5 (delta 2), reused 2 (delta 0), pack-reused 0 remote: Resolving deltas: 100% (2/2), completed with 2 local objects. remote: To github.com:OWNER/REPO.git + 4df29540eb9...67667050568 Apertyx0/stack/3 -> Apertyx0/stack/3 (forced update) ``` --- README.md | 5 +++++ src/stack_pr/cli.py | 23 ++++++++++++++++------- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ace3c48..9450c52 100644 --- a/README.md +++ b/README.md @@ -342,6 +342,7 @@ These arguments can be used with any subcommand: - `$USERNAME`: The username of the current user - `$BRANCH`: The current branch name - `$ID`: The location for the ID of the branch. The ID is determined by the order of creation of the branches. If `$ID` is not found in the template, the template will be appended with `/$ID`. +- `--max-branches-per-push`: Maximum number of branches to push to GitHub in one push operation (default: 5) ### Subcommands @@ -401,6 +402,9 @@ stack-pr config repo.reviewer=user1,user2 # Set custom branch name template stack-pr config repo.branch_name_template=$USERNAME/stack +# Set [maximum number of branches to push to Github in one operation](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/managing-repository-settings/managing-the-push-policy-for-your-repository) +stack-pr config repo.max_branches_per_push=5 + # Disable the land command (require GitHub web interface for merging) stack-pr config land.style=disable @@ -431,6 +435,7 @@ remote=origin target=main reviewer=GithubHandle1,GithubHandle2 branch_name_template=$USERNAME/$BRANCH +max_branches_per_push=5 [land] style=bottom-only ``` diff --git a/src/stack_pr/cli.py b/src/stack_pr/cli.py index cf52324..7122351 100755 --- a/src/stack_pr/cli.py +++ b/src/stack_pr/cli.py @@ -705,11 +705,12 @@ def init_local_branches( ) -def push_branches(st: list[StackEntry], remote: str, *, verbose: bool) -> None: - log(h("Updating remote branches"), level=2) - cmd = ["git", "push", "-f", remote] - cmd.extend([f"{e.head}:{e.head}" for e in st]) - run_shell_command(cmd, quiet=not verbose) +def push_branches(st: list[StackEntry], remote: str, *, verbose: bool, max_branches_per_push: int) -> None: + for i in range(0, len(st), max_branches_per_push): + log(h("Updating remote branches"), level=2) + cmd = ["git", "push", "-f", remote] + cmd.extend([f"{e.head}:{e.head}" for e in st[i:i+max_branches_per_push]]) + run_shell_command(cmd, quiet=not verbose) def print_cmd_failure_details(exc: SubprocessError) -> None: @@ -918,6 +919,7 @@ class CommonArgs: branch_name_template: str show_tips: bool land_disabled: bool + max_branches_per_push: int @classmethod def from_args(cls, args: argparse.Namespace, *, land_disabled: bool) -> CommonArgs: @@ -931,6 +933,7 @@ def from_args(cls, args: argparse.Namespace, *, land_disabled: bool) -> CommonAr args.branch_name_template, args.show_tips, land_disabled, + args.max_branches_per_push, ) @@ -1102,7 +1105,7 @@ def command_submit( reset_remote_base_branches(st, target=args.target, verbose=args.verbose) # Push local branches to remote - push_branches(st, remote=args.remote, verbose=args.verbose) + push_branches(st, remote=args.remote, verbose=args.verbose, max_branches_per_push=args.max_branches_per_push) # Now we have all the branches, so we can create the corresponding PRs log(h("Submitting PRs"), level=1) @@ -1128,7 +1131,7 @@ def command_submit( error(ERROR_CANT_UPDATE_META.format(**locals())) raise - push_branches(st, remote=args.remote, verbose=args.verbose) + push_branches(st, remote=args.remote, verbose=args.verbose, max_branches_per_push=args.max_branches_per_push) log(h("Adding cross-links to PRs"), level=1) add_cross_links(st, keep_body=keep_body, verbose=args.verbose) @@ -1575,6 +1578,12 @@ def create_argparser( default=config.getboolean("common", "show_tips", fallback=True), help="Show or hide usage tips after commands.", ) + common_parser.add_argument( + "--max-branches-per-push", + type=int, + default=config.getint("repo", "max_branches_per_push", fallback=5), + help="Maximum number of branches to push to GitHub in one push operation (default: 5).", + ) parser_submit = subparsers.add_parser( "submit",