Skip to content
Merged
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
25 changes: 24 additions & 1 deletion acp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -493,6 +504,7 @@ def show_help():
print(" -a, --add Run 'git add .' before committing changes")
print(" -b, --body <text> Custom PR body message")
print(" -i, --interactive Show PR creation URL instead of creating PR")
print(" -r, --reviewers <users> 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")
Expand All @@ -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):
Expand Down Expand Up @@ -549,6 +562,7 @@ def create_pr(
merge_method="squash",
sync=False,
add=False,
reviewers=None,
):
"""Create a PR with staged changes."""
# Validate inputs
Expand Down Expand Up @@ -657,6 +671,7 @@ def create_pr(
body,
is_fork,
verbose,
reviewers,
)

# Handle merge options
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -794,6 +816,7 @@ def main():
merge_method=args.merge_method,
sync=args.sync,
add=args.add,
reviewers=args.reviewers,
)
else:
show_help()
Expand Down
110 changes: 110 additions & 0 deletions test_acp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down Expand Up @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand Down