diff --git a/cmd/releasego/publish-announcement.go b/cmd/releasego/publish-announcement.go index d9253c66..ffe6544c 100644 --- a/cmd/releasego/publish-announcement.go +++ b/cmd/releasego/publish-announcement.go @@ -12,6 +12,7 @@ import ( "io" "os" "regexp" + "sort" "strings" "text/template" "time" @@ -19,6 +20,7 @@ import ( "github.com/microsoft/go-infra/githubutil" "github.com/microsoft/go-infra/goversion" + "github.com/microsoft/go-infra/internal/infrasort" "github.com/microsoft/go-infra/subcmd" ) @@ -56,6 +58,19 @@ type ReleaseInfo struct { // take all this methods and make it one constructor function for ReleaseInfo func NewReleaseInfo(releaseDate time.Time, versions []string, author string, security bool) *ReleaseInfo { ri := new(ReleaseInfo) + goVersions := make(infrasort.GoVersions, 0) + for _, version := range versions { + goVersions = append(goVersions, goversion.New(version)) + } + + // Sort the versions in descending order + sort.Sort(goVersions) + + // Recreate versions slice with sorted info. + versions = versions[:0] + for _, goVersion := range goVersions { + versions = append(versions, goVersion.Full()) + } // Generate human-readable title and URL-friendly slug from the Go versions. ri.Title = generateBlogPostTitle(versions) @@ -66,13 +81,12 @@ func NewReleaseInfo(releaseDate time.Time, versions []string, author string, sec ri.Tags = []string{"go", "release"} // Process each Go version, extracting release information and generating links. - for _, version := range versions { - goVersion := goversion.New(version).UpstreamFormatGitTag() + for _, goVersion := range goVersions { ri.Versions = append(ri.Versions, GoVersionData{ - MSGoVersion: "v" + version, - MSGoVersionLink: createMSGoReleaseLinkFromVersion(version), - GoVersion: goVersion, - GoVersionLink: createGoReleaseLinkFromVersion(goVersion), + MSGoVersion: "v" + goVersion.Full(), + MSGoVersionLink: createMSGoReleaseLinkFromVersion(goVersion.Full()), + GoVersion: goVersion.UpstreamFormatGitTag(), + GoVersionLink: createGoReleaseLinkFromVersion(goVersion.UpstreamFormatGitTag()), }) } diff --git a/cmd/releasego/publish-announcement_test.go b/cmd/releasego/publish-announcement_test.go index 385b2824..a71bd09b 100644 --- a/cmd/releasego/publish-announcement_test.go +++ b/cmd/releasego/publish-announcement_test.go @@ -26,7 +26,8 @@ func Test_ReleaseInfo_WriteAnnouncement(t *testing.T) { {"2024-06-04-real", NewReleaseInfo(testTime, []string{"1.22.4-1", "1.21.11-1"}, author, true)}, {"2024-06-04-nonsecurity", NewReleaseInfo(testTime, []string{"1.22.4-1", "1.21.11-1"}, author, false)}, {"only-one-branch", NewReleaseInfo(testTime, []string{"1.22.8-3"}, author, true)}, - {"three-branches", NewReleaseInfo(testTime, []string{"1.23.1-1", "1.22.8-1", "1.21.11-16"}, author, true)}, + {"three-branches", NewReleaseInfo(testTime, []string{"1.22.8-1", "1.23.1-1", "1.21.11-16"}, author, true)}, + {"2024-06-04-note", NewReleaseInfo(testTime, []string{"1.22.4-1-fips", "1.22.4-1", "1.21.11-1", "1.21.11-1-fips"}, author, false)}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/cmd/releasego/testdata/publish-announcement/2024-06-04-note.golden.md b/cmd/releasego/testdata/publish-announcement/2024-06-04-note.golden.md new file mode 100644 index 00000000..b7b9f5cb --- /dev/null +++ b/cmd/releasego/testdata/publish-announcement/2024-06-04-note.golden.md @@ -0,0 +1,19 @@ +--- +post_title: Go 1.22.4-1, 1.22.4-1-fips, 1.21.11-1, and 1.21.11-1-fips Microsoft builds now available +author1: Test Author +post_slug: go-1-22-4-1--1-22-4-1-fips-1-21-11-1-and-1-21-11-1-fips-microsoft-builds-now-available +categories: Microsoft for Go Developers +tags: go, release +featured_image: +summary: The Microsoft builds of the Go security patches released today, are now available for download. +--- + +A new set of Microsoft Go builds is now [available for download](https://github.com/microsoft/go#download-and-install). +For more information about this release and the changes included, see the table below: + +| Microsoft Release | Upstream Tag | +|-------------------|--------------| +| [v1.22.4-1](https://github.com/microsoft/go/releases/tag/v1.22.4-1) | go1.22.4 [release notes](https://go.dev/doc/devel/release#go1.22.4) | +| [v1.22.4-1-fips](https://github.com/microsoft/go/releases/tag/v1.22.4-1-fips) | go1.22.4 [release notes](https://go.dev/doc/devel/release#go1.22.4) | +| [v1.21.11-1](https://github.com/microsoft/go/releases/tag/v1.21.11-1) | go1.21.11 [release notes](https://go.dev/doc/devel/release#go1.21.11) | +| [v1.21.11-1-fips](https://github.com/microsoft/go/releases/tag/v1.21.11-1-fips) | go1.21.11 [release notes](https://go.dev/doc/devel/release#go1.21.11) | diff --git a/internal/infrasort/goversions.go b/internal/infrasort/goversions.go new file mode 100644 index 00000000..47c84344 --- /dev/null +++ b/internal/infrasort/goversions.go @@ -0,0 +1,51 @@ +package infrasort + +import ( + "strconv" + + "github.com/microsoft/go-infra/goversion" +) + +// GoVersions implements [sort.Interface] and sorts versions in descending order. +// If Major, Minor, Patch, or Revision of any GoVersion in the slice can't be parsed by +// [strconv.Atoi], the result of using this type is undefined. +type GoVersions []*goversion.GoVersion + +func (versions GoVersions) Len() int { return len(versions) } +func (versions GoVersions) Swap(i, j int) { versions[i], versions[j] = versions[j], versions[i] } +func (versions GoVersions) Less(i, j int) bool { + less := func(a, b string) bool { + intA, err := strconv.Atoi(a) + if err != nil { + return false + } + + intB, err := strconv.Atoi(b) + if err != nil { + return false + } + return intA > intB + } + + current, next := versions[i], versions[j] + + if current.Major != next.Major { + return less(current.Major, next.Major) + } + if current.Minor != next.Minor { + return less(current.Minor, next.Minor) + } + if current.Patch != next.Patch { + return less(current.Patch, next.Patch) + } + if current.Revision != next.Revision { + return less(current.Revision, next.Revision) + } + if current.Prerelease != next.Prerelease { + return current.Prerelease < next.Prerelease + } + if current.Note != next.Note { + return current.Note < next.Note + } + return false +} diff --git a/internal/infrasort/goversions_test.go b/internal/infrasort/goversions_test.go new file mode 100644 index 00000000..396ae2ba --- /dev/null +++ b/internal/infrasort/goversions_test.go @@ -0,0 +1,80 @@ +package infrasort + +import ( + "sort" + "testing" + + "github.com/microsoft/go-infra/goversion" +) + +func TestGoVersions_Sort(t *testing.T) { + tests := []struct { + name string + versions GoVersions + expected GoVersions + }{ + { + name: "basic version sorting", + versions: GoVersions{ + goversion.New("1.2.3"), + goversion.New("1.2.1"), + goversion.New("1.3.0"), + goversion.New("1.2.3-2"), + goversion.New("1.2.3-1"), + }, + expected: GoVersions{ + goversion.New("1.3.0"), + goversion.New("1.2.3-2"), + goversion.New("1.2.3"), + goversion.New("1.2.3-1"), + goversion.New("1.2.1"), + }, + }, + { + name: "version with prerelease and note", + versions: GoVersions{ + goversion.New("1.2.3-beta"), + goversion.New("1.2.3-rc1"), + goversion.New("1.2.3-1-fips"), + goversion.New("1.2.3"), + goversion.New("1.2.3-2"), + }, + expected: GoVersions{ + goversion.New("1.2.3-2"), + goversion.New("1.2.3"), + goversion.New("1.2.3-beta"), + goversion.New("1.2.3-1-fips"), + goversion.New("1.2.3-rc1"), + }, + }, + { + name: "sorting with major and minor versions", + versions: GoVersions{ + goversion.New("2.0.0"), + goversion.New("1.10.0"), + goversion.New("1.2.3"), + goversion.New("1.2.0"), + goversion.New("1.3.0"), + }, + expected: GoVersions{ + goversion.New("2.0.0"), + goversion.New("1.10.0"), + goversion.New("1.3.0"), + goversion.New("1.2.3"), + goversion.New("1.2.0"), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Sort the versions + sort.Sort(tt.versions) + for i, v := range tt.versions { + if *v != *tt.expected[i] { + t.Errorf("expected %v at index %d, got %v", tt.expected[i].Original, i, v.Original) + } + } + }) + } +}