зеркало из https://github.com/mislav/hub.git
Merge branch 'autoupdate'
This commit is contained in:
Коммит
b573a8f320
|
@ -6,7 +6,6 @@
|
||||||
"ResourcesExclude": "*.go",
|
"ResourcesExclude": "*.go",
|
||||||
"MainDirsExclude": "Godeps",
|
"MainDirsExclude": "Godeps",
|
||||||
"PackageVersion": "0.26.0",
|
"PackageVersion": "0.26.0",
|
||||||
"PrereleaseInfo": "snapshot",
|
|
||||||
"Verbosity": "v",
|
"Verbosity": "v",
|
||||||
"TaskSettings": {
|
"TaskSettings": {
|
||||||
"downloads-page": {
|
"downloads-page": {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/jingweno/gh/cmd"
|
"github.com/jingweno/gh/cmd"
|
||||||
"github.com/jingweno/gh/git"
|
"github.com/jingweno/gh/git"
|
||||||
|
"github.com/jingweno/gh/utils"
|
||||||
"github.com/kballard/go-shellquote"
|
"github.com/kballard/go-shellquote"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
@ -44,6 +45,10 @@ func (r *Runner) Execute() ExecError {
|
||||||
return newExecError(nil)
|
return newExecError(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updater := NewUpdater()
|
||||||
|
err := updater.PromptForUpdate()
|
||||||
|
utils.Check(err)
|
||||||
|
|
||||||
expandAlias(args)
|
expandAlias(args)
|
||||||
slurpGlobalFlags(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)
|
return newExecError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,8 @@
|
||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
|
||||||
"fmt"
|
|
||||||
updater "github.com/inconshreveable/go-update"
|
|
||||||
"github.com/jingweno/gh/github"
|
|
||||||
"github.com/jingweno/gh/utils"
|
"github.com/jingweno/gh/utils"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdUpdate = &Command{
|
var cmdUpdate = &Command{
|
||||||
|
@ -27,128 +17,8 @@ Examples:
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(cmd *Command, args *Args) {
|
func update(cmd *Command, args *Args) {
|
||||||
err := doUpdate()
|
updater := NewUpdater()
|
||||||
|
err := updater.Update()
|
||||||
utils.Check(err)
|
utils.Check(err)
|
||||||
os.Exit(0)
|
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"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
const Version = "0.26.0"
|
const Version = "0.27.0"
|
||||||
|
|
||||||
var cmdVersion = &Command{
|
var cmdVersion = &Command{
|
||||||
Run: runVersion,
|
Run: runVersion,
|
||||||
|
|
Загрузка…
Ссылка в новой задаче