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
|
pub fn new<C>(commits: C, version: impl Into<String>) -> Self
|
||||||
where
|
where
|
||||||
C: IntoIterator,
|
C: IntoIterator,
|
||||||
C::Item: Into<String>,
|
C::Item: AsRef<str>,
|
||||||
{
|
{
|
||||||
Self {
|
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(),
|
version: version.into(),
|
||||||
config: None,
|
config: None,
|
||||||
release_date: 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 std::{fs::read_to_string, path::Path};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
@ -264,9 +267,8 @@ mod changelog_parser {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn last_changes(changelog: &Path) -> anyhow::Result<Option<String>> {
|
pub fn last_changes(changelog: &str) -> anyhow::Result<Option<String>> {
|
||||||
let changelog = read_to_string(changelog).context("can't read changelog file")?;
|
last_changes_from_str(changelog)
|
||||||
last_changes_from_str(&changelog)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn last_changes_from_str(changelog: &str) -> anyhow::Result<Option<String>> {
|
pub fn last_changes_from_str(changelog: &str) -> anyhow::Result<Option<String>> {
|
||||||
|
@ -5,16 +5,22 @@ use std::{
|
|||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use ::semver::Version;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use clap::{Args, Parser, Subcommand};
|
use clap::{Args, Parser, Subcommand};
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
cliff::{self, changelog_parser},
|
||||||
environment::get_from_environment,
|
environment::get_from_environment,
|
||||||
git_client::VcsClient,
|
git_client::VcsClient,
|
||||||
gitea_client::GiteaClient,
|
gitea_client::{GiteaClient, Tag},
|
||||||
ui::{ConsoleUi, DynUi},
|
ui::{ConsoleUi, DynUi},
|
||||||
versioning::semver::get_most_significant_version,
|
versioning::{
|
||||||
|
conventional_parse::VersionIncrement,
|
||||||
|
next_version::NextVersion,
|
||||||
|
semver::{self, get_most_significant_version},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
@ -61,6 +67,14 @@ struct GlobalArgs {
|
|||||||
#[arg(long, global = true, help_heading = "Global")]
|
#[arg(long, global = true, help_heading = "Global")]
|
||||||
source: Option<PathBuf>,
|
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
|
/// no version control system, forces please to allow no .git/ or friends
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
@ -211,7 +225,7 @@ impl Command {
|
|||||||
let commits = client.get_commits_since(
|
let commits = client.get_commits_since(
|
||||||
self.global.owner.unwrap(),
|
self.global.owner.unwrap(),
|
||||||
self.global.repo.unwrap(),
|
self.global.repo.unwrap(),
|
||||||
sha,
|
Some(sha),
|
||||||
branch,
|
branch,
|
||||||
)?;
|
)?;
|
||||||
self.ui.write_str_ln("got commits from gitea");
|
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
|
// 2. Parse the cuddle.please.yaml let cuddle.please.yaml take precedence
|
||||||
// 2a. if not existing use default.
|
// 2a. if not existing use default.
|
||||||
// 2b. if not in a git repo abort. (unless --no-vcs is turned added)
|
// 2b. if not in a git repo abort. (unless --no-vcs is turned added)
|
||||||
let _config = self.get_config(¤t_dir, stdin)?;
|
let config = self.get_config(¤t_dir, stdin)?;
|
||||||
let _git_client = self.get_git(¤t_dir)?;
|
|
||||||
|
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
|
// 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
|
// 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
|
// 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
|
// 8. Parse conventional commits and determine next version
|
||||||
|
|
||||||
// 9a. Check for open pr.
|
let commit_strs = commits
|
||||||
// 10a. If exists parse history, rebase from master and rewrite pr
|
.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
|
// 9b. check for release commit and release, if release exists continue
|
||||||
// 10b. create release
|
// 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(())
|
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)]
|
#[derive(Debug, Clone, Subcommand)]
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
use anyhow::Context;
|
||||||
use reqwest::header::{HeaderMap, HeaderValue};
|
use reqwest::header::{HeaderMap, HeaderValue};
|
||||||
|
use semver::Version;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@ -42,12 +44,17 @@ impl GiteaClient {
|
|||||||
pub fn connect(&self, owner: impl Into<String>, repo: impl Into<String>) -> anyhow::Result<()> {
|
pub fn connect(&self, owner: impl Into<String>, repo: impl Into<String>) -> anyhow::Result<()> {
|
||||||
let client = self.create_client()?;
|
let client = self.create_client()?;
|
||||||
|
|
||||||
|
let owner = owner.into();
|
||||||
|
let repo = repo.into();
|
||||||
|
|
||||||
|
tracing::trace!(owner = &owner, repo = &repo, "gitea connect");
|
||||||
|
|
||||||
let request = client
|
let request = client
|
||||||
.get(format!(
|
.get(format!(
|
||||||
"{}/api/v1/repos/{}/{}",
|
"{}/api/v1/repos/{}/{}",
|
||||||
&self.url.trim_end_matches("/"),
|
&self.url.trim_end_matches("/"),
|
||||||
owner.into(),
|
owner,
|
||||||
repo.into()
|
repo
|
||||||
))
|
))
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
@ -91,7 +98,7 @@ impl GiteaClient {
|
|||||||
&self,
|
&self,
|
||||||
owner: impl Into<String>,
|
owner: impl Into<String>,
|
||||||
repo: impl Into<String>,
|
repo: impl Into<String>,
|
||||||
since_sha: impl Into<String>,
|
since_sha: Option<impl Into<String>>,
|
||||||
branch: impl Into<String>,
|
branch: impl Into<String>,
|
||||||
) -> anyhow::Result<Vec<Commit>> {
|
) -> anyhow::Result<Vec<Commit>> {
|
||||||
let get_commits_since_page = |owner: &str,
|
let get_commits_since_page = |owner: &str,
|
||||||
@ -147,7 +154,7 @@ impl GiteaClient {
|
|||||||
&self,
|
&self,
|
||||||
owner: impl Into<String>,
|
owner: impl Into<String>,
|
||||||
repo: impl Into<String>,
|
repo: impl Into<String>,
|
||||||
since_sha: impl Into<String>,
|
since_sha: Option<impl Into<String>>,
|
||||||
branch: impl Into<String>,
|
branch: impl Into<String>,
|
||||||
get_commits: F,
|
get_commits: F,
|
||||||
) -> anyhow::Result<Vec<Commit>>
|
) -> anyhow::Result<Vec<Commit>>
|
||||||
@ -159,20 +166,24 @@ impl GiteaClient {
|
|||||||
|
|
||||||
let owner: String = owner.into();
|
let owner: String = owner.into();
|
||||||
let repo: String = repo.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 branch: String = branch.into();
|
||||||
let mut found_commit = false;
|
let mut found_commit = false;
|
||||||
loop {
|
loop {
|
||||||
let (new_commits, has_more) = get_commits(&owner, &repo, &branch, page)?;
|
let (new_commits, has_more) = get_commits(&owner, &repo, &branch, page)?;
|
||||||
|
|
||||||
for commit in new_commits {
|
for commit in new_commits {
|
||||||
if commit.sha.contains(&since_sha) {
|
if let Some(since_sha) = &since_sha {
|
||||||
|
if commit.sha.contains(since_sha) {
|
||||||
found_commit = true;
|
found_commit = true;
|
||||||
} else {
|
} else {
|
||||||
if !found_commit {
|
if !found_commit {
|
||||||
commits.push(commit);
|
commits.push(commit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
commits.push(commit);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !has_more {
|
if !has_more {
|
||||||
@ -181,10 +192,10 @@ impl GiteaClient {
|
|||||||
page += 1;
|
page += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if found_commit == false {
|
if found_commit == false && since_sha.is_some() {
|
||||||
return Err(anyhow::anyhow!(
|
return Err(anyhow::anyhow!(
|
||||||
"sha was not found in commit chain: {} on branch: {}",
|
"sha was not found in commit chain: {} on branch: {}",
|
||||||
since_sha,
|
since_sha.unwrap_or("".into()),
|
||||||
branch
|
branch
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -355,10 +366,11 @@ impl GiteaClient {
|
|||||||
);
|
);
|
||||||
let request = client
|
let request = client
|
||||||
.patch(format!(
|
.patch(format!(
|
||||||
"{}/api/v1/repos/{}/{}/pulls",
|
"{}/api/v1/repos/{}/{}/pulls/{}",
|
||||||
&self.url.trim_end_matches("/"),
|
&self.url.trim_end_matches("/"),
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
|
index
|
||||||
))
|
))
|
||||||
.json(&request)
|
.json(&request)
|
||||||
.build()?;
|
.build()?;
|
||||||
@ -481,6 +493,29 @@ pub struct Tag {
|
|||||||
pub commit: TagCommit,
|
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)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
pub struct TagCommit {
|
pub struct TagCommit {
|
||||||
pub created: String,
|
pub created: String,
|
||||||
@ -531,7 +566,7 @@ mod test {
|
|||||||
let commits = client.get_commits_since_inner(
|
let commits = client.get_commits_since_inner(
|
||||||
"owner",
|
"owner",
|
||||||
"repo",
|
"repo",
|
||||||
sha,
|
Some(sha),
|
||||||
"some-branch",
|
"some-branch",
|
||||||
|_, _, _, page| -> anyhow::Result<(Vec<Commit>, bool)> {
|
|_, _, _, page| -> anyhow::Result<(Vec<Commit>, bool)> {
|
||||||
let commit_page = api_res.get(page - 1).unwrap();
|
let commit_page = api_res.get(page - 1).unwrap();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::cmp::Reverse;
|
use std::cmp::Reverse;
|
||||||
|
|
||||||
use crate::gitea_client::{Tag};
|
use crate::gitea_client::Tag;
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
|
|
||||||
pub fn get_most_significant_version<'a>(tags: Vec<&'a Tag>) -> Option<&'a Tag> {
|
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();
|
.collect();
|
||||||
versions.sort_unstable_by_key(|(_, version)| Reverse(version.clone()));
|
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)]
|
#[cfg(test)]
|
||||||
|
Loading…
Reference in New Issue
Block a user