summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCyborus <cyborus@noreply.codeberg.org>2024-10-05 02:52:27 +0200
committerCyborus <cyborus@noreply.codeberg.org>2024-10-05 02:52:27 +0200
commit74c10f10611844cc238389438d277839da7d57aa (patch)
treec1dd8f0374364cf1d3e515c19238096e28ada7c4
parentMerge pull request 'add `--web` flag on `pr create`' (#129) from web-pr into ... (diff)
parentfeat: `pr status --wait` (diff)
downloadforgejo-cli-74c10f10611844cc238389438d277839da7d57aa.tar.xz
forgejo-cli-74c10f10611844cc238389438d277839da7d57aa.zip
Merge pull request 'add `pr status --wait`' (#130) from status-wait into main
Reviewed-on: https://codeberg.org/Cyborus/forgejo-cli/pulls/130
-rw-r--r--src/prs.rs232
1 files changed, 155 insertions, 77 deletions
diff --git a/src/prs.rs b/src/prs.rs
index 00150fb..98d54cd 100644
--- a/src/prs.rs
+++ b/src/prs.rs
@@ -1,4 +1,4 @@
-use std::str::FromStr;
+use std::{io::Write, str::FromStr};
use clap::{Args, Subcommand};
use eyre::{Context, OptionExt};
@@ -80,6 +80,9 @@ pub enum PrSubcommand {
/// The pull request to view.
#[clap(id = "[REPO#]ID")]
id: Option<IssueId>,
+ /// Wait for all checks to finish before exiting
+ #[clap(long)]
+ wait: bool,
},
/// Checkout a pull request in a new branch
Checkout {
@@ -320,7 +323,7 @@ impl PrCommand {
}
}
}
- Status { id } => view_pr_status(repo, &api, id.map(|id| id.number)).await?,
+ Status { id, wait } => view_pr_status(repo, &api, id.map(|id| id.number), wait).await?,
Search {
query,
labels,
@@ -602,50 +605,69 @@ fn darken(r: u8, g: u8, b: u8) -> (u8, u8, u8) {
)
}
-async fn view_pr_status(repo: &RepoName, api: &Forgejo, id: Option<u64>) -> eyre::Result<()> {
- let pr = try_get_pr(repo, api, id).await?;
- let repo = repo_name_from_pr(&pr)?;
+async fn view_pr_status(
+ repo: &RepoName,
+ api: &Forgejo,
+ id: Option<u64>,
+ wait: bool,
+) -> eyre::Result<()> {
+ if wait {
+ let SpecialRender { fancy, .. } = *crate::special_render();
+ let mut wait_duration = 5.0;
+ let mut prev_statuses_len = 0;
+ let pr_status = loop {
+ let pr_status = get_pr_status(repo, api, id).await?;
+ if fancy {
+ if prev_statuses_len > 0 {
+ println!("\x1b[{prev_statuses_len}A");
+ }
+ print_pr_status(&pr_status)?;
+ } else {
+ print!(".");
+ std::io::stdout().flush()?;
+ }
+ match &pr_status {
+ PrStatus::Merged { .. } => break pr_status,
+ PrStatus::Open {
+ commit_statuses, ..
+ } => {
+ let all_finished = commit_statuses
+ .iter()
+ .flat_map(|x| x.status.as_ref())
+ .all(|state| state != "pending");
+ if all_finished {
+ break pr_status;
+ }
+ prev_statuses_len = commit_statuses.len() + 2;
+ }
+ }
+ tokio::time::sleep(std::time::Duration::from_secs_f64(wait_duration)).await;
+ wait_duration *= 1.5;
+ };
+ if !fancy {
+ print_pr_status(&pr_status)?;
+ }
+ } else {
+ let pr_status = get_pr_status(repo, api, id).await?;
+ print_pr_status(&pr_status)?;
+ }
+ Ok(())
+}
- let SpecialRender {
- bright_magenta,
- bright_red,
- bright_green,
- yellow,
- light_grey,
- dash,
- bullet,
- reset,
- ..
- } = *crate::special_render();
+enum PrStatus {
+ Merged {
+ pr: forgejo_api::structs::PullRequest,
+ },
+ Open {
+ pr: forgejo_api::structs::PullRequest,
+ commit_statuses: Vec<forgejo_api::structs::CommitStatus>,
+ },
+}
+async fn get_pr_status(repo: &RepoName, api: &Forgejo, id: Option<u64>) -> eyre::Result<PrStatus> {
+ let pr = try_get_pr(repo, api, id).await?;
if pr.merged.ok_or_eyre("pr merge status unknown")? {
- let merged_by = pr.merged_by.ok_or_eyre("pr not merged by anyone")?;
- let merged_by = merged_by
- .login
- .as_deref()
- .ok_or_eyre("pr merger does not have login")?;
- let merged_at = pr.merged_at.ok_or_eyre("pr does not have merge date")?;
- let date_format = time::macros::format_description!(
- "on [month repr:long] [day], [year], at [hour repr:12]:[minute] [period]"
- );
- let tz_format = time::macros::format_description!(
- "[offset_hour padding:zero sign:mandatory]:[offset_minute]"
- );
- let (merged_at, show_tz) = if let Ok(local_offset) = time::UtcOffset::current_local_offset()
- {
- let merged_at = merged_at.to_offset(local_offset);
- (merged_at, false)
- } else {
- (merged_at, true)
- };
- print!(
- "{bright_magenta}Merged{reset} by {merged_by} {}",
- merged_at.format(date_format)?
- );
- if show_tz {
- print!("{}", merged_at.format(tz_format)?);
- }
- println!();
+ Ok(PrStatus::Merged { pr })
} else {
let pr_number = pr.number.ok_or_eyre("pr does not have number")? as u64;
let query = forgejo_api::structs::RepoGetPullRequestCommitsQuery {
@@ -672,49 +694,105 @@ async fn view_pr_status(repo: &RepoName, api: &Forgejo, id: Option<u64>) -> eyre
let combined_status = api
.repo_get_combined_status_by_ref(repo.owner(), repo.name(), sha, query)
.await?;
+ let commit_statuses = combined_status
+ .statuses
+ .ok_or_eyre("combined status does not have status list")?;
- let state = pr.state.ok_or_eyre("pr does not have state")?;
- let is_draft = pr.title.as_deref().is_some_and(|s| s.starts_with("WIP:"));
- match state {
- StateType::Open => {
- if is_draft {
- println!("{light_grey}Draft{reset} {dash} Can't merge draft PR")
+ Ok(PrStatus::Open {
+ pr,
+ commit_statuses,
+ })
+ }
+}
+
+fn print_pr_status(pr_status: &PrStatus) -> eyre::Result<()> {
+ let SpecialRender {
+ bright_magenta,
+ bright_red,
+ bright_green,
+ yellow,
+ light_grey,
+ dash,
+ bullet,
+ reset,
+ ..
+ } = *crate::special_render();
+ match pr_status {
+ PrStatus::Merged { pr } => {
+ let merged_by = pr
+ .merged_by
+ .as_ref()
+ .ok_or_eyre("pr not merged by anyone")?;
+ let merged_by = merged_by
+ .login
+ .as_deref()
+ .ok_or_eyre("pr merger does not have login")?;
+ let merged_at = pr.merged_at.ok_or_eyre("pr does not have merge date")?;
+ let date_format = time::macros::format_description!(
+ "on [month repr:long] [day], [year], at [hour repr:12]:[minute] [period]"
+ );
+ let tz_format = time::macros::format_description!(
+ "[offset_hour padding:zero sign:mandatory]:[offset_minute]"
+ );
+ let (merged_at, show_tz) =
+ if let Ok(local_offset) = time::UtcOffset::current_local_offset() {
+ let merged_at = merged_at.to_offset(local_offset);
+ (merged_at, false)
} else {
- print!("{bright_green}Open{reset} {dash} ");
- let mergable = pr.mergeable.ok_or_eyre("pr does not have mergable")?;
- if mergable {
- println!("Can be merged");
+ (merged_at, true)
+ };
+ print!(
+ "{bright_magenta}Merged{reset} by {merged_by} {}",
+ merged_at.format(date_format)?
+ );
+ if show_tz {
+ print!("{}", merged_at.format(tz_format)?);
+ }
+ println!();
+ }
+ PrStatus::Open {
+ pr,
+ commit_statuses,
+ } => {
+ let state = pr.state.ok_or_eyre("pr does not have state")?;
+ let is_draft = pr.title.as_deref().is_some_and(|s| s.starts_with("WIP:"));
+ match state {
+ StateType::Open => {
+ if is_draft {
+ println!("{light_grey}Draft{reset} {dash} Can't merge draft PR")
} else {
- println!("{bright_red}Merge conflicts{reset}");
+ print!("{bright_green}Open{reset} {dash} ");
+ let mergable = pr.mergeable.ok_or_eyre("pr does not have mergable")?;
+ if mergable {
+ println!("Can be merged");
+ } else {
+ println!("{bright_red}Merge conflicts{reset}");
+ }
}
}
+ StateType::Closed => println!("{bright_red}Closed{reset} {dash} Reopen to merge"),
}
- StateType::Closed => println!("{bright_red}Closed{reset} {dash} Reopen to merge"),
- }
- let commit_statuses = combined_status
- .statuses
- .ok_or_eyre("combined status does not have status list")?;
- for status in commit_statuses {
- let state = status
- .status
- .as_deref()
- .ok_or_eyre("status does not have status")?;
- let context = status
- .context
- .as_deref()
- .ok_or_eyre("status does not have context")?;
- print!("{bullet} ");
- match state {
- "success" => print!("{bright_green}Success{reset}"),
- "pending" => print!("{yellow}Pending{reset}"),
- "failure" => print!("{bright_red}Failure{reset}"),
- _ => eyre::bail!("invalid status"),
- };
- println!(" {dash} {context}");
+ for status in commit_statuses {
+ let state = status
+ .status
+ .as_deref()
+ .ok_or_eyre("status does not have status")?;
+ let context = status
+ .context
+ .as_deref()
+ .ok_or_eyre("status does not have context")?;
+ print!("{bullet} ");
+ match state {
+ "success" => print!("{bright_green}Success{reset}"),
+ "pending" => print!("{yellow}Pending{reset}"),
+ "failure" => print!("{bright_red}Failure{reset}"),
+ _ => eyre::bail!("invalid status"),
+ };
+ println!(" {dash} {context}");
+ }
}
}
-
Ok(())
}