1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2024-12-12 11:15:00 +02:00
lazygit/pkg/updates/updates.go

338 lines
8.2 KiB
Go
Raw Normal View History

2018-08-18 11:54:44 +02:00
package updates
2018-08-19 15:28:29 +02:00
import (
"encoding/json"
"fmt"
2019-11-12 13:40:57 +02:00
"io"
2018-08-19 15:28:29 +02:00
"net/http"
"os"
"path/filepath"
"runtime"
2018-08-25 07:55:49 +02:00
"strings"
2018-08-23 10:43:16 +02:00
"time"
2018-08-19 15:28:29 +02:00
"github.com/go-errors/errors"
2018-08-19 15:28:29 +02:00
"github.com/kardianos/osext"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
2021-12-29 03:03:35 +02:00
"github.com/jesseduffield/lazygit/pkg/common"
2018-08-19 15:28:29 +02:00
"github.com/jesseduffield/lazygit/pkg/config"
2021-04-11 15:32:20 +02:00
"github.com/jesseduffield/lazygit/pkg/constants"
2020-10-04 02:00:48 +02:00
"github.com/jesseduffield/lazygit/pkg/utils"
2018-08-19 15:28:29 +02:00
)
2018-08-29 13:34:56 +02:00
// Updater checks for updates and does updates
2018-08-19 15:28:29 +02:00
type Updater struct {
2021-12-29 03:03:35 +02:00
*common.Common
2018-08-25 07:55:49 +02:00
Config config.AppConfigurer
OSCommand *oscommands.OSCommand
2018-08-18 11:54:44 +02:00
}
2018-08-29 13:34:56 +02:00
// Updaterer implements the check and update methods
2018-08-19 15:28:29 +02:00
type Updaterer interface {
CheckForNewUpdate()
2018-08-18 11:54:44 +02:00
Update()
}
// NewUpdater creates a new updater
2021-12-29 03:03:35 +02:00
func NewUpdater(cmn *common.Common, config config.AppConfigurer, osCommand *oscommands.OSCommand) (*Updater, error) {
2019-01-10 00:42:05 +02:00
return &Updater{
2021-12-29 03:03:35 +02:00
Common: cmn,
2018-08-25 07:55:49 +02:00
Config: config,
OSCommand: osCommand,
2019-01-10 00:42:05 +02:00
}, nil
2018-08-19 15:28:29 +02:00
}
func (u *Updater) getLatestVersionNumber() (string, error) {
2021-04-11 15:32:20 +02:00
req, err := http.NewRequest("GET", constants.Links.RepoUrl+"/releases/latest", nil)
2018-08-19 15:28:29 +02:00
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()
2019-01-10 00:42:05 +02:00
dec := json.NewDecoder(resp.Body)
data := struct {
TagName string `json:"tag_name"`
}{}
if err := dec.Decode(&data); err != nil {
2018-08-19 15:28:29 +02:00
return "", err
2018-08-18 11:54:44 +02:00
}
2019-01-10 00:42:05 +02:00
return data.TagName, nil
2018-08-18 11:54:44 +02:00
}
2018-08-29 13:34:56 +02:00
// RecordLastUpdateCheck records last time an update check was performed
2018-08-25 07:55:49 +02:00
func (u *Updater) RecordLastUpdateCheck() error {
u.Config.GetAppState().LastUpdateCheck = time.Now().Unix()
2021-10-16 03:07:24 +02:00
return u.Config.SaveAppState()
2018-08-25 07:55:49 +02:00
}
// expecting version to be of the form `v12.34.56`
func (u *Updater) majorVersionDiffers(oldVersion, newVersion string) bool {
2018-08-27 12:08:10 +02:00
if oldVersion == "unversioned" {
return false
}
2018-08-29 01:37:47 +02:00
oldVersion = strings.TrimPrefix(oldVersion, "v")
newVersion = strings.TrimPrefix(newVersion, "v")
2018-08-25 07:55:49 +02:00
return strings.Split(oldVersion, ".")[0] != strings.Split(newVersion, ".")[0]
}
2020-11-21 09:58:55 +02:00
func (u *Updater) currentVersion() string {
versionNumber := u.Config.GetVersion()
if versionNumber == "unversioned" {
return versionNumber
}
return fmt.Sprintf("v%s", u.Config.GetVersion())
}
2018-08-23 10:43:16 +02:00
func (u *Updater) checkForNewUpdate() (string, error) {
2018-08-19 15:28:29 +02:00
u.Log.Info("Checking for an updated version")
2020-11-21 09:58:55 +02:00
currentVersion := u.currentVersion()
if err := u.RecordLastUpdateCheck(); err != nil {
return "", err
}
2018-08-19 15:28:29 +02:00
newVersion, err := u.getLatestVersionNumber()
if err != nil {
return "", err
}
u.Log.Info("Current version is " + currentVersion)
2018-08-19 15:28:29 +02:00
u.Log.Info("New version is " + newVersion)
2018-08-25 07:55:49 +02:00
if newVersion == currentVersion {
2020-10-04 02:00:48 +02:00
return "", errors.New(u.Tr.OnLatestVersionErr)
2018-08-25 07:55:49 +02:00
}
if u.majorVersionDiffers(currentVersion, newVersion) {
2020-10-04 02:00:48 +02:00
errMessage := utils.ResolvePlaceholderString(
u.Tr.MajorVersionErr, map[string]string{
"newVersion": newVersion,
"currentVersion": currentVersion,
},
)
return "", errors.New(errMessage)
2018-08-25 07:55:49 +02:00
}
2018-08-23 10:43:16 +02:00
2022-01-08 06:46:35 +02:00
rawUrl := u.getBinaryUrl(newVersion)
2018-08-23 10:43:16 +02:00
u.Log.Info("Checking for resource at url " + rawUrl)
if !u.verifyResourceFound(rawUrl) {
2020-10-04 02:00:48 +02:00
errMessage := utils.ResolvePlaceholderString(
u.Tr.CouldNotFindBinaryErr, map[string]string{
2018-08-27 12:23:47 +02:00
"url": rawUrl,
},
)
2020-10-04 02:00:48 +02:00
2018-08-27 12:23:47 +02:00
return "", errors.New(errMessage)
2018-08-19 15:28:29 +02:00
}
2018-08-23 10:43:16 +02:00
u.Log.Info("Verified resource is available, ready to update")
2018-08-25 07:55:49 +02:00
2018-08-19 15:28:29 +02:00
return newVersion, nil
}
2018-08-23 10:43:16 +02:00
// CheckForNewUpdate checks if there is an available update
2018-08-25 07:55:49 +02:00
func (u *Updater) CheckForNewUpdate(onFinish func(string, error) error, userRequested bool) {
if !userRequested && u.skipUpdateCheck() {
return
}
2020-10-07 12:19:38 +02:00
go utils.Safe(func() {
2018-08-23 10:43:16 +02:00
newVersion, err := u.checkForNewUpdate()
if err = onFinish(newVersion, err); err != nil {
u.Log.Error(err)
}
2020-10-07 12:19:38 +02:00
})
2018-08-23 10:43:16 +02:00
}
2018-08-25 07:55:49 +02:00
func (u *Updater) skipUpdateCheck() bool {
2018-08-27 11:37:01 +02:00
// will remove the check for windows after adding a manifest file asking for
// the required permissions
if runtime.GOOS == "windows" {
2018-08-27 12:08:10 +02:00
u.Log.Info("Updating is currently not supported for windows until we can fix permission issues")
return true
}
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")
2018-08-27 11:37:01 +02:00
return true
}
2018-08-25 07:55:49 +02:00
if u.Config.GetBuildSource() != "buildBinary" {
2018-08-27 12:08:10 +02:00
u.Log.Info("Binary is not built with the buildBinary flag so we won't check for an update")
2018-08-25 07:55:49 +02:00
return true
}
2021-12-29 03:03:35 +02:00
userConfig := u.UserConfig
2020-10-03 06:54:55 +02:00
if userConfig.Update.Method == "never" {
2018-08-27 12:08:10 +02:00
u.Log.Info("Update method is set to never so we won't check for an update")
2018-08-25 07:55:49 +02:00
return true
}
currentTimestamp := time.Now().Unix()
lastUpdateCheck := u.Config.GetAppState().LastUpdateCheck
2020-10-03 06:54:55 +02:00
days := userConfig.Update.Days
2018-08-25 07:55:49 +02:00
2018-08-27 12:08:10 +02:00
if (currentTimestamp-lastUpdateCheck)/(60*60*24) < days {
u.Log.Info("Last update was too recent so we won't check for an update")
return true
}
return false
2018-08-25 07:55:49 +02:00
}
2018-08-19 15:28:29 +02:00
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
}
2020-11-21 09:58:55 +02:00
func (u *Updater) zipExtension() string {
2018-08-19 15:28:29 +02:00
if runtime.GOOS == "windows" {
2020-11-21 09:58:55 +02:00
return "zip"
2018-08-19 15:28:29 +02:00
}
2020-11-21 09:58:55 +02:00
return "tar.gz"
}
// example: https://github.com/jesseduffield/lazygit/releases/download/v0.1.73/lazygit_0.1.73_Darwin_x86_64.tar.gz
2022-01-08 06:46:35 +02:00
func (u *Updater) getBinaryUrl(newVersion string) string {
2018-08-19 15:28:29 +02:00
url := fmt.Sprintf(
"%s/releases/download/%s/lazygit_%s_%s_%s.%s",
2021-04-11 15:32:20 +02:00
constants.Links.RepoUrl,
2018-08-23 10:43:16 +02:00
newVersion,
newVersion[1:],
2018-08-19 15:28:29 +02:00
u.mappedOs(runtime.GOOS),
u.mappedArch(runtime.GOARCH),
2020-11-21 09:58:55 +02:00
u.zipExtension(),
2018-08-19 15:28:29 +02:00
)
2019-01-10 00:42:05 +02:00
u.Log.Info("Url for latest release is " + url)
2022-01-08 06:46:35 +02:00
return url
2018-08-19 15:28:29 +02:00
}
2018-08-23 10:43:16 +02:00
// Update downloads the latest binary and replaces the current binary with it
func (u *Updater) Update(newVersion string, onFinish func(error) error) {
2020-10-07 12:19:38 +02:00
go utils.Safe(func() {
2018-08-23 10:43:16 +02:00
err := u.update(newVersion)
if err = onFinish(err); err != nil {
u.Log.Error(err)
}
2020-10-07 12:19:38 +02:00
})
2018-08-23 10:43:16 +02:00
}
func (u *Updater) update(newVersion string) error {
2022-01-08 06:46:35 +02:00
rawUrl := u.getBinaryUrl(newVersion)
2019-01-10 00:42:05 +02:00
u.Log.Info("Updating with url " + rawUrl)
2018-08-19 15:28:29 +02:00
return u.downloadAndInstall(rawUrl)
}
func (u *Updater) downloadAndInstall(rawUrl string) error {
2019-11-12 13:40:57 +02:00
configDir := u.Config.GetUserConfigDir()
u.Log.Info("Download directory is " + configDir)
2020-11-21 09:58:55 +02:00
zipPath := filepath.Join(configDir, "temp_lazygit."+u.zipExtension())
u.Log.Info("Temp path to tarball/zip file is " + zipPath)
2019-11-12 13:40:57 +02:00
2020-11-21 09:58:55 +02:00
// remove existing zip file
if err := os.RemoveAll(zipPath); err != nil && !os.IsNotExist(err) {
return err
}
// Create the zip file
out, err := os.Create(zipPath)
2018-08-19 15:28:29 +02:00
if err != nil {
2018-08-23 10:43:16 +02:00
return err
2018-08-19 15:28:29 +02:00
}
2019-11-12 13:40:57 +02:00
defer out.Close()
2018-08-19 15:28:29 +02:00
2019-11-12 13:40:57 +02:00
// Get the data
resp, err := http.Get(rawUrl)
if err != nil {
return err
}
defer resp.Body.Close()
// Check server response
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("error while trying to download latest lazygit: %s", resp.Status)
}
2018-08-19 15:28:29 +02:00
2019-11-12 13:40:57 +02:00
// Write the body to file
_, err = io.Copy(out, resp.Body)
if err != nil {
2018-08-23 10:43:16 +02:00
return err
2018-08-19 15:28:29 +02:00
}
2020-11-21 09:58:55 +02:00
u.Log.Info("untarring tarball/unzipping zip file")
2021-12-29 05:33:38 +02:00
err = u.OSCommand.Cmd.New(fmt.Sprintf("tar -zxf %s %s", u.OSCommand.Quote(zipPath), "lazygit")).Run()
if err != nil {
2020-11-21 09:58:55 +02:00
return err
}
// the `tar` terminal cannot store things in a new location without permission
// so it creates it in the current directory. As such our path is fairly simple.
// You won't see it because it's gitignored.
tempLazygitFilePath := "lazygit"
u.Log.Infof("Path to temp binary is %s", tempLazygitFilePath)
2018-08-23 10:43:16 +02:00
// get the path of the current binary
binaryPath, err := osext.Executable()
if err != nil {
return err
2018-08-19 15:28:29 +02:00
}
2019-01-10 00:42:05 +02:00
u.Log.Info("Binary path is " + binaryPath)
2018-08-23 10:43:16 +02:00
2018-08-19 15:28:29 +02:00
// Verify the main file exists
2020-11-21 09:58:55 +02:00
if _, err := os.Stat(zipPath); err != nil {
2018-08-23 10:43:16 +02:00
return err
2018-08-19 15:28:29 +02:00
}
// swap out the old binary for the new one
2020-11-21 09:58:55 +02:00
err = os.Rename(tempLazygitFilePath, binaryPath)
2018-08-19 15:28:29 +02:00
if err != nil {
2018-08-23 10:43:16 +02:00
return err
2018-08-19 15:28:29 +02:00
}
2019-01-10 00:42:05 +02:00
u.Log.Info("Update complete!")
2018-08-18 11:54:44 +02:00
2018-08-19 15:28:29 +02:00
return nil
2018-08-18 11:54:44 +02:00
}
2018-08-23 10:43:16 +02:00
func (u *Updater) verifyResourceFound(rawUrl string) bool {
resp, err := http.Head(rawUrl)
if err != nil {
return false
}
defer resp.Body.Close()
u.Log.Info("Received status code ", resp.StatusCode)
// 403 means the resource is there (not going to bother adding extra request headers)
// 404 means its not
return resp.StatusCode == 403
}