rustc_commits.rs 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. use crate::db::rustc_commits;
  2. use crate::db::rustc_commits::get_missing_commits;
  3. use crate::jobs::Job;
  4. use crate::{
  5. github::{self, Event},
  6. handlers::Context,
  7. };
  8. use async_trait::async_trait;
  9. use std::collections::VecDeque;
  10. use tracing as log;
  11. const BORS_GH_ID: u64 = 3372342;
  12. pub async fn handle(ctx: &Context, event: &Event) -> anyhow::Result<()> {
  13. let body = match event.comment_body() {
  14. Some(v) => v,
  15. // Skip events that don't have comment bodies associated
  16. None => return Ok(()),
  17. };
  18. let event = if let Event::IssueComment(e) = event {
  19. if e.action != github::IssueCommentAction::Created {
  20. return Ok(());
  21. }
  22. e
  23. } else {
  24. return Ok(());
  25. };
  26. if !body.contains("Test successful") {
  27. return Ok(());
  28. }
  29. if event.comment.user.id != Some(BORS_GH_ID) {
  30. log::trace!("Ignoring non-bors comment, user: {:?}", event.comment.user);
  31. return Ok(());
  32. }
  33. let repo = event.issue.repository();
  34. if !(repo.organization == "rust-lang" && repo.repository == "rust") {
  35. return Ok(());
  36. }
  37. let start = "<!-- homu: ";
  38. let start = body.find(start).map(|s| s + start.len());
  39. let end = body.find(" -->");
  40. let (start, end) = if let (Some(start), Some(end)) = (start, end) {
  41. (start, end)
  42. } else {
  43. log::warn!("Unable to extract build completion from comment {:?}", body);
  44. return Ok(());
  45. };
  46. let bors: BorsMessage = match serde_json::from_str(&body[start..end]) {
  47. Ok(bors) => bors,
  48. Err(e) => {
  49. log::error!(
  50. "failed to parse build completion from {:?}: {:?}",
  51. &body[start..end],
  52. e
  53. );
  54. return Ok(());
  55. }
  56. };
  57. if bors.type_ != "BuildCompleted" {
  58. log::trace!("Not build completion? {:?}", bors);
  59. }
  60. if bors.base_ref != "master" {
  61. log::trace!("Ignoring bors merge, not on master");
  62. return Ok(());
  63. }
  64. synchronize_commits(ctx, &bors.merge_sha, event.issue.number.try_into().unwrap()).await;
  65. Ok(())
  66. }
  67. /// Fetch commits that are not present in the database.
  68. async fn synchronize_commits(ctx: &Context, sha: &str, pr: u32) {
  69. log::trace!("synchronize_commits for sha={:?}, pr={}", sha, pr);
  70. synchronize_commits_inner(ctx, Some((sha.to_owned(), pr))).await;
  71. }
  72. pub async fn synchronize_commits_inner(ctx: &Context, starter: Option<(String, u32)>) {
  73. let db = ctx.db.get().await;
  74. // List of roots to be resolved. Each root and its parents will be recursively resolved
  75. // until an existing commit is found.
  76. let mut to_be_resolved = VecDeque::new();
  77. if let Some((sha, pr)) = starter {
  78. to_be_resolved.push_back((sha.to_string(), Some(pr)));
  79. }
  80. to_be_resolved.extend(
  81. get_missing_commits(&db)
  82. .await
  83. .into_iter()
  84. .map(|c| (c, None::<u32>)),
  85. );
  86. log::info!("synchronize_commits for {:?}", to_be_resolved);
  87. let db = ctx.db.get().await;
  88. while let Some((sha, mut pr)) = to_be_resolved.pop_front() {
  89. let mut gc = match ctx.github.rust_commit(&sha).await {
  90. Some(c) => c,
  91. None => {
  92. log::error!("Could not find bors-reported sha: {:?}", sha);
  93. continue;
  94. }
  95. };
  96. let parent_sha = gc.parents.remove(0).sha;
  97. if pr.is_none() {
  98. if let Some(tail) = gc.commit.message.strip_prefix("Auto merge of #") {
  99. if let Some(end) = tail.find(' ') {
  100. if let Ok(number) = tail[..end].parse::<u32>() {
  101. pr = Some(number);
  102. }
  103. }
  104. }
  105. }
  106. let pr = match pr.take() {
  107. Some(number) => number,
  108. None => {
  109. log::warn!("Failed to find PR number for commit {}", sha);
  110. continue;
  111. }
  112. };
  113. let res = rustc_commits::record_commit(
  114. &db,
  115. rustc_commits::Commit {
  116. sha: gc.sha,
  117. parent_sha: parent_sha.clone(),
  118. time: gc.commit.author.date,
  119. pr: Some(pr),
  120. },
  121. )
  122. .await;
  123. match res {
  124. Ok(()) => {
  125. if !rustc_commits::has_commit(&db, &parent_sha).await {
  126. to_be_resolved.push_back((parent_sha, None))
  127. }
  128. }
  129. Err(e) => log::error!("Failed to record commit {:?}", e),
  130. }
  131. }
  132. }
  133. pub struct RustcCommitsJob;
  134. #[async_trait]
  135. impl Job for RustcCommitsJob {
  136. fn name(&self) -> &'static str {
  137. "rustc_commits"
  138. }
  139. async fn run(&self, ctx: &super::Context, _metadata: &serde_json::Value) -> anyhow::Result<()> {
  140. synchronize_commits_inner(ctx, None).await;
  141. Ok(())
  142. }
  143. }
  144. #[derive(Debug, serde::Deserialize)]
  145. struct BorsMessage {
  146. #[serde(rename = "type")]
  147. type_: String,
  148. base_ref: String,
  149. merge_sha: String,
  150. }