From 06b9a3d385d71363b77a623b1226d6ec3e7c41b5 Mon Sep 17 00:00:00 2001 From: Jingwen Owen Ou Date: Thu, 19 Dec 2013 11:30:17 -0800 Subject: [PATCH 1/6] Remove snapshot from build --- .goxc.json | 1 - 1 file changed, 1 deletion(-) diff --git a/.goxc.json b/.goxc.json index 186d124e..74b476a2 100644 --- a/.goxc.json +++ b/.goxc.json @@ -6,7 +6,6 @@ "ResourcesExclude": "*.go", "MainDirsExclude": "Godeps", "PackageVersion": "0.26.0", - "PrereleaseInfo": "snapshot", "Verbosity": "v", "TaskSettings": { "downloads-page": { From a54faff6691462ac8898ee732153bcd3e53763a0 Mon Sep 17 00:00:00 2001 From: Jingwen Owen Ou Date: Thu, 19 Dec 2013 11:30:29 -0800 Subject: [PATCH 2/6] Extract to Updater --- commands/update.go | 134 +------------------------------ commands/updater.go | 186 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 188 insertions(+), 132 deletions(-) create mode 100644 commands/updater.go diff --git a/commands/update.go b/commands/update.go index e79795dc..c846a9b7 100644 --- a/commands/update.go +++ b/commands/update.go @@ -1,18 +1,8 @@ package commands import ( - "archive/zip" - "fmt" - updater "github.com/inconshreveable/go-update" - "github.com/jingweno/gh/github" "github.com/jingweno/gh/utils" - "io" - "io/ioutil" - "net/http" "os" - "path/filepath" - "runtime" - "strings" ) var cmdUpdate = &Command{ @@ -27,128 +17,8 @@ Examples: } func update(cmd *Command, args *Args) { - err := doUpdate() + updater := NewUpdater() + err := updater.Update() utils.Check(err) os.Exit(0) } - -func doUpdate() (err error) { - client := github.NewClient(github.GitHubHost) - releases, err := client.Releases(github.NewProject("jingweno", "gh", github.GitHubHost)) - if err != nil { - err = fmt.Errorf("Error fetching releases: %s", err) - return - } - - latestRelease := releases[0] - tagName := latestRelease.TagName - version := strings.TrimPrefix(tagName, "v") - if version == Version { - err = fmt.Errorf("You're already on the latest version: %s", Version) - return - } - - fmt.Printf("Updating gh to release %s...\n", version) - downloadURL := fmt.Sprintf("https://github.com/jingweno/gh/releases/download/%s/gh_%s-snapshot_%s_%s.zip", tagName, version, runtime.GOOS, runtime.GOARCH) - path, err := downloadFile(downloadURL) - if err != nil { - err = fmt.Errorf("Can't download update from %s to %s", downloadURL, path) - return - } - - exec, err := unzipExecutable(path) - if err != nil { - err = fmt.Errorf("Can't unzip gh executable: %s", err) - return - } - - err, _ = updater.FromFile(exec) - if err == nil { - fmt.Println("Done!") - } - - return -} - -func unzipExecutable(path string) (exec string, err error) { - rc, err := zip.OpenReader(path) - if err != nil { - err = fmt.Errorf("Can't open zip file %s: %s", path, err) - return - } - defer rc.Close() - - for _, file := range rc.File { - if !strings.HasPrefix(file.Name, "gh") { - continue - } - - dir := filepath.Dir(path) - exec, err = unzipFile(file, dir) - break - } - - if exec == "" && err == nil { - err = fmt.Errorf("No gh executable is found in %s", path) - } - - return -} - -func unzipFile(file *zip.File, to string) (exec string, err error) { - frc, err := file.Open() - if err != nil { - err = fmt.Errorf("Can't open zip entry %s when reading: %s", file.Name, err) - return - } - defer frc.Close() - - dest := filepath.Join(to, filepath.Base(file.Name)) - f, err := os.Create(dest) - if err != nil { - return - } - defer f.Close() - - copied, err := io.Copy(f, frc) - if err != nil { - return - } - - if uint32(copied) != file.UncompressedSize { - err = fmt.Errorf("Zip entry %s is corrupted", file.Name) - return - } - - exec = f.Name() - - return -} - -func downloadFile(url string) (path string, err error) { - dir, err := ioutil.TempDir("", "gh-update") - if err != nil { - return - } - - file, err := os.Create(filepath.Join(dir, filepath.Base(url))) - if err != nil { - return - } - defer file.Close() - - resp, err := http.Get(url) - if err != nil { - return - } - defer resp.Body.Close() - - _, err = io.Copy(file, resp.Body) - if err != nil { - return - } - - path = file.Name() - - return -} diff --git a/commands/updater.go b/commands/updater.go new file mode 100644 index 00000000..c71ca57b --- /dev/null +++ b/commands/updater.go @@ -0,0 +1,186 @@ +package commands + +import ( + "archive/zip" + "fmt" + goupdate "github.com/inconshreveable/go-update" + "github.com/jingweno/gh/github" + "io" + "io/ioutil" + "math/rand" + "net/http" + "os" + "path/filepath" + "runtime" + "strings" + "time" +) + +var ( + updateTimestampPath = filepath.Join(os.Getenv("HOME"), ".config", "gh-update") +) + +func NewUpdater() *Updater { + return &Updater{Host: github.GitHubHost, CurrentVersion: Version} +} + +type Updater struct { + Host string + CurrentVersion string +} + +func (update *Updater) WantUpdate() bool { + if update.CurrentVersion == "dev" || readTime(updateTimestampPath).After(time.Now()) { + return false + } + + wait := 12*time.Hour + randDuration(8*time.Hour) + return writeTime(updateTimestampPath, time.Now().Add(wait)) +} + +func (updater *Updater) Update() (err error) { + client := github.NewClient(updater.Host) + releases, err := client.Releases(github.NewProject("jingweno", "gh", updater.Host)) + if err != nil { + err = fmt.Errorf("Error fetching releases: %s", err) + return + } + + latestRelease := releases[0] + tagName := latestRelease.TagName + version := strings.TrimPrefix(tagName, "v") + if version == updater.CurrentVersion { + fmt.Printf("You're already on the latest version: %s\n", updater.CurrentVersion) + return + } + + fmt.Printf("Updating gh to %s...\n", version) + downloadURL := fmt.Sprintf("https://%s/jingweno/gh/releases/download/%s/gh_%s_%s_%s.zip", updater.Host, tagName, version, runtime.GOOS, runtime.GOARCH) + path, err := downloadFile(downloadURL) + if err != nil { + return + } + + exec, err := unzipExecutable(path) + if err != nil { + return + } + + err, _ = goupdate.FromFile(exec) + if err == nil { + fmt.Println("Done!") + } + + return +} + +func unzipExecutable(path string) (exec string, err error) { + rc, err := zip.OpenReader(path) + if err != nil { + err = fmt.Errorf("Can't open zip file %s: %s", path, err) + return + } + defer rc.Close() + + for _, file := range rc.File { + if !strings.HasPrefix(file.Name, "gh") { + continue + } + + dir := filepath.Dir(path) + exec, err = unzipFile(file, dir) + break + } + + if exec == "" && err == nil { + err = fmt.Errorf("No gh executable is found in %s", path) + } + + return +} + +func unzipFile(file *zip.File, to string) (exec string, err error) { + frc, err := file.Open() + if err != nil { + err = fmt.Errorf("Can't open zip entry %s when reading: %s", file.Name, err) + return + } + defer frc.Close() + + dest := filepath.Join(to, filepath.Base(file.Name)) + f, err := os.Create(dest) + if err != nil { + return + } + defer f.Close() + + copied, err := io.Copy(f, frc) + if err != nil { + return + } + + if uint32(copied) != file.UncompressedSize { + err = fmt.Errorf("Zip entry %s is corrupted", file.Name) + return + } + + exec = f.Name() + + return +} + +func downloadFile(url string) (path string, err error) { + dir, err := ioutil.TempDir("", "gh-update") + if err != nil { + return + } + + resp, err := http.Get(url) + if err != nil { + return + } + defer resp.Body.Close() + + if resp.StatusCode >= 300 || resp.StatusCode < 200 { + err = fmt.Errorf("Can't download %s: %d", url, resp.StatusCode) + return + } + + file, err := os.Create(filepath.Join(dir, filepath.Base(url))) + if err != nil { + return + } + defer file.Close() + + _, err = io.Copy(file, resp.Body) + if err != nil { + return + } + + path = file.Name() + + return +} + +func randDuration(n time.Duration) time.Duration { + return time.Duration(rand.Int63n(int64(n))) +} + +func readTime(path string) time.Time { + p, err := ioutil.ReadFile(path) + if os.IsNotExist(err) { + return time.Time{} + } + if err != nil { + return time.Now().Add(1000 * time.Hour) + } + t, err := time.Parse(time.RFC3339, string(p)) + if err != nil { + return time.Now().Add(1000 * time.Hour) + } + return t +} + +func writeTime(path string, t time.Time) bool { + return ioutil.WriteFile(path, []byte(t.Format(time.RFC3339)), 0644) == nil +} From 4dfb4c58dbe02f581119d1561975e2b97e9690b1 Mon Sep 17 00:00:00 2001 From: Jingwen Owen Ou Date: Thu, 19 Dec 2013 13:40:36 -0800 Subject: [PATCH 3/6] Automatically prompt for update --- commands/runner.go | 8 +++++++- commands/updater.go | 24 ++++++++++++++++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/commands/runner.go b/commands/runner.go index d6e2228b..a3f6b64c 100644 --- a/commands/runner.go +++ b/commands/runner.go @@ -44,6 +44,12 @@ func (r *Runner) Execute() ExecError { return newExecError(nil) } + updater := NewUpdater() + err := updater.PromptForUpdate() + if err != nil { + return newExecError(err) + } + expandAlias(args) slurpGlobalFlags(args) @@ -78,7 +84,7 @@ func (r *Runner) Execute() ExecError { } } - err := git.Spawn(args.Command, args.Params...) + err = git.Spawn(args.Command, args.Params...) return newExecError(err) } diff --git a/commands/updater.go b/commands/updater.go index c71ca57b..60fcf03f 100644 --- a/commands/updater.go +++ b/commands/updater.go @@ -29,12 +29,28 @@ type Updater struct { CurrentVersion string } -func (update *Updater) WantUpdate() bool { - if update.CurrentVersion == "dev" || readTime(updateTimestampPath).After(time.Now()) { +func (updater *Updater) PromptForUpdate() (err error) { + if updater.wantUpdate() { + fmt.Println("Would you like to check for updates?") + fmt.Print("Type Y to update gh: ") + var confirm string + fmt.Scan(&confirm) + + if confirm == "Y" || confirm == "y" { + err = updater.Update() + } + } + + return +} + +func (updater *Updater) wantUpdate() bool { + if updater.CurrentVersion == "dev" || readTime(updateTimestampPath).After(time.Now()) { return false } - wait := 12*time.Hour + randDuration(8*time.Hour) + // the next update is in about 14 days + wait := 13*24*time.Hour + randDuration(24*time.Hour) return writeTime(updateTimestampPath, time.Now().Add(wait)) } @@ -174,7 +190,7 @@ func readTime(path string) time.Time { if err != nil { return time.Now().Add(1000 * time.Hour) } - t, err := time.Parse(time.RFC3339, string(p)) + t, err := time.Parse(time.RFC3339, strings.TrimSpace(string(p))) if err != nil { return time.Now().Add(1000 * time.Hour) } From 0302410abc02f3fc944bcb4b08a13453fcb96b5d Mon Sep 17 00:00:00 2001 From: Jingwen Owen Ou Date: Thu, 19 Dec 2013 13:48:20 -0800 Subject: [PATCH 4/6] Don't prompt for update if it's in dev --- commands/updater.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/commands/updater.go b/commands/updater.go index 60fcf03f..4453db11 100644 --- a/commands/updater.go +++ b/commands/updater.go @@ -21,7 +21,11 @@ var ( ) func NewUpdater() *Updater { - return &Updater{Host: github.GitHubHost, CurrentVersion: Version} + version := os.Getenv("GH_VERSION") + if version == "" { + version = Version + } + return &Updater{Host: github.GitHubHost, CurrentVersion: version} } type Updater struct { From 8f797c11fd692bffca883961cc8d04e91fd7aaa2 Mon Sep 17 00:00:00 2001 From: Jingwen Owen Ou Date: Thu, 19 Dec 2013 14:44:58 -0800 Subject: [PATCH 5/6] Only prompt for update if there's a new release Reference: https://github.com/jingweno/gh/pull/128#issuecomment-30971747 --- commands/runner.go | 5 ++- commands/updater.go | 78 +++++++++++++++++++++++++++++++-------------- commands/version.go | 2 +- 3 files changed, 57 insertions(+), 28 deletions(-) diff --git a/commands/runner.go b/commands/runner.go index a3f6b64c..15e65288 100644 --- a/commands/runner.go +++ b/commands/runner.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/jingweno/gh/cmd" "github.com/jingweno/gh/git" + "github.com/jingweno/gh/utils" "github.com/kballard/go-shellquote" "os/exec" "syscall" @@ -46,9 +47,7 @@ func (r *Runner) Execute() ExecError { updater := NewUpdater() err := updater.PromptForUpdate() - if err != nil { - return newExecError(err) - } + utils.Check(err) expandAlias(args) slurpGlobalFlags(args) diff --git a/commands/updater.go b/commands/updater.go index 4453db11..8cd00d72 100644 --- a/commands/updater.go +++ b/commands/updater.go @@ -5,6 +5,7 @@ import ( "fmt" goupdate "github.com/inconshreveable/go-update" "github.com/jingweno/gh/github" + "github.com/jingweno/go-octokit/octokit" "io" "io/ioutil" "math/rand" @@ -33,22 +34,7 @@ type Updater struct { CurrentVersion string } -func (updater *Updater) PromptForUpdate() (err error) { - if updater.wantUpdate() { - fmt.Println("Would you like to check for updates?") - fmt.Print("Type Y to update gh: ") - var confirm string - fmt.Scan(&confirm) - - if confirm == "Y" || confirm == "y" { - err = updater.Update() - } - } - - return -} - -func (updater *Updater) wantUpdate() bool { +func (updater *Updater) timeToUpdate() bool { if updater.CurrentVersion == "dev" || readTime(updateTimestampPath).After(time.Now()) { return false } @@ -58,24 +44,68 @@ func (updater *Updater) wantUpdate() bool { return writeTime(updateTimestampPath, time.Now().Add(wait)) } -func (updater *Updater) Update() (err error) { +func (updater *Updater) latestRelease() (r *octokit.Release) { client := github.NewClient(updater.Host) releases, err := client.Releases(github.NewProject("jingweno", "gh", updater.Host)) if err != nil { - err = fmt.Errorf("Error fetching releases: %s", err) return } - latestRelease := releases[0] - tagName := latestRelease.TagName - version := strings.TrimPrefix(tagName, "v") + if len(releases) > 0 { + r = &releases[0] + } + + return +} + +func (updater *Updater) latestReleaseNameAndVersion() (name, version string) { + if latestRelease := updater.latestRelease(); latestRelease != nil { + name = latestRelease.TagName + version = strings.TrimPrefix(name, "v") + } + + return +} + +func (updater *Updater) PromptForUpdate() (err error) { + if !updater.timeToUpdate() { + return + } + + releaseName, version := updater.latestReleaseNameAndVersion() + if version != "" && version != updater.CurrentVersion { + fmt.Println("There is a newer version of gh available.") + fmt.Print("Type Y to update: ") + var confirm string + fmt.Scan(&confirm) + + if confirm == "Y" || confirm == "y" { + err = updater.updateTo(releaseName, version) + } + } + + return +} + +func (updater *Updater) Update() (err error) { + releaseName, version := updater.latestReleaseNameAndVersion() + if version == "" { + err = fmt.Errorf("There is no newer version of gh available.") + return + } + if version == updater.CurrentVersion { - fmt.Printf("You're already on the latest version: %s\n", updater.CurrentVersion) - return + fmt.Printf("You're already on the latest version: %s\n", version) + } else { + err = updater.updateTo(releaseName, version) } + return +} + +func (updater *Updater) updateTo(releaseName, version string) (err error) { fmt.Printf("Updating gh to %s...\n", version) - downloadURL := fmt.Sprintf("https://%s/jingweno/gh/releases/download/%s/gh_%s_%s_%s.zip", updater.Host, tagName, version, runtime.GOOS, runtime.GOARCH) + downloadURL := fmt.Sprintf("https://%s/jingweno/gh/releases/download/%s/gh_%s_%s_%s.zip", updater.Host, releaseName, version, runtime.GOOS, runtime.GOARCH) path, err := downloadFile(downloadURL) if err != nil { return diff --git a/commands/version.go b/commands/version.go index bd57509e..a51ab8f7 100644 --- a/commands/version.go +++ b/commands/version.go @@ -7,7 +7,7 @@ import ( "os" ) -const Version = "0.26.0" +const Version = "0.27.0" var cmdVersion = &Command{ Run: runVersion, From 6faecd1887b39e538b2e5b1aa07a96bde5761b00 Mon Sep 17 00:00:00 2001 From: Jingwen Owen Ou Date: Thu, 19 Dec 2013 14:48:33 -0800 Subject: [PATCH 6/6] Don't return an error if there's no newer version --- commands/updater.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands/updater.go b/commands/updater.go index 8cd00d72..254b690a 100644 --- a/commands/updater.go +++ b/commands/updater.go @@ -90,7 +90,7 @@ func (updater *Updater) PromptForUpdate() (err error) { func (updater *Updater) Update() (err error) { releaseName, version := updater.latestReleaseNameAndVersion() if version == "" { - err = fmt.Errorf("There is no newer version of gh available.") + fmt.Println("There is no newer version of gh available.") return }