diff --git a/crates/cuddle-please/src/command.rs b/crates/cuddle-please/src/command.rs index 3fd7637..e0e7d2d 100644 --- a/crates/cuddle-please/src/command.rs +++ b/crates/cuddle-please/src/command.rs @@ -219,6 +219,24 @@ impl Command { self.ui.write_str_ln(&format!("- {}", commit.get_title())) } } + GiteaCommand::CheckPr {} => { + let pr = client.get_pull_request( + self.global.owner.unwrap(), + self.global.repo.unwrap(), + )?; + + match pr { + Some(index) => { + self.ui.write_str_ln(&format!( + "found cuddle-please (index={}) pr from gitea", + index + )); + } + None => { + self.ui.write_str_ln("found no cuddle-please pr from gitea"); + } + } + } } } Some(Commands::Doctor {}) => { @@ -315,6 +333,7 @@ enum GiteaCommand { #[arg(long)] branch: String, }, + CheckPr {}, } #[derive(Subcommand, Debug, Clone)] diff --git a/crates/cuddle-please/src/gitea_client.rs b/crates/cuddle-please/src/gitea_client.rs index a0f0a00..89289cc 100644 --- a/crates/cuddle-please/src/gitea_client.rs +++ b/crates/cuddle-please/src/gitea_client.rs @@ -191,6 +191,199 @@ impl GiteaClient { Ok(commits) } + + pub fn get_pull_request( + &self, + owner: impl Into, + repo: impl Into, + ) -> anyhow::Result> { + let request_pull_request = + |owner: &str, repo: &str, page: usize| -> anyhow::Result<(Vec, bool)> { + let client = self.create_client()?; + tracing::trace!(owner = owner, repo = repo, "fetching pull-requests"); + let request = client + .get(format!( + "{}/api/v1/repos/{}/{}/pulls?state=open&sort=recentupdate&page={}&limit={}", + &self.url.trim_end_matches("/"), + owner, + repo, + page, + 50, + )) + .build()?; + let resp = client.execute(request)?; + + let mut has_more = false; + + if let Some(gitea_has_more) = resp.headers().get("X-HasMore") { + let gitea_has_more = gitea_has_more.to_str()?; + if gitea_has_more == "true" || gitea_has_more == "True" { + has_more = true; + } + } + + if !resp.status().is_success() { + return Err(anyhow::anyhow!(resp.error_for_status().unwrap_err())); + } + let commits: Vec = resp.json()?; + + Ok((commits, has_more)) + }; + + self.get_pull_request_inner(owner, repo, request_pull_request) + } + + fn get_pull_request_inner( + &self, + owner: impl Into, + repo: impl Into, + request_pull_request: F, + ) -> anyhow::Result> + where + F: Fn(&str, &str, usize) -> anyhow::Result<(Vec, bool)>, + { + let mut page = 1; + + let owner: String = owner.into(); + let repo: String = repo.into(); + loop { + let (pull_requests, has_more) = request_pull_request(&owner, &repo, page)?; + + for pull_request in pull_requests { + if pull_request.head.r#ref.contains("cuddle-please/release") { + return Ok(Some(pull_request.number)); + } + } + + if !has_more { + break; + } + page += 1; + } + + Ok(None) + } + + pub fn create_pull_request( + &self, + owner: impl Into, + repo: impl Into, + version: impl Into, + body: impl Into, + base: impl Into, + ) -> anyhow::Result { + #[derive(Clone, Debug, Serialize, Deserialize)] + struct CreatePullRequestOption { + base: String, + body: String, + head: String, + title: String, + } + + let client = self.create_client()?; + + let owner = owner.into(); + let repo = repo.into(); + let version = version.into(); + let body = body.into(); + let base = base.into(); + + let request = CreatePullRequestOption { + base: base.clone(), + body: body.clone(), + head: "cuddle-please/release".into(), + title: format!("chore(release): {}", version), + }; + + tracing::trace!( + owner = owner, + repo = repo, + version = version, + base = base, + "create pull_request" + ); + let request = client + .post(format!( + "{}/api/v1/repos/{}/{}/pulls", + &self.url.trim_end_matches("/"), + owner, + repo, + )) + .json(&request) + .build()?; + let resp = client.execute(request)?; + + if !resp.status().is_success() { + return Err(anyhow::anyhow!(resp.error_for_status().unwrap_err())); + } + let commits: PullRequest = resp.json()?; + + Ok(commits.number) + } + + pub fn update_pull_request( + &self, + owner: impl Into, + repo: impl Into, + version: impl Into, + body: impl Into, + index: usize, + ) -> anyhow::Result { + #[derive(Clone, Debug, Serialize, Deserialize)] + struct CreatePullRequestOption { + body: String, + title: String, + } + + let client = self.create_client()?; + + let owner = owner.into(); + let repo = repo.into(); + let version = version.into(); + let body = body.into(); + + let request = CreatePullRequestOption { + body: body.clone(), + title: format!("chore(release): {}", version), + }; + + tracing::trace!( + owner = owner, + repo = repo, + version = version, + "update pull_request" + ); + let request = client + .patch(format!( + "{}/api/v1/repos/{}/{}/pulls", + &self.url.trim_end_matches("/"), + owner, + repo, + )) + .json(&request) + .build()?; + let resp = client.execute(request)?; + + if !resp.status().is_success() { + return Err(anyhow::anyhow!(resp.error_for_status().unwrap_err())); + } + let commits: PullRequest = resp.json()?; + + Ok(commits.number) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub struct PullRequest { + number: usize, + head: PRBranchInfo, +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub struct PRBranchInfo { + #[serde(alias = "ref")] + r#ref: String, + label: String, } #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]