Skip to content

Commit

Permalink
feat: more accurate release headers for changelog
Browse files Browse the repository at this point in the history
  • Loading branch information
Coenraad Human committed Jun 4, 2024
1 parent 984ae6b commit a683fab
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 50 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ fancy-regex = { version = "0.13.0" }
chrono = { version = "0.4.38" }
lazy_static = { version = "1.4.0" }
Inflector = { version = "0.11.4" }
indexmap = { version = "2.2.6" }

openssl = { version = "0.10.64", features = ["vendored"] }
libz-sys = { version = "1.1.16" }
66 changes: 34 additions & 32 deletions src/commands/changelog.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
extern crate inflector;
use inflector::Inflector;

use crate::conventional::create_conventional_commit;
use crate::git::{retrieve_branch_commits, retrieve_tags};
use crate::conventional::{create_conventional_commit, scope_filter_check};
use crate::git::{retrieve_branch_commits, retrieve_commit_tag_map};
use crate::semantic::{add_commit_to_version, Version};

// Todo: find a nice template engine for Rust to create the changelog document:
// Todo: use map for tag associated with commits on current branch:
pub fn run(path: Option<String>, _commit_git_hook: Option<String>, scope_filter: Option<String>) {
let commits = retrieve_branch_commits(path.clone());
let tags = retrieve_tags(path.clone());
let optional_oid_commit_tag_map = retrieve_commit_tag_map(path);

let mut version = Version::new(0, 0, 0);
let mut past_tag_version = Version::new(0, 0, 0);
let mut simple_changelog = "".to_owned();
let mut unreleased_count: usize = 0;

Expand All @@ -26,35 +25,38 @@ pub fn run(path: Option<String>, _commit_git_hook: Option<String>, scope_filter:

match create_conventional_commit(message) {
Some(conventional_commit) => {
simple_changelog.insert_str(0, " </tr>\n");
simple_changelog.insert_str(0, &format!(" <td>{}</td>\n", commit.committer.to_changelog_string()));
simple_changelog.insert_str(0, &format!(" <td>{}</td>\n", commit.author.to_changelog_string()));
simple_changelog.insert_str(0, &format!(" <td>{}</td>\n", if conventional_commit.is_deprecrated { 'X' } else { ' ' }));
simple_changelog.insert_str(0, &format!(" <td>{}</td>\n", if conventional_commit.is_breaking { 'X' } else { ' ' }));
simple_changelog.insert_str(0, &format!(" <td>{}.</td>\n", conventional_commit.commit_description.to_sentence_case()));
simple_changelog.insert_str(0, &format!(" <td>{}</td>\n", conventional_commit.commit_type));
simple_changelog.insert_str(0, &format!(" <td>{}</td>\n", version.format()));
simple_changelog.insert_str(0, " <tr>\n");
},
None => (),
}

let found_version_as_tag = tags.clone()
.into_iter()
.filter(|tag| tag.contains(version.format().as_str()))
.count() > 0;
if scope_filter_check(scope_filter.clone(), conventional_commit.scope) {
// Add commit to log:
simple_changelog.insert_str(0, " </tr>\n");
simple_changelog.insert_str(0, &format!(" <td>{}</td>\n", commit.committer.to_changelog_string()));
simple_changelog.insert_str(0, &format!(" <td>{}</td>\n", commit.author.to_changelog_string()));
simple_changelog.insert_str(0, &format!(" <td>{}</td>\n", if conventional_commit.is_deprecrated { 'X' } else { ' ' }));
simple_changelog.insert_str(0, &format!(" <td>{}</td>\n", if conventional_commit.is_breaking { 'X' } else { ' ' }));
simple_changelog.insert_str(0, &format!(" <td>{}.</td>\n", conventional_commit.commit_description.to_sentence_case()));
simple_changelog.insert_str(0, &format!(" <td>{}</td>\n", conventional_commit.commit_type));
simple_changelog.insert_str(0, &format!(" <td>{}</td>\n", version.format()));
simple_changelog.insert_str(0, " <tr>\n");

let version_updated_since_previous_releate = !past_tag_version.equal(&version);

if found_version_as_tag && version_updated_since_previous_releate && unreleased_count != 0 {
simple_changelog.insert_str(0, " </tr>\n");
simple_changelog.insert_str(0, &format!(" <td colspan=\"7\"><em><strong>Release: {}</strong></em></td>\n", version.format()));
simple_changelog.insert_str(0, " <tr>\n");

past_tag_version = version.clone();
unreleased_count = 0;
} else {
unreleased_count = unreleased_count + 1;
match optional_oid_commit_tag_map {
Some(ref map) => {
// Add release entry to log:
match map.get(&commit.oid) {
Some(found_tag) => {
simple_changelog.insert_str(0, " </tr>\n");
simple_changelog.insert_str(0, &format!(" <td colspan=\"7\"><em><strong>Release: {}</strong></em></td>\n", found_tag));
simple_changelog.insert_str(0, " <tr>\n");
unreleased_count = 0;
},
// No release associated with commit, add to counter to determine no release header:
None => unreleased_count = unreleased_count + 1,
}
},
// No release associated with commit, add to counter to determine no release header:
None => unreleased_count = unreleased_count + 1,
};
}
},
None => {},
}
},
None => {},
Expand Down
56 changes: 45 additions & 11 deletions src/git.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::env;
use git2::Repository;
use indexmap::IndexMap;

pub struct Author {

Expand Down Expand Up @@ -56,6 +57,7 @@ pub struct Commit {
pub message: Option<String>,
pub author: Author,
pub committer: Committer,
pub oid: String

}

Expand Down Expand Up @@ -85,22 +87,54 @@ fn determine_path(path: Option<String>) -> String {
}
}

pub fn retrieve_tags(path: Option<String>) -> Vec<String> {
pub fn retrieve_commit_tag_map(path: Option<String>) -> Option<IndexMap<String, String>> {
// Determine repository path:
let found_path = determine_path(path);

// Open repository:
let repo = retrieve_git_repository(found_path);

match repo.tag_names(None) {
Ok(found_tags) => {
found_tags
.into_iter()
.filter(|value| value.is_some())
.map(|value| value.unwrap().to_owned())
.collect()
},
Err(_) => Vec::new(),
// Vector for keeping tags and commit associated:
let mut tags: Vec<(git2::Commit, String)> = Vec::new();

// Retrieve tags from repository:
let tag_names = match repo.tag_names(None) {
Ok(tags) => tags,
Err(_) => panic!("Could not retrieve tag names"),
};

if tag_names.len() == 0 {
return None;
}

// For each tag find commit:
for name in tag_names.iter().flatten().map(String::from) {
// Retrieve git object found for tag:
let git_object = match repo.revparse_single(&name) {
Ok(object) => object,
Err(_) => panic!("Could not retrieve git object for processing"),
};
// See if git object is commit:
if let Ok(commit) = git_object.clone().into_commit() {
tags.push((commit, name))

// If it is not commit, it might be a tag, ignore git tree and blobs:
} else if let Some(tag) = git_object.as_tag() {

// Find commit for tag object:
if let Some(commit) = tag.target().ok().and_then(|target| target.into_commit().ok()) {
tags.push((commit, name));
}
}
}

return Some(tags
.into_iter()
.map(|(commit, tag)| (commit.id().to_string(), tag))
.collect());
}


pub fn retrieve_branch_commits(path: Option<String>) -> Vec<Commit> {
let found_path = determine_path(path);
let repo = retrieve_git_repository(found_path);
Expand Down Expand Up @@ -147,7 +181,7 @@ pub fn retrieve_branch_commits(path: Option<String>) -> Vec<Commit> {
},
};

Commit { message, author, committer }
Commit { message, author, committer, oid: oid.to_string() }
},
Err(_) => panic!("Could not retrieve oid of commit"),
}
Expand Down
7 changes: 0 additions & 7 deletions src/semantic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,6 @@ impl Version {
format!("{}.{}.{}", self.major, self.minor, self.patch)
}

pub fn equal(&self, comparator: &Version) -> bool {
self.major == comparator.major && self.minor == comparator.minor && self.patch == comparator.patch
}

pub fn clone(&self) -> Version {
Version { major: self.major, minor: self.minor, patch: self.patch }
}
}

pub enum Impact {
Expand Down

0 comments on commit a683fab

Please sign in to comment.