From a63c6ce3be16604deaffb37abc9a443e22ec9a77 Mon Sep 17 00:00:00 2001 From: kjuulh Date: Sun, 30 Jul 2023 13:24:40 +0200 Subject: [PATCH] feat: can get commit chain Signed-off-by: kjuulh --- crates/cuddle-please/src/command.rs | 60 ++++-- crates/cuddle-please/src/gitea_client.rs | 256 ++++++++++++++++++++++- 2 files changed, 295 insertions(+), 21 deletions(-) diff --git a/crates/cuddle-please/src/command.rs b/crates/cuddle-please/src/command.rs index fdb7e8b..ca34418 100644 --- a/crates/cuddle-please/src/command.rs +++ b/crates/cuddle-please/src/command.rs @@ -163,23 +163,45 @@ impl Command { self.ui.write_str_ln(&format!("cuddle-config")); } }, - Some(Commands::Gitea { command }) => match command { - GiteaCommand::Connect {} => { - let git_url = url::Url::parse(&self.global.api_url.unwrap())?; + Some(Commands::Gitea { command }) => { + let git_url = url::Url::parse(&self.global.api_url.unwrap())?; - let mut url = String::new(); - url.push_str(git_url.scheme()); - url.push_str("://"); - url.push_str(&git_url.host().unwrap().to_string()); - if let Some(port) = git_url.port() { - url.push_str(format!(":{port}").as_str()); - } - - let client = GiteaClient::new(url, self.global.token); - client.connect(self.global.owner.unwrap(), self.global.repo.unwrap())?; - self.ui.write_str_ln("connected succesfully go gitea"); + let mut url = String::new(); + url.push_str(git_url.scheme()); + url.push_str("://"); + url.push_str(&git_url.host().unwrap().to_string()); + if let Some(port) = git_url.port() { + url.push_str(format!(":{port}").as_str()); } - }, + + let client = GiteaClient::new(url, self.global.token); + match command { + GiteaCommand::Connect {} => { + client.connect(self.global.owner.unwrap(), self.global.repo.unwrap())?; + self.ui.write_str_ln("connected succesfully go gitea"); + } + GiteaCommand::Tags {} => { + let tags = client + .get_tags(self.global.owner.unwrap(), self.global.repo.unwrap())?; + self.ui.write_str_ln("got tags from gitea"); + for tag in tags { + self.ui.write_str_ln(&format!("- {}", tag.name)) + } + } + GiteaCommand::SinceCommit { sha, branch } => { + let commits = client.get_commits_since( + self.global.owner.unwrap(), + self.global.repo.unwrap(), + sha, + branch, + )?; + self.ui.write_str_ln("got commits from gitea"); + for commit in commits { + self.ui.write_str_ln(&format!("- {}", commit.get_title())) + } + } + } + } None => { tracing::debug!("running bare command"); // 2. Parse the cuddle.please.yaml let cuddle.please.yaml take precedence @@ -247,6 +269,14 @@ enum ConfigCommand { #[derive(Subcommand, Debug, Clone)] enum GiteaCommand { Connect {}, + Tags {}, + SinceCommit { + #[arg(long)] + sha: String, + + #[arg(long)] + branch: String, + }, } fn get_current_path( diff --git a/crates/cuddle-please/src/gitea_client.rs b/crates/cuddle-please/src/gitea_client.rs index b3f1b30..9dc2a04 100644 --- a/crates/cuddle-please/src/gitea_client.rs +++ b/crates/cuddle-please/src/gitea_client.rs @@ -55,6 +55,7 @@ impl GiteaClient { if !resp.status().is_success() { resp.error_for_status()?; + return Ok(()); } Ok(()) @@ -63,20 +64,263 @@ impl GiteaClient { pub fn get_tags( &self, owner: impl Into, - repository: impl Into, + repo: impl Into, ) -> anyhow::Result> { - todo!() + let client = self.create_client()?; + + let request = client + .get(format!( + "{}/api/v1/repos/{}/{}/tags", + &self.url.trim_end_matches("/"), + owner.into(), + repo.into() + )) + .build()?; + + let resp = client.execute(request)?; + + if !resp.status().is_success() { + return Err(anyhow::anyhow!(resp.error_for_status().unwrap_err())); + } + let tags: Vec = resp.json()?; + + Ok(tags) } pub fn get_commits_since( &self, owner: impl Into, - repository: impl Into, + repo: impl Into, since_sha: impl Into, - ) -> anyhow::Result> { - todo!() + branch: impl Into, + ) -> anyhow::Result> { + let get_commits_since_page = |owner: &str, + repo: &str, + branch: &str, + page: usize| + -> anyhow::Result<(Vec, bool)> { + let client = self.create_client()?; + tracing::trace!( + owner = owner, + repo = repo, + branch = branch, + page = page, + "fetching tags" + ); + let request = client + .get(format!( + "{}/api/v1/repos/{}/{}/commits?page={}&limit={}&sha={}&stat=false&verification=false&files=false", + &self.url.trim_end_matches("/"), + owner, + repo, + page, + 50, + branch, + )) + .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)) + }; + + let commits = + self.get_commits_since_inner(owner, repo, since_sha, branch, get_commits_since_page)?; + + Ok(commits) + } + + fn get_commits_since_inner( + &self, + owner: impl Into, + repo: impl Into, + since_sha: impl Into, + branch: impl Into, + get_commits: F, + ) -> anyhow::Result> + where + F: Fn(&str, &str, &str, usize) -> anyhow::Result<(Vec, bool)>, + { + let mut commits = Vec::new(); + let mut page = 1; + + let owner: String = owner.into(); + let repo: String = repo.into(); + let since_sha: String = since_sha.into(); + let branch: String = branch.into(); + let mut found_commit = false; + loop { + let (mut new_commits, has_more) = get_commits(&owner, &repo, &branch, page)?; + + for commit in new_commits { + if commit.sha.contains(&since_sha) { + found_commit = true; + } else { + if !found_commit { + commits.push(commit); + } + } + } + + if !has_more { + break; + } + page += 1; + } + + if found_commit == false { + return Err(anyhow::anyhow!( + "sha was not found in commit chain: {} on branch: {}", + since_sha, + branch + )); + } + + Ok(commits) } } +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub struct Commit { + sha: String, + pub created: String, + pub commit: CommitDetails, +} + +impl Commit { + pub fn get_title(&self) -> String { + self.commit + .message + .split("\n") + .take(1) + .collect::>() + .join("\n") + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub struct CommitDetails { + pub message: String, +} + #[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Tag {} +pub struct Tag { + pub id: String, + pub message: String, + pub name: String, + pub commit: TagCommit, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct TagCommit { + created: String, + pub sha: String, + url: String, +} + +#[cfg(test)] +mod test { + use tracing_test::traced_test; + + use crate::gitea_client::{Commit, CommitDetails}; + + use super::GiteaClient; + + fn get_api_res() -> Vec> { + let api_results = vec![ + vec![Commit { + sha: "first-sha".into(), + created: "".into(), + commit: CommitDetails { + message: "first-message".into(), + }, + }], + vec![Commit { + sha: "second-sha".into(), + created: "".into(), + commit: CommitDetails { + message: "second-message".into(), + }, + }], + vec![Commit { + sha: "third-sha".into(), + created: "".into(), + commit: CommitDetails { + message: "third-message".into(), + }, + }], + ]; + + api_results + } + + fn get_commits(sha: String) -> anyhow::Result<(Vec>, Vec)> { + let api_res = get_api_res(); + let client = GiteaClient::new("", Some("")); + + let commits = client.get_commits_since_inner( + "owner", + "repo", + sha, + "some-branch", + |_, _, _, page| -> anyhow::Result<(Vec, bool)> { + let commit_page = api_res.get(page - 1).unwrap(); + + Ok((commit_page.clone(), page != 3)) + }, + )?; + + Ok((api_res, commits)) + } + + #[test] + #[traced_test] + fn finds_tag_in_list() { + let (expected, actual) = get_commits("second-sha".into()).unwrap(); + + assert_eq!( + expected.get(0).unwrap().clone().as_slice(), + actual.as_slice() + ); + } + + #[test] + #[traced_test] + fn finds_tag_in_list_already_newest_commit() { + let (_, actual) = get_commits("first-sha".into()).unwrap(); + + assert_eq!(0, actual.len()); + } + + #[test] + #[traced_test] + fn finds_tag_in_list_is_base() { + let (expected, actual) = get_commits("third-sha".into()).unwrap(); + + assert_eq!(expected[0..=1].concat().as_slice(), actual.as_slice()); + } + + #[test] + #[traced_test] + fn finds_didnt_find_tag_in_list() { + let error = get_commits("not-found-sha".into()).unwrap_err(); + + assert_eq!( + "sha was not found in commit chain: not-found-sha on branch: some-branch", + error.to_string() + ); + } +}