mirror of
https://github.com/orhun/git-cliff.git
synced 2024-09-11 15:05:30 +03:00
Merge 68b7e79e75
into 3eb828e69a
This commit is contained in:
commit
6fb2295134
@ -499,7 +499,7 @@ impl<'a> Changelog<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Generates the changelog and writes it to the given output.
|
/// Generates the changelog and writes it to the given output.
|
||||||
pub fn generate<W: Write>(&self, out: &mut W) -> Result<()> {
|
pub fn generate<W: Write + ?Sized>(&self, out: &mut W) -> Result<()> {
|
||||||
debug!("Generating changelog...");
|
debug!("Generating changelog...");
|
||||||
let postprocessors = self
|
let postprocessors = self
|
||||||
.config
|
.config
|
||||||
@ -567,7 +567,7 @@ impl<'a> Changelog<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Generates a changelog and prepends it to the given changelog.
|
/// Generates a changelog and prepends it to the given changelog.
|
||||||
pub fn prepend<W: Write>(
|
pub fn prepend<W: Write + ?Sized>(
|
||||||
&self,
|
&self,
|
||||||
mut changelog: String,
|
mut changelog: String,
|
||||||
out: &mut W,
|
out: &mut W,
|
||||||
@ -582,7 +582,7 @@ impl<'a> Changelog<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Prints the changelog context to the given output.
|
/// Prints the changelog context to the given output.
|
||||||
pub fn write_context<W: Write>(&self, out: &mut W) -> Result<()> {
|
pub fn write_context<W: Write + ?Sized>(&self, out: &mut W) -> Result<()> {
|
||||||
let output = Releases {
|
let output = Releases {
|
||||||
releases: &self.releases,
|
releases: &self.releases,
|
||||||
}
|
}
|
||||||
|
@ -55,14 +55,14 @@ impl Repository {
|
|||||||
/// Sorts the commits by their time.
|
/// Sorts the commits by their time.
|
||||||
pub fn commits(
|
pub fn commits(
|
||||||
&self,
|
&self,
|
||||||
range: Option<String>,
|
range: Option<&str>,
|
||||||
include_path: Option<Vec<Pattern>>,
|
include_path: Option<&[Pattern]>,
|
||||||
exclude_path: Option<Vec<Pattern>>,
|
exclude_path: Option<&[Pattern]>,
|
||||||
) -> Result<Vec<Commit>> {
|
) -> Result<Vec<Commit>> {
|
||||||
let mut revwalk = self.inner.revwalk()?;
|
let mut revwalk = self.inner.revwalk()?;
|
||||||
revwalk.set_sorting(Sort::TOPOLOGICAL)?;
|
revwalk.set_sorting(Sort::TOPOLOGICAL)?;
|
||||||
if let Some(range) = range {
|
if let Some(range) = range {
|
||||||
revwalk.push_range(&range)?;
|
revwalk.push_range(range)?;
|
||||||
} else {
|
} else {
|
||||||
revwalk.push_head()?;
|
revwalk.push_head()?;
|
||||||
}
|
}
|
||||||
@ -72,31 +72,31 @@ impl Repository {
|
|||||||
.collect();
|
.collect();
|
||||||
if include_path.is_some() || exclude_path.is_some() {
|
if include_path.is_some() || exclude_path.is_some() {
|
||||||
commits.retain(|commit| {
|
commits.retain(|commit| {
|
||||||
if let Ok(prev_commit) = commit.parent(0) {
|
let Ok(prev_commit) = commit.parent(0) else {
|
||||||
if let Ok(diff) = self.inner.diff_tree_to_tree(
|
return false;
|
||||||
commit.tree().ok().as_ref(),
|
};
|
||||||
prev_commit.tree().ok().as_ref(),
|
let Ok(diff) = self.inner.diff_tree_to_tree(
|
||||||
None,
|
commit.tree().ok().as_ref(),
|
||||||
) {
|
prev_commit.tree().ok().as_ref(),
|
||||||
return diff
|
None,
|
||||||
.deltas()
|
) else {
|
||||||
.filter_map(|delta| delta.new_file().path())
|
return false;
|
||||||
.any(|new_file_path| {
|
};
|
||||||
if let Some(include_path) = &include_path {
|
diff.deltas()
|
||||||
include_path
|
.filter_map(|delta| delta.new_file().path())
|
||||||
.iter()
|
.any(|new_file_path| {
|
||||||
.any(|glob| glob.matches_path(new_file_path))
|
if let Some(include_path) = include_path {
|
||||||
} else if let Some(exclude_path) = &exclude_path {
|
return include_path
|
||||||
!exclude_path
|
.iter()
|
||||||
.iter()
|
.any(|glob| glob.matches_path(new_file_path));
|
||||||
.any(|glob| glob.matches_path(new_file_path))
|
}
|
||||||
} else {
|
if let Some(exclude_path) = exclude_path {
|
||||||
false
|
return !exclude_path
|
||||||
}
|
.iter()
|
||||||
});
|
.any(|glob| glob.matches_path(new_file_path));
|
||||||
}
|
}
|
||||||
}
|
unreachable!()
|
||||||
false
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Ok(commits)
|
Ok(commits)
|
||||||
|
@ -44,11 +44,8 @@ use std::fs::{
|
|||||||
self,
|
self,
|
||||||
File,
|
File,
|
||||||
};
|
};
|
||||||
use std::io::{
|
use std::io;
|
||||||
self,
|
use std::path::Path;
|
||||||
Write,
|
|
||||||
};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::time::{
|
use std::time::{
|
||||||
SystemTime,
|
SystemTime,
|
||||||
UNIX_EPOCH,
|
UNIX_EPOCH,
|
||||||
@ -88,31 +85,28 @@ fn process_repository<'a>(
|
|||||||
let mut tags = repository.tags(&config.git.tag_pattern, args.topo_order)?;
|
let mut tags = repository.tags(&config.git.tag_pattern, args.topo_order)?;
|
||||||
let skip_regex = config.git.skip_tags.as_ref();
|
let skip_regex = config.git.skip_tags.as_ref();
|
||||||
let ignore_regex = config.git.ignore_tags.as_ref();
|
let ignore_regex = config.git.ignore_tags.as_ref();
|
||||||
tags = tags
|
tags.retain(|_, tag| {
|
||||||
.into_iter()
|
let name = &tag.name;
|
||||||
.filter(|(_, tag)| {
|
|
||||||
let name = &tag.name;
|
|
||||||
|
|
||||||
// Keep skip tags to drop commits in the later stage.
|
// Keep skip tags to drop commits in the later stage.
|
||||||
let skip = skip_regex.map(|r| r.is_match(name)).unwrap_or_default();
|
let skip = skip_regex.is_some_and(|r| r.is_match(name));
|
||||||
|
if skip {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
let ignore = ignore_regex
|
let ignore = ignore_regex.is_some_and(|r| {
|
||||||
.map(|r| {
|
if r.as_str().trim().is_empty() {
|
||||||
if r.as_str().trim().is_empty() {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let ignore_tag = r.is_match(name);
|
let ignore_tag = r.is_match(name);
|
||||||
if ignore_tag {
|
if ignore_tag {
|
||||||
trace!("Ignoring release: {}", name)
|
trace!("Ignoring release: {}", name)
|
||||||
}
|
}
|
||||||
ignore_tag
|
ignore_tag
|
||||||
})
|
});
|
||||||
.unwrap_or_default();
|
!ignore
|
||||||
|
});
|
||||||
skip || !ignore
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if !config.remote.github.is_set() {
|
if !config.remote.github.is_set() {
|
||||||
match repository.upstream_remote() {
|
match repository.upstream_remote() {
|
||||||
@ -213,14 +207,12 @@ fn process_repository<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut commits = repository.commits(
|
let mut commits = repository.commits(
|
||||||
commit_range,
|
commit_range.as_deref(),
|
||||||
args.include_path.clone(),
|
args.include_path.as_deref(),
|
||||||
args.exclude_path.clone(),
|
args.exclude_path.as_deref(),
|
||||||
)?;
|
)?;
|
||||||
if let Some(commit_limit_value) = config.git.limit_commits {
|
if let Some(commit_limit_value) = config.git.limit_commits {
|
||||||
commits = commits
|
commits.truncate(commit_limit_value);
|
||||||
.drain(..commits.len().min(commit_limit_value))
|
|
||||||
.collect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update tags.
|
// Update tags.
|
||||||
@ -239,25 +231,18 @@ fn process_repository<'a>(
|
|||||||
|
|
||||||
// Process releases.
|
// Process releases.
|
||||||
let mut releases = vec![Release::default()];
|
let mut releases = vec![Release::default()];
|
||||||
let mut release_index = 0;
|
|
||||||
let mut previous_release = Release::default();
|
let mut previous_release = Release::default();
|
||||||
let mut first_processed_tag = None;
|
let mut first_processed_tag = None;
|
||||||
for git_commit in commits.iter().rev() {
|
for git_commit in commits.iter().rev() {
|
||||||
|
let release = releases.last_mut().unwrap();
|
||||||
let commit = Commit::from(git_commit);
|
let commit = Commit::from(git_commit);
|
||||||
let commit_id = commit.id.to_string();
|
let commit_id = commit.id.to_string();
|
||||||
if args.sort == Sort::Newest {
|
release.commits.push(commit);
|
||||||
releases[release_index].commits.insert(0, commit);
|
|
||||||
} else {
|
|
||||||
releases[release_index].commits.push(commit);
|
|
||||||
}
|
|
||||||
if let Some(tag) = tags.get(&commit_id) {
|
if let Some(tag) = tags.get(&commit_id) {
|
||||||
let tag_name = &tag.name;
|
release.version = Some(tag.name.to_string());
|
||||||
|
release.message = tag.message.clone();
|
||||||
releases[release_index].version = Some(tag_name.clone());
|
release.commit_id = Some(commit_id);
|
||||||
releases[release_index].message = tag.message.clone();
|
release.timestamp = if args.tag.as_deref() == Some(tag.name.as_str()) {
|
||||||
releases[release_index].commit_id = Some(commit_id);
|
|
||||||
releases[release_index].timestamp = if args.tag == Some(tag_name.clone())
|
|
||||||
{
|
|
||||||
SystemTime::now()
|
SystemTime::now()
|
||||||
.duration_since(UNIX_EPOCH)?
|
.duration_since(UNIX_EPOCH)?
|
||||||
.as_secs()
|
.as_secs()
|
||||||
@ -269,27 +254,32 @@ fn process_repository<'a>(
|
|||||||
first_processed_tag = Some(tag);
|
first_processed_tag = Some(tag);
|
||||||
}
|
}
|
||||||
previous_release.previous = None;
|
previous_release.previous = None;
|
||||||
releases[release_index].previous = Some(Box::new(previous_release));
|
release.previous = Some(Box::new(previous_release));
|
||||||
previous_release = releases[release_index].clone();
|
previous_release = release.clone();
|
||||||
releases.push(Release::default());
|
releases.push(Release::default());
|
||||||
release_index += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if release_index > 0 {
|
debug_assert!(!releases.is_empty());
|
||||||
|
|
||||||
|
if releases.len() > 1 {
|
||||||
previous_release.previous = None;
|
previous_release.previous = None;
|
||||||
releases[release_index].previous = Some(Box::new(previous_release));
|
releases.last_mut().unwrap().previous = Some(Box::new(previous_release));
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.sort == Sort::Newest {
|
||||||
|
for release in &mut releases {
|
||||||
|
release.commits.reverse();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add custom commit messages to the latest release.
|
// Add custom commit messages to the latest release.
|
||||||
if let Some(custom_commits) = &args.with_commit {
|
if let Some(custom_commits) = &args.with_commit {
|
||||||
if let Some(latest_release) = releases.iter_mut().last() {
|
releases
|
||||||
custom_commits.iter().for_each(|message| {
|
.last_mut()
|
||||||
latest_release
|
.unwrap()
|
||||||
.commits
|
.commits
|
||||||
.push(Commit::from(message.to_string()))
|
.extend(custom_commits.iter().cloned().map(Commit::from));
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set custom message for the latest release.
|
// Set custom message for the latest release.
|
||||||
@ -300,12 +290,11 @@ fn process_repository<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set the previous release if the first release does not have one set.
|
// Set the previous release if the first release does not have one set.
|
||||||
if !releases.is_empty() &&
|
if releases[0]
|
||||||
releases
|
.previous
|
||||||
.first()
|
.as_ref()
|
||||||
.and_then(|r| r.previous.as_ref())
|
.and_then(|p| p.version.as_ref())
|
||||||
.and_then(|p| p.version.as_ref())
|
.is_none()
|
||||||
.is_none()
|
|
||||||
{
|
{
|
||||||
// Get the previous tag of the first processed tag in the release loop.
|
// Get the previous tag of the first processed tag in the release loop.
|
||||||
let first_tag = first_processed_tag
|
let first_tag = first_processed_tag
|
||||||
@ -547,6 +536,15 @@ pub fn run(mut args: Opt) -> Result<()> {
|
|||||||
let mut changelog = Changelog::new(releases, &config)?;
|
let mut changelog = Changelog::new(releases, &config)?;
|
||||||
|
|
||||||
// Print the result.
|
// Print the result.
|
||||||
|
let mut out: Box<dyn io::Write> = if let Some(path) = &args.output {
|
||||||
|
if path == Path::new("-") {
|
||||||
|
Box::new(io::stdout())
|
||||||
|
} else {
|
||||||
|
Box::new(io::BufWriter::new(File::create(path)?))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Box::new(io::stdout())
|
||||||
|
};
|
||||||
if args.bump || args.bumped_version {
|
if args.bump || args.bumped_version {
|
||||||
let next_version = if let Some(next_version) = changelog.bump_version()? {
|
let next_version = if let Some(next_version) = changelog.bump_version()? {
|
||||||
next_version
|
next_version
|
||||||
@ -559,40 +557,23 @@ pub fn run(mut args: Opt) -> Result<()> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
if args.bumped_version {
|
if args.bumped_version {
|
||||||
if let Some(path) = args.output {
|
writeln!(out, "{next_version}")?;
|
||||||
let mut output = File::create(path)?;
|
|
||||||
output.write_all(next_version.as_bytes())?;
|
|
||||||
} else {
|
|
||||||
println!("{next_version}");
|
|
||||||
}
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.context {
|
if args.context {
|
||||||
return if let Some(path) = args.output {
|
changelog.write_context(&mut out)?;
|
||||||
let mut output = File::create(path)?;
|
return Ok(());
|
||||||
changelog.write_context(&mut output)
|
|
||||||
} else {
|
|
||||||
changelog.write_context(&mut io::stdout())
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
if let Some(ref path) = args.prepend {
|
|
||||||
changelog.prepend(fs::read_to_string(path)?, &mut File::create(path)?)?;
|
if let Some(path) = &args.prepend {
|
||||||
|
let mut out = io::BufWriter::new(File::create(path)?);
|
||||||
|
changelog.prepend(fs::read_to_string(path)?, &mut out)?;
|
||||||
}
|
}
|
||||||
if let Some(path) = args.output {
|
if args.output.is_some() || args.prepend.is_none() {
|
||||||
let mut output: Box<dyn Write> = if path == PathBuf::from("-") {
|
changelog.generate(&mut out)?;
|
||||||
Box::new(io::stdout())
|
|
||||||
} else {
|
|
||||||
Box::new(File::create(path)?)
|
|
||||||
};
|
|
||||||
if args.context {
|
|
||||||
changelog.write_context(&mut output)
|
|
||||||
} else {
|
|
||||||
changelog.generate(&mut output)
|
|
||||||
}
|
|
||||||
} else if args.prepend.is_none() {
|
|
||||||
changelog.generate(&mut io::stdout())
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user