Skip to content

Commit 5d7e473

Browse files
committed
Support force merging when merging upstream.
1 parent 95e873f commit 5d7e473

File tree

2 files changed

+54
-2
lines changed

2 files changed

+54
-2
lines changed

src/actions.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ impl<'a> Action for Step<'a> {
112112
// These are unused for query.
113113
default_branch: "master".to_string(),
114114
fork: false,
115+
parent: None,
115116
};
116117

117118
for QueryMap { name, kind, query } in queries {

src/github.rs

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -987,6 +987,7 @@ pub struct Repository {
987987
pub default_branch: String,
988988
#[serde(default)]
989989
pub fork: bool,
990+
pub parent: Option<Box<Repository>>,
990991
}
991992

992993
#[derive(Copy, Clone)]
@@ -1485,16 +1486,66 @@ impl Repository {
14851486
}
14861487

14871488
/// Synchronize a branch (in a forked repository) by pulling in its upstream contents.
1489+
///
1490+
/// **Warning**: This will to a force update if there are conflicts.
14881491
pub async fn merge_upstream(&self, client: &GithubClient, branch: &str) -> anyhow::Result<()> {
14891492
let url = format!("{}/merge-upstream", self.url());
1490-
client
1493+
let merge_error = match client
14911494
.send_req(client.post(&url).json(&serde_json::json!({
14921495
"branch": branch,
14931496
})))
14941497
.await
1498+
{
1499+
Ok(_) => return Ok(()),
1500+
Err(e) => {
1501+
if e.downcast_ref::<reqwest::Error>().map_or(false, |e| {
1502+
matches!(
1503+
e.status(),
1504+
Some(StatusCode::UNPROCESSABLE_ENTITY | StatusCode::CONFLICT)
1505+
)
1506+
}) {
1507+
e
1508+
} else {
1509+
return Err(e);
1510+
}
1511+
}
1512+
};
1513+
// 409 is a clear error that there is a merge conflict.
1514+
// However, I don't understand how/why 422 might happen. The docs don't really say.
1515+
// The gh cli falls back to trying to force a sync, so let's try that.
1516+
log::info!(
1517+
"{} failed to merge upstream branch {branch}, trying force sync: {merge_error:?}",
1518+
self.full_name
1519+
);
1520+
let parent = self.parent.as_ref().ok_or_else(|| {
1521+
anyhow::anyhow!(
1522+
"{} failed to merge upstream branch {branch}, \
1523+
force sync could not determine parent",
1524+
self.full_name
1525+
)
1526+
})?;
1527+
// Note: I'm not sure how to handle the case where the branch name
1528+
// differs to the upstream. For example, if I create a branch off
1529+
// master in my fork, somehow GitHub knows that my branch should push
1530+
// to upstream/master (not upstream/my-branch-name). I can't find a
1531+
// way to find that branch name. Perhaps GitHub assumes it is the
1532+
// default branch if there is no matching branch name?
1533+
let branch_ref = format!("heads/{branch}");
1534+
let latest_parent_commit = parent
1535+
.get_reference(client, &branch_ref)
1536+
.await
1537+
.with_context(|| {
1538+
format!(
1539+
"failed to get head branch {branch} when merging upstream to {}",
1540+
self.full_name
1541+
)
1542+
})?;
1543+
let sha = latest_parent_commit.object.sha;
1544+
self.update_reference(client, &branch_ref, &sha)
1545+
.await
14951546
.with_context(|| {
14961547
format!(
1497-
"{} failed to merge upstream branch {branch}",
1548+
"failed to force update {branch} to {sha} for {}",
14981549
self.full_name
14991550
)
15001551
})?;

0 commit comments

Comments
 (0)