bb_cli/commands/
pr_retarget.rs1use crate::api::models::PullRequest;
9use crate::commands::pr::Ctx;
10use crate::error::{BbError, Result};
11use crate::output::{self, Format};
12use serde::Serialize;
13
14#[derive(Serialize)]
15struct RetargetRow {
16 id: u64,
17 title: String,
18 source: String,
19 destination: String,
20 url: String,
21}
22
23impl RetargetRow {
24 fn from(pr: &PullRequest) -> Self {
25 Self {
26 id: pr.id,
27 title: pr.title.clone().unwrap_or_default(),
28 source: pr.source_branch().to_string(),
29 destination: pr.destination_branch().to_string(),
30 url: pr.html_url().to_string(),
31 }
32 }
33}
34
35pub async fn run(ctx: &Ctx, id: u64, to: &str) -> Result<()> {
36 let path = ctx.path(&format!("/pullrequests/{id}"));
37 let pr: PullRequest = ctx.client.get_json(&path).await?;
38
39 let state = pr.state.as_deref().unwrap_or("UNKNOWN");
42 if !state.eq_ignore_ascii_case("OPEN") {
43 return Err(BbError::Config(format!(
44 "pull request #{id} is {state}, and only an open one can be retargeted"
45 )));
46 }
47
48 let from = pr.destination_branch().to_string();
49 if from == to {
50 let row = RetargetRow::from(&pr);
51 match ctx.format {
52 Format::Json => output::print_json(&row)?,
53 Format::Human => output::info(&format!(
54 "pull request #{id} already targets {to} — nothing to do"
55 )),
56 }
57 return Ok(());
58 }
59
60 let title = pr.title.clone().unwrap_or_default();
61 let body = serde_json::json!({
62 "title": title,
63 "destination": { "branch": { "name": to } },
64 });
65 let updated: PullRequest = ctx.client.put_json(&path, &body).await?;
66
67 let row = RetargetRow::from(&updated);
68 match ctx.format {
69 Format::Json => output::print_json(&row)?,
70 Format::Human => {
71 output::success(&format!("pull request #{id} retargeted {from} → {to}"));
72 output::warn(
73 "the diff was recomputed; inline comments on the old base may now read as outdated",
74 );
75 }
76 }
77 Ok(())
78}