feat: can get commit chain

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
Kasper Juul Hermansen 2023-07-30 13:24:40 +02:00
parent 1b9f8b26e6
commit a63c6ce3be
Signed by: kjuulh
GPG Key ID: 9AA7BC13CE474394
2 changed files with 295 additions and 21 deletions

View File

@ -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(

View File

@ -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<String>,
repository: impl Into<String>,
repo: impl Into<String>,
) -> anyhow::Result<Vec<Tag>> {
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<Tag> = resp.json()?;
Ok(tags)
}
pub fn get_commits_since(
&self,
owner: impl Into<String>,
repository: impl Into<String>,
repo: impl Into<String>,
since_sha: impl Into<String>,
) -> anyhow::Result<Vec<Tag>> {
todo!()
branch: impl Into<String>,
) -> anyhow::Result<Vec<Commit>> {
let get_commits_since_page = |owner: &str,
repo: &str,
branch: &str,
page: usize|
-> anyhow::Result<(Vec<Commit>, 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<Commit> = 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<F>(
&self,
owner: impl Into<String>,
repo: impl Into<String>,
since_sha: impl Into<String>,
branch: impl Into<String>,
get_commits: F,
) -> anyhow::Result<Vec<Commit>>
where
F: Fn(&str, &str, &str, usize) -> anyhow::Result<(Vec<Commit>, 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::<Vec<&str>>()
.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<Vec<Commit>> {
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<Commit>>, Vec<Commit>)> {
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<Commit>, 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()
);
}
}