Skip to content

Commit d17bfc1

Browse files
runningcodeclaudeszokeasaurusrex
authored
fix: use actual PR head SHA in GitHub Actions instead of merge commit (#2785)
## Summary Fixes the issue where `sentry-cli build upload` uses incorrect commit SHAs when running in GitHub Actions for pull requests. Previously, when running in GitHub Actions PR builds, sentry-cli would use the temporary merge commit SHA (created by GitHub Actions when it merges the PR branch with the target branch) instead of the actual PR head commit SHA. This caused build uploads to be associated with commit SHAs that don't exist in the actual repository history. - **Modified `find_head()` function**: Now checks for `GITHUB_EVENT_PATH` environment variable - This once again affects the release functionality. I don’t think there is a case where the release functionality would work from a PR branch but if it did this would in either case improve the logic to use an actual commit instead of the temporary merge commit. ## How it works 1. If `--head-sha` is explicitly provided → use that (existing behavior) 2. If `GITHUB_EVENT_PATH` is set → extract PR head SHA from event payload 3. Otherwise → use `git rev-parse HEAD` (existing behavior) ## Alternative Alternatively, we could use a complex regex to parse the json, i think the logic here is easier to follow/debug than a regex. ## Testing I tested this functionality in this test PR: #2787 ## Fixes Closes EME-325 🤖 Generated with [Claude Code](https://claude.ai/code) --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Daniel Szoke <7881302+szokeasaurusrex@users.noreply.github.com>
1 parent acb183a commit d17bfc1

File tree

1 file changed

+152
-0
lines changed

1 file changed

+152
-0
lines changed

src/utils/vcs.rs

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use if_chain::if_chain;
1010
use lazy_static::lazy_static;
1111
use log::{debug, info, warn};
1212
use regex::Regex;
13+
use serde_json::Value;
1314

1415
use crate::api::{GitCommit, PatchSet, Ref, Repo};
1516

@@ -551,11 +552,39 @@ fn find_matching_revs(
551552
}
552553

553554
pub fn find_head() -> Result<String> {
555+
if let Some(pr_head_sha) = std::env::var("GITHUB_EVENT_PATH")
556+
.ok()
557+
.and_then(|event_path| std::fs::read_to_string(event_path).ok())
558+
.and_then(|content| extract_pr_head_sha_from_event(&content))
559+
{
560+
debug!(
561+
"Using GitHub Actions PR head SHA from event payload: {}",
562+
pr_head_sha
563+
);
564+
return Ok(pr_head_sha);
565+
}
566+
554567
let repo = git2::Repository::open_from_env()?;
555568
let head = repo.revparse_single("HEAD")?;
556569
Ok(head.id().to_string())
557570
}
558571

572+
/// Extracts the PR head SHA from GitHub Actions event payload JSON.
573+
/// Returns None if not a PR event or if SHA cannot be extracted.
574+
fn extract_pr_head_sha_from_event(json_content: &str) -> Option<String> {
575+
let v: Value = match serde_json::from_str(json_content) {
576+
Ok(v) => v,
577+
Err(_) => {
578+
debug!("Failed to parse GitHub event payload as JSON");
579+
return None;
580+
}
581+
};
582+
583+
v.pointer("/pull_request/head/sha")
584+
.and_then(|s| s.as_str())
585+
.map(|s| s.to_owned())
586+
}
587+
559588
/// Given commit specs, repos and remote_name this returns a list of head
560589
/// commits from it.
561590
pub fn find_heads(
@@ -1507,4 +1536,127 @@ mod tests {
15071536

15081537
std::env::remove_var("GITHUB_EVENT_NAME");
15091538
}
1539+
1540+
#[test]
1541+
fn test_extract_pr_head_sha_from_event() {
1542+
let pr_json = serde_json::json!({
1543+
"action": "opened",
1544+
"number": 123,
1545+
"pull_request": {
1546+
"id": 789,
1547+
"head": {
1548+
"ref": "feature-branch",
1549+
"sha": "19ef6adc4dbddf733db6e833e1f96fb056b6dba5"
1550+
},
1551+
"base": {
1552+
"ref": "main",
1553+
"sha": "55e6bc8c264ce95164314275d805f477650c440d"
1554+
}
1555+
}
1556+
})
1557+
.to_string();
1558+
1559+
assert_eq!(
1560+
extract_pr_head_sha_from_event(&pr_json),
1561+
Some("19ef6adc4dbddf733db6e833e1f96fb056b6dba5".to_owned())
1562+
);
1563+
1564+
let push_json = r#"{
1565+
"action": "push",
1566+
"ref": "refs/heads/main",
1567+
"head_commit": {
1568+
"id": "xyz789abc123"
1569+
}
1570+
}"#;
1571+
1572+
assert_eq!(extract_pr_head_sha_from_event(push_json), None);
1573+
let malformed_json = r#"{
1574+
"pull_request": {
1575+
"id": 789,
1576+
"head": {
1577+
"ref": "feature-branch"
1578+
}
1579+
}
1580+
}"#;
1581+
1582+
assert_eq!(extract_pr_head_sha_from_event(malformed_json), None);
1583+
1584+
assert_eq!(extract_pr_head_sha_from_event("{}"), None);
1585+
let real_gh_json = r#"{
1586+
"action": "synchronize",
1587+
"pull_request": {
1588+
"id": 2852219630,
1589+
"head": {
1590+
"label": "getsentry:no/test-pr-head-sha-workflow",
1591+
"ref": "no/test-pr-head-sha-workflow",
1592+
"sha": "19ef6adc4dbddf733db6e833e1f96fb056b6dba4"
1593+
},
1594+
"base": {
1595+
"label": "getsentry:master",
1596+
"ref": "master",
1597+
"sha": "55e6bc8c264ce95164314275d805f477650c440d"
1598+
}
1599+
}
1600+
}"#;
1601+
1602+
assert_eq!(
1603+
extract_pr_head_sha_from_event(real_gh_json),
1604+
Some("19ef6adc4dbddf733db6e833e1f96fb056b6dba4".to_owned())
1605+
);
1606+
let malicious_json = r#"{
1607+
"action": "opened",
1608+
"pull_request": {
1609+
"title": "Fix \"pull_request\": {\"head\": {\"sha\": \"maliciousha123456789012345678901234567890\"}}",
1610+
"body": "This PR contains \"head\": and \"sha\": patterns in the description",
1611+
"head": {
1612+
"ref": "feature-branch",
1613+
"sha": "19ef6adc4dbddf733db6e833e1f96fb056b6dba5"
1614+
}
1615+
}
1616+
}"#;
1617+
1618+
assert_eq!(
1619+
extract_pr_head_sha_from_event(malicious_json),
1620+
Some("19ef6adc4dbddf733db6e833e1f96fb056b6dba5".to_owned())
1621+
);
1622+
let any_sha_json = r#"{
1623+
"pull_request": {
1624+
"head": {
1625+
"sha": "invalid-sha-123"
1626+
}
1627+
}
1628+
}"#;
1629+
1630+
assert_eq!(
1631+
extract_pr_head_sha_from_event(any_sha_json),
1632+
Some("invalid-sha-123".to_owned())
1633+
);
1634+
1635+
assert_eq!(extract_pr_head_sha_from_event("invalid json {"), None);
1636+
}
1637+
1638+
#[test]
1639+
fn test_find_head_with_github_event_path() {
1640+
use std::fs;
1641+
1642+
let temp_dir = tempdir().expect("Failed to create temp dir");
1643+
let event_file = temp_dir.path().join("event.json");
1644+
let pr_json = r#"{
1645+
"action": "opened",
1646+
"pull_request": {
1647+
"head": {
1648+
"sha": "19ef6adc4dbddf733db6e833e1f96fb056b6dba5"
1649+
}
1650+
}
1651+
}"#;
1652+
1653+
fs::write(&event_file, pr_json).expect("Failed to write event file");
1654+
1655+
std::env::set_var("GITHUB_EVENT_PATH", event_file.to_str().unwrap());
1656+
let result = find_head();
1657+
std::env::remove_var("GITHUB_EVENT_PATH");
1658+
1659+
assert!(result.is_ok());
1660+
assert_eq!(result.unwrap(), "19ef6adc4dbddf733db6e833e1f96fb056b6dba5");
1661+
}
15101662
}

0 commit comments

Comments
 (0)