Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support pulling current version from tags. #224

Merged
merged 2 commits into from
Aug 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions docs/src/config/packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,17 @@ This is the relevant part of Knope's own `knope.toml`, where we keep release not

### `versioned_files`

A package, by Knope's definition, has a single version. There can, however, be multiple files which contain this version (e.g., `Cargo.toml` for a Rust crate and `pyproject.toml` for a Python wrapper around it). As such, you can define an array of `versioned_files` for each package as long as they all have the same version and all are supported formats. The file must be named exactly the way that `knope` expects, but it can be in nested directories. The supported file types (and names) are:
A package, by Knope's definition, has a single version. There can, however, be multiple files which contain this version (e.g., `Cargo.toml` for a Rust crate and `pyproject.toml` for a Python wrapper around it). As such, you can define an array of `versioned_files` for each package as long as they all have the same version and all are supported formats. If no file is included in `versioned_files`, the latest Git tag in the format created by the [`Release`] step will be used. The file must be named exactly the way that `knope` expects, but it can be in nested directories. The supported file types (and names) are:

1. `Cargo.toml` for Rust projects
2. `pyproject.toml` for Python projects (using [Poetry's metadata](https://python-poetry.org))
3. `package.json` for Node projects

Want to bump the version of a file that isn't natively supported? [Request it as a feature] and, in the mean time, you can write a script to manually bump that file with the version produced by [`BumpVersion`] or [`PrepareRelease`] using a [`Command`] step, like this:
Want to bump the version of a file that isn't natively supported? [Request it as a feature] and, in the meantime, you can write a script to manually bump that file with the version produced by [`BumpVersion`] or [`PrepareRelease`] using a [`Command`] step, like this:

```toml
[[packages]]
versioned_files = ["Cargo.toml"] # there must still be at least one supported file to read the current version from
versioned_files = [] # With no versioned_files, the version will be determined via Git tag
changelog = "CHANGELOG.md"

[[workflows]]
Expand Down
28 changes: 28 additions & 0 deletions src/releases/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::io::Write;
use git_repository::object::Kind;
use git_repository::open;
use git_repository::refs::transaction::PreviousValue;
use semver::Version;

use crate::releases::Release;
use crate::step::StepError;
Expand All @@ -28,3 +29,30 @@ pub(crate) fn release(

Ok(RunType::Real(state))
}

pub(crate) fn get_current_version_from_tag() -> Result<Version, StepError> {
let repo = open(current_dir()?).map_err(|_e| StepError::NotAGitRepo)?;
repo.references()
.map_err(|_e| StepError::NotAGitRepo)?
.tags()
.map_err(|_e| StepError::NotAGitRepo)?
.flat_map(|tag| {
tag.map(|reference| {
reference
.name()
.as_bstr()
.to_string()
.split('/')
.last()
.map(String::from)
})
})
.flatten()
.find_map(|version_string| {
version_string
.starts_with('v')
.then(|| Version::parse(&version_string[1..version_string.len()]).ok())
})
.flatten()
.map_or_else(|| Ok(Version::new(0, 0, 0)), Ok)
}
15 changes: 8 additions & 7 deletions src/releases/semver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::fmt::Display;
use semver::{Prerelease, Version};
use serde::{Deserialize, Serialize};

use crate::releases::git::get_current_version_from_tag;
use crate::releases::package::{Package, VersionedFile};
use crate::step::StepError;
use crate::{state, RunType};
Expand Down Expand Up @@ -107,12 +108,9 @@ pub(crate) fn get_version(packages: &[PackageConfig]) -> Result<PackageVersion,
return Err(StepError::TooManyPackages);
}
let package = &packages[0];
if package.versioned_files.is_empty() {
return Err(StepError::NoVersionedFiles);
}
let package = Package::try_from(package.clone())?;

let version_string = package
let version = package
.versioned_files
.iter()
.map(VersionedFile::get_version)
Expand All @@ -126,9 +124,12 @@ pub(crate) fn get_version(packages: &[PackageConfig]) -> Result<PackageVersion,
}
(_, Err(err)) | (Err(err), _) => Err(err),
})
.ok_or(StepError::NoVersionedFiles)??;
let version = Version::parse(&version_string)
.map_err(|_| StepError::InvalidSemanticVersion(version_string))?;
.map_or_else(get_current_version_from_tag, |version_result| {
version_result.and_then(|version_string| {
Version::parse(&version_string)
.map_err(|_| StepError::InvalidSemanticVersion(version_string))
})
})?;

Ok(PackageVersion { version, package })
}
Expand Down
13 changes: 3 additions & 10 deletions src/step.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,9 +245,9 @@ pub(super) enum StepError {
IncompleteCheckout(#[source] git2::Error),
#[error("Could not list tags for the project")]
#[diagnostic(
code(step::list_tags_error),
help("We couldn't list the tags for the project. This step requires at least one existing tag."),
url("https://knope-dev.github.io/knope/config/step/PrepareRelease.html")
code(step::list_tags_error),
help("We couldn't list the tags for the project. This step requires at least one existing tag."),
url("https://knope-dev.github.io/knope/config/step/PrepareRelease.html")
)]
ListTagsError(#[source] git2::Error),
#[error("Unknown Git error.")]
Expand Down Expand Up @@ -311,13 +311,6 @@ pub(super) enum StepError {
url("https://github.com/knope-dev/knope/issues/153")
)]
TooManyPackages,
#[error("No versioned files defined")]
#[diagnostic(
code(step::no_versioned_files),
help("You must define at least one versioned_files per package in the [[packages]] section of knope.toml."),
url("https://knope-dev.github.io/knope/config/packages.html")
)]
NoVersionedFiles,
#[error("File {0} does not exist")]
#[diagnostic(
code(step::file_not_found),
Expand Down
8 changes: 4 additions & 4 deletions tests/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fn generate_no_remote() {
// Arrange
let temp_dir = tempfile::tempdir().unwrap();
let temp_path = temp_dir.path();
let source_path = Path::new("tests/generate_no_remote");
let source_path = Path::new("tests/generate/no_remote");
init(temp_path);

// Act
Expand All @@ -42,7 +42,7 @@ fn generate_packages(#[case] source_files: &[&str], #[case] target_file: &str) {
// Arrange
let temp_dir = tempfile::tempdir().unwrap();
let temp_path = temp_dir.path();
let source_path = Path::new("tests/generate_packages");
let source_path = Path::new("tests/generate/packages");
init(temp_path);
commit(temp_path, "feat: Existing Feature");
tag(temp_path, "v1.0.0");
Expand Down Expand Up @@ -86,7 +86,7 @@ fn generate_packages_changelog(#[case] has_changelog: bool, #[case] target_file:
// Arrange
let temp_dir = tempfile::tempdir().unwrap();
let temp_path = temp_dir.path();
let source_path = Path::new("tests/generate_package_changelog");
let source_path = Path::new("tests/generate/package_changelog");
init(temp_path);
copy(source_path.join("Cargo.toml"), temp_path.join("Cargo.toml")).unwrap();
if has_changelog {
Expand All @@ -113,7 +113,7 @@ fn generate_github() {
// Arrange
let temp_dir = tempfile::tempdir().unwrap();
let temp_path = temp_dir.path();
let source_path = Path::new("tests/generate_github");
let source_path = Path::new("tests/generate/github");
init(temp_path);
add_remote(temp_path, "github.com/knope-dev/knope");

Expand Down
File renamed without changes.
File renamed without changes.
83 changes: 57 additions & 26 deletions tests/snapshot_tests.rs → tests/prepare_release.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,13 @@ use git_repo_helpers::*;

mod git_repo_helpers;

/// Run `--validate` with a config file that has lots of problems.
#[test]
fn test_validate() {
// Arrange.
let temp_dir = tempfile::tempdir().unwrap();
let temp_path = temp_dir.path();
let source_path = Path::new("tests/validate");
init(temp_path);
commit(temp_path, "Initial commit");
tag(temp_path, "1.0.0");
copy(source_path.join("knope.toml"), temp_path.join("knope.toml")).unwrap();

let assert = Command::new(cargo_bin!("knope"))
.arg("--validate")
.current_dir(temp_path)
.assert();
assert.failure().stderr_eq_path("tests/validate/output.txt");
}

/// Run a `PrepareRelease` as a pre-release in a repo which already contains a release.
#[test]
fn prerelease_after_release() {
// Arrange.
let temp_dir = tempfile::tempdir().unwrap();
let temp_path = temp_dir.path();
let source_path = Path::new("tests/prerelease_after_release");
let source_path = Path::new("tests/prepare_release/prerelease_after_release");

init(temp_path);
commit(temp_path, "feat: New feature in existing release");
Expand Down Expand Up @@ -80,7 +61,7 @@ fn second_prerelease() {
// Arrange.
let temp_dir = tempfile::tempdir().unwrap();
let temp_path = temp_dir.path();
let source_path = Path::new("tests/second_prerelease");
let source_path = Path::new("tests/prepare_release/second_prerelease");

init(temp_path);
commit(temp_path, "feat: New feature in first RC");
Expand Down Expand Up @@ -130,7 +111,7 @@ fn prepare_release_selects_files(#[case] knope_toml: &str, #[case] versioned_fil
// Arrange.
let temp_dir = tempfile::tempdir().unwrap();
let temp_path = temp_dir.path();
let source_path = Path::new("tests/prepare_release_package_selection");
let source_path = Path::new("tests/prepare_release/package_selection");

init(temp_path);
commit(temp_path, "feat: Existing feature");
Expand Down Expand Up @@ -189,7 +170,7 @@ fn test_prepare_release_multiple_packages() {
// Arrange.
let temp_dir = tempfile::tempdir().unwrap();
let temp_path = temp_dir.path();
let source_path = Path::new("tests/prepare_release_package_selection");
let source_path = Path::new("tests/prepare_release/package_selection");

init(temp_path);
commit(temp_path, "feat: Existing feature");
Expand Down Expand Up @@ -245,7 +226,7 @@ fn test_prepare_release_multiple_files_inconsistent_versions() {
// Arrange.
let temp_dir = tempfile::tempdir().unwrap();
let temp_path = temp_dir.path();
let source_path = Path::new("tests/prepare_release_package_selection");
let source_path = Path::new("tests/prepare_release/package_selection");

init(temp_path);
commit(temp_path, "feat: Existing feature");
Expand Down Expand Up @@ -301,7 +282,7 @@ fn test_prepare_release_invalid_versioned_file_format() {
// Arrange.
let temp_dir = tempfile::tempdir().unwrap();
let temp_path = temp_dir.path();
let source_path = Path::new("tests/prepare_release_package_selection");
let source_path = Path::new("tests/prepare_release/package_selection");

init(temp_path);
commit(temp_path, "feat: Existing feature");
Expand Down Expand Up @@ -361,7 +342,7 @@ fn prepare_release_changelog_selection(#[case] changelog: Option<&str>) {
// Arrange.
let temp_dir = tempfile::tempdir().unwrap();
let temp_path = temp_dir.path();
let source_path = Path::new("tests/prepare_release_changelog_selection");
let source_path = Path::new("tests/prepare_release/changelog_selection");

init(temp_path);
commit(temp_path, "feat: Existing feature");
Expand Down Expand Up @@ -432,3 +413,53 @@ fn prepare_release_changelog_selection(#[case] changelog: Option<&str>) {
read_to_string(temp_path.join("Cargo.toml")).unwrap(),
);
}

/// If `PrepareRelease` is run with no `versioned_files`, it should determine the version from the
/// previous valid tag.
#[test]
fn no_versioned_files() {
// Arrange.
let temp_dir = tempfile::tempdir().unwrap();
let temp_path = temp_dir.path();
let source_path = Path::new("tests/prepare_release/no_versioned_files");

init(temp_path);
commit(temp_path, "feat: Existing feature");
tag(temp_path, "v1.0.0");
commit(temp_path, "feat: New feature");

copy(source_path.join("knope.toml"), temp_path.join("knope.toml")).unwrap();
copy(
source_path.join("CHANGELOG.md"),
temp_path.join("CHANGELOG.md"),
)
.unwrap();

// Act.
let dry_run_assert = Command::new(cargo_bin!("knope"))
.arg("release")
.arg("--dry-run")
.current_dir(temp_dir.path())
.assert();
let actual_assert = Command::new(cargo_bin!("knope"))
.arg("release")
.current_dir(temp_dir.path())
.assert();

// Assert.
dry_run_assert
.success()
.stdout_eq_path(source_path.join("dry_run_output.txt"));
actual_assert
.success()
.stdout_eq_path(source_path.join("output.txt"));
assert_eq_path(
source_path.join("EXPECTED_CHANGELOG.md"),
read_to_string(temp_path.join("CHANGELOG.md")).unwrap(),
);

// The release step should have created a tag with the right new version.
let expected_tag = "v1.1.0";
let actual_tag = describe(temp_path);
assert_eq!(expected_tag, actual_tag);
}
9 changes: 9 additions & 0 deletions tests/prepare_release/no_versioned_files/dry_run_output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Would bump version to 1.1.0
Would add the following to CHANGELOG.md:
## 1.1.0

### Features

- New feature

Would create Git tag v1.1.0
12 changes: 12 additions & 0 deletions tests/prepare_release/no_versioned_files/knope.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[[packages]]
versioned_files = []
changelog = "CHANGELOG.md"

[[workflows]]
name = "release"

[[workflows.steps]]
type = "PrepareRelease"

[[workflows.steps]]
type = "Release"
5 changes: 5 additions & 0 deletions tests/prepare_release/package_selection/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## 1.0.0

### Features

- Existing features
11 changes: 11 additions & 0 deletions tests/prepare_release/package_selection/EXPECTED_CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## 1.1.0

### Features

- New feature

## 1.0.0

### Features

- Existing features
Empty file.
27 changes: 27 additions & 0 deletions tests/validate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use std::fs::copy;
use std::path::Path;

use snapbox::cmd::{cargo_bin, Command};

use git_repo_helpers::*;

mod git_repo_helpers;

/// Run `--validate` with a config file that has lots of problems.
#[test]
fn test_validate() {
// Arrange.
let temp_dir = tempfile::tempdir().unwrap();
let temp_path = temp_dir.path();
let source_path = Path::new("tests/validate");
init(temp_path);
commit(temp_path, "Initial commit");
tag(temp_path, "1.0.0");
copy(source_path.join("knope.toml"), temp_path.join("knope.toml")).unwrap();

let assert = Command::new(cargo_bin!("knope"))
.arg("--validate")
.current_dir(temp_path)
.assert();
assert.failure().stderr_eq_path("tests/validate/output.txt");
}