Skip to content

Commit d7d4bab

Browse files
authored
Merge pull request #1856 from ehuss/merge-conflict
Add merge-conflict notifications
2 parents 742b66b + 5301321 commit d7d4bab

File tree

4 files changed

+456
-1
lines changed

4 files changed

+456
-1
lines changed

src/config.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ pub(crate) struct Config {
4545
pub(crate) validate_config: Option<ValidateConfig>,
4646
pub(crate) pr_tracking: Option<ReviewPrefsConfig>,
4747
pub(crate) transfer: Option<TransferConfig>,
48+
pub(crate) merge_conflicts: Option<MergeConflictConfig>,
4849
}
4950

5051
#[derive(PartialEq, Eq, Debug, serde::Deserialize)]
@@ -350,6 +351,18 @@ pub(crate) struct ReviewPrefsConfig {
350351
#[serde(deny_unknown_fields)]
351352
pub(crate) struct TransferConfig {}
352353

354+
#[derive(Clone, PartialEq, Eq, Debug, serde::Deserialize)]
355+
#[serde(rename_all = "kebab-case")]
356+
#[serde(deny_unknown_fields)]
357+
pub(crate) struct MergeConflictConfig {
358+
#[serde(default)]
359+
pub remove: HashSet<String>,
360+
#[serde(default)]
361+
pub add: HashSet<String>,
362+
#[serde(default)]
363+
pub unless: HashSet<String>,
364+
}
365+
353366
fn get_cached_config(repo: &str) -> Option<Result<Arc<Config>, ConfigurationError>> {
354367
let cache = CONFIG_CACHE.read().unwrap();
355368
cache.get(repo).and_then(|(config, fetch_time)| {
@@ -527,6 +540,7 @@ mod tests {
527540
validate_config: Some(ValidateConfig {}),
528541
pr_tracking: None,
529542
transfer: None,
543+
merge_conflicts: None,
530544
}
531545
);
532546
}

src/github.rs

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,8 @@ pub struct Issue {
356356
/// Whether it is open or closed.
357357
pub state: IssueState,
358358
pub milestone: Option<Milestone>,
359+
/// Whether a PR has merge conflicts.
360+
pub mergeable: Option<bool>,
359361
}
360362

361363
#[derive(Debug, serde::Deserialize, Eq, PartialEq)]
@@ -1826,6 +1828,101 @@ impl Repository {
18261828
.await
18271829
.with_context(|| format!("{} failed to get issue {issue_num}", self.full_name))
18281830
}
1831+
1832+
/// Fetches information about merge conflicts on open PRs.
1833+
pub async fn get_merge_conflict_prs(
1834+
&self,
1835+
client: &GithubClient,
1836+
) -> anyhow::Result<Vec<MergeConflictInfo>> {
1837+
let mut prs = Vec::new();
1838+
let mut after = None;
1839+
loop {
1840+
let mut data = client
1841+
.graphql_query(
1842+
"query($owner:String!, $repo:String!, $after:String) {
1843+
repository(owner: $owner, name: $repo) {
1844+
pullRequests(states: OPEN, first: 100, after: $after) {
1845+
edges {
1846+
node {
1847+
number
1848+
mergeable
1849+
baseRefName
1850+
}
1851+
}
1852+
pageInfo {
1853+
hasNextPage
1854+
endCursor
1855+
}
1856+
}
1857+
}
1858+
}",
1859+
serde_json::json!({
1860+
"owner": self.owner(),
1861+
"repo": self.name(),
1862+
"after": after,
1863+
}),
1864+
)
1865+
.await?;
1866+
let edges = data["data"]["repository"]["pullRequests"]["edges"].take();
1867+
let serde_json::Value::Array(edges) = edges else {
1868+
anyhow::bail!("expected array edges, got {edges:?}");
1869+
};
1870+
let this_page = edges
1871+
.into_iter()
1872+
.map(|mut edge| {
1873+
serde_json::from_value(edge["node"].take())
1874+
.with_context(|| "failed to deserialize merge conflicts")
1875+
})
1876+
.collect::<Result<Vec<_>, _>>()?;
1877+
prs.extend(this_page);
1878+
if !data["data"]["repository"]["pullRequests"]["pageInfo"]["hasNextPage"]
1879+
.as_bool()
1880+
.unwrap_or(false)
1881+
{
1882+
break;
1883+
}
1884+
after = Some(
1885+
data["data"]["repository"]["pullRequests"]["pageInfo"]["endCursor"]
1886+
.as_str()
1887+
.expect("endCursor is string")
1888+
.to_string(),
1889+
);
1890+
}
1891+
Ok(prs)
1892+
}
1893+
1894+
/// Returns a list of PRs "associated" with a commit.
1895+
pub async fn pulls_for_commit(
1896+
&self,
1897+
client: &GithubClient,
1898+
sha: &str,
1899+
) -> anyhow::Result<Vec<Issue>> {
1900+
let url = format!("{}/commits/{sha}/pulls", self.url(client));
1901+
client
1902+
.json(client.get(&url))
1903+
.await
1904+
.with_context(|| format!("{} failed to get pulls for commit {sha}", self.full_name))
1905+
}
1906+
}
1907+
1908+
/// Information about a merge conflict on a PR.
1909+
#[derive(Debug, serde::Deserialize)]
1910+
#[serde(rename_all = "camelCase")]
1911+
pub struct MergeConflictInfo {
1912+
/// Pull request number.
1913+
pub number: u64,
1914+
/// Whether this pull can be merged.
1915+
pub mergeable: MergeableState,
1916+
/// The branch name where this PR is requesting to be merged to.
1917+
pub base_ref_name: String,
1918+
}
1919+
1920+
#[derive(Debug, serde::Deserialize, PartialEq)]
1921+
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
1922+
pub enum MergeableState {
1923+
Conflicting,
1924+
Mergeable,
1925+
Unknown,
18291926
}
18301927

18311928
pub struct Query<'a> {
@@ -2081,9 +2178,14 @@ pub struct CreateEvent {
20812178

20822179
#[derive(Debug, serde::Deserialize)]
20832180
pub struct PushEvent {
2181+
/// The SHA of the most recent commit on `ref` after the push.
2182+
pub after: String,
2183+
/// The full git ref that was pushed.
2184+
///
2185+
/// Example: `refs/heads/main` or `refs/tags/v3.14.1`.
20842186
#[serde(rename = "ref")]
20852187
pub git_ref: String,
2086-
repository: Repository,
2188+
pub repository: Repository,
20872189
sender: User,
20882190
}
20892191

src/handlers.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ mod github_releases;
3131
mod glacier;
3232
mod major_change;
3333
mod mentions;
34+
mod merge_conflicts;
3435
mod milestone_prs;
3536
mod no_merges;
3637
mod nominate;
@@ -144,6 +145,20 @@ pub async fn handle(ctx: &Context, event: &Event) -> Vec<HandlerError> {
144145
}
145146
}
146147

148+
if let Some(conflict_config) = config
149+
.as_ref()
150+
.ok()
151+
.and_then(|c| c.merge_conflicts.as_ref())
152+
{
153+
if let Err(e) = merge_conflicts::handle(ctx, event, conflict_config).await {
154+
log::error!(
155+
"failed to process event {:?} with merge_conflicts handler: {:?}",
156+
event,
157+
e
158+
);
159+
}
160+
}
161+
147162
errors
148163
}
149164

0 commit comments

Comments
 (0)