Skip to content

Commit

Permalink
feat: add include_config option (#904)
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcoIeni committed Aug 14, 2023
1 parent aff9dfb commit e2476d9
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 107 deletions.
7 changes: 7 additions & 0 deletions crates/release_plz/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ pub struct PackageSpecificConfig {
/// This changelog_path needs to be propagated to all the commands:
/// `update`, `release-pr` and `release`.
changelog_path: Option<PathBuf>,
/// List of package names.
/// Include the changelogs of these packages in the changelog of the current package.
changelog_include: Option<Vec<String>>,
}

impl PackageSpecificConfig {
Expand All @@ -150,6 +153,7 @@ impl PackageSpecificConfig {
update: self.update.merge(default.update),
release: self.release.merge(default.release),
changelog_path: self.changelog_path,
changelog_include: self.changelog_include,
}
}
}
Expand Down Expand Up @@ -215,6 +219,7 @@ impl From<PackageSpecificConfig> for release_plz_core::PackageUpdateConfig {
Self {
generic: config.update.into(),
changelog_path: config.changelog_path,
changelog_include: config.changelog_include.unwrap_or_default(),
}
}
}
Expand Down Expand Up @@ -481,6 +486,7 @@ mod tests {
..Default::default()
},
changelog_path: Some("./CHANGELOG.md".into()),
changelog_include: Some(vec!["pkg1".to_string()]),
},
}]
.into(),
Expand All @@ -504,6 +510,7 @@ mod tests {
git_release_type = "prod"
git_release_draft = false
changelog_path = "./CHANGELOG.md"
changelog_include = ["pkg1"]
"#]]
.assert_eq(&toml::to_string(&config).unwrap());
}
Expand Down
7 changes: 7 additions & 0 deletions crates/release_plz_core/src/diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,11 @@ impl Diff {
pub fn set_semver_check(&mut self, semver_check: SemverCheck) {
self.semver_check = semver_check
}
pub fn add_commits(&mut self, commits: &[String]) {
for c in commits {
if !self.commits.contains(c) {
self.commits.push(c.clone());
}
}
}
}
227 changes: 120 additions & 107 deletions crates/release_plz_core/src/next_ver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use next_version::NextVersion;
use rayon::prelude::{IntoParallelRefMutIterator, ParallelIterator};
use regex::Regex;
use std::{
collections::{BTreeMap, HashSet},
collections::{BTreeMap, HashMap, HashSet},
fs, io,
path::{Path, PathBuf},
};
Expand Down Expand Up @@ -71,6 +71,7 @@ impl From<UpdateConfig> for PackageUpdateConfig {
Self {
generic: config,
changelog_path: None,
changelog_include: vec![],
}
}
}
Expand Down Expand Up @@ -113,6 +114,9 @@ pub struct PackageUpdateConfig {
/// This path needs to be a relative path to the Cargo.toml of the project.
/// I.e. if you have a workspace, it needs to be relative to the workspace root.
pub changelog_path: Option<PathBuf>,
/// List of package names.
/// Include the changelogs of these packages in the changelog of the current package.
pub changelog_include: Vec<String>,
}

impl PackageUpdateConfig {
Expand Down Expand Up @@ -513,26 +517,33 @@ impl Updater<'_> {
.publishable_packages()
.iter()
.map(|&p| {
let diff = get_diff(
p,
registry_packages,
repository,
self.project,
workspace_packages,
)?;
let diff = self.get_diff(p, registry_packages, repository, workspace_packages)?;
Ok((p, diff))
})
.collect();

let mut packages_diffs = packages_diffs_res?;

let packages_commits: HashMap<String, Vec<String>> = packages_diffs
.iter()
.map(|(p, d)| (p.name.clone(), d.commits.clone()))
.collect();

let semver_check_result: anyhow::Result<()> =
packages_diffs.par_iter_mut().try_for_each(|(p, diff)| {
let registry_package = registry_packages.get_package(&p.name);
if let Some(registry_package) = registry_package {
let package_path = get_package_path(p, repository, &self.project.root)
.context("can't retrieve package path")?;
let run_semver_check = self.req.get_package_config(&p.name).semver_check();
if should_check_semver(p, run_semver_check) && diff.should_update_version() {
let package_config = self.req.get_package_config(&p.name);
for pkg_to_include in &package_config.changelog_include {
if let Some(commits) = packages_commits.get(pkg_to_include) {
diff.add_commits(commits);
}
}
if should_check_semver(p, package_config.semver_check())
&& diff.should_update_version()
{
let registry_package_path = registry_package
.package_path()
.context("can't retrieve registry package path")?;
Expand Down Expand Up @@ -651,6 +662,105 @@ impl Updater<'_> {
semver_check,
})
}

/// This operation is not thread-safe, because we do `git checkout` on the repository.
#[instrument(
skip_all,
fields(package = %package.name)
)]
fn get_diff(
&self,
package: &Package,
registry_packages: &PackagesCollection,
repository: &Repo,
workspace_packages: &[&Package],
) -> anyhow::Result<Diff> {
let package_path = get_package_path(package, repository, &self.project.root)?;

repository.checkout_head()?;
let registry_package = registry_packages.get_package(&package.name);
let mut diff = Diff::new(registry_package.is_some());
if let Err(err) = repository.checkout_last_commit_at_path(&package_path) {
if err
.to_string()
.contains("Your local changes to the following files would be overwritten")
{
return Err(err.context("The allow-dirty option can't be used in this case"));
} else {
info!("{}: there are no commits", package.name);
return Ok(diff);
}
}
let ignored_dirs: anyhow::Result<Vec<PathBuf>> = workspace_packages
.iter()
.filter(|p| p.name != package.name)
.map(|p| get_package_path(p, repository, &self.project.root))
.collect();
let ignored_dirs = ignored_dirs?;

let tag_commit = {
let git_tag = self
.project
.git_tag(&package.name, &package.version.to_string());
repository.get_tag_commit(&git_tag)
};
loop {
let current_commit_message = repository.current_commit_message()?;
let current_commit_hash = repository.current_commit_hash()?;
if let Some(registry_package) = registry_package {
debug!("package {} found in cargo registry", registry_package.name);
let registry_package_path = registry_package.package_path()?;
let are_packages_equal =
are_packages_equal(&package_path, registry_package_path, ignored_dirs.clone())
.context("cannot compare packages")?;
if are_packages_equal
|| is_commit_too_old(repository, tag_commit.as_deref(), &current_commit_hash)
{
debug!(
"next version calculated starting from commits after `{current_commit_hash}`"
);
if diff.commits.is_empty() {
let are_dependencies_updated = are_toml_dependencies_updated(
&registry_package.dependencies,
&package.dependencies,
)
|| lock_compare::are_lock_dependencies_updated(
&self.project.cargo_lock_path(),
registry_package_path,
)
.context("Can't check if Cargo.lock dependencies are up to date")?;
if are_dependencies_updated {
diff.commits.push("chore: update dependencies".to_string());
} else {
info!("{}: already up to date", package.name);
}
}
// The local package is identical to the registry one, which means that
// the package was published at this commit, so we will not count this commit
// as part of the release.
// We can process the next create.
break;
} else if registry_package.version != package.version {
info!("{}: the local package has already a different version with respect to the registry package, so release-plz will not update it", package.name);
diff.set_version_unpublished();
break;
} else {
debug!("packages are different");
// At this point of the git history, the two packages are different,
// which means that this commit is not present in the published package.
diff.commits.push(current_commit_message.clone());
}
} else {
diff.commits.push(current_commit_message.clone());
}
if let Err(_err) = repository.checkout_previous_commit_at_path(&package_path) {
debug!("there are no other commits");
break;
}
}
repository.checkout_head()?;
Ok(diff)
}
}

fn new_workspace_version(
Expand Down Expand Up @@ -729,103 +839,6 @@ fn get_repo_path(
Ok(result_path)
}

/// This operation is not thread-safe, because we do `git checkout` on the repository.
#[instrument(
skip_all,
fields(package = %package.name)
)]
fn get_diff(
package: &Package,
registry_packages: &PackagesCollection,
repository: &Repo,
project: &Project,
workspace_packages: &[&Package],
) -> anyhow::Result<Diff> {
let package_path = get_package_path(package, repository, &project.root)?;

repository.checkout_head()?;
let registry_package = registry_packages.get_package(&package.name);
let mut diff = Diff::new(registry_package.is_some());
if let Err(err) = repository.checkout_last_commit_at_path(&package_path) {
if err
.to_string()
.contains("Your local changes to the following files would be overwritten")
{
return Err(err.context("The allow-dirty option can't be used in this case"));
} else {
info!("{}: there are no commits", package.name);
return Ok(diff);
}
}
let ignored_dirs: anyhow::Result<Vec<PathBuf>> = workspace_packages
.iter()
.filter(|p| p.name != package.name)
.map(|p| get_package_path(p, repository, &project.root))
.collect();
let ignored_dirs = ignored_dirs?;

let tag_commit = {
let git_tag = project.git_tag(&package.name, &package.version.to_string());
repository.get_tag_commit(&git_tag)
};
loop {
let current_commit_message = repository.current_commit_message()?;
let current_commit_hash = repository.current_commit_hash()?;
if let Some(registry_package) = registry_package {
debug!("package {} found in cargo registry", registry_package.name);
let registry_package_path = registry_package.package_path()?;
let are_packages_equal =
are_packages_equal(&package_path, registry_package_path, ignored_dirs.clone())
.context("cannot compare packages")?;
if are_packages_equal
|| is_commit_too_old(repository, tag_commit.as_deref(), &current_commit_hash)
{
debug!(
"next version calculated starting from commits after `{current_commit_hash}`"
);
if diff.commits.is_empty() {
let are_dependencies_updated = are_toml_dependencies_updated(
&registry_package.dependencies,
&package.dependencies,
)
|| lock_compare::are_lock_dependencies_updated(
&project.cargo_lock_path(),
registry_package_path,
)
.context("Can't check if Cargo.lock dependencies are up to date")?;
if are_dependencies_updated {
diff.commits.push("chore: update dependencies".to_string());
} else {
info!("{}: already up to date", package.name);
}
}
// The local package is identical to the registry one, which means that
// the package was published at this commit, so we will not count this commit
// as part of the release.
// We can process the next create.
break;
} else if registry_package.version != package.version {
info!("{}: the local package has already a different version with respect to the registry package, so release-plz will not update it", package.name);
diff.set_version_unpublished();
break;
} else {
debug!("packages are different");
// At this point of the git history, the two packages are different,
// which means that this commit is not present in the published package.
diff.commits.push(current_commit_message.clone());
}
} else {
diff.commits.push(current_commit_message.clone());
}
if let Err(_err) = repository.checkout_previous_commit_at_path(&package_path) {
debug!("there are no other commits");
break;
}
}
repository.checkout_head()?;
Ok(diff)
}

/// Check if commit belongs to a previous version of the package.
fn is_commit_too_old(
repository: &Repo,
Expand Down
3 changes: 3 additions & 0 deletions release-plz.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[[package]] # the double square brackets define a TOML table array
name = "release-plz" # name of the package to configure
changelog_include = ["release_plz_core"] # include commits from `release_plz_core` in the changelog
17 changes: 17 additions & 0 deletions website/docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ semver_check = false # disable API breaking changes checks

[[package]] # the double square brackets define a TOML table array
name = "package_a"
changelog_include = ["package_b"] # include commits from `package_b` in the changelog
changelog_path = "docs/CHANGELOG.md" # use a custom changelog path for `package_a`
changelog_update = true # enable changelog update for `package_a`
git_release_enable = true # enable GitHub/Gitea releases for `package_a`
Expand Down Expand Up @@ -57,6 +58,7 @@ the following sections:
- [`semver_check`](#the-semver_check-field) — Run [cargo-semver-checks].
- [`[[package]]`](#the-package-section) — Package-specific configurations.
- [`name`](#the-name-field) — Package name. *(Required)*.
- [`changelog_include`](#the-changelog_include) — Include commits from other packages.
- [`changelog_path`](#the-changelog_path-field-package-section) — Changelog path.
- [`changelog_update`](#the-changelog_update-field-package-section) — Update changelog.
- [`git_release_enable`](#the-git_release_enable-field-package-section) — Enable git release.
Expand Down Expand Up @@ -195,6 +197,21 @@ publish_no_verify = true
Name of the package to which the configuration applies.
*(Required field)*.

#### The `changelog_include` field

By default, release-plz populates the changelog of a package with commits
containing changes in files of the package directory.
You can use the `changelog_include` field to include commits that belong to other packages.
For example, the changelog of the `release-plz` package of this repository
includes commits of the `release_plz_core` package, because they affect the
`release-plz` package, too.

Example:

```toml
changelog_include = ["release_plz_core"]
```

#### The `changelog_path` field (`package` section)

By default, release-plz looks for the changelog in the `CHANGELOG.md` file
Expand Down
3 changes: 3 additions & 0 deletions website/docs/extra/single-changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ changelog_update = true
# set the path of the changelog to the root of the repository
changelog_path = "./CHANGELOG.md"
```

To include commits of other packages in the changelog of
your main package, use the [changelog_include](../config.md#the-changelog_include-field) field.

0 comments on commit e2476d9

Please sign in to comment.