next-version.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. // Copyright (C) 2025 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. //go:build tools
  7. // +build tools
  8. package main
  9. import (
  10. "flag"
  11. "fmt"
  12. "os"
  13. "os/exec"
  14. "strconv"
  15. "strings"
  16. "github.com/coreos/go-semver/semver"
  17. )
  18. const suffix = "rc"
  19. func main() {
  20. pre := flag.Bool("pre", false, "Create a prerelease")
  21. flag.Parse()
  22. // Get the latest "v1.22.3" or "v1.22.3-rc.1" style tag.
  23. latestTag, err := cmd("git", "describe", "--abbrev=0", "--match", "v[0-9].*")
  24. if err != nil {
  25. fmt.Println(err)
  26. os.Exit(1)
  27. }
  28. latest, err := semver.NewVersion(latestTag[1:])
  29. if err != nil {
  30. fmt.Println(err)
  31. os.Exit(1)
  32. }
  33. // Get the latest "v1.22.3" style tag, excludeing prereleases.
  34. latestStableTag, err := cmd("git", "describe", "--abbrev=0", "--match", "v[0-9].*", "--exclude", "*-*")
  35. if err != nil {
  36. fmt.Println(err)
  37. os.Exit(1)
  38. }
  39. latestStable, err := semver.NewVersion(latestStableTag[1:])
  40. if err != nil {
  41. fmt.Println(err)
  42. os.Exit(1)
  43. }
  44. // Get the commit logs since the latest stable tag.
  45. logsSinceLatest, err := cmd("git", "log", "--pretty=format:%s", latestStableTag+"..HEAD")
  46. if err != nil {
  47. fmt.Println(err)
  48. os.Exit(1)
  49. }
  50. // Check if the next version should be a feature or a patch release
  51. nextIsFeature := false
  52. for _, line := range strings.Split(logsSinceLatest, "\n") {
  53. if strings.HasPrefix(line, "feat") {
  54. nextIsFeature = true
  55. break
  56. }
  57. }
  58. next := *latestStable
  59. if nextIsFeature {
  60. next.BumpMinor()
  61. } else {
  62. next.BumpPatch()
  63. }
  64. if latest.PreRelease != "" {
  65. if !*pre {
  66. // We want a stable release. Simply remove the prerelease
  67. // suffix.
  68. latest.PreRelease = ""
  69. fmt.Println("v" + latest.String())
  70. return
  71. }
  72. // We want the next prerelease. We are already on a prerelease. If
  73. // it's the correct prerelease compared to the logs we just got, or
  74. // newer, we should just bump the prerelease counter. We compare
  75. // against the latest without the prerelease part, as otherwise it
  76. // would compare less than next if they represent the same version
  77. // -- pre being less than stable.
  78. latestNoPre := *latest
  79. latestNoPre.PreRelease = ""
  80. if !latestNoPre.LessThan(next) {
  81. parts := latest.PreRelease.Slice()
  82. for i, p := range parts {
  83. if v, err := strconv.Atoi(p); err == nil {
  84. parts[i] = strconv.Itoa(v + 1)
  85. latest.PreRelease = semver.PreRelease(strings.Join(parts, "."))
  86. fmt.Println("v" + latest.String())
  87. return
  88. }
  89. }
  90. }
  91. // Otherwise we generate a new rc.1 for the correct next version.
  92. next.PreRelease = suffix + ".1"
  93. fmt.Println("v" + next.String())
  94. return
  95. }
  96. if nextIsFeature {
  97. latest.BumpMinor()
  98. } else {
  99. latest.BumpPatch()
  100. }
  101. if *pre {
  102. latest.PreRelease = suffix + ".1"
  103. }
  104. fmt.Println("v" + latest.String())
  105. }
  106. func cmd(name string, args ...string) (string, error) {
  107. cmd := exec.Command(name, args...)
  108. bs, err := cmd.CombinedOutput()
  109. if err != nil {
  110. return "", err
  111. }
  112. return strings.TrimSpace(string(bs)), nil
  113. }