Bitbucket: Add push_commands support when PR is updated (#2085)
Some checks are pending
Build-and-test / build-and-test (push) Waiting to run

* Add push_commands support when PR is updated

* code review
This commit is contained in:
sharoneyal 2025-10-29 18:03:38 +02:00 committed by GitHub
parent a84ba36cf4
commit 8bd134e30c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -83,16 +83,79 @@ def _get_username(data):
return "" return ""
async def _validate_time_from_last_commit_to_pr_update(data: dict) -> bool:
is_valid_push = False
try:
data_inner = data.get('data', {})
if not data_inner:
get_logger().error("No data found in the webhook payload")
return True
pull_request = data_inner.get('pullrequest', {})
commits_api = pull_request.get('links', {}).get('commits', {}).get('href')
if not commits_api:
return False
if not pull_request.get('updated_on'):
return False
bearer_token = context.get('bitbucket_bearer_token')
headers = {
'Authorization': f'Bearer {bearer_token}',
'Accept': 'application/json'
}
response = requests.get(commits_api, headers=headers)
if response.status_code != 200:
get_logger().warning(f"Bitbucket commits API returned {response.status_code} for {commits_api}")
return False
username =_get_username(data)
commits_data = response.json() or {}
values = commits_data.get('values') or []
if (not values or not isinstance(values, list) or not values[0].get('author') or not values[0]['author'].get('user')
or not values[0]['author']['user'].get('display_name')):
get_logger().warning("No commits returned for pull request or one of the required fields missing; skipping push validation",
artifact={'values': values})
return False
commit_username = commits_data['values'][0]['author']['user']['display_name']
if username != commit_username:
get_logger().warning(f"Mismatch in username {username} vs. commit_username {commit_username}")
return False
time_pr_updated = pull_request['updated_on']
time_last_commit = commits_data['values'][0]['date']
from datetime import datetime
ts1 = datetime.fromisoformat(time_pr_updated)
ts2 = datetime.fromisoformat(time_last_commit)
diff = (ts1 - ts2).total_seconds()
max_delta_seconds = 15
if diff > 0 and diff < max_delta_seconds:
is_valid_push = True
else:
get_logger().debug(f"Too much time passed since last commit",
artifact={'updated': time_pr_updated, 'last_commit': time_last_commit})
except Exception as e:
get_logger().exception(f"Failed to validate time difference between last commit and PR update",
artifact={'error': e, 'data': data})
return is_valid_push
async def _perform_commands_bitbucket(commands_conf: str, agent: PRAgent, api_url: str, log_context: dict, data: dict): async def _perform_commands_bitbucket(commands_conf: str, agent: PRAgent, api_url: str, log_context: dict, data: dict):
apply_repo_settings(api_url) apply_repo_settings(api_url)
if commands_conf == "pr_commands" and get_settings().config.disable_auto_feedback: # auto commands for PR, and auto feedback is disabled if commands_conf == "pr_commands" and get_settings().config.disable_auto_feedback: # auto commands for PR, and auto feedback is disabled
get_logger().info(f"Auto feedback is disabled, skipping auto commands for PR {api_url=}") get_logger().info(f"Auto feedback is disabled, skipping auto commands for PR {api_url=}")
return return
if commands_conf == "push_commands":
if not get_settings().get("bitbucket_app.handle_push_trigger"):
get_logger().info(
"Bitbucket push trigger handling disabled via config; skipping push commands")
return
if data.get("event", "") == "pullrequest:created": if data.get("event", "") == "pullrequest:created":
if not should_process_pr_logic(data): if not should_process_pr_logic(data):
return return
commands = get_settings().get(f"bitbucket_app.{commands_conf}", {}) commands = get_settings().get(f"bitbucket_app.{commands_conf}", {})
get_settings().set("config.is_auto_command", True) get_settings().set("config.is_auto_command", True)
if commands_conf == "push_commands":
is_valid_push = await _validate_time_from_last_commit_to_pr_update(data)
if not is_valid_push:
get_logger().info(f"Bitbucket skipping 'pullrequest:updated' for push commands")
return
for command in commands: for command in commands:
try: try:
split_command = command.split(" ") split_command = command.split(" ")
@ -215,11 +278,21 @@ async def handle_github_webhooks(background_tasks: BackgroundTasks, request: Req
log_context["event"] = "pull_request" log_context["event"] = "pull_request"
if pr_url: if pr_url:
with get_logger().contextualize(**log_context): with get_logger().contextualize(**log_context):
apply_repo_settings(pr_url)
if get_identity_provider().verify_eligibility("bitbucket", if get_identity_provider().verify_eligibility("bitbucket",
sender_id, pr_url) is not Eligibility.NOT_ELIGIBLE: sender_id, pr_url) is not Eligibility.NOT_ELIGIBLE:
if get_settings().get("bitbucket_app.pr_commands"): if get_settings().get("bitbucket_app.pr_commands"):
await _perform_commands_bitbucket("pr_commands", PRAgent(), pr_url, log_context, data) await _perform_commands_bitbucket("pr_commands", agent, pr_url, log_context, data)
elif event == "pullrequest:updated": # PR updated, might be from a push (we will validate this later)
pr_url = data["data"]["pullrequest"]["links"]["html"]["href"]
log_context["api_url"] = pr_url
log_context["event"] = "pull_request"
if pr_url:
with get_logger().contextualize(**log_context):
if get_identity_provider().verify_eligibility("bitbucket",
sender_id, pr_url) is not Eligibility.NOT_ELIGIBLE:
if get_settings().get("bitbucket_app.push_commands"):
await _perform_commands_bitbucket("push_commands", agent, pr_url, log_context, data)
elif event == "pullrequest:comment_created": elif event == "pullrequest:comment_created":
pr_url = data["data"]["pullrequest"]["links"]["html"]["href"] pr_url = data["data"]["pullrequest"]["links"]["html"]["href"]
log_context["api_url"] = pr_url log_context["api_url"] = pr_url