feat: add support for providing patterns to match tags (#2098)

Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
Tonye Jack 2024-05-20 22:17:52 -06:00 committed by GitHub
parent 5f0139347a
commit 03c184259a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 250 additions and 5 deletions

View File

@ -235,6 +235,15 @@ inputs:
description: "Use POSIX path separator `/` for output file paths on Windows."
required: false
default: "false"
tags_pattern:
description: "Tags pattern to include."
required: false
default: "*"
tags_ignore_pattern:
description: "Tags pattern to ignore."
required: false
default: ""
outputs:
added_files:

BIN
dist/index.js generated vendored

Binary file not shown.

BIN
dist/index.js.map generated vendored

Binary file not shown.

View File

@ -53,6 +53,8 @@ exports[`getInputs should correctly parse boolean inputs 1`] = `
"since": "",
"sinceLastRemoteCommit": "false",
"skipInitialFetch": "true",
"tagsIgnorePattern": "",
"tagsPattern": "*",
"token": "",
"until": "",
"usePosixPathSeparator": "false",
@ -113,6 +115,8 @@ exports[`getInputs should correctly parse numeric inputs 1`] = `
"since": "",
"sinceLastRemoteCommit": false,
"skipInitialFetch": false,
"tagsIgnorePattern": "",
"tagsPattern": "",
"token": "",
"until": "",
"usePosixPathSeparator": false,
@ -171,6 +175,8 @@ exports[`getInputs should correctly parse string inputs 1`] = `
"since": "",
"sinceLastRemoteCommit": false,
"skipInitialFetch": false,
"tagsIgnorePattern": "",
"tagsPattern": "",
"token": "token",
"until": "",
"usePosixPathSeparator": false,
@ -231,6 +237,8 @@ exports[`getInputs should handle invalid numeric inputs correctly 1`] = `
"since": "",
"sinceLastRemoteCommit": false,
"skipInitialFetch": false,
"tagsIgnorePattern": "",
"tagsPattern": "",
"token": "",
"until": "",
"usePosixPathSeparator": false,
@ -291,6 +299,8 @@ exports[`getInputs should handle negative numeric inputs correctly 1`] = `
"since": "",
"sinceLastRemoteCommit": false,
"skipInitialFetch": false,
"tagsIgnorePattern": "",
"tagsPattern": "",
"token": "",
"until": "",
"usePosixPathSeparator": false,
@ -352,6 +362,8 @@ exports[`getInputs should return default values when no inputs are provided 1`]
"since": "",
"sinceLastRemoteCommit": false,
"skipInitialFetch": false,
"tagsIgnorePattern": "",
"tagsPattern": "*",
"token": "",
"until": "",
"usePosixPathSeparator": false,

View File

@ -1,10 +1,12 @@
import * as core from '@actions/core'
import * as exec from '@actions/exec'
import {ChangeTypeEnum} from '../changedFiles'
import {Inputs} from '../inputs'
import {
getDirname,
getDirnameMaxDepth,
getFilteredChangedFiles,
getPreviousGitTag,
normalizeSeparators,
warnUnsupportedRESTAPIInputs
} from '../utils'
@ -638,7 +640,9 @@ describe('utils test', () => {
useRestApi: false,
excludeSubmodules: false,
fetchMissingHistoryMaxRetries: 10,
usePosixPathSeparator: false
usePosixPathSeparator: false,
tagsPattern: '*',
tagsIgnorePattern: ''
}
const coreWarningSpy = jest.spyOn(core, 'warning')
@ -654,4 +658,193 @@ describe('utils test', () => {
expect(coreWarningSpy).toHaveBeenCalledTimes(1)
})
})
describe('getPreviousGitTag', () => {
// Function returns the second latest tag and its SHA
it('should return the second latest tag and its SHA when multiple tags are present', async () => {
jest
.spyOn(exec, 'getExecOutput')
.mockResolvedValueOnce({
stdout: 'v1.0.1\nv1.0.0\nv0.9.9',
stderr: '',
exitCode: 0
})
.mockResolvedValueOnce({
stdout: 'abc123',
stderr: '',
exitCode: 0
})
const result = await getPreviousGitTag({
cwd: '.',
tagsPattern: '*',
tagsIgnorePattern: ''
})
expect(result).toEqual({tag: 'v1.0.0', sha: 'abc123'})
})
// Tags are filtered by a specified pattern when 'tagsPattern' is provided
it('should filter tags by the specified pattern', async () => {
jest
.spyOn(exec, 'getExecOutput')
.mockResolvedValueOnce({
stdout: 'v1.0.1\nv1.0.0\nv0.9.9',
stderr: '',
exitCode: 0
})
.mockResolvedValueOnce({
stdout: 'def456',
stderr: '',
exitCode: 0
})
const result = await getPreviousGitTag({
cwd: '.',
tagsPattern: 'v1.*',
tagsIgnorePattern: ''
})
expect(result).toEqual({tag: 'v1.0.0', sha: 'def456'})
})
// Tags are excluded by a specified ignore pattern when 'tagsIgnorePattern' is provided
it('should exclude tags by the specified ignore pattern', async () => {
jest
.spyOn(exec, 'getExecOutput')
.mockResolvedValueOnce({
stdout: 'v1.0.1\nv1.0.0\nv0.9.9',
stderr: '',
exitCode: 0
})
.mockResolvedValueOnce({
stdout: 'ghi789',
stderr: '',
exitCode: 0
})
const result = await getPreviousGitTag({
cwd: '.',
tagsPattern: '*',
tagsIgnorePattern: 'v0.*.*'
})
expect(result).toEqual({tag: 'v1.0.0', sha: 'ghi789'})
})
// Function executes silently when debug mode is not active
it('should execute silently when debug mode is not active', async () => {
jest.spyOn(core, 'isDebug').mockReturnValue(false)
const spy = jest
.spyOn(exec, 'getExecOutput')
.mockResolvedValueOnce({
stdout: 'v1.0.1\nv1.0.0',
stderr: '',
exitCode: 0
})
.mockResolvedValueOnce({
stdout: 'jkl012',
stderr: '',
exitCode: 0
})
await getPreviousGitTag({
cwd: '.',
tagsPattern: '*',
tagsIgnorePattern: ''
})
expect(spy).toHaveBeenCalledWith('git', ['tag', '--sort=-creatordate'], {
cwd: '.',
silent: true
})
})
// No tags are available in the repository
it('should return empty values when no tags are available in the repository', async () => {
jest.spyOn(exec, 'getExecOutput').mockResolvedValueOnce({
stdout: '',
stderr: '',
exitCode: 0
})
const result = await getPreviousGitTag({
cwd: '.',
tagsPattern: '*',
tagsIgnorePattern: ''
})
expect(result).toEqual({tag: '', sha: ''})
})
// Only one tag is available, making it impossible to find a previous tag
it('should return empty values when only one tag is available', async () => {
jest.spyOn(exec, 'getExecOutput').mockResolvedValueOnce({
stdout: 'v1.0.1',
stderr: '',
exitCode: 0
})
const result = await getPreviousGitTag({
cwd: '.',
tagsPattern: '*',
tagsIgnorePattern: ''
})
expect(result).toEqual({tag: '', sha: ''})
})
// Provided 'tagsPattern' matches no tags
it('should return empty values when provided tagsPattern matches no tags', async () => {
jest.spyOn(exec, 'getExecOutput').mockResolvedValueOnce({
stdout: 'v1.0.1\nv1.0.0',
stderr: '',
exitCode: 0
})
const result = await getPreviousGitTag({
cwd: '.',
tagsPattern: 'nonexistent*',
tagsIgnorePattern: ''
})
expect(result).toEqual({tag: '', sha: ''})
})
// Provided 'tagsIgnorePattern' excludes all tags
it('should return empty values when provided tagsIgnorePattern excludes all tags', async () => {
jest.spyOn(exec, 'getExecOutput').mockResolvedValueOnce({
stdout: 'v1.0.1\nv1.0.0',
stderr: '',
exitCode: 0
})
const result = await getPreviousGitTag({
cwd: '.',
tagsPattern: '*',
tagsIgnorePattern: 'v*'
})
expect(result).toEqual({tag: '', sha: ''})
})
// Git commands fail and throw errors
it('should throw an error when git commands fail', async () => {
jest
.spyOn(exec, 'getExecOutput')
.mockRejectedValue(new Error('git command failed'))
await expect(
getPreviousGitTag({cwd: '.', tagsPattern: '*', tagsIgnorePattern: ''})
).rejects.toThrow('git command failed')
})
// Debug mode logs additional information
it('should log additional information when debug mode is active', async () => {
jest.spyOn(core, 'isDebug').mockReturnValue(true)
const spy = jest
.spyOn(exec, 'getExecOutput')
.mockResolvedValueOnce({
stdout: 'v1.0.1\nv1.0.0',
stderr: '',
exitCode: 0
})
.mockResolvedValueOnce({
stdout: 'mno345',
stderr: '',
exitCode: 0
})
await getPreviousGitTag({
cwd: '.',
tagsPattern: '*',
tagsIgnorePattern: ''
})
expect(spy).toHaveBeenCalledWith('git', ['tag', '--sort=-creatordate'], {
cwd: '.',
silent: false
})
})
})
})

View File

@ -240,7 +240,11 @@ export const getSHAForNonPullRequestEvent = async ({
}
} else if (isTag) {
core.debug('Getting previous SHA for tag...')
const {sha, tag} = await getPreviousGitTag({cwd: workingDirectory})
const {sha, tag} = await getPreviousGitTag({
cwd: workingDirectory,
tagsPattern: inputs.tagsPattern,
tagsIgnorePattern: inputs.tagsIgnorePattern
})
previousSha = sha
targetBranch = tag
} else {

View File

@ -23,5 +23,7 @@ export const DEFAULT_VALUES_OF_UNSUPPORTED_API_INPUTS: Partial<Inputs> = {
dirNamesDeletedFilesIncludeOnlyDeletedDirs: false,
excludeSubmodules: false,
fetchMissingHistoryMaxRetries: 10,
usePosixPathSeparator: false
usePosixPathSeparator: false,
tagsPattern: '*',
tagsIgnorePattern: ''
}

View File

@ -57,6 +57,8 @@ export type Inputs = {
excludeSubmodules: boolean
fetchMissingHistoryMaxRetries?: number
usePosixPathSeparator: boolean
tagsPattern: string
tagsIgnorePattern?: string
}
export const getInputs = (): Inputs => {
@ -259,6 +261,15 @@ export const getInputs = (): Inputs => {
}
)
const tagsPattern = core.getInput('tags_pattern', {
required: false,
trimWhitespace: false
})
const tagsIgnorePattern = core.getInput('tags_ignore_pattern', {
required: false,
trimWhitespace: false
})
const inputs: Inputs = {
files,
filesSeparator,
@ -300,6 +311,8 @@ export const getInputs = (): Inputs => {
dirNamesDeletedFilesIncludeOnlyDeletedDirs,
excludeSubmodules,
usePosixPathSeparator,
tagsPattern,
tagsIgnorePattern,
// End Not Supported via REST API
dirNames,
dirNamesExcludeCurrentDir,

View File

@ -832,9 +832,13 @@ export const cleanShaInput = async ({
return stdout.trim()
}
export const getPreviousGitTag = async ({
cwd
cwd,
tagsPattern,
tagsIgnorePattern
}: {
cwd: string
tagsPattern: string
tagsIgnorePattern?: string
}): Promise<{tag: string; sha: string}> => {
const {stdout} = await exec.getExecOutput(
'git',
@ -845,7 +849,15 @@ export const getPreviousGitTag = async ({
}
)
const tags = stdout.trim().split('\n')
let tags = stdout.trim().split('\n')
if (tagsPattern) {
tags = tags.filter(tag => mm.isMatch(tag, tagsPattern))
}
if (tagsIgnorePattern) {
tags = tags.filter(tag => !mm.isMatch(tag, tagsIgnorePattern))
}
if (tags.length < 2) {
core.warning('No previous tag found')