Skip to content
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
14 changes: 14 additions & 0 deletions .autover/changes/d496df5b-8330-4f44-9484-47181d9fe909.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"Projects": [
{
"Name": "AutoVer",
"Type": "Minor",
"ChangelogMessages": [
"Add --project-name to `version --current`, so one project's version can be captured from a repository that has several: VERSION=$(autover version --current --project-name \"My Image\") prints a bare value however many projects are configured. Previously only a single-project repository produced capturable output - with several, --current prints them labeled, which is readable but not assignable to a variable",
"An unknown --project-name is an error that lists the configured project names, rather than empty output. Left unvalidated, a typo in a pipeline would set VERSION=\"\" and publish an untagged or mis-tagged artifact",
"Naming a project whose Paths have drifted to different versions is an error rather than a silent pick of the first, since those files are configured to share one version",
"The name matches the same way `change --project-name` does: exact and case-sensitive"
]
}
]
}
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ tags the release.
| `--increment-type <type>` | `Major`, `Minor`, `Patch` or `None`. Overrides the configured default. |
| `--use-version <version>` | Release a specific version instead of an increment. |
| `--current` | Print the current version without changing anything. |
| `--project-name <name>` | With `--current`, print only this project's version, as a bare value. |
| `--skip-version-tag-check` | Don't require each project to already carry a version. |
| `--no-commit` | Bump the version without committing (implies no tag). |
| `--no-tag` | Commit the bump without tagging. |
Expand All @@ -78,6 +79,28 @@ tags the release.
`--current` prints a bare value for a single project, so it can be captured
directly: `VERSION=$(autover version --current)`.

With several projects it prints them labeled instead, since there is no single
"the" version to capture:

```
Project1: 1.2.3
Project2: 4.5.6
```

Name one to get a bare value back, which is what a pipeline publishing several
artifacts from one repository needs:

```bash
VERSION=$(autover version --current --project-name "Project2") # 4.5.6
```

The name matches the `Name` in `Projects`, exactly as `change --project-name`
does, and an unknown name is an error listing the configured ones rather than
empty output - a typo in a pipeline would otherwise set `VERSION=""` and publish
a mis-tagged artifact. A project using `Paths` shares one version across its
files, so naming it prints that; if those files have drifted apart, that is an
error too rather than a silent pick.

### `autover changelog`

Generates `CHANGELOG.md` for the current release and commits it.
Expand Down
13 changes: 12 additions & 1 deletion src/AutoVer/Commands/CommandFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,15 @@ private Command BuildVersionCommand()
{
Description = "Print the current version of each project and exit, without incrementing, committing, or tagging anything."
};
// Only meaningful alongside --current. With several projects, --current prints them
// labeled, which is readable but not capturable - a caller wanting one version in a
// variable had no way to ask for it. Naming the project makes it print bare, so
// VERSION=$(autover version --current --project-name "My Image") works with any number
// of projects. Same option name and matching rules as `change --project-name`.
Option<string> projectNameOption = new("--project-name")
{
Description = "With --current, print only this project's version, as a bare value. Without --current, ignored."
};

versionCommand.Add(OptionProjectPath);
versionCommand.Add(OptionIncrementType);
Expand All @@ -112,6 +121,7 @@ private Command BuildVersionCommand()
versionCommand.Add(noTagOption);
versionCommand.Add(useVersionOption);
versionCommand.Add(currentOption);
versionCommand.Add(projectNameOption);
versionCommand.Add(OptionVerbose);

versionCommand.SetAction((parseResult, cancellationToken) => ExecuteCommandAsync(parseResult, async () =>
Expand All @@ -123,6 +133,7 @@ private Command BuildVersionCommand()
var optionNoTag = parseResult.GetValue(noTagOption);
var optionUseVersion = parseResult.GetValue(useVersionOption);
var optionCurrent = parseResult.GetValue(currentOption);
var optionProjectName = parseResult.GetValue(projectNameOption);

var command = new VersionCommand(
projectHandler,
Expand All @@ -132,7 +143,7 @@ private Command BuildVersionCommand()
versionHandler,
versionIncrementer,
toolInteractiveService);
await command.ExecuteAsync(optionProjectPath, optionIncrementType, optionSkipVersionTagCheck, optionNoCommit, optionNoTag, optionUseVersion, optionCurrent);
await command.ExecuteAsync(optionProjectPath, optionIncrementType, optionSkipVersionTagCheck, optionNoCommit, optionNoTag, optionUseVersion, optionCurrent, optionProjectName);
}));

return versionCommand;
Expand Down
48 changes: 45 additions & 3 deletions src/AutoVer/Commands/VersionCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,16 @@ public async Task ExecuteAsync(
bool optionNoCommit,
bool optionNoTag,
string? optionUseVersion,
bool optionCurrent)
bool optionCurrent,
string? optionProjectName = null)
{
var incrementType = IncrementTypeParser.Parse(optionIncrementType);

var userConfiguration = await configurationManager.RetrieveUserConfiguration(optionProjectPath, incrementType);

if (optionCurrent)
{
PrintCurrentVersions(userConfiguration);
PrintCurrentVersions(userConfiguration, optionProjectName);
return;
}

Expand Down Expand Up @@ -295,8 +296,23 @@ private static IncrementType ResolveIncrementTypeFromTag(
// `changelog --release-name`'s convention for shell capture, e.g.
// VERSION=$(autover version --current). Multiple projects print
// labeled, since there's no single "the" version to capture bare.
private void PrintCurrentVersions(UserConfiguration userConfiguration)
private void PrintCurrentVersions(UserConfiguration userConfiguration, string? projectName = null)
{
// Naming a project makes the output bare regardless of how many projects there are, so a
// caller can capture one version - VERSION=$(autover version --current --project-name X).
// Resolved before the VersionFromTag branch below so an unknown name is always an error
// rather than silently printing the repository version.
ProjectContainer? requestedProject = null;
if (!string.IsNullOrEmpty(projectName))
{
// Same matching as `change --project-name`: exact, case-sensitive.
requestedProject = userConfiguration.Projects.FirstOrDefault(x => x.Name.Equals(projectName));
if (requestedProject is null)
throw new InvalidProjectNameSpecifiedException(
$"The project '{projectName}' does not exist. Please specify a valid project name. " +
$"Configured: {string.Join(", ", userConfiguration.Projects.Select(x => $"'{x.Name}'"))}.");
}

if (userConfiguration.VersionFromTag)
{
var tagVersion = versionHandler.GetCurrentTagVersion(userConfiguration);
Expand All @@ -305,10 +321,36 @@ private void PrintCurrentVersions(UserConfiguration userConfiguration)
$"The Git repository '{userConfiguration.GitRoot}' has no release tag to read a version from yet. " +
"Please run 'autover version' first.");

// One tag carries one version, so every project reports the same thing here - naming
// one is redundant but not wrong, and validating it above still catches a typo.
toolInteractiveService.WriteLine(tagVersion.ToString());
return;
}

if (requestedProject is not null)
{
var requestedVersions = requestedProject.Projects
.Select(project => project.ProjectDefinition.Version)
.Distinct()
.ToList();

if (requestedVersions.Count == 0)
throw new InvalidProjectNameSpecifiedException(
$"The project '{projectName}' has no project files to read a version from.");

// A container's files share one version by definition (that is what Paths means), so
// disagreement means one was edited by hand. Printing the first would quietly publish
// a version that does not match what is in the other file.
if (requestedVersions.Count > 1)
throw new InvalidProjectException(
$"The files of project '{projectName}' carry different versions " +
$"({string.Join(", ", requestedVersions)}), so there is no single version to print. " +
"They are configured to share one - reconcile them, or list them as separate projects.");

toolInteractiveService.WriteLine(requestedVersions[0]);
return;
}

var allProjects = userConfiguration.Projects
.SelectMany(container => container.Projects)
.ToList();
Expand Down
54 changes: 54 additions & 0 deletions test/AutoVer.IntegrationTests/VersionCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,60 @@ public async Task Current_PrintsVersionWithoutMutating()
await Assert.That(GitUtilities.GetAllTags(_tempDir)).IsEmpty();
}

// M. With several projects, `--current` labels each one. Asserted so the bare-value
// contract in L stays specific to the single-project case: a caller doing
// VERSION=$(autover version --current) on a multi-project repository gets labeled lines,
// not a version, which is exactly why --project-name in N exists.
[Test]
public async Task Current_WithSeveralProjects_LabelsEachVersion()
{
await SetUpTwoProjectRepo("Project1", "1.2.3", "Project2", "4.5.6", useSameVersionForAllProjects: false);

var (exitCode, output, _) = await AutoVerUtilities.RunCapturingOutput(["version", "--project-path", _tempDir, "--current"]);

await Assert.That(exitCode).IsEqualTo(CommandReturnCodes.Success);
await Assert.That(output).Contains("Project1: 1.2.3");
await Assert.That(output).Contains("Project2: 4.5.6");
}

// N. `--current --project-name` prints that one project's version bare, however many
// projects exist. This is what makes VERSION=$(autover version --current --project-name X)
// work in a pipeline that publishes several artifacts from one repository - previously the
// only capturable output was a single-project repository's.
[Test]
public async Task CurrentWithProjectName_PrintsThatProjectBare()
{
await SetUpTwoProjectRepo("Project1", "1.2.3", "Project2", "4.5.6", useSameVersionForAllProjects: false);

var (exitCode, output, _) = await AutoVerUtilities.RunCapturingOutput(
["version", "--project-path", _tempDir, "--current", "--project-name", "Project2"]);

await Assert.That(exitCode).IsEqualTo(CommandReturnCodes.Success);
await Assert.That(output.Trim()).IsEqualTo("4.5.6");
// Bare means bare: no label, and nothing about the project it was not asked about.
await Assert.That(output).DoesNotContain("Project2:");
await Assert.That(output).DoesNotContain("1.2.3");
}

// O. An unknown --project-name is an error rather than empty output. Left unvalidated, a
// typo in a pipeline would set VERSION="" and publish an untagged or mis-tagged artifact.
[Test]
public async Task CurrentWithUnknownProjectName_FailsAndListsConfiguredProjects()
{
await SetUpTwoProjectRepo("Project1", "1.2.3", "Project2", "4.5.6", useSameVersionForAllProjects: false);

var (exitCode, output, error) = await AutoVerUtilities.RunCapturingOutput(
["version", "--project-path", _tempDir, "--current", "--project-name", "DoesNotExist"]);

await Assert.That(exitCode).IsEqualTo(CommandReturnCodes.UserError);
await Assert.That(error).Contains("DoesNotExist");
// The message names what IS configured, so the fix does not need a second command.
await Assert.That(error).Contains("Project1");
await Assert.That(error).Contains("Project2");
await Assert.That(error).DoesNotContain("at AutoVer.");
await Assert.That(output.Trim()).IsEmpty();
}

// `changeFilesDetermineIncrementType` must be true for scenarios that rely on a change
// file to pick the increment type (B/C/D/K), and false for scenarios with no change
// file at all (G/H/I/J) — with it true and no change file, VersionCommand resolves the
Expand Down
Loading