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
5 changes: 2 additions & 3 deletions Dockerfile
Comment thread
myakove marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ RUN systemd-machine-id-setup
RUN dnf --nodocs --setopt=install_weak_deps=False --disable-repo=fedora-cisco-openh264 -y install dnf-plugins-core \
&& dnf --nodocs --setopt=install_weak_deps=False --disable-repo=fedora-cisco-openh264 -y update \
&& dnf --nodocs --setopt=install_weak_deps=False --disable-repo=fedora-cisco-openh264 -y install \
gh \
git \
unzip \
gcc \
Expand Down Expand Up @@ -71,9 +72,7 @@ RUN set -ex \
&& curl --fail -vL https://mirror.openshift.com/pub/openshift-v4/clients/rosa/latest/rosa-linux.tar.gz | tar -C $BIN_DIR -xzvf - rosa \
&& chmod +x $BIN_DIR/rosa \
&& curl --fail -vL https://github.com/regclient/regclient/releases/latest/download/regctl-linux-amd64 -o $BIN_DIR/regctl \
&& chmod +x $BIN_DIR/regctl \
&& curl --fail -vL https://github.com/mislav/hub/releases/download/v2.14.2/hub-linux-amd64-2.14.2.tgz | tar --wildcards --strip-components=2 -C $BIN_DIR -xzvf - '*/bin/hub' \
&& chmod +x $BIN_DIR/hub
&& chmod +x $BIN_DIR/regctl

# Copy dependency manifests first for uv sync cache stability
COPY --chown=$USERNAME:$USERNAME pyproject.toml uv.lock README.md $APP_DIR/
Expand Down
69 changes: 60 additions & 9 deletions webhook_server/libs/handlers/runner_handler.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import asyncio
import contextlib
import os
import re
import shlex
import shutil
Expand Down Expand Up @@ -714,20 +715,28 @@ async def cherry_pick(

async with self._checkout_worktree(pull_request=pull_request) as (success, worktree_path, out, err):
git_cmd = f"git --work-tree={worktree_path} --git-dir={worktree_path}/.git"
hub_cmd = f"GITHUB_TOKEN={github_token} hub --work-tree={worktree_path} --git-dir={worktree_path}/.git"
assignee_flag = f" -a {shlex.quote(pr_author)}" if assign_to_pr_owner else ""
commands: list[str] = [
assignee_flag = f" --assignee {shlex.quote(pr_author)}" if assign_to_pr_owner else ""
pr_title = f"{CHERRY_PICKED_LABEL}: [{target_branch}] {commit_msg_striped}"
pr_body = (
f"Cherry-pick from `{source_branch}` branch, original PR: {pull_request_url}, PR owner: {pr_author}"
)
repo_full_name = self.github_webhook.repository_full_name
git_commands: list[str] = [
f"{git_cmd} checkout {target_branch}",
f"{git_cmd} pull origin {target_branch}",
f"{git_cmd} checkout -b {new_branch_name} origin/{target_branch}",
f"{git_cmd} cherry-pick {commit_hash}",
f"{git_cmd} push origin {new_branch_name}",
f'bash -c "{hub_cmd} pull-request -b {target_branch} '
f"-h {new_branch_name} -l {CHERRY_PICKED_LABEL} {assignee_flag} "
f"-m '{CHERRY_PICKED_LABEL}: [{target_branch}] "
f"{commit_msg_striped}' -m 'Cherry-pick from `{source_branch}` branch, "
f"original PR: {pull_request_url}, PR owner: {pr_author}'\"",
]
gh_pr_command = (
f"gh pr create --repo {shlex.quote(repo_full_name)}"
f" --base {shlex.quote(target_branch)}"
f" --head {shlex.quote(new_branch_name)}"
f" --label {shlex.quote(CHERRY_PICKED_LABEL)}"
f"{assignee_flag}"
f" --title {shlex.quote(pr_title)}"
f" --body {shlex.quote(pr_body)}"
)

output: CheckRunOutput = {
"title": "Cherry-pick details",
Expand All @@ -737,8 +746,9 @@ async def cherry_pick(
if not success:
output["text"] = self.check_run_handler.get_check_run_text(out=out, err=err)
await self.check_run_handler.set_check_failure(name=CHERRY_PICKED_LABEL, output=output)
return

for cmd in commands:
for cmd in git_commands:
rc, out, err = await run_command(
command=cmd,
log_prefix=self.log_prefix,
Expand Down Expand Up @@ -776,6 +786,47 @@ async def cherry_pick(
)
return

# Run gh pr create with GH_TOKEN passed via env (not command prefix)
# Each subprocess gets its own env copy, safe for parallel execution
rc, out, err = await run_command(
command=gh_pr_command,
log_prefix=self.log_prefix,
redact_secrets=[github_token],
mask_sensitive=self.github_webhook.mask_sensitive,
env={**os.environ, "GH_TOKEN": github_token},
)
if not rc:
output["text"] = self.check_run_handler.get_check_run_text(err=err, out=out)
await self.check_run_handler.set_check_failure(name=CHERRY_PICKED_LABEL, output=output)
await asyncio.to_thread(
pull_request.create_issue_comment,
f"**Cherry-pick branch created, but PR creation failed**\n"
f"Branch `{new_branch_name}` was pushed to the repository.\n"
f"Create the PR manually:\n"
"```\n"
f"gh pr create --repo {repo_full_name}"
f" --base {target_branch}"
f" --head {new_branch_name}"
f" --label {CHERRY_PICKED_LABEL}"
f" --title '{pr_title}'"
f" --body '{pr_body}'\n"
"```",
)
redacted_out = _redact_secrets(
out,
[github_token],
mask_sensitive=self.github_webhook.mask_sensitive,
)
redacted_err = _redact_secrets(
err,
[github_token],
mask_sensitive=self.github_webhook.mask_sensitive,
)
self.logger.error(
f"{self.log_prefix} Cherry pick PR creation failed: {redacted_out} --- {redacted_err}"
)
return
Comment thread
coderabbitai[bot] marked this conversation as resolved.

output["text"] = self.check_run_handler.get_check_run_text(err=err, out=out)

await self.check_run_handler.set_check_success(name=CHERRY_PICKED_LABEL, output=output)
Expand Down
16 changes: 8 additions & 8 deletions webhook_server/tests/test_runner_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1263,8 +1263,9 @@ async def test_cherry_pick_assigns_pr_author(self, runner_handler: RunnerHandler
mocks.comment.assert_called_once()
assert mocks.to_thread.call_count == 3
last_cmd = mocks.run_cmd.call_args_list[-1]
hub_command = last_cmd.kwargs.get("command", last_cmd.args[0] if last_cmd.args else "")
assert "-a 'test-pr-author'" in hub_command or "-a test-pr-author" in hub_command
gh_command = last_cmd.kwargs.get("command", last_cmd.args[0] if last_cmd.args else "")
assert "--assignee" in gh_command
assert "test-pr-author" in gh_command

@pytest.mark.asyncio
async def test_cherry_pick_requested_by_uses_pr_owner(
Expand All @@ -1277,12 +1278,11 @@ async def test_cherry_pick_requested_by_uses_pr_owner(
mocks.set_success.assert_called_once()
mocks.comment.assert_called_once()
last_cmd = mocks.run_cmd.call_args_list[-1]
hub_command = last_cmd.kwargs.get("command", last_cmd.args[0] if last_cmd.args else "")
expected_msg = (
f"Cherry-pick from `main` branch, original PR: {mock_pull_request.html_url}, PR owner: test-pr-author"
)
assert expected_msg in hub_command
assert "-a 'test-pr-author'" in hub_command or "-a test-pr-author" in hub_command
gh_command = last_cmd.kwargs.get("command", last_cmd.args[0] if last_cmd.args else "")
assert "Cherry-pick from" in gh_command
assert mock_pull_request.html_url in gh_command
assert "test-pr-author" in gh_command
assert "--assignee" in gh_command
assert mocks.to_thread.call_count == 3

@pytest.mark.asyncio
Expand Down