|
| 1 | +use crate::github::{GithubClient, GithubCommit, Issue, IssuesEvent, Repository}; |
| 2 | +use tracing as log; |
| 3 | + |
| 4 | +/// Default threshold for parent commit age in days to trigger a warning |
| 5 | +pub const DEFAULT_PARENT_AGE_THRESHOLD: usize = 14; |
| 6 | + |
| 7 | +/// Check if the PR is based on an old parent commit |
| 8 | +pub async fn behind_upstream( |
| 9 | + age_threshold: usize, |
| 10 | + event: &IssuesEvent, |
| 11 | + client: &GithubClient, |
| 12 | +) -> Option<String> { |
| 13 | + if !event.issue.is_pr() { |
| 14 | + return None; |
| 15 | + } |
| 16 | + |
| 17 | + log::debug!("Checking if PR #{} is behind upstream", event.issue.number); |
| 18 | + |
| 19 | + // First try the parent commit age check as it's more accurate |
| 20 | + match is_parent_commit_too_old(&event.issue, client, age_threshold).await { |
| 21 | + Ok(Some(days_old)) => { |
| 22 | + log::info!( |
| 23 | + "PR #{} has a parent commit that is {} days old", |
| 24 | + event.issue.number, |
| 25 | + days_old |
| 26 | + ); |
| 27 | + |
| 28 | + return Some(format!( |
| 29 | + r"This PR is based on an upstream commit that is {} days old. |
| 30 | +It's recommended to update your branch according to the |
| 31 | +[Rustc Dev Guide](https://rustc-dev-guide.rust-lang.org/contributing.html#keeping-your-branch-up-to-date).", |
| 32 | + days_old |
| 33 | + )); |
| 34 | + } |
| 35 | + Ok(None) => { |
| 36 | + // Parent commit is not too old, log and do nothing |
| 37 | + log::debug!("PR #{} parent commit is not too old", event.issue.number); |
| 38 | + } |
| 39 | + Err(e) => { |
| 40 | + // Error checking parent commit age, log and do nothing |
| 41 | + log::error!( |
| 42 | + "Error checking parent commit age for PR #{}: {}", |
| 43 | + event.issue.number, |
| 44 | + e |
| 45 | + ); |
| 46 | + } |
| 47 | + } |
| 48 | + |
| 49 | + None |
| 50 | +} |
| 51 | + |
| 52 | +/// Checks if the PR's parent commit is too old. |
| 53 | +/// |
| 54 | +/// This determines if a PR needs updating by examining the first parent of the PR's head commit, |
| 55 | +/// which typically represents the base branch commit that the PR is based on. |
| 56 | +/// |
| 57 | +/// If this parent commit is older than the specified threshold, it suggests the PR |
| 58 | +/// should be updated/rebased to a more recent version of the base branch. |
| 59 | +/// |
| 60 | +/// Returns: |
| 61 | +/// - Ok(Some(days_old)) - If parent commit is older than the threshold |
| 62 | +/// - Ok(None) |
| 63 | +/// - If not a PR |
| 64 | +/// - If can't get commit details (both for the head and parent) |
| 65 | +/// - If parent is within threshold |
| 66 | +/// - Err(...) - If an error occurred during processing |
| 67 | +pub async fn is_parent_commit_too_old( |
| 68 | + pull_request: &Issue, |
| 69 | + client: &GithubClient, |
| 70 | + max_days_old: usize, |
| 71 | +) -> anyhow::Result<Option<usize>> { |
| 72 | + if !pull_request.is_pr() { |
| 73 | + return Ok(None); |
| 74 | + } |
| 75 | + |
| 76 | + // Get the head commit SHA |
| 77 | + let Some(head) = &pull_request.head else { |
| 78 | + return Ok(None); |
| 79 | + }; |
| 80 | + // Get the commit details |
| 81 | + let commit: GithubCommit = head.repo.github_commit(client, &head.sha).await?; |
| 82 | + |
| 83 | + // Get the first parent (it should be from the base branch) |
| 84 | + let Some(parent_sha) = commit.parents.get(0).map(|c| c.sha.clone()) else { |
| 85 | + return Ok(None); |
| 86 | + }; |
| 87 | + |
| 88 | + let days_old = commit_days_old(&parent_sha, &head.repo, client).await?; |
| 89 | + |
| 90 | + if days_old > max_days_old { |
| 91 | + Ok(Some(days_old)) |
| 92 | + } else { |
| 93 | + Ok(None) |
| 94 | + } |
| 95 | +} |
| 96 | + |
| 97 | +/// Returns the number of days old the commit is |
| 98 | +pub async fn commit_days_old( |
| 99 | + sha: &str, |
| 100 | + repo: &Repository, |
| 101 | + client: &GithubClient, |
| 102 | +) -> anyhow::Result<usize> { |
| 103 | + // Get the commit details to check its date |
| 104 | + let commit: GithubCommit = repo.github_commit(client, &sha).await?; |
| 105 | + |
| 106 | + // compute the number of days old the commit is |
| 107 | + let commit_date = commit.commit.author.date; |
| 108 | + let now = chrono::Utc::now().with_timezone(&commit_date.timezone()); |
| 109 | + let days_old = (now - commit_date).num_days() as usize; |
| 110 | + |
| 111 | + Ok(days_old) |
| 112 | +} |
0 commit comments