Skip to content

Commit

Permalink
refactor (#222)
Browse files Browse the repository at this point in the history
Presentation is now entirely data driven, instead of being mixed into
the functions that figure out what's going on.

This helps to keep things clean and lean to facilitate adding version
handling on top.
  • Loading branch information
Byron committed Oct 17, 2021
1 parent b8a5fc8 commit 51a5d36
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 71 deletions.
5 changes: 3 additions & 2 deletions cargo-smart-release/src/command/changelog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ pub fn changelog(opts: Options, crates: Vec<String>) -> anyhow::Result<()> {
let bump_spec = opts.dependencies.then(|| BumpSpec::Auto).unwrap_or(BumpSpec::Keep);
let ctx = crate::Context::new(crates, false, bump_spec, bump_spec)?;
let crates = if opts.dependencies {
crate::traverse::dependencies(&ctx, false, true)?
let add_production_crates = true;
crate::traverse::dependencies(&ctx, add_production_crates)?
.into_iter()
.filter_map(|d| matches!(d.kind, dependency::Kind::ToBePublished).then(|| d.package))
.filter_map(|d| matches!(d.kind, dependency::Outcome::ToBePublished { .. }).then(|| d.package))
.collect()
} else {
ctx.crate_names
Expand Down
76 changes: 73 additions & 3 deletions cargo-smart-release/src/command/release/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
changelog,
changelog::{write::Linkables, Section},
command::release::Options,
traverse::dependency,
traverse::{self, dependency},
utils::{
is_dependency_with_version_requirement, names_and_versions, package_by_id, package_by_name,
package_eq_dependency, package_for_dependency, tag_name, will, workspace_package_by_id,
Expand Down Expand Up @@ -99,9 +99,11 @@ fn release_depth_first(ctx: Context, options: Options) -> anyhow::Result<()> {
.map(|name| package_by_name(&ctx.base.meta, name))
.collect::<Result<Vec<_>, _>>()?
} else {
crate::traverse::dependencies(&ctx.base, options.verbose, options.allow_auto_publish_of_stable_crates)?
let dependencies = crate::traverse::dependencies(&ctx.base, options.allow_auto_publish_of_stable_crates)?;
present_dependencies(&dependencies, &ctx.base.crate_names, options.verbose);
dependencies
.into_iter()
.filter_map(|d| matches!(d.kind, dependency::Kind::ToBePublished).then(|| d.package))
.filter_map(|d| matches!(d.kind, dependency::Outcome::ToBePublished { .. }).then(|| d.package))
.collect()
};

Expand Down Expand Up @@ -156,6 +158,74 @@ fn release_depth_first(ctx: Context, options: Options) -> anyhow::Result<()> {
Ok(())
}

fn present_dependencies(deps: &[traverse::Dependency<'_>], provided_crate_names: &[String], verbose: bool) {
use dependency::{PublishReason, SkippedReason};
if verbose {
for dep in deps {
match &dep.kind {
dependency::Outcome::ToBePublished {
reason: PublishReason::UnpublishedDependencyOfUserSelection { wanted_tag_name },
} => {
log::info!(
"Package '{}' wasn't tagged with {} yet and thus needs a release",
dep.package.name,
wanted_tag_name
);
}
dependency::Outcome::ToBePublished {
reason: PublishReason::ChangedDependencyOfUserSelection,
} => {
log::info!(
"Adding '{}' v{} to set of published crates as it changed since last release",
dep.package.name,
dep.package.version
);
}
dependency::Outcome::Skipped {
reason: SkippedReason::Unchanged,
} => {
if provided_crate_names.contains(&dep.package.name) {
log::info!(
"Skipping provided {} v{} hasn't changed since last released",
dep.package.name,
dep.package.version
);
} else {
log::info!(
"'{}' v{} - skipped release as it didn't change",
dep.package.name,
dep.package.version
);
}
}
dependency::Outcome::Skipped {
reason: SkippedReason::DeniedAutopublishOfProductionCrate,
} => {
log::warn!(
"Production crate '{}' v{} changed since last release - consider releasing it beforehand.",
dep.package.name,
dep.package.version
);
}
dependency::Outcome::ToBePublished {
reason: PublishReason::UserSelected,
} => {}
}
}
} else {
let num_skipped = deps
.iter()
.filter(|dep| matches!(&dep.kind, dependency::Outcome::Skipped { .. }))
.count();
if num_skipped != 0 {
log::info!(
"Skipped {} dependent crates as they didn't change since their last release. Use --verbose/-v to see much more.",
num_skipped
);
}
}
}

fn assure_working_tree_is_unchanged(options: Options) -> anyhow::Result<()> {
if !options.allow_dirty {
if let Err(err) = crate::git::assure_clean_working_tree() {
Expand Down
25 changes: 13 additions & 12 deletions cargo-smart-release/src/git/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,19 @@ use crate::utils::{component_to_bytes, tag_name};

pub mod history;

pub fn has_changed_since_last_release(package: &Package, ctx: &crate::Context, verbose: bool) -> anyhow::Result<bool> {
#[derive(Clone)]
pub enum PackageChangeKind {
Untagged { wanted_tag_name: String },
ChangedOrNew,
}

pub fn change_since_last_release(package: &Package, ctx: &crate::Context) -> anyhow::Result<Option<PackageChangeKind>> {
let version_tag_name = tag_name(package, &package.version.to_string(), &ctx.repo);
let mut tag_ref = match ctx.repo.try_find_reference(&version_tag_name)? {
None => {
if verbose {
log::info!(
"Package '{}' wasn't tagged with {} yet and thus needs a release",
package.name,
version_tag_name
);
}
return Ok(true);
return Ok(Some(PackageChangeKind::Untagged {
wanted_tag_name: version_tag_name,
}));
}
Some(r) => r,
};
Expand All @@ -36,7 +37,7 @@ pub fn has_changed_since_last_release(package: &Package, ctx: &crate::Context, v
let released_target = tag_ref.peel_to_id_in_place()?;

match repo_relative_crate_dir {
None => current_commit != released_target,
None => (current_commit != released_target).then(|| PackageChangeKind::ChangedOrNew),
Some(dir) => {
let components = dir.components().map(component_to_bytes);
let current_dir_id = current_commit
Expand All @@ -54,11 +55,11 @@ pub fn has_changed_since_last_release(package: &Package, ctx: &crate::Context, v
.expect("path must exist as it was supposedly released there")
.oid;

released_dir_id != current_dir_id
(released_dir_id != current_dir_id).then(|| PackageChangeKind::ChangedOrNew)
}
}
}
None => true,
None => Some(PackageChangeKind::ChangedOrNew),
})
}

Expand Down
110 changes: 56 additions & 54 deletions cargo-smart-release/src/traverse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,33 @@ use crate::{
};

pub mod dependency {
#[derive(Copy, Clone)]
pub enum Kind {
ToBePublished,
Skipped,
#[derive(Clone, Debug)]
pub enum PublishReason {
UserSelected,
ChangedDependencyOfUserSelection,
UnpublishedDependencyOfUserSelection { wanted_tag_name: String },
}

#[derive(Copy, Clone, Debug)]
pub enum SkippedReason {
Unchanged,
DeniedAutopublishOfProductionCrate,
}

#[derive(Clone, Debug)]
pub enum Outcome {
ToBePublished { reason: PublishReason },
Skipped { reason: SkippedReason },
}
}

#[derive(Debug)]
pub struct Dependency<'meta> {
pub package: &'meta Package,
pub kind: dependency::Kind,
pub kind: dependency::Outcome,
}

pub fn dependencies(
ctx: &crate::Context,
verbose: bool,
add_production_crates: bool,
) -> anyhow::Result<Vec<Dependency<'_>>> {
pub fn dependencies(ctx: &crate::Context, add_production_crates: bool) -> anyhow::Result<Vec<Dependency<'_>>> {
let mut seen = BTreeSet::new();
let mut crates = Vec::new();
for crate_name in &ctx.crate_names {
Expand All @@ -38,31 +48,24 @@ pub fn dependencies(
crates.clear();
}
let num_crates_for_publishing_without_dependencies = crates.len();
let current_skipped =
depth_first_traversal(ctx, add_production_crates, &mut seen, &mut crates, package, verbose)?;
if !verbose && !current_skipped.is_empty() {
log::info!(
"Skipped {} dependent crates as they didn't change since their last release. Use --verbose/-v to see much more.",
current_skipped.len()
);
}
crates.extend(current_skipped.into_iter().map(|p| Dependency {
package: p,
kind: dependency::Kind::Skipped,
}));
let current_skipped = depth_first_traversal(ctx, add_production_crates, &mut seen, &mut crates, package)?;
crates.extend(current_skipped);
if num_crates_for_publishing_without_dependencies == crates.len()
&& !git::has_changed_since_last_release(package, ctx, verbose)?
&& git::change_since_last_release(package, ctx)?.is_none()
{
log::info!(
"Skipping provided {} v{} hasn't changed since last released",
package.name,
package.version
);
crates.push(Dependency {
package,
kind: dependency::Outcome::Skipped {
reason: dependency::SkippedReason::Unchanged,
},
});
continue;
}
crates.push(Dependency {
package,
kind: dependency::Kind::ToBePublished,
kind: dependency::Outcome::ToBePublished {
reason: dependency::PublishReason::UserSelected,
},
});
seen.insert(&package.id);
}
Expand All @@ -75,8 +78,7 @@ fn depth_first_traversal<'meta>(
seen: &mut BTreeSet<&'meta PackageId>,
crates: &mut Vec<Dependency<'meta>>,
root: &Package,
verbose: bool,
) -> anyhow::Result<Vec<&'meta Package>> {
) -> anyhow::Result<Vec<Dependency<'meta>>> {
let mut skipped = Vec::new();
for dependency in root.dependencies.iter().filter(|d| d.kind == DependencyKind::Normal) {
let workspace_dependency = match workspace_package_by_name(&ctx.meta, &dependency.name) {
Expand All @@ -93,37 +95,37 @@ fn depth_first_traversal<'meta>(
seen,
crates,
workspace_dependency,
verbose,
)?);
if git::has_changed_since_last_release(workspace_dependency, ctx, verbose)? {
if let Some(change) = git::change_since_last_release(workspace_dependency, ctx)? {
if is_pre_release_version(&workspace_dependency.version) || add_production_crates {
if verbose {
log::info!(
"Adding '{}' v{} to set of published crates as it changed since last release",
workspace_dependency.name,
workspace_dependency.version
);
}
crates.push(Dependency {
package: workspace_dependency,
kind: dependency::Kind::ToBePublished,
kind: dependency::Outcome::ToBePublished {
reason: match change {
git::PackageChangeKind::ChangedOrNew => {
dependency::PublishReason::ChangedDependencyOfUserSelection
}
git::PackageChangeKind::Untagged { wanted_tag_name } => {
dependency::PublishReason::UnpublishedDependencyOfUserSelection { wanted_tag_name }
}
},
},
});
} else {
log::warn!(
"Production crate '{}' v{} changed since last release - consider releasing it beforehand.",
workspace_dependency.name,
workspace_dependency.version
);
crates.push(Dependency {
package: workspace_dependency,
kind: dependency::Outcome::Skipped {
reason: dependency::SkippedReason::DeniedAutopublishOfProductionCrate,
},
});
}
} else {
if verbose {
log::info!(
"'{}' v{} - skipped release as it didn't change",
workspace_dependency.name,
workspace_dependency.version
);
}
skipped.push(workspace_dependency);
skipped.push(Dependency {
package: workspace_dependency,
kind: dependency::Outcome::Skipped {
reason: dependency::SkippedReason::Unchanged,
},
});
}
}
Ok(skipped)
Expand Down

0 comments on commit 51a5d36

Please sign in to comment.