diff --git a/apply/apply.go b/apply/apply.go index 26c92c4..8b60600 100644 --- a/apply/apply.go +++ b/apply/apply.go @@ -20,6 +20,8 @@ type applierCommon interface { MavenScmBranch(moduleBranch string) MavenDependencyVersionSet(module project.Module, groupId string, artifactId string, newVersion string) + + ChangelogRelease(path string, revision string) error } type Applier interface { diff --git a/apply/execute.go b/apply/execute.go index 66a82cc..789dde4 100644 --- a/apply/execute.go +++ b/apply/execute.go @@ -2,6 +2,7 @@ package apply import ( "fmt" + "github.com/Graylog2/graylog-project-cli/changelog" "github.com/Graylog2/graylog-project-cli/git" "github.com/Graylog2/graylog-project-cli/logger" "github.com/Graylog2/graylog-project-cli/pom" @@ -29,6 +30,9 @@ func NewExecuteApplier(profiles []string) Applier { return applier } +// Check that executeApplier implements the Applier interface +var _ Applier = (*executeApplier)(nil) + // An apply.Applier implementation that actually executes the commands. type executeApplier struct { CommonMaven @@ -125,3 +129,7 @@ func (execute executeApplier) NpmVersionCommit(module project.Module, version st git.Git("commit", "-m", commitMsg, "package.json") }) } + +func (execute executeApplier) ChangelogRelease(path string, revision string) error { + return changelog.ReleaseInPath(path, revision, changelog.SemverVersionPattern) +} diff --git a/apply/noop.go b/apply/noop.go index 70876bd..db80e26 100644 --- a/apply/noop.go +++ b/apply/noop.go @@ -13,6 +13,9 @@ func NewNoopApplier(profiles []string) Applier { return applier } +// Check that noopApplier implements the Applier interface +var _ Applier = (*noopApplier)(nil) + // A no-op implementation of the apply.Applier interface which just prints the commands. type noopApplier struct { CommonMaven @@ -37,3 +40,8 @@ func (noop noopApplier) NpmVersionSet(module project.Module, newVersion string) func (noop noopApplier) NpmVersionCommit(module project.Module, newVersion string) { fmt.Println("commit web module version: " + newVersion) } + +func (noop noopApplier) ChangelogRelease(path string, revision string) error { + fmt.Println("rotating changelog: ", path, revision) + return nil +} diff --git a/changelog/config.go b/changelog/config.go index e60176b..41b16fd 100644 --- a/changelog/config.go +++ b/changelog/config.go @@ -1,5 +1,9 @@ package changelog +import "regexp" + +var SemverVersionPattern = regexp.MustCompile("^\\d+\\.\\d+\\.\\d+$") + type Config struct { RenderFormat string RenderGitHubLinks bool diff --git a/changelog/release.go b/changelog/release.go index 4aea0ff..abefbc8 100644 --- a/changelog/release.go +++ b/changelog/release.go @@ -3,45 +3,63 @@ package changelog import ( "fmt" "github.com/Graylog2/graylog-project-cli/git" + "github.com/Graylog2/graylog-project-cli/logger" p "github.com/Graylog2/graylog-project-cli/project" "github.com/Graylog2/graylog-project-cli/utils" + "os" "path/filepath" + "regexp" ) -func Release(project p.Project) error { +const gitkeepContent = "# Keep the directory in Git" + +func Release(project p.Project, versionPattern *regexp.Regexp) error { return p.ForEachSelectedModuleE(project, func(module p.Module) error { - err := utils.InDirectoryE(module.Path, func() error { - unreleasedChangelogPath := filepath.Join("changelog", "unreleased") - versionChangelogPath := filepath.Join("changelog", module.Revision) + err := ReleaseInPath(module.Path, module.Revision, versionPattern) + if err != nil { + return fmt.Errorf("couldn't release changelog in path %s: %w", module.Path, err) + } + return nil + }) +} - if !utils.FileExists(unreleasedChangelogPath) { - // Nothing to do - return nil - } +func ReleaseInPath(path string, version string, versionPattern *regexp.Regexp) error { + if !versionPattern.MatchString(version) { + return fmt.Errorf("invalid release version: %s (pattern: %s)", version, SemverVersionPattern) + } - fmt.Printf("%s --> %s\n", unreleasedChangelogPath, versionChangelogPath) + return utils.InDirectoryE(path, func() error { + unreleasedChangelogPath := filepath.Join("changelog", "unreleased") + versionChangelogPath := filepath.Join("changelog", version) - if module.ApplyExecute { - out, err := git.GitE("mv", "-v", unreleasedChangelogPath, versionChangelogPath) - fmt.Println(out) - if err != nil { - return err - } - } else { - // Use Git's "-n" parameter to simulate the rename operation - out, err := git.GitE("mv", "-v", "-n", unreleasedChangelogPath, versionChangelogPath) - fmt.Println(out) - if err != nil { - return err - } - } + if !utils.FileExists(unreleasedChangelogPath) { + return fmt.Errorf("couldn't find unreleased changelog path: %s", filepath.Join(path, unreleasedChangelogPath)) + } - return nil - }) + out, err := git.GitE("mv", "-v", unreleasedChangelogPath, versionChangelogPath) + logger.Debug(out) if err != nil { return err } + if err := os.MkdirAll(unreleasedChangelogPath, 0755); err != nil { + return fmt.Errorf("couldn't create new unreleased changelog path: %w", err) + } + + gitKeepPath := filepath.Join(unreleasedChangelogPath, ".gitkeep") + if err := os.WriteFile(gitKeepPath, []byte(gitkeepContent), 0644); err != nil { + return fmt.Errorf("couldn't create new .gitkeep file: %w", err) + } + + if _, err := git.GitE("add", unreleasedChangelogPath); err != nil { + return fmt.Errorf("couldn't add unreleased changelog path to Git: %w", err) + } + + commitMsg := fmt.Sprintf("Release changelog for version %s", version) + if _, err := git.GitE("commit", "-m", commitMsg); err != nil { + return fmt.Errorf("couldn't commit changelog release: %w", err) + } + return nil }) } diff --git a/cmd/apply_manifest.go b/cmd/apply_manifest.go index 8cb5cce..6acab60 100644 --- a/cmd/apply_manifest.go +++ b/cmd/apply_manifest.go @@ -153,6 +153,15 @@ func applyManifestCommand(cmd *cobra.Command, args []string) { applier.MavenRun("clean", "package") } + msg("Rotate changelogs for release") + apply.ForEachModule(proj, false, func(module project.Module) { + applyManifestInDirectory(module.Path, func() { + if err := applier.ChangelogRelease(module.Path, module.Revision); err != nil { + logger.Fatal("ERROR: %s", err) + } + }) + }) + // Committing new version in web modules // Run this before the maven scm checkin is pushing to GitHub msg("Committing new version in web modules") diff --git a/cmd/changelog.go b/cmd/changelog.go index 8b0084f..9ba0774 100644 --- a/cmd/changelog.go +++ b/cmd/changelog.go @@ -81,6 +81,7 @@ var changelogRenderFormat string var changelogDisableGitHubLinks bool var changelogReleaseDate string var changelogReleaseVersion string +var changelogReleaseVersionPattern string var changelogProduct string var changelogEntryEdit bool var changelogEntryMinimalTemplate bool @@ -97,11 +98,14 @@ func init() { changelogRenderCmd.Flags().BoolVarP(&changelogDisableGitHubLinks, "no-links", "N", false, "Do not render issue or pull-request links for entries.") changelogRenderCmd.Flags().StringVarP(&changelogReleaseDate, "date", "d", time.Now().Format("2006-01-02"), "The release date.") changelogRenderCmd.Flags().StringVarP(&changelogReleaseVersion, "version", "V", "0.0.0", "The release version.") + changelogRenderCmd.Flags().StringVarP(&changelogReleaseVersionPattern, "version-pattern", "P", changelog.SemverVersionPattern.String(), "version number pattern") changelogRenderCmd.Flags().StringVarP(&changelogProduct, "product", "p", "Graylog", "The product name. (e.g., \"Graylog\", \"Graylog Enterprise\")") changelogNewCmd.Flags().BoolVarP(&changelogEntryEdit, "edit", "e", false, "Start $EDITOR after creating new entry") changelogNewCmd.Flags().BoolVarP(&changelogEntryMinimalTemplate, "minimal-template", "m", false, "Use a minimal entry template") changelogNewCmd.Flags().BoolVarP(&changelogEntryInteractive, "interactive", "i", false, "Fill template values interactively") + + changelogReleaseCmd.Flags().StringVarP(&changelogReleaseVersionPattern, "version-pattern", "P", changelog.SemverVersionPattern.String(), "version number pattern") } func changelogRenderCommand(cmd *cobra.Command, args []string) { @@ -132,17 +136,26 @@ func changelogRenderCommand(cmd *cobra.Command, args []string) { return path }) + versionPattern, err := regexp.Compile(changelogReleaseVersionPattern) + if err != nil { + logger.Fatal("Invalid version pattern: %s", changelogReleaseVersionPattern) + } + // By convention, we use the version in the first snippet path if it's a valid one and no version flag is given. releaseVersion := changelogReleaseVersion if releaseVersion == "0.0.0" { versionPath := filepath.Base(snippetsPaths[0]) - if regexp.MustCompile("^\\d+\\.\\d+\\.\\d+$").MatchString(versionPath) { + if versionPattern.MatchString(versionPath) { releaseVersion = versionPath } else { logger.Fatal("Missing --version flag and snippets directory doesn't contain a valid version") } } + if !versionPattern.MatchString(releaseVersion) { + logger.Fatal("Invalid version: %s", releaseVersion) + } + config := changelog.Config{ RenderFormat: changelogRenderFormat, RenderGitHubLinks: !changelogDisableGitHubLinks, @@ -162,9 +175,13 @@ func changelogReleaseCommand(cmd *cobra.Command, args []string) { manifestFiles := manifest.ReadState().Files() project := p.New(config, manifestFiles) - if err := changelog.Release(project); err != nil { - logger.Error(err.Error()) - os.Exit(1) + versionPattern, err := regexp.Compile(changelogReleaseVersionPattern) + if err != nil { + logger.Fatal("Invalid version pattern: %s", changelogReleaseVersionPattern) + } + + if err := changelog.Release(project, versionPattern); err != nil { + logger.Fatal(err.Error()) } }