feat: with all the way through
Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
parent
86eabad6fe
commit
df96de1cd0
@ -20,10 +20,13 @@ impl ChangeLogBuilder {
|
||||
pub fn new<C>(commits: C, version: impl Into<String>) -> Self
|
||||
where
|
||||
C: IntoIterator,
|
||||
C::Item: Into<String>,
|
||||
C::Item: AsRef<str>,
|
||||
{
|
||||
Self {
|
||||
commits: commits.into_iter().map(|s| s.into()).collect(),
|
||||
commits: commits
|
||||
.into_iter()
|
||||
.map(|s| s.as_ref().to_string())
|
||||
.collect(),
|
||||
version: version.into(),
|
||||
config: None,
|
||||
release_date: None,
|
||||
@ -236,7 +239,7 @@ fn default_changelog_body_config(release_link: Option<&str>) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
mod changelog_parser {
|
||||
pub mod changelog_parser {
|
||||
use std::{fs::read_to_string, path::Path};
|
||||
|
||||
use anyhow::Context;
|
||||
@ -264,9 +267,8 @@ mod changelog_parser {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn last_changes(changelog: &Path) -> anyhow::Result<Option<String>> {
|
||||
let changelog = read_to_string(changelog).context("can't read changelog file")?;
|
||||
last_changes_from_str(&changelog)
|
||||
pub fn last_changes(changelog: &str) -> anyhow::Result<Option<String>> {
|
||||
last_changes_from_str(changelog)
|
||||
}
|
||||
|
||||
pub fn last_changes_from_str(changelog: &str) -> anyhow::Result<Option<String>> {
|
||||
|
@ -5,16 +5,22 @@ use std::{
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use ::semver::Version;
|
||||
use anyhow::Context;
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
cliff::{self, changelog_parser},
|
||||
environment::get_from_environment,
|
||||
git_client::VcsClient,
|
||||
gitea_client::GiteaClient,
|
||||
gitea_client::{GiteaClient, Tag},
|
||||
ui::{ConsoleUi, DynUi},
|
||||
versioning::semver::get_most_significant_version,
|
||||
versioning::{
|
||||
conventional_parse::VersionIncrement,
|
||||
next_version::NextVersion,
|
||||
semver::{self, get_most_significant_version},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Parser)]
|
||||
@ -61,6 +67,14 @@ struct GlobalArgs {
|
||||
#[arg(long, global = true, help_heading = "Global")]
|
||||
source: Option<PathBuf>,
|
||||
|
||||
/// which branch is being run from
|
||||
#[arg(long, global = true, help_heading = "Global")]
|
||||
branch: Option<String>,
|
||||
|
||||
/// whether to run in dry run mode (i.e. no pushes or releases)
|
||||
#[arg(long, global = true, help_heading = "Global")]
|
||||
dry_run: bool,
|
||||
|
||||
/// no version control system, forces please to allow no .git/ or friends
|
||||
#[arg(
|
||||
long,
|
||||
@ -211,7 +225,7 @@ impl Command {
|
||||
let commits = client.get_commits_since(
|
||||
self.global.owner.unwrap(),
|
||||
self.global.repo.unwrap(),
|
||||
sha,
|
||||
Some(sha),
|
||||
branch,
|
||||
)?;
|
||||
self.ui.write_str_ln("got commits from gitea");
|
||||
@ -258,23 +272,127 @@ impl Command {
|
||||
// 2. Parse the cuddle.please.yaml let cuddle.please.yaml take precedence
|
||||
// 2a. if not existing use default.
|
||||
// 2b. if not in a git repo abort. (unless --no-vcs is turned added)
|
||||
let _config = self.get_config(¤t_dir, stdin)?;
|
||||
let _git_client = self.get_git(¤t_dir)?;
|
||||
let config = self.get_config(¤t_dir, stdin)?;
|
||||
|
||||
let owner = self.global.owner.as_ref().expect("owner to be set");
|
||||
let repo = self.global.repo.as_ref().expect("repo to be set");
|
||||
let branch = self.global.branch.as_ref().expect("branch to be set");
|
||||
|
||||
let git_client = self.get_git(¤t_dir)?;
|
||||
|
||||
// 3. Create gitea client and do a health check
|
||||
let gitea_client = self.get_gitea_client();
|
||||
gitea_client
|
||||
.connect(owner, repo)
|
||||
.context("failed to connect to gitea repository")?;
|
||||
// 4. Fetch git tags for the current repository
|
||||
// 5. Fetch git commits since last git tag
|
||||
let tags = gitea_client.get_tags(owner, repo)?;
|
||||
|
||||
// 6. Slice commits since last git tag
|
||||
let significant_tag = get_most_significant_version(tags.iter().collect());
|
||||
|
||||
// 5. Fetch git commits since last git tag
|
||||
let commits = gitea_client.get_commits_since(
|
||||
owner,
|
||||
repo,
|
||||
significant_tag.map(|st| st.commit.sha.clone()),
|
||||
branch,
|
||||
)?;
|
||||
|
||||
// 7. Create a versioning client
|
||||
let current_version = significant_tag
|
||||
.map(|st| Version::try_from(st).unwrap())
|
||||
.unwrap_or(Version::new(0, 1, 0));
|
||||
|
||||
// 8. Parse conventional commits and determine next version
|
||||
|
||||
// 9a. Check for open pr.
|
||||
// 10a. If exists parse history, rebase from master and rewrite pr
|
||||
let commit_strs = commits
|
||||
.iter()
|
||||
.map(|c| c.commit.message.as_str())
|
||||
.collect::<Vec<&str>>();
|
||||
|
||||
if commit_strs.is_empty() {
|
||||
tracing::info!("no commits to base release on");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let next_version = current_version.next(&commit_strs);
|
||||
|
||||
// Compose changelog
|
||||
let builder =
|
||||
cliff::ChangeLogBuilder::new(&commit_strs, next_version.to_string()).build();
|
||||
|
||||
let changelog_placement = self
|
||||
.global
|
||||
.source
|
||||
.as_ref()
|
||||
.map(|s| s.join("CHANGELOG.md"))
|
||||
.unwrap_or(PathBuf::from("CHANGELOG.md"));
|
||||
|
||||
let changelog = match std::fs::read_to_string(&changelog_placement).ok() {
|
||||
Some(existing_changelog) => builder.prepend(existing_changelog)?,
|
||||
None => builder.generate()?,
|
||||
};
|
||||
|
||||
let changelog_last_changes = changelog_parser::last_changes(&changelog)?;
|
||||
|
||||
// 9b. check for release commit and release, if release exists continue
|
||||
// 10b. create release
|
||||
if let Some(first_commit) = commit_strs.first() {
|
||||
if first_commit.contains("chore(release): ") {
|
||||
if !self.global.dry_run {
|
||||
gitea_client.create_release(
|
||||
owner,
|
||||
repo,
|
||||
next_version.to_string(),
|
||||
changelog_last_changes.unwrap(),
|
||||
!next_version.pre.is_empty(),
|
||||
)?;
|
||||
} else {
|
||||
tracing::debug!("creating release (dry_run)");
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// 9a. Create / Update Pr
|
||||
// Create or update branch
|
||||
git_client.checkout_branch()?;
|
||||
|
||||
std::fs::write(changelog_placement, changelog.as_bytes())?;
|
||||
|
||||
git_client.commit_and_push(next_version.to_string(), self.global.dry_run)?;
|
||||
|
||||
let pr_number = match gitea_client.get_pull_request(owner, repo)? {
|
||||
Some(existing_pr) => {
|
||||
if !self.global.dry_run {
|
||||
gitea_client.update_pull_request(
|
||||
owner,
|
||||
repo,
|
||||
next_version.to_string(),
|
||||
changelog_last_changes.unwrap(),
|
||||
existing_pr,
|
||||
)?
|
||||
} else {
|
||||
tracing::debug!("updating pull request (dry_run)");
|
||||
1
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if !self.global.dry_run {
|
||||
gitea_client.create_pull_request(
|
||||
owner,
|
||||
repo,
|
||||
next_version.to_string(),
|
||||
changelog,
|
||||
self.global.branch.clone().unwrap(),
|
||||
)?
|
||||
} else {
|
||||
tracing::debug!("creating pull request (dry_run)");
|
||||
1
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -295,6 +413,13 @@ impl Command {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_gitea_client(&self) -> GiteaClient {
|
||||
GiteaClient::new(
|
||||
self.global.api_url.clone().expect("api_url to be set"),
|
||||
self.global.token.clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Subcommand)]
|
||||
|
@ -1,4 +1,6 @@
|
||||
use anyhow::Context;
|
||||
use reqwest::header::{HeaderMap, HeaderValue};
|
||||
use semver::Version;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[allow(dead_code)]
|
||||
@ -42,12 +44,17 @@ impl GiteaClient {
|
||||
pub fn connect(&self, owner: impl Into<String>, repo: impl Into<String>) -> anyhow::Result<()> {
|
||||
let client = self.create_client()?;
|
||||
|
||||
let owner = owner.into();
|
||||
let repo = repo.into();
|
||||
|
||||
tracing::trace!(owner = &owner, repo = &repo, "gitea connect");
|
||||
|
||||
let request = client
|
||||
.get(format!(
|
||||
"{}/api/v1/repos/{}/{}",
|
||||
&self.url.trim_end_matches("/"),
|
||||
owner.into(),
|
||||
repo.into()
|
||||
owner,
|
||||
repo
|
||||
))
|
||||
.build()?;
|
||||
|
||||
@ -91,7 +98,7 @@ impl GiteaClient {
|
||||
&self,
|
||||
owner: impl Into<String>,
|
||||
repo: impl Into<String>,
|
||||
since_sha: impl Into<String>,
|
||||
since_sha: Option<impl Into<String>>,
|
||||
branch: impl Into<String>,
|
||||
) -> anyhow::Result<Vec<Commit>> {
|
||||
let get_commits_since_page = |owner: &str,
|
||||
@ -147,7 +154,7 @@ impl GiteaClient {
|
||||
&self,
|
||||
owner: impl Into<String>,
|
||||
repo: impl Into<String>,
|
||||
since_sha: impl Into<String>,
|
||||
since_sha: Option<impl Into<String>>,
|
||||
branch: impl Into<String>,
|
||||
get_commits: F,
|
||||
) -> anyhow::Result<Vec<Commit>>
|
||||
@ -159,19 +166,23 @@ impl GiteaClient {
|
||||
|
||||
let owner: String = owner.into();
|
||||
let repo: String = repo.into();
|
||||
let since_sha: String = since_sha.into();
|
||||
let since_sha: Option<String> = since_sha.map(|ss| ss.into());
|
||||
let branch: String = branch.into();
|
||||
let mut found_commit = false;
|
||||
loop {
|
||||
let (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 let Some(since_sha) = &since_sha {
|
||||
if commit.sha.contains(since_sha) {
|
||||
found_commit = true;
|
||||
} else {
|
||||
if !found_commit {
|
||||
commits.push(commit);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
commits.push(commit);
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,10 +192,10 @@ impl GiteaClient {
|
||||
page += 1;
|
||||
}
|
||||
|
||||
if found_commit == false {
|
||||
if found_commit == false && since_sha.is_some() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"sha was not found in commit chain: {} on branch: {}",
|
||||
since_sha,
|
||||
since_sha.unwrap_or("".into()),
|
||||
branch
|
||||
));
|
||||
}
|
||||
@ -355,10 +366,11 @@ impl GiteaClient {
|
||||
);
|
||||
let request = client
|
||||
.patch(format!(
|
||||
"{}/api/v1/repos/{}/{}/pulls",
|
||||
"{}/api/v1/repos/{}/{}/pulls/{}",
|
||||
&self.url.trim_end_matches("/"),
|
||||
owner,
|
||||
repo,
|
||||
index
|
||||
))
|
||||
.json(&request)
|
||||
.build()?;
|
||||
@ -481,6 +493,29 @@ pub struct Tag {
|
||||
pub commit: TagCommit,
|
||||
}
|
||||
|
||||
impl TryFrom<Tag> for Version {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: Tag) -> Result<Self, Self::Error> {
|
||||
tracing::trace!(name = &value.name, "parsing tag into version");
|
||||
value
|
||||
.name
|
||||
.parse::<Version>()
|
||||
.context("could not get version from tag")
|
||||
}
|
||||
}
|
||||
impl TryFrom<&Tag> for Version {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: &Tag) -> Result<Self, Self::Error> {
|
||||
tracing::trace!(name = &value.name, "parsing tag into version");
|
||||
value
|
||||
.name
|
||||
.parse::<Version>()
|
||||
.context("could not get version from tag")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct TagCommit {
|
||||
pub created: String,
|
||||
@ -531,7 +566,7 @@ mod test {
|
||||
let commits = client.get_commits_since_inner(
|
||||
"owner",
|
||||
"repo",
|
||||
sha,
|
||||
Some(sha),
|
||||
"some-branch",
|
||||
|_, _, _, page| -> anyhow::Result<(Vec<Commit>, bool)> {
|
||||
let commit_page = api_res.get(page - 1).unwrap();
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::cmp::Reverse;
|
||||
|
||||
use crate::gitea_client::{Tag};
|
||||
use crate::gitea_client::Tag;
|
||||
use semver::Version;
|
||||
|
||||
pub fn get_most_significant_version<'a>(tags: Vec<&'a Tag>) -> Option<&'a Tag> {
|
||||
@ -16,7 +16,13 @@ pub fn get_most_significant_version<'a>(tags: Vec<&'a Tag>) -> Option<&'a Tag> {
|
||||
.collect();
|
||||
versions.sort_unstable_by_key(|(_, version)| Reverse(version.clone()));
|
||||
|
||||
versions.first().map(|(tag, _)| *tag)
|
||||
let tag = versions.first().map(|(tag, _)| *tag);
|
||||
|
||||
if let Some(tag) = tag {
|
||||
tracing::trace!(name = &tag.name, "found most significant tag with version");
|
||||
}
|
||||
|
||||
tag
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
Loading…
Reference in New Issue
Block a user