summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCyborus <cyborus@cyborus.xyz>2024-05-01 00:45:43 +0200
committerCyborus <cyborus@cyborus.xyz>2024-05-07 21:24:57 +0200
commit6fd197601337227c7e2b6ae54fe98dd951e01eca (patch)
treefff52b565b9e992f336319d10d3666d3bf21f95b
parentadd pr commands (diff)
downloadforgejo-cli-6fd197601337227c7e2b6ae54fe98dd951e01eca.tar.xz
forgejo-cli-6fd197601337227c7e2b6ae54fe98dd951e01eca.zip
add pr change viewing commands
-rw-r--r--Cargo.toml2
-rw-r--r--src/prs.rs173
2 files changed, 172 insertions, 3 deletions
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:<additions_width$} \x1b[91m-{deletions:<deletions_width$}\x1b[0m {name}");
+ }
+ Ok(())
+}
+
+async fn view_pr_commits(
+ repo: &RepoName,
+ api: &Forgejo,
+ pr: u64,
+ oneline: bool,
+) -> 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:<additions_width$} \x1b[91m-{deletions:<deletions_width$}\x1b[0m {name}");
+ } else {
+ let author = repo_commit
+ .author
+ .as_ref()
+ .ok_or_eyre("commit has no author")?;
+ let author_name = author.name.as_deref().ok_or_eyre("author has no name")?;
+ let author_email = author.email.as_deref().ok_or_eyre("author has no email")?;
+ let date = commit
+ .created
+ .as_ref()
+ .ok_or_eyre("commit as no creation date")?;
+
+ println!("\x1b[33mcommit {sha}\x1b[0m (\x1b[92m+{additions}\x1b[0m, \x1b[91m-{deletions}\x1b[0m)");
+ println!("Author: {author_name} <{author_email}>");
+ 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(())
+}