add pr commands

This commit is contained in:
Cyborus 2024-04-30 13:08:10 -04:00
parent 0a30d14035
commit 460ac72451
No known key found for this signature in database
3 changed files with 322 additions and 9 deletions

View file

@ -176,7 +176,7 @@ async fn create_issue(
Ok(())
}
async fn view_issue(repo: &RepoName, api: &Forgejo, id: u64) -> eyre::Result<()> {
pub async fn view_issue(repo: &RepoName, api: &Forgejo, id: u64) -> eyre::Result<()> {
let issue = api.issue_get_issue(repo.owner(), repo.name(), id).await?;
let title = issue
.title
@ -253,7 +253,7 @@ async fn view_issues(
Ok(())
}
async fn view_comment(repo: &RepoName, api: &Forgejo, id: u64, idx: usize) -> eyre::Result<()> {
pub async fn view_comment(repo: &RepoName, api: &Forgejo, id: u64, idx: usize) -> eyre::Result<()> {
let query = IssueGetCommentsQuery {
since: None,
before: None,
@ -268,7 +268,7 @@ async fn view_comment(repo: &RepoName, api: &Forgejo, id: u64, idx: usize) -> ey
Ok(())
}
async fn view_comments(repo: &RepoName, api: &Forgejo, id: u64) -> eyre::Result<()> {
pub async fn view_comments(repo: &RepoName, api: &Forgejo, id: u64) -> eyre::Result<()> {
let query = IssueGetCommentsQuery {
since: None,
before: None,
@ -307,7 +307,7 @@ fn print_comment(comment: &Comment) -> eyre::Result<()> {
Ok(())
}
async fn browse_issue(repo: &RepoName, api: &Forgejo, id: Option<u64>) -> eyre::Result<()> {
pub async fn browse_issue(repo: &RepoName, api: &Forgejo, id: Option<u64>) -> eyre::Result<()> {
match id {
Some(id) => {
let issue = api.issue_get_issue(repo.owner(), repo.name(), id).await?;
@ -329,7 +329,7 @@ async fn browse_issue(repo: &RepoName, api: &Forgejo, id: Option<u64>) -> eyre::
Ok(())
}
async fn add_comment(
pub async fn add_comment(
repo: &RepoName,
api: &Forgejo,
issue: u64,
@ -356,7 +356,7 @@ async fn add_comment(
Ok(())
}
async fn edit_title(
pub async fn edit_title(
repo: &RepoName,
api: &Forgejo,
issue: u64,
@ -402,7 +402,7 @@ async fn edit_title(
Ok(())
}
async fn edit_body(
pub async fn edit_body(
repo: &RepoName,
api: &Forgejo,
issue: u64,
@ -442,7 +442,7 @@ async fn edit_body(
Ok(())
}
async fn edit_comment(
pub async fn edit_comment(
repo: &RepoName,
api: &Forgejo,
issue: u64,
@ -490,7 +490,7 @@ async fn edit_comment(
Ok(())
}
async fn close_issue(
pub async fn close_issue(
repo: &RepoName,
api: &Forgejo,
issue: u64,

View file

@ -9,6 +9,7 @@ use keys::*;
mod auth;
mod issues;
mod prs;
mod release;
mod repo;
@ -27,6 +28,7 @@ pub enum Command {
#[clap(subcommand)]
Repo(repo::RepoCommand),
Issue(issues::IssueCommand),
Pr(prs::PrCommand),
#[command(name = "whoami")]
WhoAmI {
#[clap(long, short)]
@ -50,6 +52,7 @@ async fn main() -> eyre::Result<()> {
match args.command {
Command::Repo(subcommand) => subcommand.run(&keys, host_name).await?,
Command::Issue(subcommand) => subcommand.run(&keys, host_name).await?,
Command::Pr(subcommand) => subcommand.run(&keys, host_name).await?,
Command::WhoAmI { remote } => {
let url = repo::RepoInfo::get_current(host_name, None, remote.as_deref())
.wrap_err("could not find host, try specifying with --host")?

310
src/prs.rs Normal file
View file

@ -0,0 +1,310 @@
use clap::{Args, Subcommand};
use eyre::OptionExt;
use forgejo_api::{
structs::{CreatePullRequestOption, MergePullRequestOption},
Forgejo,
};
use crate::repo::{RepoInfo, RepoName};
#[derive(Args, Clone, Debug)]
pub struct PrCommand {
#[clap(long, short = 'R')]
remote: Option<String>,
#[clap(long, short)]
repo: Option<String>,
#[clap(subcommand)]
command: PrSubcommand,
}
#[derive(Subcommand, Clone, Debug)]
pub enum PrSubcommand {
Create {
base: String,
head: String,
title: String,
#[clap(long)]
body: Option<String>,
},
Edit {
pr: u64,
#[clap(subcommand)]
command: EditCommand,
},
Merge {
pr: u64,
#[clap(long, short)]
method: Option<MergeMethod>,
#[clap(long, short)]
delete: bool,
},
Comment {
pr: u64,
body: Option<String>,
},
Close {
pr: u64,
#[clap(long, short)]
with_msg: Option<Option<String>>,
},
Search {
query: Option<String>,
#[clap(long, short)]
labels: Option<String>,
#[clap(long, short)]
creator: Option<String>,
#[clap(long, short)]
assignee: Option<String>,
#[clap(long, short)]
state: Option<crate::issues::State>,
},
View {
id: u64,
#[clap(subcommand)]
command: Option<ViewCommand>,
},
Browse {
id: Option<u64>,
},
}
#[derive(clap::ValueEnum, Clone, Copy, Debug)]
pub enum MergeMethod {
Merge,
Rebase,
RebaseMerge,
Squash,
Manual,
}
impl From<MergeMethod> for forgejo_api::structs::MergePullRequestOptionDo {
fn from(value: MergeMethod) -> Self {
use forgejo_api::structs::MergePullRequestOptionDo::*;
match value {
MergeMethod::Merge => Merge,
MergeMethod::Rebase => Rebase,
MergeMethod::RebaseMerge => RebaseMerge,
MergeMethod::Squash => Squash,
MergeMethod::Manual => ManuallyMerged,
}
}
}
#[derive(Subcommand, Clone, Debug)]
pub enum EditCommand {
Title {
new_title: Option<String>,
},
Body {
new_body: Option<String>,
},
Comment {
idx: usize,
new_body: Option<String>,
},
}
#[derive(Subcommand, Clone, Debug)]
pub enum ViewCommand {
Body,
Comment { idx: usize },
Comments,
}
impl PrCommand {
pub async fn run(self, keys: &crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> {
use PrSubcommand::*;
let repo = RepoInfo::get_current(host_name, self.repo.as_deref(), self.remote.as_deref())?;
let api = keys.get_api(repo.host_url())?;
let repo = repo
.name()
.ok_or_eyre("couldn't get repo name, try specifying with --repo")?;
match self.command {
Create {
title,
base,
head,
body,
} => create_pr(&repo, &api, title, base, head, body).await?,
Merge { pr, method, delete } => merge_pr(&repo, &api, pr, method, delete).await?,
View { id, command } => match command.unwrap_or(ViewCommand::Body) {
ViewCommand::Body => crate::issues::view_issue(&repo, &api, id).await?,
ViewCommand::Comment { idx } => {
crate::issues::view_comment(&repo, &api, id, idx).await?
}
ViewCommand::Comments => crate::issues::view_comments(&repo, &api, id).await?,
},
Search {
query,
labels,
creator,
assignee,
state,
} => view_prs(&repo, &api, query, labels, creator, assignee, state).await?,
Edit { pr, command } => match command {
EditCommand::Title { new_title } => {
crate::issues::edit_title(&repo, &api, pr, new_title).await?
}
EditCommand::Body { new_body } => {
crate::issues::edit_body(&repo, &api, pr, new_body).await?
}
EditCommand::Comment { idx, new_body } => {
crate::issues::edit_comment(&repo, &api, pr, idx, new_body).await?
}
},
Close { pr, with_msg } => crate::issues::close_issue(&repo, &api, pr, with_msg).await?,
Browse { id } => crate::issues::browse_issue(&repo, &api, id).await?,
Comment { pr, body } => crate::issues::add_comment(&repo, &api, pr, body).await?,
}
Ok(())
}
}
async fn create_pr(
repo: &RepoName,
api: &Forgejo,
title: String,
base: String,
head: String,
body: Option<String>,
) -> eyre::Result<()> {
let (repo_owner, repo_name, base, head) = match base.strip_prefix("^") {
Some(parent_base) => {
let mut repo_data = api.repo_get(repo.owner(), repo.name()).await?;
let parent = *repo_data
.parent
.take()
.ok_or_eyre("cannot create pull request upstream, there is no upstream")?;
let parent_owner = parent
.owner
.ok_or_eyre("parent has no owner")?
.login
.ok_or_eyre("parent owner has no login")?;
let parent_name = parent.name.ok_or_eyre("parent has no name")?;
(
parent_owner,
parent_name,
parent_base.to_owned(),
format!("{}:{}", repo.owner(), head),
)
}
None => (repo.owner().to_owned(), repo.name().to_owned(), base, head),
};
let body = match body {
Some(body) => body,
None => {
let mut body = String::new();
crate::editor(&mut body, Some("md")).await?;
body
}
};
let pr = api
.repo_create_pull_request(
&repo_owner,
&repo_name,
CreatePullRequestOption {
assignee: None,
assignees: None,
base: Some(base.to_owned()),
body: Some(body),
due_date: None,
head: Some(head),
labels: None,
milestone: None,
title: Some(title),
},
)
.await?;
let number = pr
.number
.ok_or_else(|| eyre::eyre!("pr does not have number"))?;
let title = pr
.title
.as_ref()
.ok_or_else(|| eyre::eyre!("pr does not have title"))?;
println!("created pull request #{}: {}", number, title);
Ok(())
}
async fn merge_pr(
repo: &RepoName,
api: &Forgejo,
pr: u64,
method: Option<MergeMethod>,
delete: bool,
) -> eyre::Result<()> {
let repo_info = api.repo_get(repo.owner(), repo.name()).await?;
let default_merge = repo_info
.default_merge_style
.map(|x| x.into())
.unwrap_or(forgejo_api::structs::MergePullRequestOptionDo::Merge);
let body = MergePullRequestOption {
r#do: method.map(|x| x.into()).unwrap_or(default_merge),
merge_commit_id: None,
merge_message_field: None,
merge_title_field: None,
delete_branch_after_merge: Some(delete),
force_merge: None,
head_commit_id: None,
merge_when_checks_succeed: None,
};
api.repo_merge_pull_request(repo.owner(), repo.name(), pr, body)
.await?;
Ok(())
}
async fn view_prs(
repo: &RepoName,
api: &Forgejo,
query_str: Option<String>,
labels: Option<String>,
creator: Option<String>,
assignee: Option<String>,
state: Option<crate::issues::State>,
) -> eyre::Result<()> {
let labels = labels
.map(|s| s.split(',').map(|s| s.to_string()).collect::<Vec<_>>())
.unwrap_or_default();
let query = forgejo_api::structs::IssueListIssuesQuery {
q: query_str,
labels: Some(labels.join(",")),
created_by: creator,
assigned_by: assignee,
state: state.map(|s| s.into()),
r#type: Some(forgejo_api::structs::IssueListIssuesQueryType::Pulls),
milestones: None,
since: None,
before: None,
mentioned_by: None,
page: None,
limit: None,
};
let prs = api
.issue_list_issues(repo.owner(), repo.name(), query)
.await?;
if prs.len() == 1 {
println!("1 pull request");
} else {
println!("{} pull requests", prs.len());
}
for pr in prs {
let number = pr
.number
.ok_or_else(|| eyre::eyre!("pr does not have number"))?;
let title = pr
.title
.as_ref()
.ok_or_else(|| eyre::eyre!("pr does not have title"))?;
let user = pr
.user
.as_ref()
.ok_or_else(|| eyre::eyre!("pr does not have creator"))?;
let username = user
.login
.as_ref()
.ok_or_else(|| eyre::eyre!("user does not have login"))?;
println!("#{}: {} (by {})", number, title, username);
}
Ok(())
}