зеркало из https://github.com/mislav/hub.git
Merge branch 'autoupdate'
This commit is contained in:
Коммит
b573a8f320
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
Загрузка…
Ссылка в новой задаче