42
42
// "v2.3.4") or "none". If the version is "none", gorelease will not compare the
43
43
// current version against any previous version; it will only validate the
44
44
// current version. This is useful for checking the first release of a new major
45
- // version.
45
+ // version. If -base is not specified, gorelease will attempt to infer a base
46
+ // version from the -version flag and available released versions.
46
47
//
47
48
// -version=version: The proposed version to be released. If specified,
48
49
// gorelease will confirm whether this version is consistent with changes made
@@ -66,6 +67,7 @@ import (
66
67
"os/exec"
67
68
"path"
68
69
"path/filepath"
70
+ "sort"
69
71
"strings"
70
72
71
73
"golang.org/x/mod/modfile"
@@ -102,12 +104,6 @@ import (
102
104
// the APIs are still compatible, just with a different module split).
103
105
104
106
// TODO(jayconrod):
105
- // * Automatically detect base version if unspecified.
106
- // If -version is vX.Y.(Z+1), use vX.Y.Z (with a message if it doesn't exist)
107
- // If -version is vX.(Y+1).0, use vX.Y.0
108
- // If -version is vX.0.0, use none
109
- // If -version is a prerelease, use same base as if it were a release.
110
- // If -version is not set, use latest release version or none.
111
107
// * Allow -base to be an arbitrary revision name that resolves to a version
112
108
// or pseudo-version.
113
109
// * Don't accept -version that increments minor or patch version by more than 1
@@ -176,10 +172,7 @@ func runRelease(w io.Writer, dir string, args []string) (success bool, err error
176
172
if len (fs .Args ()) > 0 {
177
173
return false , usageErrorf ("no arguments allowed" )
178
174
}
179
- if baseVersion == "" {
180
- return false , usageErrorf ("-base flag must be specified.\n Use -base=none if there is no previous version." )
181
- }
182
- if baseVersion != "none" {
175
+ if baseVersion != "" && baseVersion != "none" {
183
176
if c := semver .Canonical (baseVersion ); c != baseVersion {
184
177
return false , usageErrorf ("base version %q is not a canonical semantic version" , baseVersion )
185
178
}
@@ -189,7 +182,7 @@ func runRelease(w io.Writer, dir string, args []string) (success bool, err error
189
182
return false , usageErrorf ("release version %q is not a canonical semantic version" , releaseVersion )
190
183
}
191
184
}
192
- if baseVersion != "none" && releaseVersion != "" {
185
+ if baseVersion != "" && baseVersion != " none" && releaseVersion != "" {
193
186
if cmp := semver .Compare (baseVersion , releaseVersion ); cmp == 0 {
194
187
return false , usageErrorf ("-base and -version must be different" )
195
188
} else if cmp > 0 {
@@ -232,6 +225,8 @@ func runRelease(w io.Writer, dir string, args []string) (success bool, err error
232
225
// should be set to modRoot.
233
226
//
234
227
// baseVersion is a previously released version of the module to compare.
228
+ // If baseVersion is "", a base version will be detected automatically, based
229
+ // on releaseVersion or the latest available version of the module.
235
230
// If baseVersion is "none", no comparison will be performed, and
236
231
// the returned report will only describe problems with the release version.
237
232
//
@@ -267,6 +262,12 @@ func makeReleaseReport(modRoot, repoRoot, baseVersion, releaseVersion string) (r
267
262
panic (fmt .Sprintf ("could not find version suffix in module path %q" , modPath ))
268
263
}
269
264
265
+ baseVersionInferred := baseVersion == ""
266
+ if baseVersionInferred {
267
+ if baseVersion , err = inferBaseVersion (modPath , releaseVersion ); err != nil {
268
+ return report {}, err
269
+ }
270
+ }
270
271
if baseVersion != "none" {
271
272
if err := module .Check (modPath , baseVersion ); err != nil {
272
273
return report {}, fmt .Errorf ("can't compare major versions: base version %s does not belong to module %s" , baseVersion , modPath )
@@ -388,11 +389,12 @@ func makeReleaseReport(modRoot, repoRoot, baseVersion, releaseVersion string) (r
388
389
return false
389
390
}
390
391
r := report {
391
- modulePath : modPath ,
392
- baseVersion : baseVersion ,
393
- releaseVersion : releaseVersion ,
394
- tagPrefix : tagPrefix ,
395
- diagnostics : diagnostics ,
392
+ modulePath : modPath ,
393
+ baseVersion : baseVersion ,
394
+ baseVersionInferred : baseVersionInferred ,
395
+ releaseVersion : releaseVersion ,
396
+ tagPrefix : tagPrefix ,
397
+ diagnostics : diagnostics ,
396
398
}
397
399
for _ , pair := range zipPackages (basePkgs , releasePkgs ) {
398
400
basePkg , releasePkg := pair .base , pair .release
@@ -512,6 +514,85 @@ func checkModPath(modPath string) error {
512
514
return module .CheckPath (modPath )
513
515
}
514
516
517
+ // inferBaseVersion returns an appropriate base version if one was not
518
+ // specified explicitly.
519
+ //
520
+ // If releaseVersion is not "", inferBaseVersion returns the highest available
521
+ // release version of the module lower than releaseVersion.
522
+ // Otherwise, inferBaseVersion returns the highest available release version.
523
+ // Pre-release versions are not considered. If there is no available version,
524
+ // and releaseVersion appears to be the first release version (for example,
525
+ // "v0.1.0", "v2.0.0"), "none" is returned.
526
+ func inferBaseVersion (modPath , releaseVersion string ) (baseVersion string , err error ) {
527
+ defer func () {
528
+ if err != nil {
529
+ err = & baseVersionError {err : err }
530
+ }
531
+ }()
532
+
533
+ versions , err := loadVersions (modPath )
534
+ if err != nil {
535
+ return "" , err
536
+ }
537
+
538
+ for i := len (versions ) - 1 ; i >= 0 ; i -- {
539
+ v := versions [i ]
540
+ if semver .Prerelease (v ) == "" &&
541
+ (releaseVersion == "" || semver .Compare (v , releaseVersion ) < 0 ) {
542
+ return v , nil
543
+ }
544
+ }
545
+
546
+ if releaseVersion == "" || maybeFirstVersion (releaseVersion ) {
547
+ return "none" , nil
548
+ }
549
+ return "" , fmt .Errorf ("no versions found lower than %s" , releaseVersion )
550
+ }
551
+
552
+ // loadVersions loads the list of versions for the given module using
553
+ // 'go list -m -versions'. The returned versions are sorted in ascending
554
+ // semver order.
555
+ func loadVersions (modPath string ) ([]string , error ) {
556
+ tmpDir , err := ioutil .TempDir ("" , "" )
557
+ if err != nil {
558
+ return nil , err
559
+ }
560
+ defer os .Remove (tmpDir )
561
+ cmd := exec .Command ("go" , "list" , "-m" , "-versions" , "--" , modPath )
562
+ cmd .Dir = tmpDir
563
+ cmd .Env = append (os .Environ (), "GO111MODULE=on" )
564
+ out , err := cmd .Output ()
565
+ if err != nil {
566
+ return nil , cleanCmdError (err )
567
+ }
568
+ versions := strings .Fields (string (out ))
569
+ if len (versions ) > 0 {
570
+ versions = versions [1 :] // skip module path
571
+ }
572
+
573
+ // Sort versions defensively. 'go list -m -versions' should always returns
574
+ // a sorted list of versions, but it's fast and easy to sort them here, too.
575
+ sort .Slice (versions , func (i , j int ) bool {
576
+ return semver .Compare (versions [i ], versions [j ]) < 0
577
+ })
578
+ return versions , nil
579
+ }
580
+
581
+ // maybeFirstVersion returns whether v appears to be the first version
582
+ // of a module.
583
+ func maybeFirstVersion (v string ) bool {
584
+ major , minor , patch , _ , _ , err := parseVersion (v )
585
+ if err != nil {
586
+ return false
587
+ }
588
+ if major == "0" {
589
+ return minor == "0" && patch == "0" ||
590
+ minor == "0" && patch == "1" ||
591
+ minor == "1" && patch == "0"
592
+ }
593
+ return minor == "0" && patch == "0"
594
+ }
595
+
515
596
// dirMajorSuffix returns a major version suffix for a slash-separated path.
516
597
// For example, for the path "foo/bar/v2", dirMajorSuffix would return "v2".
517
598
// If no major version suffix is found, "" is returned.
@@ -625,7 +706,7 @@ func copyModuleToTempDir(modPath, modRoot string) (dir string, err error) {
625
706
func downloadModule (m module.Version ) (modRoot string , err error ) {
626
707
defer func () {
627
708
if err != nil {
628
- err = & downloadError {m : m , err : err }
709
+ err = & downloadError {m : m , err : cleanCmdError ( err ) }
629
710
}
630
711
}()
631
712
0 commit comments