6464// "latest", or "none". If the version is "none", gorelease will not compare the
6565// current version against any previous version; it will only validate the
6666// current version. This is useful for checking the first release of a new major
67- // version. If -base is not specified, gorelease will attempt to infer a base
68- // version from the -version flag and available released versions.
67+ // version. The version may be preceded by a different module path and an '@',
68+ // like -base=example.com/mod/v2@v2.5.2. This is useful to compare against
69+ // an earlier major version or a fork. If -base is not specified, gorelease will
70+ // attempt to infer a base version from the -version flag and available released
71+ // versions.
6972//
7073// -version=version: The proposed version to be released. If specified,
7174// gorelease will confirm whether this version is consistent with changes made
@@ -160,8 +163,8 @@ func runRelease(w io.Writer, dir string, args []string) (success bool, err error
160163 fs := flag .NewFlagSet ("gorelease" , flag .ContinueOnError )
161164 fs .Usage = func () {}
162165 fs .SetOutput (ioutil .Discard )
163- var baseVersion , releaseVersion string
164- fs .StringVar (& baseVersion , "base" , "" , "previous version to compare against" )
166+ var baseOpt , releaseVersion string
167+ fs .StringVar (& baseOpt , "base" , "" , "previous version to compare against" )
165168 fs .StringVar (& releaseVersion , "version" , "" , "proposed version to be released" )
166169 if err := fs .Parse (args ); err != nil {
167170 return false , & usageError {err : err }
@@ -170,6 +173,7 @@ func runRelease(w io.Writer, dir string, args []string) (success bool, err error
170173 if len (fs .Args ()) > 0 {
171174 return false , usageErrorf ("no arguments allowed" )
172175 }
176+
173177 if releaseVersion != "" {
174178 if semver .Build (releaseVersion ) != "" {
175179 return false , usageErrorf ("release version %q is not a canonical semantic version: build metadata is not supported" , releaseVersion )
@@ -178,12 +182,26 @@ func runRelease(w io.Writer, dir string, args []string) (success bool, err error
178182 return false , usageErrorf ("release version %q is not a canonical semantic version" , releaseVersion )
179183 }
180184 }
181- if baseVersion != "" && semver .Canonical (baseVersion ) == baseVersion && releaseVersion != "" {
182- if cmp := semver .Compare (baseVersion , releaseVersion ); cmp == 0 {
183- return false , usageErrorf ("-base and -version must be different" )
184- } else if cmp > 0 {
185- return false , usageErrorf ("base version (%q) must be lower than release version (%q)" , baseVersion , releaseVersion )
185+
186+ var baseModPath , baseVersion string
187+ if at := strings .Index (baseOpt , "@" ); at >= 0 {
188+ baseModPath = baseOpt [:at ]
189+ baseVersion = baseOpt [at + 1 :]
190+ } else if dot , slash := strings .Index (baseOpt , "." ), strings .Index (baseOpt , "/" ); dot >= 0 && slash >= 0 && dot < slash {
191+ baseModPath = baseOpt
192+ } else {
193+ baseVersion = baseOpt
194+ }
195+ if baseModPath == "" {
196+ if baseVersion != "" && semver .Canonical (baseVersion ) == baseVersion && releaseVersion != "" {
197+ if cmp := semver .Compare (baseOpt , releaseVersion ); cmp == 0 {
198+ return false , usageErrorf ("-base and -version must be different" )
199+ } else if cmp > 0 {
200+ return false , usageErrorf ("base version (%q) must be lower than release version (%q)" , baseVersion , releaseVersion )
201+ }
186202 }
203+ } else if baseModPath != "" && baseVersion == "none" {
204+ return false , usageErrorf (`base version (%q) cannot have version "none" with explicit module path` , baseOpt )
187205 }
188206
189207 // Find the local module and repository root directories.
@@ -201,8 +219,12 @@ func runRelease(w io.Writer, dir string, args []string) (success bool, err error
201219
202220 // Find the base version if there is one, download it, and load packages from
203221 // the module cache.
204- baseModPath := release .modPath // TODO(golang.org/issue/39666): allow different module path
205- base , err := loadDownloadedModule (baseModPath , baseVersion , releaseVersion )
222+ var max string
223+ if baseModPath == "" {
224+ baseModPath = release .modPath
225+ max = releaseVersion
226+ }
227+ base , err := loadDownloadedModule (baseModPath , baseVersion , max )
206228 if err != nil {
207229 return false , err
208230 }
@@ -381,6 +403,9 @@ func loadLocalModule(modRoot, repoRoot, version string) (m moduleInfo, err error
381403// to max will not be considered. Typically, loadDownloadedModule is used to
382404// load the base version, and max is the release version.
383405func loadDownloadedModule (modPath , version , max string ) (m moduleInfo , err error ) {
406+ // TODO(#39666): support downloaded modules that are "soft forks", where the
407+ // module path in go.mod is different from modPath.
408+
384409 // Check the module path and version.
385410 // If the version is a query, resolve it to a canonical version.
386411 m = moduleInfo {modPath : modPath }
@@ -474,18 +499,12 @@ func loadDownloadedModule(modPath, version, max string) (m moduleInfo, err error
474499// version control tag to use (with an appropriate prefix, for modules not
475500// in the repository root directory).
476501func makeReleaseReport (base , release moduleInfo ) (report , error ) {
477- if base .modPath != release .modPath {
478- // TODO(golang.org/issue/39666): allow base and release path to be different.
479- panic (fmt .Sprintf ("base module path %q is different than release module path %q" , base .modPath , release .modPath ))
480- }
481- modPath := release .modPath
482-
483502 // Compare each pair of packages.
484503 // Ignore internal packages.
485504 // If we don't have a base version to compare against,
486505 // just check the new packages for errors.
487506 shouldCompare := base .version != "none"
488- isInternal := func (pkgPath string ) bool {
507+ isInternal := func (modPath , pkgPath string ) bool {
489508 if ! hasPathPrefix (pkgPath , modPath ) {
490509 panic (fmt .Sprintf ("package %s not in module %s" , pkgPath , modPath ))
491510 }
@@ -501,17 +520,17 @@ func makeReleaseReport(base, release moduleInfo) (report, error) {
501520 base : base ,
502521 release : release ,
503522 }
504- for _ , pair := range zipPackages (base .pkgs , release .pkgs ) {
523+ for _ , pair := range zipPackages (base .modPath , base . pkgs , release . modPath , release .pkgs ) {
505524 basePkg , releasePkg := pair .base , pair .release
506525 switch {
507526 case releasePkg == nil :
508527 // Package removed
509- if ! isInternal (basePkg .PkgPath ) || len (basePkg .Errors ) > 0 {
528+ if internal := isInternal (base . modPath , basePkg .PkgPath ); ! internal || len (basePkg .Errors ) > 0 {
510529 pr := packageReport {
511530 path : basePkg .PkgPath ,
512531 baseErrors : basePkg .Errors ,
513532 }
514- if ! isInternal ( basePkg . PkgPath ) {
533+ if ! internal {
515534 pr .Report = apidiff.Report {
516535 Changes : []apidiff.Change {{
517536 Message : "package removed" ,
@@ -524,12 +543,12 @@ func makeReleaseReport(base, release moduleInfo) (report, error) {
524543
525544 case basePkg == nil :
526545 // Package added
527- if ! isInternal (releasePkg .PkgPath ) && shouldCompare || len (releasePkg .Errors ) > 0 {
546+ if internal := isInternal (release . modPath , releasePkg .PkgPath ); ! internal && shouldCompare || len (releasePkg .Errors ) > 0 {
528547 pr := packageReport {
529548 path : releasePkg .PkgPath ,
530549 releaseErrors : releasePkg .Errors ,
531550 }
532- if ! isInternal ( releasePkg . PkgPath ) && shouldCompare {
551+ if ! internal && shouldCompare {
533552 // If we aren't comparing against a base version, don't say
534553 // "package added". Only report packages with errors.
535554 pr .Report = apidiff.Report {
@@ -544,7 +563,10 @@ func makeReleaseReport(base, release moduleInfo) (report, error) {
544563
545564 default :
546565 // Matched packages
547- if ! isInternal (basePkg .PkgPath ) && basePkg .Name != "main" && releasePkg .Name != "main" {
566+ // Both packages are internal or neither; we only consider path components
567+ // after the module path.
568+ internal := isInternal (release .modPath , releasePkg .PkgPath )
569+ if ! internal && basePkg .Name != "main" && releasePkg .Name != "main" {
548570 pr := packageReport {
549571 path : basePkg .PkgPath ,
550572 baseErrors : basePkg .Errors ,
@@ -558,7 +580,7 @@ func makeReleaseReport(base, release moduleInfo) (report, error) {
558580
559581 if release .version != "" {
560582 r .validateVersion ()
561- } else {
583+ } else if r . similarModPaths () {
562584 r .suggestVersion ()
563585 }
564586
@@ -1056,24 +1078,27 @@ type packagePair struct {
10561078// If a package is in one list but not the other (because it was added or
10571079// removed between releases), a pair will be returned with a nil
10581080// base or release field.
1059- func zipPackages (basePkgs , releasePkgs []* packages.Package ) []packagePair {
1081+ func zipPackages (baseModPath string , basePkgs [] * packages. Package , releaseModPath string , releasePkgs []* packages.Package ) []packagePair {
10601082 baseIndex , releaseIndex := 0 , 0
10611083 var pairs []packagePair
10621084 for baseIndex < len (basePkgs ) || releaseIndex < len (releasePkgs ) {
10631085 var basePkg , releasePkg * packages.Package
1086+ var baseSuffix , releaseSuffix string
10641087 if baseIndex < len (basePkgs ) {
10651088 basePkg = basePkgs [baseIndex ]
1089+ baseSuffix = trimPathPrefix (basePkg .PkgPath , baseModPath )
10661090 }
10671091 if releaseIndex < len (releasePkgs ) {
10681092 releasePkg = releasePkgs [releaseIndex ]
1093+ releaseSuffix = trimPathPrefix (releasePkg .PkgPath , releaseModPath )
10691094 }
10701095
10711096 var pair packagePair
1072- if basePkg != nil && (releasePkg == nil || basePkg . PkgPath < releasePkg . PkgPath ) {
1097+ if basePkg != nil && (releasePkg == nil || baseSuffix < releaseSuffix ) {
10731098 // Package removed
10741099 pair = packagePair {basePkg , nil }
10751100 baseIndex ++
1076- } else if releasePkg != nil && (basePkg == nil || releasePkg . PkgPath < basePkg . PkgPath ) {
1101+ } else if releasePkg != nil && (basePkg == nil || releaseSuffix < baseSuffix ) {
10771102 // Package added
10781103 pair = packagePair {nil , releasePkg }
10791104 releaseIndex ++
0 commit comments