diff --git a/docs/faq/gitdirty.md b/docs/faq/gitdirty.md index cd9cc369..4131e985 100644 --- a/docs/faq/gitdirty.md +++ b/docs/faq/gitdirty.md @@ -22,3 +22,15 @@ Add or modify an existing `.gitignore` file to ignore the offending files listed - Ensure no tracked files are unexpectedly modified - Prevent the creation of temporary files. If this isn't possible, you can fall back to using a `.gitignore` file. + +### use includeArtifacts + +Defines a list of files that uplift will ignore when checking the status of the current repository. If a change is detected that is not defined in this list, uplift will assume its default behaviour and fail due to the repository being in a dirty state. + +An example usecase is if a file has been generated as part of your release e.g. software bill of materials which you wish to include in your versioning. + +```yaml +includeArtifacts: + - file.txt + - path/to/file.txt +``` diff --git a/docs/reference/config.md b/docs/reference/config.md index c33ea206..a120adcb 100644 --- a/docs/reference/config.md +++ b/docs/reference/config.md @@ -242,6 +242,15 @@ git: - option: ci.variable="MAX_RETRIES=10" skipTag: true skipBranch: false + + # A list of case sensitive files that will be committed to the repo when the + # release is created. Typically these files are managed as part of the + # release process e.g. generated by `hooks`. This allows additional + # information to be included in a release such as a + # Software Bill Of Materials (SBOM) that is created by an external tool. + includeArtifacts: + - file.txt + - path/to/file.txt ``` ## gitea diff --git a/docs/static/schema.json b/docs/static/schema.json index 3ec8cbf6..5b8b2682 100644 --- a/docs/static/schema.json +++ b/docs/static/schema.json @@ -217,6 +217,14 @@ }, "type": "array", "minItems": 1 + }, + "includeArtifacts": { + "items": { + "$comment": "https://upliftci.dev/reference/config#git", + "description": "Defines a list of files that uplift will ignore when checking the status of the current repository. If a change is detected that is not defined in this list, uplift will assume its default behaviour and fail due to the repository being in a dirty state" + }, + "type": "array", + "minItems": 1 } }, "type": "object", diff --git a/internal/config/uplift.go b/internal/config/uplift.go index 6761d3e3..c6ab3e54 100644 --- a/internal/config/uplift.go +++ b/internal/config/uplift.go @@ -91,9 +91,10 @@ type Changelog struct { // Git defines configuration for how uplift interacts with git type Git struct { - IgnoreDetached bool `yaml:"ignoreDetached"` - IgnoreShallow bool `yaml:"ignoreShallow"` - PushOptions []GitPushOption `yaml:"pushOptions" validate:"dive"` + IgnoreDetached bool `yaml:"ignoreDetached"` + IgnoreShallow bool `yaml:"ignoreShallow"` + PushOptions []GitPushOption `yaml:"pushOptions" validate:"dive"` + IncludeArtifacts []string `yaml:"includeArtifacts" validate:"omitempty,dive,min=1,file"` } // GitPushOption provides a way of supplying additional options to diff --git a/internal/context/context.go b/internal/context/context.go index bb48cae2..0a7add2d 100644 --- a/internal/context/context.go +++ b/internal/context/context.go @@ -38,6 +38,7 @@ type Context struct { CommitDetails git.CommitDetails Config config.Uplift CurrentVersion semver.Version + IncludeArtifacts []string DryRun bool Debug bool FetchTags bool @@ -107,5 +108,15 @@ func New(cfg config.Uplift, out io.Writer) *Context { SCM: SCM{ Provider: Unrecognised, }, + IncludeArtifacts: IncludeArtifacts(cfg), } } + +// For nil safe object getting +func IncludeArtifacts(c config.Uplift) []string { + if c.Git == nil { + return nil + } + + return c.Git.IncludeArtifacts +} diff --git a/internal/task/gitcheck/check.go b/internal/task/gitcheck/check.go index 2a1e44f7..f42eb911 100644 --- a/internal/task/gitcheck/check.go +++ b/internal/task/gitcheck/check.go @@ -23,8 +23,6 @@ SOFTWARE. package gitcheck import ( - "strings" - "github.com/apex/log" "github.com/gembaadvantage/uplift/internal/context" ) @@ -57,13 +55,16 @@ func (t Task) Run(ctx *context.Context) error { } if len(status) > 0 { - // Convert status into string friendly output - out := make([]string, 0, len(status)) - for _, s := range status { - out = append(out, s.String()) + + if len(ctx.IncludeArtifacts) == 0 { + return ErrDirty{status} } - return ErrDirty{status: strings.Join(out, "\n")} + for _, sts := range status { + if !stringInSlice(sts.Path, ctx.IncludeArtifacts) { + return ErrDirty{status} + } + } } log.Debug("checking for detached head") @@ -86,3 +87,12 @@ func (t Task) Run(ctx *context.Context) error { return nil } + +func stringInSlice(stringToFind string, listOfStrings []string) bool { + for _, value := range listOfStrings { + if value == stringToFind { + return true + } + } + return false +} diff --git a/internal/task/gitcheck/check_test.go b/internal/task/gitcheck/check_test.go index 8d6bb096..1c82fa07 100644 --- a/internal/task/gitcheck/check_test.go +++ b/internal/task/gitcheck/check_test.go @@ -124,9 +124,18 @@ func TestRun_Dirty(t *testing.T) { err := Task{}.Run(&context.Context{}) assert.EqualError(t, err, `uplift cannot reliably run if the repository is in a dirty state. Changes detected: -?? testing.go +[?? testing.go] Please check and resolve the status of these files before retrying. For further details visit: https://upliftci.dev/faq/gitdirty `) } + +func TestRun_IncludeArtifactsWithConfiguredFiles(t *testing.T) { + gittest.InitRepository(t, gittest.WithFiles("testing.go")) + + err := Task{}.Run(&context.Context{ + IncludeArtifacts: []string{"testing.go"}, + }) + assert.NoError(t, err) +} diff --git a/internal/task/gitcheck/errors.go b/internal/task/gitcheck/errors.go index 0b942189..b6471883 100644 --- a/internal/task/gitcheck/errors.go +++ b/internal/task/gitcheck/errors.go @@ -25,11 +25,13 @@ package gitcheck import ( "errors" "fmt" + + git "github.com/purpleclay/gitz" ) // ErrDirty is raised when a git repository has un-committed and/or un-staged changes type ErrDirty struct { - status string + status []git.FileStatus } // Error returns a formatted message of the current error