Skip to content

Commit

Permalink
feat(git): support generating changelog for multiple git repositories (
Browse files Browse the repository at this point in the history
  • Loading branch information
orhun committed Jan 7, 2023
1 parent ce1b7c3 commit 8b17a1f
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 105 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ git-cliff [FLAGS] [OPTIONS] [--] [RANGE]
```
-c, --config <PATH> Sets the configuration file [env: GIT_CLIFF_CONFIG=] [default: cliff.toml]
-w, --workdir <PATH> Sets the working directory [env: GIT_CLIFF_WORKDIR=]
-r, --repository <PATH> Sets the git repository [env: GIT_CLIFF_REPOSITORY=]
-r, --repository <PATH>... Sets the git repository [env: GIT_CLIFF_REPOSITORY=]
--include-path <PATTERN>... Sets the path to include related commits [env: GIT_CLIFF_INCLUDE_PATH=]
--exclude-path <PATTERN>... Sets the path to exclude related commits [env: GIT_CLIFF_EXCLUDE_PATH=]
--with-commit <MSG>... Sets custom commit messages to include in the changelog [env: GIT_CLIFF_WITH_COMMIT=]
Expand Down Expand Up @@ -245,6 +245,13 @@ git cliff --include-path "**/*.toml" --include-path "*.md"
git cliff --exclude-path ".github/*"
```

Generate a changelog for multiple git repositories:

```sh
# merges the commit history
git cliff --repository path1 path2
```

Generate a changelog that includes yet unexisting commit messages:

```sh
Expand Down
10 changes: 8 additions & 2 deletions git-cliff/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,14 @@ pub struct Opt {
#[clap(short, long, env = "GIT_CLIFF_WORKDIR", value_name = "PATH")]
pub workdir: Option<PathBuf>,
/// Sets the git repository.
#[clap(short, long, env = "GIT_CLIFF_REPOSITORY", value_name = "PATH")]
pub repository: Option<PathBuf>,
#[clap(
short,
long,
env = "GIT_CLIFF_REPOSITORY",
value_name = "PATH",
multiple_values = true
)]
pub repository: Option<Vec<PathBuf>>,
/// Sets the path to include related commits.
#[clap(
long,
Expand Down
231 changes: 129 additions & 102 deletions git-cliff/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,103 +53,17 @@ fn check_new_version() {
}
}

/// Runs `git-cliff`.
pub fn run(mut args: Opt) -> Result<()> {
// Check if there is a new version available.
#[cfg(feature = "update-informer")]
check_new_version();

// Create the configuration file if init flag is given.
if args.init {
info!("Saving the configuration file to {:?}", DEFAULT_CONFIG);
fs::write(DEFAULT_CONFIG, EmbeddedConfig::get_config()?)?;
return Ok(());
}

// Set the working directory.
if let Some(ref workdir) = args.workdir {
args.config = workdir.join(args.config);
args.repository = match args.repository {
Some(repository) => Some(workdir.join(repository)),
None => Some(workdir.clone()),
};
if let Some(changelog) = args.prepend {
args.prepend = Some(workdir.join(changelog));
}
}

// Parse the configuration file.
let mut path = args.config.clone();
if !path.exists() {
if let Some(config_path) = dirs_next::config_dir()
.map(|dir| dir.join(env!("CARGO_PKG_NAME")).join(DEFAULT_CONFIG))
{
path = config_path;
}
}

// Load the default configuration if necessary.
let mut config = if path.exists() {
Config::parse(&path)?
} else {
if !args.context {
warn!(
"{:?} is not found, using the default configuration.",
args.config
);
}
EmbeddedConfig::parse()?
};
if config.changelog.body.is_none() && !args.context {
warn!("Changelog body is not specified, using the default template.");
config.changelog.body = EmbeddedConfig::parse()?.changelog.body;
}

// Update the configuration based on command line arguments and vice versa.
match args.strip {
Some(Strip::Header) => {
config.changelog.header = None;
}
Some(Strip::Footer) => {
config.changelog.footer = None;
}
Some(Strip::All) => {
config.changelog.header = None;
config.changelog.footer = None;
}
None => {}
}
if args.prepend.is_some() {
config.changelog.footer = None;
if !(args.unreleased || args.latest || args.range.is_some()) {
return Err(Error::ArgumentError(String::from(
"'-u' or '-l' is not specified",
)));
}
}
if args.body.is_some() {
config.changelog.body = args.body.clone();
}
if args.sort == Sort::Oldest {
if let Some(ref sort_commits) = config.git.sort_commits {
args.sort = Sort::from_str(sort_commits, true)
.expect("Incorrect config value for 'sort_commits'");
}
}
if !args.topo_order {
if let Some(topo_order) = config.git.topo_order {
args.topo_order = topo_order;
}
}

// Initialize the git repository.
let repository =
Repository::init(args.repository.clone().unwrap_or(env::current_dir()?))?;

// Parse tags.
/// Processes the tags and commits for creating release entries for the
/// changelog.
///
/// This function uses the configuration and arguments to process the given
/// repository individually.
fn process_repository<'a>(
repository: &'static Repository,
mut config: Config,
args: &Opt,
) -> Result<Vec<Release<'a>>> {
let mut tags = repository.tags(&config.git.tag_pattern, args.topo_order)?;

// Skip tags.
config.git.skip_tags = config.git.skip_tags.filter(|r| !r.as_str().is_empty());
let skip_regex = config.git.skip_tags.as_ref();
let ignore_regex = config.git.ignore_tags.as_ref();
Expand Down Expand Up @@ -182,7 +96,7 @@ pub fn run(mut args: Opt) -> Result<()> {
log::trace!("{:#?}", config);

// Parse commits.
let mut commit_range = args.range;
let mut commit_range = args.range.clone();
if args.unreleased {
if let Some(last_tag) = tags.last().map(|(k, _)| k) {
commit_range = Some(format!("{last_tag}..HEAD"));
Expand Down Expand Up @@ -229,8 +143,11 @@ pub fn run(mut args: Opt) -> Result<()> {
}
}
}
let mut commits =
repository.commits(commit_range, args.include_path, args.exclude_path)?;
let mut commits = repository.commits(
commit_range,
args.include_path.clone(),
args.exclude_path.clone(),
)?;
if let Some(commit_limit_value) = config.git.limit_commits {
commits = commits.drain(..commit_limit_value).collect();
}
Expand Down Expand Up @@ -281,10 +198,12 @@ pub fn run(mut args: Opt) -> Result<()> {
}

// 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() {
custom_commits.into_iter().for_each(|message| {
latest_release.commits.push(Commit::from(message))
custom_commits.iter().for_each(|message| {
latest_release
.commits
.push(Commit::from(message.to_string()))
});
}
}
Expand All @@ -303,6 +222,114 @@ pub fn run(mut args: Opt) -> Result<()> {
}
}

Ok(releases)
}

/// Runs `git-cliff`.
pub fn run(mut args: Opt) -> Result<()> {
// Check if there is a new version available.
#[cfg(feature = "update-informer")]
check_new_version();

// Create the configuration file if init flag is given.
if args.init {
info!("Saving the configuration file to {:?}", DEFAULT_CONFIG);
fs::write(DEFAULT_CONFIG, EmbeddedConfig::get_config()?)?;
return Ok(());
}

// Set the working directory.
if let Some(ref workdir) = args.workdir {
args.config = workdir.join(args.config);
match args.repository.as_mut() {
Some(repository) => {
repository
.iter_mut()
.for_each(|r| *r = workdir.join(r.clone()));
}
None => args.repository = Some(vec![workdir.clone()]),
}
if let Some(changelog) = args.prepend {
args.prepend = Some(workdir.join(changelog));
}
}

// Parse the configuration file.
let mut path = args.config.clone();
if !path.exists() {
if let Some(config_path) = dirs_next::config_dir()
.map(|dir| dir.join(env!("CARGO_PKG_NAME")).join(DEFAULT_CONFIG))
{
path = config_path;
}
}

// Load the default configuration if necessary.
let mut config = if path.exists() {
Config::parse(&path)?
} else {
if !args.context {
warn!(
"{:?} is not found, using the default configuration.",
args.config
);
}
EmbeddedConfig::parse()?
};
if config.changelog.body.is_none() && !args.context {
warn!("Changelog body is not specified, using the default template.");
config.changelog.body = EmbeddedConfig::parse()?.changelog.body;
}

// Update the configuration based on command line arguments and vice versa.
match args.strip {
Some(Strip::Header) => {
config.changelog.header = None;
}
Some(Strip::Footer) => {
config.changelog.footer = None;
}
Some(Strip::All) => {
config.changelog.header = None;
config.changelog.footer = None;
}
None => {}
}
if args.prepend.is_some() {
config.changelog.footer = None;
if !(args.unreleased || args.latest || args.range.is_some()) {
return Err(Error::ArgumentError(String::from(
"'-u' or '-l' is not specified",
)));
}
}
if args.body.is_some() {
config.changelog.body = args.body.clone();
}
if args.sort == Sort::Oldest {
if let Some(ref sort_commits) = config.git.sort_commits {
args.sort = Sort::from_str(sort_commits, true)
.expect("Incorrect config value for 'sort_commits'");
}
}
if !args.topo_order {
if let Some(topo_order) = config.git.topo_order {
args.topo_order = topo_order;
}
}

// Process the repository.
let repositories = args.repository.clone().unwrap_or(vec![env::current_dir()?]);
let mut releases = Vec::<Release>::new();
for repository in repositories {
let repository = Repository::init(repository)?;
releases.extend(process_repository(
Box::leak(Box::new(repository)),
config.clone(),
&args,
)?);
}

// Generate output.
let changelog = Changelog::new(releases, &config)?;
if args.context {
Expand Down

0 comments on commit 8b17a1f

Please sign in to comment.