diff --git a/Cargo.toml b/Cargo.toml index 217b441..d62c8dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ open = "5.0.0" serde = { version = "1.0.170", features = ["derive"] } serde_json = "1.0.100" soft_assert = "0.1.1" -time = { version = "0.3.30", features = ["formatting"] } +time = { version = "0.3.30", features = ["formatting", "macros"] } tokio = { version = "1.29.1", features = ["full"] } url = "2.4.0" uuid = { version = "1.5.0", features = ["v4"] } diff --git a/src/prs.rs b/src/prs.rs index 5560724..134422c 100644 --- a/src/prs.rs +++ b/src/prs.rs @@ -1,7 +1,10 @@ use clap::{Args, Subcommand}; use eyre::OptionExt; use forgejo_api::{ - structs::{CreatePullRequestOption, MergePullRequestOption}, + structs::{ + CreatePullRequestOption, MergePullRequestOption, RepoGetPullRequestCommitsQuery, + RepoGetPullRequestFilesQuery, + }, Forgejo, }; @@ -107,8 +110,24 @@ pub enum EditCommand { #[derive(Subcommand, Clone, Debug)] pub enum ViewCommand { Body, - Comment { idx: usize }, + Comment { + idx: usize, + }, Comments, + Diff { + /// Get the diff in patch format + #[clap(long, short)] + patch: bool, + /// View the diff in your text editor + #[clap(long, short)] + editor: bool, + }, + Files, + Commits { + /// View one commit per line + #[clap(long, short)] + oneline: bool, + }, } impl PrCommand { @@ -133,6 +152,13 @@ impl PrCommand { crate::issues::view_comment(&repo, &api, id, idx).await? } ViewCommand::Comments => crate::issues::view_comments(&repo, &api, id).await?, + ViewCommand::Diff { patch, editor } => { + view_diff(&repo, &api, id, patch, editor).await? + } + ViewCommand::Files => view_pr_files(&repo, &api, id).await?, + ViewCommand::Commits { oneline } => { + view_pr_commits(&repo, &api, id, oneline).await? + } }, Search { query, @@ -308,3 +334,146 @@ async fn view_prs( } Ok(()) } + +async fn view_diff( + repo: &RepoName, + api: &Forgejo, + pr: u64, + patch: bool, + editor: bool, +) -> eyre::Result<()> { + let diff_type = if patch { "patch" } else { "diff" }; + let diff = api + .repo_download_pull_diff_or_patch( + repo.owner(), + repo.name(), + pr, + diff_type, + forgejo_api::structs::RepoDownloadPullDiffOrPatchQuery::default(), + ) + .await?; + if editor { + let mut view = diff.clone(); + crate::editor(&mut view, Some(diff_type)).await?; + if view != diff { + println!("changes made to the diff will not persist"); + } + } else { + println!("{diff}"); + } + Ok(()) +} + +async fn view_pr_files(repo: &RepoName, api: &Forgejo, pr: u64) -> eyre::Result<()> { + let query = RepoGetPullRequestFilesQuery { + limit: Some(u32::MAX), + ..Default::default() + }; + let (headers, files) = api + .repo_get_pull_request_files(repo.owner(), repo.name(), pr, query) + .await?; + let max_additions = files + .iter() + .map(|x| x.additions.unwrap_or_default()) + .max() + .unwrap_or_default(); + let max_deletions = files + .iter() + .map(|x| x.deletions.unwrap_or_default()) + .max() + .unwrap_or_default(); + + let additions_width = max_additions.checked_ilog10().unwrap_or_default() as usize + 1; + let deletions_width = max_deletions.checked_ilog10().unwrap_or_default() as usize + 1; + + for file in files { + let name = file.filename.as_deref().unwrap_or("???"); + let additions = file.additions.unwrap_or_default(); + let deletions = file.deletions.unwrap_or_default(); + println!("\x1b[92m+{additions: eyre::Result<()> { + let query = RepoGetPullRequestCommitsQuery { + limit: Some(u32::MAX), + files: Some(false), + ..Default::default() + }; + let (_headers, commits) = api + .repo_get_pull_request_commits(repo.owner(), repo.name(), pr, query) + .await?; + + let max_additions = commits + .iter() + .filter_map(|x| x.stats.as_ref()) + .map(|x| x.additions.unwrap_or_default()) + .max() + .unwrap_or_default(); + let max_deletions = commits + .iter() + .filter_map(|x| x.stats.as_ref()) + .map(|x| x.deletions.unwrap_or_default()) + .max() + .unwrap_or_default(); + + let additions_width = max_additions.checked_ilog10().unwrap_or_default() as usize + 1; + let deletions_width = max_deletions.checked_ilog10().unwrap_or_default() as usize + 1; + + for commit in commits { + let repo_commit = commit + .commit + .as_ref() + .ok_or_eyre("commit does not have commit?")?; + + let message = repo_commit.message.as_deref().unwrap_or("[no msg]"); + let name = message.lines().next().unwrap_or(&message); + + let sha = commit + .sha + .as_deref() + .ok_or_eyre("commit does not have sha")?; + let short_sha = &sha[..7]; + + let stats = commit + .stats + .as_ref() + .ok_or_eyre("commit does not have stats")?; + let additions = stats.additions.unwrap_or_default(); + let deletions = stats.deletions.unwrap_or_default(); + + if oneline { + println!("\x1b[33m{short_sha}\x1b[0m \x1b[92m+{additions:"); + print!("Date: "); + let format = time::macros::format_description!("[weekday repr:short] [month repr:short] [day] [hour repr:24]:[minute]:[second] [year] [offset_hour sign:mandatory][offset_minute]"); + date.format_into(&mut std::io::stdout().lock(), format)?; + println!(); + println!(); + for line in message.lines() { + println!(" {line}"); + } + println!(); + } + } + Ok(()) +}