This commit is contained in:
Jingwen Owen Ou 2013-12-19 15:04:38 -08:00
Родитель 9bf17e1fd6 6faecd1887
Коммит b573a8f320
5 изменённых файлов: 245 добавлений и 135 удалений

Просмотреть файл

@ -6,7 +6,6 @@
"ResourcesExclude": "*.go",
"MainDirsExclude": "Godeps",
"PackageVersion": "0.26.0",
"PrereleaseInfo": "snapshot",
"Verbosity": "v",
"TaskSettings": {
"downloads-page": {

Просмотреть файл

@ -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"
@ -44,6 +45,10 @@ func (r *Runner) Execute() ExecError {
return newExecError(nil)
}
updater := NewUpdater()
err := updater.PromptForUpdate()
utils.Check(err)
expandAlias(args)
slurpGlobalFlags(args)
@ -78,7 +83,7 @@ func (r *Runner) Execute() ExecError {
}
}
err := git.Spawn(args.Command, args.Params...)
err = git.Spawn(args.Command, args.Params...)
return newExecError(err)
}

Просмотреть файл

@ -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
}

236
commands/updater.go Normal file
Просмотреть файл

@ -0,0 +1,236 @@
package commands
import (
"archive/zip"
"fmt"
goupdate "github.com/inconshreveable/go-update"
"github.com/jingweno/gh/github"
"github.com/jingweno/go-octokit/octokit"
"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 {
version := os.Getenv("GH_VERSION")
if version == "" {
version = Version
}
return &Updater{Host: github.GitHubHost, CurrentVersion: version}
}
type Updater struct {
Host string
CurrentVersion string
}
func (updater *Updater) timeToUpdate() bool {
if updater.CurrentVersion == "dev" || readTime(updateTimestampPath).After(time.Now()) {
return false
}
// the next update is in about 14 days
wait := 13*24*time.Hour + randDuration(24*time.Hour)
return writeTime(updateTimestampPath, time.Now().Add(wait))
}
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 {
return
}
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 == "" {
fmt.Println("There is no newer version of gh available.")
return
}
if version == updater.CurrentVersion {
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, releaseName, 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, strings.TrimSpace(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
}

Просмотреть файл

@ -7,7 +7,7 @@ import (
"os"
)
const Version = "0.26.0"
const Version = "0.27.0"
var cmdVersion = &Command{
Run: runVersion,