mirror of
https://github.com/PaulHatch/semantic-version.git
synced 2025-12-27 04:58:17 +00:00
Add "patch pattern" support for bump each commit versioning
This commit is contained in:
parent
59b55a49a0
commit
cc7cc19f01
9 changed files with 165 additions and 11 deletions
|
|
@ -55,9 +55,13 @@ inputs:
|
|||
required: true
|
||||
default: "csv"
|
||||
enable_prerelease_mode:
|
||||
description: Prevents pre-v1.0.0 version from automatically incrementing the major version. If enabled, when the major version is 0, major releases will be treated as minor and minor as patch. Note that the version_type output is unchanged.
|
||||
description: "Prevents pre-v1.0.0 version from automatically incrementing the major version. If enabled, when the major version is 0, major releases will be treated as minor and minor as patch. Note that the version_type output is unchanged."
|
||||
required: true
|
||||
default: "false"
|
||||
bump_each_commit_patch_pattern:
|
||||
description: "If bump_each_commit is also set to true, setting this value will cause the version to increment only if the pattern specified is matched."
|
||||
required: true
|
||||
default: ""
|
||||
outputs:
|
||||
major:
|
||||
description: "Current major number"
|
||||
|
|
|
|||
45
guide.md
Normal file
45
guide.md
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# Configuration Guide
|
||||
|
||||
|
||||
## Choosing a Release Strategy
|
||||
|
||||
This section is designed to help you choose a release strategy for your project and help you configure GitHub Workflow to use that strategy. It is organized starting from the most simple with each strategy supporting more complex needs, allowing you to start at the top and continue until you find the simplest strategy that meets your needs.
|
||||
|
||||
### Increment Every Release
|
||||
|
||||
If your project has no gating requirements and you want to release every time a commit is pushed to the default branch, you can use the _Increment Every Release_ strategy. This may be appropriate for documentation projects, very small projects, or in cases where "shipping" a broken version isn't a big deal. The key limitation of this strategy is that once you push a commit, the version is going to increments no matter what. If you push a version and your build or automated tests fail, you'll have a version that is broken and you'll have to increment the version again to fix it.
|
||||
|
||||
### Increment from Commit Message
|
||||
|
||||
Very similar to the strategy above, using the _Increment from Commit Message_ means that you are making the decision to increment the version at the time you commit the code, however by using the `bump_each_commit_patch_pattern` parameter introduced in v5.1.0, you can prevent the version from incrementing for a commit unless it matches one of the patters (major, minor, or patch).
|
||||
|
||||
Compared to the _Increment Every Release_ strategy, this strategy allows you to make a decision about whether or not to increment the version for a particular commit, allowing you to add commits to a repo that do not increment the version. Again, you make the decision to increment the version at the time you commit the code, so this strategy may not be appropriate for some project types.
|
||||
|
||||
On the other hand, if you have a fast deployment strategy, such as "every commit goes to prod" and don't mind versions being created for failed builds, this may be the right choice.
|
||||
|
||||
### Tag Versioning
|
||||
|
||||
This strategy is the most common and is the best option for many projects. It allows you to make the decision to release a version after the build has run, which is essentially the primary motivation and main purpose for this action.
|
||||
|
||||
The only real limitation of this strategy is that it does not allow for multiple versions to receive ongoing updates, which may be necessary for certain types of projects which are distributed and receive ongoing maintenance of older versions. This is in contrast to projects that are developed only for a single deployment and are not distributed.
|
||||
|
||||
Tags should generally not be created automatically as part of the build, which can cause strange behavior unless you've taken care to prevent race conditions. Creating tags automatically also largely negates the purpose of this strategy as only build automated test.
|
||||
|
||||
|
||||
## Branch Versioning
|
||||
|
||||
Moving past tag versioning is where things get a little more complicated, as there are many different ways to version branches.
|
||||
### Version Branches with Tag Versioning
|
||||
|
||||
It is possible to use version branches while still
|
||||
|
||||
|
||||
### Version from Branch Name (Non-Predictive)
|
||||
|
||||
|
||||
|
||||
### Version from Branch Name (Predictive)
|
||||
|
||||
## Branch Versioning: GitFlow
|
||||
|
||||
|
||||
|
|
@ -97,6 +97,8 @@ it will be given the new version if the build were to be retriggered, for exampl
|
|||
namespace: my-service
|
||||
# If this is set to true, *every* commit will be treated as a new version.
|
||||
bump_each_commit: false
|
||||
# If bump_each_commit is also set to true, setting this value will cause the version to increment only if the pattern specified is matched.
|
||||
bump_each_commit_patch_pattern: ""
|
||||
# If true, the body of commits will also be searched for major/minor patterns to determine the version type.
|
||||
search_commit_body: false
|
||||
# The output method used to generate list of users, 'csv' or 'json'.
|
||||
|
|
|
|||
|
|
@ -28,4 +28,6 @@ export class ActionConfig {
|
|||
public userFormatType: string = "csv";
|
||||
/** Prevents pre-v1.0.0 version from automatically incrementing the major version. If enabled, when the major version is 0, major releases will be treated as minor and minor as patch. Note that the versionType output is unchanged. */
|
||||
public enablePrereleaseMode: boolean = false;
|
||||
/** If bump_each_commit is also set to true, setting this value will cause the version to increment only if the pattern specified is matched. */
|
||||
public bumpEachCommitPatchPattern: string = "";
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ export async function runAction(configurationProvider: ConfigurationProvider): P
|
|||
const commitsProvider = configurationProvider.GetCommitsProvider();
|
||||
const versionClassifier = configurationProvider.GetVersionClassifier();
|
||||
const versionFormatter = configurationProvider.GetVersionFormatter();
|
||||
const tagFormmater = configurationProvider.GetTagFormatter();
|
||||
const tagFormatter = configurationProvider.GetTagFormatter();
|
||||
const userFormatter = configurationProvider.GetUserFormatter();
|
||||
|
||||
if (await currentCommitResolver.IsEmptyRepoAsync()) {
|
||||
|
|
@ -23,7 +23,7 @@ export async function runAction(configurationProvider: ConfigurationProvider): P
|
|||
versionInfo.increment,
|
||||
versionInfo.type,
|
||||
versionFormatter.Format(versionInfo),
|
||||
tagFormmater.Format(versionInfo),
|
||||
tagFormatter.Format(versionInfo),
|
||||
versionInfo.changed,
|
||||
versionInfo.isTagged,
|
||||
userFormatter.Format('author', []),
|
||||
|
|
@ -34,7 +34,7 @@ export async function runAction(configurationProvider: ConfigurationProvider): P
|
|||
}
|
||||
|
||||
const currentCommit = await currentCommitResolver.ResolveAsync();
|
||||
const lastRelease = await lastReleaseResolver.ResolveAsync(currentCommit, tagFormmater);
|
||||
const lastRelease = await lastReleaseResolver.ResolveAsync(currentCommit, tagFormatter);
|
||||
const commitSet = await commitsProvider.GetCommitsAsync(lastRelease.hash, currentCommit);
|
||||
const classification = await versionClassifier.ClassifyAsync(lastRelease, commitSet);
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ export async function runAction(configurationProvider: ConfigurationProvider): P
|
|||
versionInfo.increment,
|
||||
versionInfo.type,
|
||||
versionFormatter.Format(versionInfo),
|
||||
tagFormmater.Format(versionInfo),
|
||||
tagFormatter.Format(versionInfo),
|
||||
versionInfo.changed,
|
||||
versionInfo.isTagged,
|
||||
userFormatter.Format('author', authors),
|
||||
|
|
|
|||
|
|
@ -805,6 +805,89 @@ test('Patch increments each time when bump each commit is set', async () => {
|
|||
|
||||
}, timeout);
|
||||
|
||||
test('Patch does not increment on bump each commit if a patch pattern is set', async () => {
|
||||
const repo = createTestRepo({ tagPrefix: '', versionFormat: "${major}.${minor}.${patch}+${increment}", bumpEachCommit: true, bumpEachCommitPatchPattern: '(PATCH)' });
|
||||
|
||||
repo.makeCommit('Initial Commit');
|
||||
repo.exec('git tag 0.0.1');
|
||||
const firstResult = await repo.runAction();
|
||||
repo.makeCommit('Second Commit');
|
||||
const secondResult = await repo.runAction();
|
||||
repo.makeCommit('Third Commit');
|
||||
const thirdResult = await repo.runAction();
|
||||
repo.makeCommit('fourth Commit');
|
||||
const fourthResult = await repo.runAction();
|
||||
|
||||
|
||||
expect(firstResult.formattedVersion).toBe('0.0.1+0');
|
||||
expect(secondResult.formattedVersion).toBe('0.0.1+1');
|
||||
expect(thirdResult.formattedVersion).toBe('0.0.1+2');
|
||||
expect(fourthResult.formattedVersion).toBe('0.0.1+3');
|
||||
|
||||
}, timeout);
|
||||
|
||||
test('Patch pattern increment is correct on empty repo', async () => {
|
||||
const repo = createTestRepo({ tagPrefix: '', versionFormat: "${major}.${minor}.${patch}+${increment}", bumpEachCommit: true, bumpEachCommitPatchPattern: '(PATCH)' });
|
||||
|
||||
|
||||
const initialResult = await repo.runAction();
|
||||
repo.makeCommit('Initial Commit');
|
||||
const firstResult = await repo.runAction();
|
||||
repo.makeCommit('Second Commit');
|
||||
const secondResult = await repo.runAction();
|
||||
repo.makeCommit('Third Commit');
|
||||
const thirdResult = await repo.runAction();
|
||||
repo.makeCommit('fourth Commit');
|
||||
const fourthResult = await repo.runAction();
|
||||
|
||||
expect(initialResult.formattedVersion).toBe('0.0.0+0');
|
||||
expect(firstResult.formattedVersion).toBe('0.0.1+0');
|
||||
expect(secondResult.formattedVersion).toBe('0.0.1+1');
|
||||
expect(thirdResult.formattedVersion).toBe('0.0.1+2');
|
||||
expect(fourthResult.formattedVersion).toBe('0.0.1+3');
|
||||
|
||||
}, timeout);
|
||||
|
||||
test('Patch pattern increment is correct/matches non-bumped on empty repo', async () => {
|
||||
const repo = createTestRepo({ tagPrefix: '', versionFormat: "${major}.${minor}.${patch}+${increment}", bumpEachCommit: true, bumpEachCommitPatchPattern: '(PATCH)' });
|
||||
|
||||
|
||||
repo.makeCommit('Initial Commit');
|
||||
const firstResult = await repo.runAction();
|
||||
repo.makeCommit('Second Commit');
|
||||
const secondResult = await repo.runAction();
|
||||
repo.makeCommit('Third Commit');
|
||||
const thirdResult = await repo.runAction();
|
||||
repo.makeCommit('fourth Commit');
|
||||
const fourthResult = await repo.runAction();
|
||||
|
||||
expect(firstResult.formattedVersion).toBe('0.0.1+0');
|
||||
expect(secondResult.formattedVersion).toBe('0.0.1+1');
|
||||
expect(thirdResult.formattedVersion).toBe('0.0.1+2');
|
||||
expect(fourthResult.formattedVersion).toBe('0.0.1+3');
|
||||
|
||||
}, timeout);
|
||||
|
||||
test('Patch pattern applied when present', async () => {
|
||||
const repo = createTestRepo({ tagPrefix: '', versionFormat: "${major}.${minor}.${patch}+${increment}", bumpEachCommit: true, bumpEachCommitPatchPattern: '(PATCH)' });
|
||||
|
||||
|
||||
repo.makeCommit('Initial Commit');
|
||||
const firstResult = await repo.runAction();
|
||||
repo.makeCommit('Second Commit');
|
||||
const secondResult = await repo.runAction();
|
||||
repo.makeCommit('Third Commit (PATCH)');
|
||||
const thirdResult = await repo.runAction();
|
||||
repo.makeCommit('fourth Commit');
|
||||
const fourthResult = await repo.runAction();
|
||||
|
||||
expect(firstResult.formattedVersion).toBe('0.0.1+0');
|
||||
expect(secondResult.formattedVersion).toBe('0.0.1+1');
|
||||
expect(thirdResult.formattedVersion).toBe('0.0.2+0');
|
||||
expect(fourthResult.formattedVersion).toBe('0.0.2+3');
|
||||
|
||||
}, timeout);
|
||||
|
||||
test('Current commit is provided', async () => {
|
||||
const repo = createTestRepo({ tagPrefix: '', versionFormat: "${major}.${minor}.${patch}" });
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ export async function run() {
|
|||
searchCommitBody: core.getInput('search_commit_body') === 'true',
|
||||
userFormatType: core.getInput('user_format_type'),
|
||||
enablePrereleaseMode: core.getInput('enable_prerelease_mode') === 'true',
|
||||
bumpEachCommitPatchPattern: core.getInput('bump_each_commit_patch_pattern')
|
||||
};
|
||||
|
||||
if (config.versionFormat === '' && core.getInput('format') !== '') {
|
||||
|
|
|
|||
|
|
@ -8,9 +8,14 @@ import { VersionType } from "./VersionType";
|
|||
|
||||
export class BumpAlwaysVersionClassifier extends DefaultVersionClassifier {
|
||||
|
||||
protected patchPattern: (commit: CommitInfo) => boolean;
|
||||
|
||||
constructor(config: ActionConfig) {
|
||||
super(config);
|
||||
// Placeholder for consistency
|
||||
|
||||
this.patchPattern = !config.bumpEachCommitPatchPattern ?
|
||||
_ => true :
|
||||
this.parsePattern(config.bumpEachCommitPatchPattern, "", config.searchCommitBody);
|
||||
}
|
||||
|
||||
public override async ClassifyAsync(lastRelease: ReleaseInformation, commitSet: CommitInfoSet): Promise<VersionClassification> {
|
||||
|
|
@ -21,6 +26,7 @@ export class BumpAlwaysVersionClassifier extends DefaultVersionClassifier {
|
|||
|
||||
let { major, minor, patch } = lastRelease;
|
||||
let type = VersionType.None;
|
||||
let increment = 0;
|
||||
|
||||
if (commitSet.commits.length === 0) {
|
||||
return new VersionClassification(type, 0, false, major, minor, patch);
|
||||
|
|
@ -32,16 +38,25 @@ export class BumpAlwaysVersionClassifier extends DefaultVersionClassifier {
|
|||
minor = 0;
|
||||
patch = 0;
|
||||
type = VersionType.Major;
|
||||
increment = 0;
|
||||
} else if (this.minorPattern(commit)) {
|
||||
minor += 1;
|
||||
patch = 0;
|
||||
type = VersionType.Minor;
|
||||
increment = 0;
|
||||
} else {
|
||||
patch += 1;
|
||||
type = VersionType.Patch;
|
||||
if (this.patchPattern(commit) ||
|
||||
(major === 0 && minor === 0 && patch === 0 && commitSet.commits.length > 0)) {
|
||||
patch += 1;
|
||||
type = VersionType.Patch;
|
||||
increment = 0;
|
||||
} else {
|
||||
type = VersionType.None;
|
||||
increment++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new VersionClassification(type, 0, true, major, minor, patch);
|
||||
return new VersionClassification(type, increment, true, major, minor, patch);
|
||||
}
|
||||
}
|
||||
|
|
@ -20,8 +20,10 @@ export class DefaultVersionClassifier implements VersionClassifier {
|
|||
}
|
||||
|
||||
protected parsePattern(pattern: string, flags: string, searchBody: boolean): (pattern: CommitInfo) => boolean {
|
||||
if (pattern.startsWith('/') && pattern.endsWith('/')) {
|
||||
var regex = new RegExp(pattern.slice(1, -1), flags);
|
||||
if (/^\/.+\/[i]*$/.test(pattern)) {
|
||||
const regexEnd = pattern.lastIndexOf('/');
|
||||
const parsedFlags = pattern.slice(pattern.lastIndexOf('/') + 1);
|
||||
const regex = new RegExp(pattern.slice(1, regexEnd), parsedFlags || flags);
|
||||
return searchBody ?
|
||||
(commit: CommitInfo) => regex.test(commit.subject) || regex.test(commit.body) :
|
||||
(commit: CommitInfo) => regex.test(commit.subject);
|
||||
|
|
|
|||
Loading…
Reference in a new issue