mirror of
https://codeberg.org/Cyborus/forgejo-cli.git
synced 2024-11-12 21:19:49 +01:00
add pr commands
This commit is contained in:
parent
0a30d14035
commit
460ac72451
3 changed files with 322 additions and 9 deletions
|
@ -176,7 +176,7 @@ async fn create_issue(
|
||||||
Ok(())
|
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 issue = api.issue_get_issue(repo.owner(), repo.name(), id).await?;
|
||||||
let title = issue
|
let title = issue
|
||||||
.title
|
.title
|
||||||
|
@ -253,7 +253,7 @@ async fn view_issues(
|
||||||
Ok(())
|
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 {
|
let query = IssueGetCommentsQuery {
|
||||||
since: None,
|
since: None,
|
||||||
before: None,
|
before: None,
|
||||||
|
@ -268,7 +268,7 @@ async fn view_comment(repo: &RepoName, api: &Forgejo, id: u64, idx: usize) -> ey
|
||||||
Ok(())
|
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 {
|
let query = IssueGetCommentsQuery {
|
||||||
since: None,
|
since: None,
|
||||||
before: None,
|
before: None,
|
||||||
|
@ -307,7 +307,7 @@ fn print_comment(comment: &Comment) -> eyre::Result<()> {
|
||||||
Ok(())
|
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 {
|
match id {
|
||||||
Some(id) => {
|
Some(id) => {
|
||||||
let issue = api.issue_get_issue(repo.owner(), repo.name(), id).await?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn add_comment(
|
pub async fn add_comment(
|
||||||
repo: &RepoName,
|
repo: &RepoName,
|
||||||
api: &Forgejo,
|
api: &Forgejo,
|
||||||
issue: u64,
|
issue: u64,
|
||||||
|
@ -356,7 +356,7 @@ async fn add_comment(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn edit_title(
|
pub async fn edit_title(
|
||||||
repo: &RepoName,
|
repo: &RepoName,
|
||||||
api: &Forgejo,
|
api: &Forgejo,
|
||||||
issue: u64,
|
issue: u64,
|
||||||
|
@ -402,7 +402,7 @@ async fn edit_title(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn edit_body(
|
pub async fn edit_body(
|
||||||
repo: &RepoName,
|
repo: &RepoName,
|
||||||
api: &Forgejo,
|
api: &Forgejo,
|
||||||
issue: u64,
|
issue: u64,
|
||||||
|
@ -442,7 +442,7 @@ async fn edit_body(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn edit_comment(
|
pub async fn edit_comment(
|
||||||
repo: &RepoName,
|
repo: &RepoName,
|
||||||
api: &Forgejo,
|
api: &Forgejo,
|
||||||
issue: u64,
|
issue: u64,
|
||||||
|
@ -490,7 +490,7 @@ async fn edit_comment(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn close_issue(
|
pub async fn close_issue(
|
||||||
repo: &RepoName,
|
repo: &RepoName,
|
||||||
api: &Forgejo,
|
api: &Forgejo,
|
||||||
issue: u64,
|
issue: u64,
|
||||||
|
|
|
@ -9,6 +9,7 @@ use keys::*;
|
||||||
|
|
||||||
mod auth;
|
mod auth;
|
||||||
mod issues;
|
mod issues;
|
||||||
|
mod prs;
|
||||||
mod release;
|
mod release;
|
||||||
mod repo;
|
mod repo;
|
||||||
|
|
||||||
|
@ -27,6 +28,7 @@ pub enum Command {
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
Repo(repo::RepoCommand),
|
Repo(repo::RepoCommand),
|
||||||
Issue(issues::IssueCommand),
|
Issue(issues::IssueCommand),
|
||||||
|
Pr(prs::PrCommand),
|
||||||
#[command(name = "whoami")]
|
#[command(name = "whoami")]
|
||||||
WhoAmI {
|
WhoAmI {
|
||||||
#[clap(long, short)]
|
#[clap(long, short)]
|
||||||
|
@ -50,6 +52,7 @@ async fn main() -> eyre::Result<()> {
|
||||||
match args.command {
|
match args.command {
|
||||||
Command::Repo(subcommand) => subcommand.run(&keys, host_name).await?,
|
Command::Repo(subcommand) => subcommand.run(&keys, host_name).await?,
|
||||||
Command::Issue(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 } => {
|
Command::WhoAmI { remote } => {
|
||||||
let url = repo::RepoInfo::get_current(host_name, None, remote.as_deref())
|
let url = repo::RepoInfo::get_current(host_name, None, remote.as_deref())
|
||||||
.wrap_err("could not find host, try specifying with --host")?
|
.wrap_err("could not find host, try specifying with --host")?
|
||||||
|
|
310
src/prs.rs
Normal file
310
src/prs.rs
Normal 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(())
|
||||||
|
}
|
Loading…
Reference in a new issue