summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2018-08-19 23:28:29 +1000
committerJesse Duffield <jessedduffield@gmail.com>2018-08-20 19:52:20 +1000
commitd938a437a2a98370cc215da88b0195bf1d499dc6 (patch)
tree99e559188e2c2f6d322a028e8de0a2fb95e22892
parent1e440019101bcd26ab425700274103ab5a1c1593 (diff)
WIP auto updates
-rw-r--r--pkg/app/app.go8
-rw-r--r--pkg/gui/gui.go21
-rw-r--r--pkg/updates/updates.go186
3 files changed, 205 insertions, 10 deletions
diff --git a/pkg/app/app.go b/pkg/app/app.go
index aaa925e53..e1a26e13d 100644
--- a/pkg/app/app.go
+++ b/pkg/app/app.go
@@ -10,6 +10,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui"
"github.com/jesseduffield/lazygit/pkg/i18n"
+ "github.com/jesseduffield/lazygit/pkg/updates"
)
// App struct
@@ -22,6 +23,7 @@ type App struct {
GitCommand *commands.GitCommand
Gui *gui.Gui
Tr *i18n.Localizer
+ Updater *updates.Updater // may only need this on the Gui
}
func newLogger(config config.AppConfigurer) *logrus.Logger {
@@ -60,7 +62,11 @@ func NewApp(config config.AppConfigurer) (*App, error) {
if err != nil {
return app, err
}
- app.Gui, err = gui.NewGui(app.Log, app.GitCommand, app.OSCommand, app.Tr, config)
+ app.Updater, err = updates.NewUpdater(app.Log, config, app.OSCommand)
+ if err != nil {
+ return app, err
+ }
+ app.Gui, err = gui.NewGui(app.Log, app.GitCommand, app.OSCommand, app.Tr, config, app.Updater)
if err != nil {
return app, err
}
diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go
index 1e7b6156b..d1b41e4d0 100644
--- a/pkg/gui/gui.go
+++ b/pkg/gui/gui.go
@@ -21,6 +21,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/i18n"
+ "github.com/jesseduffield/lazygit/pkg/updates"
)
// OverlappingEdges determines if panel edges overlap
@@ -64,6 +65,7 @@ type Gui struct {
Config config.AppConfigurer
Tr *i18n.Localizer
Errors SentinelErrors
+ Updater *updates.Updater
}
type guiState struct {
@@ -81,7 +83,7 @@ type guiState struct {
}
// NewGui builds a new gui handler
-func NewGui(log *logrus.Logger, gitCommand *commands.GitCommand, oSCommand *commands.OSCommand, tr *i18n.Localizer, config config.AppConfigurer) (*Gui, error) {
+func NewGui(log *logrus.Logger, gitCommand *commands.GitCommand, oSCommand *commands.OSCommand, tr *i18n.Localizer, config config.AppConfigurer, updater *updates.Updater) (*Gui, error) {
initialState := guiState{
Files: make([]commands.File, 0),
PreviousView: "files",
@@ -101,6 +103,7 @@ func NewGui(log *logrus.Logger, gitCommand *commands.GitCommand, oSCommand *comm
State: initialState,
Config: config,
Tr: tr,
+ Updater: updater,
}
gui.GenerateSentinelErrors()
@@ -261,6 +264,19 @@ func (gui *Gui) layout(g *gocui.Gui) error {
return err
}
+ newVersion, err := gui.Updater.CheckForNewUpdate()
+ if err != nil {
+ return err
+ }
+ gui.Updater.NewVersion = "v0.1.75"
+ newVersion = "v0.1.75"
+ if newVersion != "" {
+ if err := gui.Updater.Update(); err != nil {
+ panic(err)
+ return err
+ }
+ }
+
// these are only called once
gui.handleFileSelect(g, filesView)
gui.refreshFiles(g)
@@ -318,6 +334,9 @@ func (gui *Gui) Run() error {
}
defer g.Close()
+ // TODO: do this more elegantly
+ gui.Updater.CheckForNewUpdate()
+
gui.g = g // TODO: always use gui.g rather than passing g around everywhere
if err := gui.SetColorScheme(); err != nil {
diff --git a/pkg/updates/updates.go b/pkg/updates/updates.go
index 77d491119..3599808fb 100644
--- a/pkg/updates/updates.go
+++ b/pkg/updates/updates.go
@@ -1,26 +1,196 @@
package updates
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "os"
+ "path/filepath"
+ "runtime"
+
+ "github.com/kardianos/osext"
+
+ "github.com/Sirupsen/logrus"
+ getter "github.com/hashicorp/go-getter"
+ "github.com/jesseduffield/lazygit/pkg/commands"
+ "github.com/jesseduffield/lazygit/pkg/config"
+)
+
// Update checks for updates and does updates
-type Update struct {
+type Updater struct {
LastChecked string
+ Log *logrus.Logger
+ Config config.AppConfigurer
+ NewVersion string
+ OSCommand *commands.OSCommand
}
// Updater implements the check and update methods
-type Updater interface {
- Check()
+type Updaterer interface {
+ CheckForNewUpdate()
Update()
}
+var (
+ projectUrl = "https://github.com/jesseduffield/lazygit"
+)
+
// NewUpdater creates a new updater
-func NewUpdater() *Update {
+func NewUpdater(log *logrus.Logger, config config.AppConfigurer, osCommand *commands.OSCommand) (*Updater, error) {
- update := &Update{
+ updater := &Updater{
LastChecked: "today",
+ Log: log,
+ Config: config,
+ OSCommand: osCommand,
+ }
+ return updater, nil
+}
+
+func (u *Updater) getLatestVersionNumber() (string, error) {
+ req, err := http.NewRequest("GET", projectUrl+"/releases/latest", nil)
+ if err != nil {
+ return "", err
+ }
+ req.Header.Set("Accept", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ return "", err
+ }
+ defer resp.Body.Close()
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return "", err
+ }
+
+ byt := []byte(body)
+ var dat map[string]interface{}
+ if err := json.Unmarshal(byt, &dat); err != nil {
+ return "", err
}
- return update
+ return dat["tag_name"].(string), nil
}
-// Check checks if there is an available update
-func (u *Update) Check() {
+// CheckForNewUpdate checks if there is an available update
+func (u *Updater) CheckForNewUpdate() (string, error) {
+ u.Log.Info("Checking for an updated version")
+ if u.Config.GetVersion() == "unversioned" {
+ u.Log.Info("Current version is not built from an official release so we won't check for an update")
+ return "", nil
+ }
+ newVersion, err := u.getLatestVersionNumber()
+ if err != nil {
+ return "", err
+ }
+ u.NewVersion = newVersion
+ u.Log.Info("Current version is " + u.Config.GetVersion())
+ u.Log.Info("New version is " + newVersion)
+ if newVersion == u.Config.GetVersion() {
+ return "", nil
+ }
+ // TODO: verify here that there is a binary available for this OS/arch
+ return newVersion, nil
+}
+
+func (u *Updater) mappedOs(os string) string {
+ osMap := map[string]string{
+ "darwin": "Darwin",
+ "linux": "Linux",
+ "windows": "Windows",
+ }
+ result, found := osMap[os]
+ if found {
+ return result
+ }
+ return os
+}
+
+func (u *Updater) mappedArch(arch string) string {
+ archMap := map[string]string{
+ "386": "32-bit",
+ "amd64": "x86_64",
+ }
+ result, found := archMap[arch]
+ if found {
+ return result
+ }
+ return arch
+}
+
+// example: https://github.com/jesseduffield/lazygit/releases/download/v0.1.73/lazygit_0.1.73_Darwin_x86_64.tar.gz
+func (u *Updater) getBinaryUrl() (string, error) {
+ if u.NewVersion == "" {
+ return "", errors.New("Must run CheckForUpdate() before running getBinaryUrl() to get the new version number")
+ }
+ extension := "tar.gz"
+ if runtime.GOOS == "windows" {
+ extension = "zip"
+ }
+ url := fmt.Sprintf(
+ "%s/releases/download/%s/lazygit_%s_%s_%s.%s",
+ projectUrl,
+ u.NewVersion,
+ u.NewVersion[1:],
+ u.mappedOs(runtime.GOOS),
+ u.mappedArch(runtime.GOARCH),
+ extension,
+ )
+ u.Log.Info("url for latest release is " + url)
+ return url, nil
+}
+
+func (u *Updater) Update() error {
+ rawUrl, err := u.getBinaryUrl()
+ if err != nil {
+ return err
+ }
+ return u.downloadAndInstall(rawUrl)
+}
+
+func (u *Updater) downloadAndInstall(rawUrl string) error {
+ url, err := url.Parse(rawUrl)
+ if err != nil {
+ panic(err)
+ }
+
+ g := new(getter.HttpGetter)
+ tempDir, err := ioutil.TempDir("", "lazygit")
+ if err != nil {
+ panic(err)
+ }
+ defer os.RemoveAll(tempDir)
+
+ // Get it!
+ if err := g.Get(tempDir, url); err != nil {
+ panic(err)
+ }
+
+ extension := ""
+ if runtime.GOOS == "windows" {
+ extension = ".exe"
+ }
+
+ // Verify the main file exists
+ tempPath := filepath.Join(tempDir, "lazygit"+extension)
+ if _, err := os.Stat(tempPath); err != nil {
+ panic(err)
+ }
+
+ // get the path of the current binary
+ execPath, err := osext.Executable()
+ if err != nil {
+ panic(err)
+ }
+
+ // swap out the old binary for the new one
+ err = os.Rename(tempPath, execPath+"2")
+ if err != nil {
+ panic(err)
+ }
+ return nil
}