When gh was built from source, the version number will look something
like this since it's taken from `git describe`:
v1.4.0-34-g{SHA}
When compared as semver against `v1.4.0`, the latter version is falsely
reported as newer. This is because the output of `git describe` wasn't
meant to be interpreted as semver.
The solution is to translate the `git describe` string to faux-semver so
it can be safely compared with the version reported from the server.
Fixes this case:
A new release of gh is available: v1.4.0-41-g2f9e4cb1 → v1.4.0
https://github.com/cli/cli/releases/tag/v1.4.0
101 lines
2.4 KiB
Go
101 lines
2.4 KiB
Go
package update
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/cli/cli/api"
|
|
"github.com/cli/cli/internal/ghinstance"
|
|
"github.com/hashicorp/go-version"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
var gitDescribeSuffixRE = regexp.MustCompile(`\d+-\d+-g[a-f0-9]{8}$`)
|
|
|
|
// ReleaseInfo stores information about a release
|
|
type ReleaseInfo struct {
|
|
Version string `json:"tag_name"`
|
|
URL string `json:"html_url"`
|
|
}
|
|
|
|
type StateEntry struct {
|
|
CheckedForUpdateAt time.Time `yaml:"checked_for_update_at"`
|
|
LatestRelease ReleaseInfo `yaml:"latest_release"`
|
|
}
|
|
|
|
// CheckForUpdate checks whether this software has had a newer release on GitHub
|
|
func CheckForUpdate(client *api.Client, stateFilePath, repo, currentVersion string) (*ReleaseInfo, error) {
|
|
stateEntry, _ := getStateEntry(stateFilePath)
|
|
if stateEntry != nil && time.Since(stateEntry.CheckedForUpdateAt).Hours() < 24 {
|
|
return nil, nil
|
|
}
|
|
|
|
releaseInfo, err := getLatestReleaseInfo(client, repo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = setStateEntry(stateFilePath, time.Now(), *releaseInfo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if versionGreaterThan(releaseInfo.Version, currentVersion) {
|
|
return releaseInfo, nil
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func getLatestReleaseInfo(client *api.Client, repo string) (*ReleaseInfo, error) {
|
|
var latestRelease ReleaseInfo
|
|
err := client.REST(ghinstance.Default(), "GET", fmt.Sprintf("repos/%s/releases/latest", repo), nil, &latestRelease)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &latestRelease, nil
|
|
}
|
|
|
|
func getStateEntry(stateFilePath string) (*StateEntry, error) {
|
|
content, err := ioutil.ReadFile(stateFilePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var stateEntry StateEntry
|
|
err = yaml.Unmarshal(content, &stateEntry)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &stateEntry, nil
|
|
}
|
|
|
|
func setStateEntry(stateFilePath string, t time.Time, r ReleaseInfo) error {
|
|
data := StateEntry{CheckedForUpdateAt: t, LatestRelease: r}
|
|
content, err := yaml.Marshal(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_ = ioutil.WriteFile(stateFilePath, content, 0600)
|
|
|
|
return nil
|
|
}
|
|
|
|
func versionGreaterThan(v, w string) bool {
|
|
w = gitDescribeSuffixRE.ReplaceAllStringFunc(w, func(m string) string {
|
|
idx := strings.IndexRune(m, '-')
|
|
n, _ := strconv.Atoi(m[0:idx])
|
|
return fmt.Sprintf("%d-pre.0", n+1)
|
|
})
|
|
|
|
vv, ve := version.NewVersion(v)
|
|
vw, we := version.NewVersion(w)
|
|
|
|
return ve == nil && we == nil && vv.GreaterThan(vw)
|
|
}
|