diff --git a/build.go b/build.go index 7fe899f..e6602d8 100644 --- a/build.go +++ b/build.go @@ -8,6 +8,7 @@ import ( func buildCommand() *cobra.Command { var imageFlag string + var strict bool cmd := &cobra.Command{ Use: "build ", @@ -26,9 +27,9 @@ func buildCommand() *cobra.Command { svcPath := cfg.ServicePath(svc) - version, err := runOutput(svcPath, "git", "describe", "--tags", "--always", "--dirty") + version, err := serviceVersion(svcPath, strict) if err != nil { - return fmt.Errorf("git describe in %s: %w", svcPath, err) + return err } fmt.Printf("Version: %s\n", version) @@ -69,5 +70,6 @@ func buildCommand() *cobra.Command { } cmd.Flags().StringVar(&imageFlag, "image", "", "build only this image") + cmd.Flags().BoolVar(&strict, "strict", false, "require clean git tag (no dirty tree, no commit offset)") return cmd } diff --git a/push.go b/push.go index 611d0cb..dd1b262 100644 --- a/push.go +++ b/push.go @@ -8,6 +8,7 @@ import ( func pushCommand() *cobra.Command { var imageFlag string + var strict bool cmd := &cobra.Command{ Use: "push ", @@ -26,9 +27,9 @@ func pushCommand() *cobra.Command { svcPath := cfg.ServicePath(svc) - version, err := runOutput(svcPath, "git", "describe", "--tags", "--always", "--dirty") + version, err := serviceVersion(svcPath, strict) if err != nil { - return fmt.Errorf("git describe in %s: %w", svcPath, err) + return err } fmt.Printf("Version: %s\n", version) @@ -63,5 +64,6 @@ func pushCommand() *cobra.Command { } cmd.Flags().StringVar(&imageFlag, "image", "", "push only this image") + cmd.Flags().BoolVar(&strict, "strict", false, "require clean git tag (no dirty tree, no commit offset)") return cmd } diff --git a/version.go b/version.go new file mode 100644 index 0000000..121da9c --- /dev/null +++ b/version.go @@ -0,0 +1,37 @@ +package main + +import ( + "fmt" + "strings" +) + +// serviceVersion runs git describe in the service directory and returns +// the version string. If strict is true, it rejects versions that are +// dirty or not exactly on a tag. +func serviceVersion(svcPath string, strict bool) (string, error) { + version, err := runOutput(svcPath, "git", "describe", "--tags", "--always", "--dirty") + if err != nil { + return "", fmt.Errorf("git describe in %s: %w", svcPath, err) + } + + if strict { + if strings.HasSuffix(version, "-dirty") { + return "", fmt.Errorf("--strict: working tree is dirty (%s); commit or stash changes before building", version) + } + // git describe produces "v1.0.0-3-gabcdef" when HEAD is 3 commits past a tag. + // A clean tag has no hyphen after the version (except pre-release like v1.0.0-rc1, + // which we allow). Detect the "-N-gHASH" suffix. + if parts := strings.Split(version, "-"); len(parts) >= 3 { + // Check if second-to-last part is a number (commit count). + secondToLast := parts[len(parts)-2] + if len(secondToLast) > 0 && secondToLast[0] >= '0' && secondToLast[0] <= '9' { + last := parts[len(parts)-1] + if strings.HasPrefix(last, "g") { + return "", fmt.Errorf("--strict: HEAD is not on a tag (%s); create a tag before building", version) + } + } + } + } + + return version, nil +}