Skip to content

Commit f870632

Browse files
committed
Add handler for push event
1 parent cfbf7bc commit f870632

File tree

4 files changed

+239
-4
lines changed

4 files changed

+239
-4
lines changed

site/src/api.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,12 +394,51 @@ pub mod github {
394394
pub number: u32,
395395
pub comments_url: String,
396396
pub repository_url: String,
397+
pub labels: Vec<Label>,
398+
}
399+
400+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
401+
pub struct Label {
402+
pub name: String,
397403
}
398404

399405
#[derive(Debug, Clone, Serialize, Deserialize)]
400406
#[serde(untagged)]
401407
pub enum Request {
402408
Issue { issue: Issue, comment: Comment },
409+
Push(Push),
410+
}
411+
412+
#[derive(Debug, Clone, Serialize, Deserialize)]
413+
pub struct Push {
414+
pub r#ref: String,
415+
pub sender: Sender,
416+
pub committer: Committer,
417+
pub head_commit: HeadCommit,
418+
pub before: String,
419+
pub commits: Vec<Commit>,
420+
}
421+
422+
#[derive(Debug, Clone, Serialize, Deserialize)]
423+
pub struct Commit {
424+
#[serde(rename = "id")]
425+
pub sha: String,
426+
pub message: String,
427+
}
428+
429+
#[derive(Debug, Clone, Serialize, Deserialize)]
430+
pub struct HeadCommit {
431+
pub message: String,
432+
}
433+
434+
#[derive(Debug, Clone, Serialize, Deserialize)]
435+
pub struct Sender {
436+
pub login: String,
437+
}
438+
439+
#[derive(Debug, Clone, Serialize, Deserialize)]
440+
pub struct Committer {
441+
pub username: String,
403442
}
404443

405444
#[derive(Debug, Clone, Serialize, Deserialize)]

site/src/github.rs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,86 @@ pub async fn create_pr(
296296
Ok(response.json().await.context("deserializing failed")?)
297297
}
298298

299+
#[derive(serde::Serialize)]
300+
struct UpdateBranchRequest<'a> {
301+
sha: &'a str,
302+
force: bool,
303+
}
304+
305+
pub async fn update_branch(
306+
client: &reqwest::Client,
307+
ctxt: &SiteCtxt,
308+
repository_url: &str,
309+
branch: &str,
310+
sha: &str,
311+
) -> anyhow::Result<()> {
312+
let timer_token = ctxt
313+
.config
314+
.keys
315+
.github_api_token
316+
.clone()
317+
.expect("needs github API token");
318+
let url = format!("{}/git/refs/{}", repository_url, branch);
319+
let commit_response = client
320+
.patch(&url)
321+
.json(&UpdateBranchRequest { sha, force: true })
322+
.header(USER_AGENT, "perf-rust-lang-org-server")
323+
.basic_auth("rust-timer", Some(timer_token))
324+
.send()
325+
.await
326+
.context("PATCH git/refs failed")?;
327+
if commit_response.status() != reqwest::StatusCode::OK {
328+
anyhow::bail!("{:?} != 200 OK", commit_response.status());
329+
}
330+
331+
Ok(())
332+
}
333+
334+
#[derive(serde::Serialize)]
335+
struct MergeBranchRequest<'a> {
336+
base: &'a str,
337+
head: &'a str,
338+
commit_message: &'a str,
339+
}
340+
#[derive(serde::Deserialize)]
341+
struct MergeBranchResponse {
342+
sha: String,
343+
}
344+
345+
pub async fn merge_branch(
346+
client: &reqwest::Client,
347+
ctxt: &SiteCtxt,
348+
repository_url: &str,
349+
branch: &str,
350+
sha: &str,
351+
commit_message: &str,
352+
) -> anyhow::Result<String> {
353+
let timer_token = ctxt
354+
.config
355+
.keys
356+
.github_api_token
357+
.clone()
358+
.expect("needs github API token");
359+
let url = format!("{}/merges", repository_url);
360+
let response = client
361+
.patch(&url)
362+
.json(&MergeBranchRequest {
363+
base: branch,
364+
head: sha,
365+
commit_message,
366+
})
367+
.header(USER_AGENT, "perf-rust-lang-org-server")
368+
.basic_auth("rust-timer", Some(timer_token))
369+
.send()
370+
.await
371+
.context("POST git/commits failed")?;
372+
if !response.status().is_success() {
373+
anyhow::bail!("{:?} != 201 CREATED", response.status());
374+
}
375+
376+
Ok(response.json::<MergeBranchResponse>().await?.sha)
377+
}
378+
299379
#[derive(serde::Serialize)]
300380
struct CreateCommitRequest<'a> {
301381
message: &'a str,
@@ -346,6 +426,30 @@ pub async fn create_commit(
346426
.sha)
347427
}
348428

429+
pub async fn get_issue(
430+
client: &reqwest::Client,
431+
ctxt: &SiteCtxt,
432+
repository_url: &str,
433+
number: u64,
434+
) -> anyhow::Result<Issue> {
435+
let timer_token = ctxt
436+
.config
437+
.keys
438+
.github_api_token
439+
.clone()
440+
.expect("needs github API token");
441+
let url = format!("{}/issues/{}", repository_url, number);
442+
let response = client
443+
.get(&url)
444+
.header(USER_AGENT, "perf-rust-lang-org-server")
445+
.basic_auth("rust-timer", Some(timer_token))
446+
.send()
447+
.await
448+
.context("cannot get commit")?;
449+
450+
Ok(response.json().await?)
451+
}
452+
349453
pub async fn get_commit(
350454
client: &reqwest::Client,
351455
ctxt: &SiteCtxt,

site/src/request_handlers/github.rs

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::api::{github, ServerResult};
22
use crate::github::{
3-
branch_for_rollup, enqueue_sha, get_authorized_users, parse_homu_comment, post_comment,
4-
pr_and_try_for_rollup,
3+
branch_for_rollup, enqueue_sha, get_authorized_users, get_commit, get_issue, merge_branch,
4+
parse_homu_comment, post_comment, pr_and_try_for_rollup, update_branch,
55
};
66
use crate::load::SiteCtxt;
77

@@ -10,6 +10,8 @@ use std::sync::Arc;
1010
use regex::{Captures, Regex};
1111

1212
lazy_static::lazy_static! {
13+
static ref ROLLUP_PR_NUMBER: Regex =
14+
Regex::new(r#"^Auto merge of #(\d+)"#).unwrap();
1315
static ref BODY_TRY_COMMIT: Regex =
1416
Regex::new(r#"(?:\W|^)@rust-timer\s+build\s+(\w+)(?:\W|$)(?:include=(\S+))?\s*(?:exclude=(\S+))?\s*(?:runs=(\d+))?"#).unwrap();
1517
static ref BODY_QUEUE: Regex =
@@ -27,9 +29,100 @@ pub async fn handle_github(
2729
log::info!("handle_github({:?})", request);
2830
match request {
2931
github::Request::Issue { issue, comment } => handle_issue(ctxt, issue, comment).await,
32+
github::Request::Push(p) => handle_push(ctxt, p).await,
3033
}
3134
}
3235

36+
async fn handle_push(ctxt: Arc<SiteCtxt>, push: github::Push) -> ServerResult<github::Response> {
37+
let client = reqwest::Client::new();
38+
let repository_url = "https://github.com/rust-lang-ci/rust";
39+
if push.r#ref != "refs/heads/master"
40+
|| !is_rollup(&client, &ctxt, repository_url, &push).await?
41+
{
42+
return Ok(github::Response);
43+
}
44+
45+
let previous_master = &push.before;
46+
let rollup_merges = push
47+
.commits
48+
.iter()
49+
.rev()
50+
.skip(1) // skip the head commit
51+
.take_while(|c| c.message.starts_with("Rollup merge of "))
52+
.map(|c| &c.sha);
53+
54+
for rollup_merge in rollup_merges {
55+
// Fetch the rollup merge commit which should have two parents.
56+
// The first parent is in the chain of rollup merge commits all the way back to `previous_master`.
57+
// The second parent is the head of the PR that was rolled up. We want the second parent.
58+
let commit = get_commit(&client, &ctxt, repository_url, &rollup_merge)
59+
.await
60+
.map_err(|e| format!("Error getting rollup merge commit: {e:?}"))?;
61+
assert!(
62+
commit.parents.len() == 2,
63+
"What we thought was a merge commit was not a merge commit"
64+
);
65+
let rolled_up_head = &commit.parents[1].sha;
66+
67+
// Reset perf-tmp to the previous master
68+
update_branch(&client, &ctxt, repository_url, "perf-tmp", previous_master)
69+
.await
70+
.map_err(|e| format!("Error updating perf-tmp with previous master: {e:?}"))?;
71+
72+
// Merge in the rolled up PR's head commit into the previous master
73+
let sha = merge_branch(
74+
&client,
75+
&ctxt,
76+
repository_url,
77+
"perf-tmp",
78+
rolled_up_head,
79+
"merge",
80+
)
81+
.await
82+
.map_err(|e| format!("Error merging commit into perf-tmp: {e:?}"))?;
83+
84+
// Force the `try-perf` branch to point to what the perf-tmp branch points to
85+
update_branch(&client, &ctxt, repository_url, "try-perf", &sha)
86+
.await
87+
.map_err(|e| format!("Error updating the try-perf branch: {e:?}"))?;
88+
89+
// Wait to ensure there's enough time for GitHub to checkout these changes before they are overwritten
90+
tokio::time::sleep(std::time::Duration::from_secs(15)).await
91+
}
92+
Ok(github::Response)
93+
}
94+
95+
async fn is_rollup(
96+
client: &reqwest::Client,
97+
ctxt: &SiteCtxt,
98+
repository_url: &str,
99+
push: &github::Push,
100+
) -> ServerResult<bool> {
101+
macro_rules! get {
102+
($x:expr) => {
103+
match $x {
104+
Some(x) => x,
105+
None => return Ok(false),
106+
}
107+
};
108+
}
109+
let is_bors = push.sender.login == "bors"
110+
&& push.committer.username == "bors"
111+
&& push.head_commit.message.starts_with("Auto merge of");
112+
113+
if !is_bors {
114+
return Ok(false);
115+
}
116+
let captures = get!(ROLLUP_PR_NUMBER.captures(&push.head_commit.message));
117+
let number = get!(get!(captures.get(0)).as_str().parse::<u64>().ok());
118+
119+
let issue = get_issue(client, ctxt, repository_url, number)
120+
.await
121+
.map_err(|e| format!("Error fetching PR #{number} {e:?}"))?;
122+
123+
Ok(issue.labels.iter().any(|l| l.name == "rollup"))
124+
}
125+
33126
async fn handle_issue(
34127
ctxt: Arc<SiteCtxt>,
35128
issue: github::Issue,
@@ -142,7 +235,6 @@ fn extract_update_pr_for(body: &str) -> impl Iterator<Item = &str> + '_ {
142235

143236
fn extract_rollup_merge(capture: Captures) -> Option<&str> {
144237
capture.get(1).map(|c| {
145-
println!("{}", c.as_str());
146238
c.as_str()
147239
.trim_start_matches("https://github.com/rust-lang/rust/commit/")
148240
})

site/src/server.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,7 @@ async fn serve_req(server: Server, req: Request) -> Result<Response, ServerError
464464
}
465465
};
466466
match event.as_str() {
467-
"issue_comment" => Ok(to_response(
467+
"issue_comment" | "pushes" => Ok(to_response(
468468
request_handlers::handle_github(check!(parse_body(&body)), ctxt.clone()).await,
469469
&compression,
470470
)),

0 commit comments

Comments
 (0)