Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ec08104
core: 1. sketch out two helper primitives be useful
cds-amal Apr 23, 2026
165ec61
core: 2. add range-endpoint fields to GitConfig
cds-amal Apr 23, 2026
579ad15
cli: add range-endpoint fields to Opt
cds-amal Apr 24, 2026
d92a8d3
WIP(range): CommitRange
cds-amal Apr 24, 2026
5a0bafe
refactor(cli): route determine_commit_range through range pipeline
cds-amal Apr 24, 2026
b1d8aaf
cli: expose range-endpoint options on the command line
cds-amal Apr 24, 2026
537ab8f
cli: add --dry-run flag to preview range selection
cds-amal Apr 24, 2026
b2538da
rebase against main
cds-amal Apr 24, 2026
cf9eb43
lint: address clippy
cds-amal Apr 24, 2026
ed406c0
refactor(range): extract transform_range helpers per translation row
cds-amal Apr 25, 2026
b981255
refactor(range): tighten module API and pull dry-run formatting into …
cds-amal Apr 25, 2026
0bb3a35
test(fixtures): cover --start-at / --end-before end-to-end
cds-amal Apr 25, 2026
f6f52a1
refactor(range): take &str instead of &String in resolve_endpoint
cds-amal Apr 25, 2026
21f0801
ci(nix): skip resolve_with tests in flake check
cds-amal Apr 25, 2026
a6a08f8
fmt: cargo +nightly fmt
cds-amal Apr 25, 2026
ec0ad75
refactor(range): split format_dry_run helper out of print_dry_run
cds-amal Apr 25, 2026
9978ebe
test(range): cover branches missed by codecov
cds-amal Apr 25, 2026
89bb203
test(range): consolidate endpoint and range render tests into matrix …
cds-amal Apr 25, 2026
a4c317c
docs(website): document range-endpoint options and --dry-run
cds-amal Apr 25, 2026
d159d4a
refactor(range): rename unset-left placeholder to <first-commit>
cds-amal Apr 26, 2026
432c873
feat(range): let CLI endpoint flags override config per side
cds-amal Apr 26, 2026
0559202
docs(range): clarify CLI-overrides-config precedence is per-side
cds-amal Apr 26, 2026
28df16a
fix(range): accept revspecs as the single-ref walker input
cds-amal Apr 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/fixtures/test-range-end-before-exclusive/cliff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# git-cliff ~ configuration file
# https://git-cliff.org/docs/configuration

[changelog]
# A Tera template to be rendered for each release in the changelog.
# See https://keats.github.io/tera/docs/#introduction
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [unreleased]
{% endif %}\

{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits %}
- {{ commit.message | upper_first }}\
{% endfor %}
{% endfor %}\n
"""

[git]
# An array of regex based parsers for extracting data from the commit message.
# Assigns commits to groups.
# Optionally sets the commit's `scope` and can decide to exclude commits from further processing.
commit_parsers = [
{ message = "^feat", group = "Features", default_scope = "app" },
{ message = "^fix", group = "Bug Fixes", scope = "cli" },
]
11 changes: 11 additions & 0 deletions .github/fixtures/test-range-end-before-exclusive/commit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -e

GIT_COMMITTER_DATE="2022-04-06 01:25:08" git commit --allow-empty -m "Initial commit"
GIT_COMMITTER_DATE="2022-04-06 01:25:09" git commit --allow-empty -m "feat: add feature 1"
GIT_COMMITTER_DATE="2022-04-06 01:25:10" git commit --allow-empty -m "fix: fix feature 1"
git tag v0.1.0
GIT_COMMITTER_DATE="2022-04-06 01:25:11" git commit --allow-empty -m "feat(gui): add feature 2"
GIT_COMMITTER_DATE="2022-04-06 01:25:12" git commit --allow-empty -m "fix(gui): fix feature 2"
git tag v0.2.0
GIT_COMMITTER_DATE="2022-04-06 01:25:13" git commit --allow-empty -m "test: add tests"
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
## [unreleased]

### Features

- Add feature 2

29 changes: 29 additions & 0 deletions .github/fixtures/test-range-end-before-only/cliff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# git-cliff ~ configuration file
# https://git-cliff.org/docs/configuration

[changelog]
# A Tera template to be rendered for each release in the changelog.
# See https://keats.github.io/tera/docs/#introduction
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [unreleased]
{% endif %}\

{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits %}
- {{ commit.message | upper_first }}\
{% endfor %}
{% endfor %}\n
"""

[git]
# An array of regex based parsers for extracting data from the commit message.
# Assigns commits to groups.
# Optionally sets the commit's `scope` and can decide to exclude commits from further processing.
commit_parsers = [
{ message = "^feat", group = "Features", default_scope = "app" },
{ message = "^fix", group = "Bug Fixes", scope = "cli" },
]
11 changes: 11 additions & 0 deletions .github/fixtures/test-range-end-before-only/commit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -e

GIT_COMMITTER_DATE="2022-04-06 01:25:08" git commit --allow-empty -m "Initial commit"
GIT_COMMITTER_DATE="2022-04-06 01:25:09" git commit --allow-empty -m "feat: add feature 1"
GIT_COMMITTER_DATE="2022-04-06 01:25:10" git commit --allow-empty -m "fix: fix feature 1"
git tag v0.1.0
GIT_COMMITTER_DATE="2022-04-06 01:25:11" git commit --allow-empty -m "feat(gui): add feature 2"
GIT_COMMITTER_DATE="2022-04-06 01:25:12" git commit --allow-empty -m "fix(gui): fix feature 2"
git tag v0.2.0
GIT_COMMITTER_DATE="2022-04-06 01:25:13" git commit --allow-empty -m "test: add tests"
16 changes: 16 additions & 0 deletions .github/fixtures/test-range-end-before-only/expected.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
## [unreleased]

### Features

- Add feature 2

## [0.1.0] - 2022-04-06

### Bug Fixes

- Fix feature 1

### Features

- Add feature 1

29 changes: 29 additions & 0 deletions .github/fixtures/test-range-start-at-inclusive/cliff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# git-cliff ~ configuration file
# https://git-cliff.org/docs/configuration

[changelog]
# A Tera template to be rendered for each release in the changelog.
# See https://keats.github.io/tera/docs/#introduction
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [unreleased]
{% endif %}\

{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits %}
- {{ commit.message | upper_first }}\
{% endfor %}
{% endfor %}\n
"""

[git]
# An array of regex based parsers for extracting data from the commit message.
# Assigns commits to groups.
# Optionally sets the commit's `scope` and can decide to exclude commits from further processing.
commit_parsers = [
{ message = "^feat", group = "Features", default_scope = "app" },
{ message = "^fix", group = "Bug Fixes", scope = "cli" },
]
11 changes: 11 additions & 0 deletions .github/fixtures/test-range-start-at-inclusive/commit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -e

GIT_COMMITTER_DATE="2022-04-06 01:25:08" git commit --allow-empty -m "Initial commit"
GIT_COMMITTER_DATE="2022-04-06 01:25:09" git commit --allow-empty -m "feat: add feature 1"
GIT_COMMITTER_DATE="2022-04-06 01:25:10" git commit --allow-empty -m "fix: fix feature 1"
git tag v0.1.0
GIT_COMMITTER_DATE="2022-04-06 01:25:11" git commit --allow-empty -m "feat(gui): add feature 2"
GIT_COMMITTER_DATE="2022-04-06 01:25:12" git commit --allow-empty -m "fix(gui): fix feature 2"
git tag v0.2.0
GIT_COMMITTER_DATE="2022-04-06 01:25:13" git commit --allow-empty -m "test: add tests"
16 changes: 16 additions & 0 deletions .github/fixtures/test-range-start-at-inclusive/expected.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
## [0.2.0] - 2022-04-06

### Bug Fixes

- Fix feature 2

### Features

- Add feature 2

## [0.1.0] - 2022-04-06

### Bug Fixes

- Fix feature 1

6 changes: 6 additions & 0 deletions .github/workflows/test-fixtures.yml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ jobs:
- fixtures-name: test-commit-range-with-sort-commits
- fixtures-name: test-commit-range-with-given-range
command: a140cef^..a9d4050 --ignore-tags "."
- fixtures-name: test-range-start-at-inclusive
command: --start-at v0.1.0 --end-at v0.2.0
- fixtures-name: test-range-end-before-exclusive
command: --start-after v0.1.0 --end-before v0.2.0
- fixtures-name: test-range-end-before-only
command: --end-before v0.2.0
- fixtures-name: test-override-scope
- fixtures-name: test-regex-json-array
- fixtures-name: test-release-statistics
Expand Down
1 change: 1 addition & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
checkFlags = [
"--skip=command"
"--skip=repo"
"--skip=resolve_with"
];
meta = with pkgs.lib; {
description = "A highly customizable Changelog Generator that follows Conventional Commit specifications";
Expand Down
4 changes: 4 additions & 0 deletions git-cliff-core/src/changelog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,10 @@ mod test {
recurse_submodules: None,
include_paths: Vec::new(),
exclude_paths: Vec::new(),
start_at: None,
start_after: None,
end_at: None,
end_before: None,
},
remote: RemoteConfig {
offline: false,
Expand Down
8 changes: 8 additions & 0 deletions git-cliff-core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,14 @@ pub struct GitConfig {
/// Exclude unrelated commits with changes at the specified paths.
#[serde(with = "serde_pattern", default)]
pub exclude_paths: Vec<Pattern>,
/// Include this revision as the lower bound (inclusive left endpoint).
pub start_at: Option<String>,
/// Exclude this revision; start walking forward from its successor.
pub start_after: Option<String>,
/// Include this revision as the upper bound (inclusive right endpoint).
pub end_at: Option<String>,
/// Exclude this revision; stop walking before reaching it.
pub end_before: Option<String>,
}

/// Processing steps for commits.
Expand Down
62 changes: 58 additions & 4 deletions git-cliff-core/src/repo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,17 +144,21 @@ impl Repository {

/// Sets the range for the commit search.
///
/// When a single SHA is provided as the range, start from the
/// root.
/// When the range is a single ref or revspec (anything without `..`),
/// it's resolved with `revparse_single` and used as the walk's starting
/// point. This accepts plain SHAs, tags, branches, and parent
/// expressions like `<sha>^` (which `Oid::from_str` would reject).
fn set_commit_range(
&self,
revwalk: &mut git2::Revwalk<'_>,
range: Option<&str>,
) -> StdResult<(), git2::Error> {
if let Some(range) = range {
if range.contains("..") {
revwalk.push_range(range)?;
} else {
revwalk.push(Oid::from_str(range)?)?;
let oid = self.inner.revparse_single(range)?.id();
revwalk.push(oid)?;
}
} else {
revwalk.push_head()?;
Expand All @@ -179,7 +183,7 @@ impl Repository {
revwalk.set_sorting(Sort::TIME)?;
}

Self::set_commit_range(&mut revwalk, range).map_err(|e| {
self.set_commit_range(&mut revwalk, range).map_err(|e| {
Error::SetCommitRangeError(range.map_or_else(|| "?".to_string(), String::from), e)
})?;
let mut commits: Vec<Commit> = revwalk
Expand Down Expand Up @@ -499,6 +503,22 @@ impl Repository {
None
}

/// Resolves a revision string (tag, branch, SHA prefix, `HEAD`, etc.) to a
/// full commit SHA. Returns an error if the revision cannot be resolved.
pub fn resolve_rev(&self, rev: &str) -> Result<String> {
let obj = self.inner.revparse_single(rev)?;
let commit = obj.peel_to_commit()?;
Ok(commit.id().to_string())
}

/// Returns `true` if the given revision resolves to the root commit (a
/// commit with no parents).
pub fn is_root_commit(&self, rev: &str) -> Result<bool> {
let obj = self.inner.revparse_single(rev)?;
let commit = obj.peel_to_commit()?;
Ok(commit.parent_count() == 0)
}

/// Decide whether to include tag.
///
/// `head_commit` is the `latest` commit to generate changelog. It can be a
Expand Down Expand Up @@ -1169,4 +1189,38 @@ mod test {
assert!(!retain, "exclude: **/*.txt");
}
}

#[test]
fn resolve_rev_resolves_head() -> Result<()> {
let repository = get_repository()?;
let sha = repository.resolve_rev("HEAD")?;
assert_eq!(sha, get_last_commit_hash()?);
Ok(())
}

#[test]
fn resolve_rev_errors_on_unknown() -> Result<()> {
let repository = get_repository()?;
assert!(
repository
.resolve_rev("this-ref-does-not-exist-xyz")
.is_err()
);
Ok(())
}

#[test]
fn is_root_commit_true_for_root() -> Result<()> {
let repository = get_repository()?;
let root = get_root_commit_hash()?;
assert!(repository.is_root_commit(&root)?);
Ok(())
}

#[test]
fn is_root_commit_false_for_head() -> Result<()> {
let repository = get_repository()?;
assert!(!repository.is_root_commit("HEAD")?);
Ok(())
}
}
4 changes: 4 additions & 0 deletions git-cliff-core/tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ fn generate_changelog() -> Result<()> {
recurse_submodules: None,
include_paths: Vec::new(),
exclude_paths: Vec::new(),
start_at: None,
start_after: None,
end_at: None,
end_before: None,
};

let mut commit_with_author = Commit::new(
Expand Down
Loading
Loading