From 24fe08f65feabc906f852748a1f26dd3453abf89 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Mon, 16 Feb 2026 20:25:48 +0300 Subject: [PATCH] feat: add new --reviewers flag to acp --- acp.py | 25 +++++++++++- test_acp.py | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+), 1 deletion(-) diff --git a/acp.py b/acp.py index 2bca377..eca749a 100755 --- a/acp.py +++ b/acp.py @@ -206,7 +206,14 @@ def build_compare_url(upstream_repo, fork_repo, temp_branch, is_fork): def create_github_pr( - upstream_repo, fork_repo, temp_branch, commit_message, body, is_fork, verbose + upstream_repo, + fork_repo, + temp_branch, + commit_message, + body, + is_fork, + verbose, + reviewers=None, ): """Create a GitHub PR using gh CLI. @@ -234,6 +241,10 @@ def create_github_pr( else: gh_cmd.extend(["--head", temp_branch]) + # Add reviewers if specified + if reviewers: + gh_cmd.extend(["--reviewer", reviewers]) + pr_url = run(gh_cmd, quiet=True) if verbose: @@ -493,6 +504,7 @@ def show_help(): print(" -a, --add Run 'git add .' before committing changes") print(" -b, --body Custom PR body message") print(" -i, --interactive Show PR creation URL instead of creating PR") + print(" -r, --reviewers Comma-separated list of GitHub usernames") print(" -v, --verbose Show detailed output") print(" --merge Merge PR immediately after creation") print(" --auto-merge Enable GitHub auto-merge after PR creation") @@ -506,6 +518,7 @@ def show_help(): print("Examples:") print(' acp pr "fix: some typo" -i') print(' acp pr "fix: urgent" -b "Closes issue #123" --merge --merge-method rebase') + print(' acp pr "feat: new feature" -r vbvictor,octodad') def strip_branch_prefix(branch): @@ -549,6 +562,7 @@ def create_pr( merge_method="squash", sync=False, add=False, + reviewers=None, ): """Create a PR with staged changes.""" # Validate inputs @@ -657,6 +671,7 @@ def create_pr( body, is_fork, verbose, + reviewers, ) # Handle merge options @@ -761,6 +776,13 @@ def main(): action="store_true", help="Run 'git add .' before committing changes", ) + parser.add_argument( + "-r", + "--reviewers", + type=str, + default=None, + help="Comma-separated list of GitHub usernames to request reviews from", + ) parser.add_argument("-h", "--help", action="store_true", help=argparse.SUPPRESS) parser.add_argument("--version", action="store_true", help=argparse.SUPPRESS) @@ -794,6 +816,7 @@ def main(): merge_method=args.merge_method, sync=args.sync, add=args.add, + reviewers=args.reviewers, ) else: show_help() diff --git a/test_acp.py b/test_acp.py index 0119fec..5d672dd 100644 --- a/test_acp.py +++ b/test_acp.py @@ -717,6 +717,71 @@ def subprocess_side_effect(*args, **kwargs): assert "Failed to enable auto-merge" in captured.err assert "auto-merge is not enabled" in captured.err + @mock.patch("subprocess.run") + @mock.patch("acp.run_interactive") + @mock.patch("acp.run") + @mock.patch("acp.run_check") + def test_create_pr_with_reviewers( + self, mock_run_check, mock_run, mock_run_interactive, mock_subprocess + ): + """Test PR creation with reviewers.""" + mock_run_check.side_effect = [False, True] + + # No upstream remote + mock_subprocess.return_value = mock.Mock(returncode=1, stdout="") + + mock_run.side_effect = [ + "main", # current branch + "testuser", # gh username + "git@github.com:user/repo.git", # origin + None, # git checkout -b + # git commit now uses run_interactive, not run + # git push now uses run_interactive, not run + None, # git checkout original + "https://github.com/user/repo/pull/1", # gh pr create + ] + + acp.create_pr( + "test commit", verbose=False, body="", reviewers="vbvictor,octodad" + ) + + # Verify PR was created with reviewers + calls = mock_run.call_args_list + pr_create_call = calls[5] # gh pr create call + assert "--reviewer" in str(pr_create_call) + assert "vbvictor,octodad" in str(pr_create_call) + + @mock.patch("subprocess.run") + @mock.patch("acp.run_interactive") + @mock.patch("acp.run") + @mock.patch("acp.run_check") + def test_create_pr_without_reviewers( + self, mock_run_check, mock_run, mock_run_interactive, mock_subprocess + ): + """Test PR creation without reviewers (default behavior).""" + mock_run_check.side_effect = [False, True] + + # No upstream remote + mock_subprocess.return_value = mock.Mock(returncode=1, stdout="") + + mock_run.side_effect = [ + "main", # current branch + "testuser", # gh username + "git@github.com:user/repo.git", # origin + None, # git checkout -b + # git commit now uses run_interactive, not run + # git push now uses run_interactive, not run + None, # git checkout original + "https://github.com/user/repo/pull/1", # gh pr create + ] + + acp.create_pr("test commit", verbose=False, body="") + + # Verify PR was created without reviewers + calls = mock_run.call_args_list + pr_create_call = calls[5] # gh pr create call + assert "--reviewer" not in str(pr_create_call) + class TestAddFlag: """Test the --add flag functionality.""" @@ -843,6 +908,7 @@ def test_valid_pr_command(self, mock_run_check, mock_create_pr): merge_method="squash", sync=False, add=False, + reviewers=None, ) @mock.patch("acp.create_pr") @@ -860,6 +926,7 @@ def test_verbose_flag(self, mock_create_pr): merge_method="squash", sync=False, add=False, + reviewers=None, ) @mock.patch("acp.create_pr") @@ -877,6 +944,7 @@ def test_merge_flag(self, mock_create_pr): merge_method="squash", sync=False, add=False, + reviewers=None, ) @mock.patch("acp.create_pr") @@ -894,6 +962,7 @@ def test_auto_merge_flag(self, mock_create_pr): merge_method="squash", sync=False, add=False, + reviewers=None, ) @mock.patch("acp.create_pr") @@ -913,6 +982,7 @@ def test_merge_method_flag(self, mock_create_pr): merge_method="rebase", sync=False, add=False, + reviewers=None, ) @mock.patch("acp.create_pr") @@ -930,6 +1000,7 @@ def test_add_flag(self, mock_create_pr): merge_method="squash", sync=False, add=True, + reviewers=None, ) @mock.patch("acp.create_pr") @@ -947,6 +1018,45 @@ def test_add_flag_short(self, mock_create_pr): merge_method="squash", sync=False, add=True, + reviewers=None, + ) + + @mock.patch("acp.create_pr") + def test_reviewers_flag(self, mock_create_pr): + """Test --reviewers flag is passed.""" + with mock.patch.object( + sys, "argv", ["acp", "pr", "test", "--reviewers", "vbvictor,octodad"] + ): + acp.main() + mock_create_pr.assert_called_once_with( + "test", + verbose=False, + body="", + interactive=False, + merge=False, + auto_merge=False, + merge_method="squash", + sync=False, + add=False, + reviewers="vbvictor,octodad", + ) + + @mock.patch("acp.create_pr") + def test_reviewers_flag_short(self, mock_create_pr): + """Test -r flag is passed.""" + with mock.patch.object(sys, "argv", ["acp", "pr", "test", "-r", "user1"]): + acp.main() + mock_create_pr.assert_called_once_with( + "test", + verbose=False, + body="", + interactive=False, + merge=False, + auto_merge=False, + merge_method="squash", + sync=False, + add=False, + reviewers="user1", ) @mock.patch("subprocess.run")