mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-04-19 12:12:42 +02:00
Merge branch 'master' into feature/rebasing
This commit is contained in:
commit
3d343e9b57
13
Gopkg.lock
generated
13
Gopkg.lock
generated
@ -189,11 +189,19 @@
|
|||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:66bb9b4a5abb704642fccba52a84a7f7feef2d9623f87b700e52a6695044723f"
|
digest = "1:9b266d7748a5d94985fd9e323494f5b8ae1ab3e910418e898dfe7f03339ddbcd"
|
||||||
name = "github.com/jesseduffield/gocui"
|
name = "github.com/jesseduffield/gocui"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
pruneopts = "NUT"
|
pruneopts = "NUT"
|
||||||
revision = "03e26ff3f1de2c1bc2205113c3aba661312eee00"
|
revision = "cfa9e452ba5ebf014041846851152d64a59dce14"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:a46c2f4863e5284ddb255c28750298e04bc8c0fc896bed6056e947673168b7be"
|
||||||
|
name = "github.com/jesseduffield/pty"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = "NUT"
|
||||||
|
revision = "02db52c7e406c7abec44c717a173c7715e4c1b62"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
@ -616,6 +624,7 @@
|
|||||||
"github.com/heroku/rollrus",
|
"github.com/heroku/rollrus",
|
||||||
"github.com/jesseduffield/go-getter",
|
"github.com/jesseduffield/go-getter",
|
||||||
"github.com/jesseduffield/gocui",
|
"github.com/jesseduffield/gocui",
|
||||||
|
"github.com/jesseduffield/pty",
|
||||||
"github.com/kardianos/osext",
|
"github.com/kardianos/osext",
|
||||||
"github.com/mgutz/str",
|
"github.com/mgutz/str",
|
||||||
"github.com/nicksnyder/go-i18n/v2/i18n",
|
"github.com/nicksnyder/go-i18n/v2/i18n",
|
||||||
|
@ -37,6 +37,10 @@
|
|||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/jesseduffield/gocui"
|
name = "github.com/jesseduffield/gocui"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/jesseduffield/pty"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "gopkg.in/src-d/go-git.v4"
|
name = "gopkg.in/src-d/go-git.v4"
|
||||||
revision = "43d17e14b714665ab5bc2ecc220b6740779d733f"
|
revision = "43d17e14b714665ab5bc2ecc220b6740779d733f"
|
||||||
|
3
go.mod
3
go.mod
@ -18,7 +18,8 @@ require (
|
|||||||
github.com/heroku/rollrus v0.0.0-20180515183152-fc0cef2ff331
|
github.com/heroku/rollrus v0.0.0-20180515183152-fc0cef2ff331
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99
|
||||||
github.com/jesseduffield/go-getter v0.0.0-20180822080847-906e15686e63
|
github.com/jesseduffield/go-getter v0.0.0-20180822080847-906e15686e63
|
||||||
github.com/jesseduffield/gocui v0.0.0-20180921065632-03e26ff3f1de
|
github.com/jesseduffield/gocui v0.0.0-20190115084758-cfa9e452ba5e
|
||||||
|
github.com/jesseduffield/pty v0.0.0-20181218102224-02db52c7e406
|
||||||
github.com/jesseduffield/termbox-go v0.0.0-20180919093808-1e272ff78dcb
|
github.com/jesseduffield/termbox-go v0.0.0-20180919093808-1e272ff78dcb
|
||||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8
|
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8
|
||||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1
|
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1
|
||||||
|
10
go.sum
10
go.sum
@ -33,8 +33,10 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl
|
|||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||||
github.com/jesseduffield/go-getter v0.0.0-20180822080847-906e15686e63 h1:Nrr/yUxNjXWYK0B3IqcFlYh1ICnesJDB4ogcfOVc5Ns=
|
github.com/jesseduffield/go-getter v0.0.0-20180822080847-906e15686e63 h1:Nrr/yUxNjXWYK0B3IqcFlYh1ICnesJDB4ogcfOVc5Ns=
|
||||||
github.com/jesseduffield/go-getter v0.0.0-20180822080847-906e15686e63/go.mod h1:fNqjRf+4XnTo2PrGN1JRb79b/BeoHwP4lU00f39SQY0=
|
github.com/jesseduffield/go-getter v0.0.0-20180822080847-906e15686e63/go.mod h1:fNqjRf+4XnTo2PrGN1JRb79b/BeoHwP4lU00f39SQY0=
|
||||||
github.com/jesseduffield/gocui v0.0.0-20180919095827-4fca348422d8 h1:XxX+IqNOFDh1PnU4eZDzUomoKbuKCvwyEm5an/IxLQU=
|
github.com/jesseduffield/gocui v0.0.0-20181209104758-fe55a32c8a4c h1:jEfh/vAtfF3pQ8xFhpYR/0S4iHo11VfaYelJmzZJm94=
|
||||||
github.com/jesseduffield/gocui v0.0.0-20180919095827-4fca348422d8/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
github.com/jesseduffield/gocui v0.0.0-20181209104758-fe55a32c8a4c/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||||
|
github.com/jesseduffield/pty v0.0.0-20181218102224-02db52c7e406 h1:iYMH6h6SuWuBkIzRtymosE8NpSgTK0oRMfyTdVWgxzc=
|
||||||
|
github.com/jesseduffield/pty v0.0.0-20181218102224-02db52c7e406/go.mod h1:7jlS40+UhOqkZJDIG1B/H21xnuET/+fvbbnHCa8wSIo=
|
||||||
github.com/jesseduffield/termbox-go v0.0.0-20180919093808-1e272ff78dcb h1:cFHYEWpQEfzFZVKiKZytCUX4UwQixKSw0kd3WhluPsY=
|
github.com/jesseduffield/termbox-go v0.0.0-20180919093808-1e272ff78dcb h1:cFHYEWpQEfzFZVKiKZytCUX4UwQixKSw0kd3WhluPsY=
|
||||||
github.com/jesseduffield/termbox-go v0.0.0-20180919093808-1e272ff78dcb/go.mod h1:anMibpZtqNxjDbxrcDEAwSdaJ37vyUeM1f/M4uekib4=
|
github.com/jesseduffield/termbox-go v0.0.0-20180919093808-1e272ff78dcb/go.mod h1:anMibpZtqNxjDbxrcDEAwSdaJ37vyUeM1f/M4uekib4=
|
||||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Aowv3l/EQUlocDHW2Cp4G9WJVH7uyH8QFJE=
|
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Aowv3l/EQUlocDHW2Cp4G9WJVH7uyH8QFJE=
|
||||||
@ -61,8 +63,8 @@ github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699 h1:KXZJFdun
|
|||||||
github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/nicksnyder/go-i18n v0.0.0-20180803040939-a16b91a3ba80 h1:7ory6RlsEkeK89iyV7Imz3sVz8YHeSw29w3PehpCWC0=
|
github.com/nicksnyder/go-i18n v0.0.0-20180803040939-a16b91a3ba80 h1:7ory6RlsEkeK89iyV7Imz3sVz8YHeSw29w3PehpCWC0=
|
||||||
github.com/nicksnyder/go-i18n v0.0.0-20180803040939-a16b91a3ba80/go.mod h1:e4Di5xjP9oTVrC6y3C7C0HoSYXjSbhh/dU0eUV32nB4=
|
github.com/nicksnyder/go-i18n v0.0.0-20180803040939-a16b91a3ba80/go.mod h1:e4Di5xjP9oTVrC6y3C7C0HoSYXjSbhh/dU0eUV32nB4=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.5 h1:/TjjTS4kg7vC+05gD0LE4+97f/+PRFICnK/7wJPk7kE=
|
github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.6 h1:SooTCzUOOs899x/M7gmSS+dgL+AUfSWqAcHLN3auCic=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.5/go.mod h1:4Opqa6/HIv0lhG3WRAkqzO0afezkRhxXI0P8EJkqeRU=
|
github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.6/go.mod h1:4Opqa6/HIv0lhG3WRAkqzO0afezkRhxXI0P8EJkqeRU=
|
||||||
github.com/pelletier/go-buffruneio v0.2.0 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA=
|
github.com/pelletier/go-buffruneio v0.2.0 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA=
|
||||||
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
|
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
|
||||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||||
|
100
pkg/commands/exec_live_default.go
Normal file
100
pkg/commands/exec_live_default.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/jesseduffield/pty"
|
||||||
|
"github.com/mgutz/str"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RunCommandWithOutputLiveWrapper runs a command and return every word that gets written in stdout
|
||||||
|
// Output is a function that executes by every word that gets read by bufio
|
||||||
|
// As return of output you need to give a string that will be written to stdin
|
||||||
|
// NOTE: If the return data is empty it won't written anything to stdin
|
||||||
|
func RunCommandWithOutputLiveWrapper(c *OSCommand, command string, output func(string) string) error {
|
||||||
|
splitCmd := str.ToArgv(command)
|
||||||
|
cmd := exec.Command(splitCmd[0], splitCmd[1:]...)
|
||||||
|
|
||||||
|
cmd.Env = os.Environ()
|
||||||
|
cmd.Env = append(cmd.Env, "LANG=en_US.UTF-8", "LC_ALL=en_US.UTF-8")
|
||||||
|
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
|
||||||
|
ptmx, err := pty.Start(cmd)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
scanner := bufio.NewScanner(ptmx)
|
||||||
|
scanner.Split(scanWordsWithNewLines)
|
||||||
|
for scanner.Scan() {
|
||||||
|
toOutput := strings.Trim(scanner.Text(), " ")
|
||||||
|
_, _ = ptmx.WriteString(output(toOutput))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = cmd.Wait()
|
||||||
|
ptmx.Close()
|
||||||
|
if err != nil {
|
||||||
|
return errors.New(stderr.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanWordsWithNewLines is a copy of bufio.ScanWords but this also captures new lines
|
||||||
|
// For specific comments about this function take a look at: bufio.ScanWords
|
||||||
|
func scanWordsWithNewLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||||
|
start := 0
|
||||||
|
for width := 0; start < len(data); start += width {
|
||||||
|
var r rune
|
||||||
|
r, width = utf8.DecodeRune(data[start:])
|
||||||
|
if !isSpace(r) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for width, i := 0, start; i < len(data); i += width {
|
||||||
|
var r rune
|
||||||
|
r, width = utf8.DecodeRune(data[i:])
|
||||||
|
if isSpace(r) {
|
||||||
|
return i + width, data[start:i], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if atEOF && len(data) > start {
|
||||||
|
return len(data), data[start:], nil
|
||||||
|
}
|
||||||
|
return start, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isSpace is also copied from the bufio package and has been modified to also captures new lines
|
||||||
|
// For specific comments about this function take a look at: bufio.isSpace
|
||||||
|
func isSpace(r rune) bool {
|
||||||
|
if r <= '\u00FF' {
|
||||||
|
switch r {
|
||||||
|
case ' ', '\t', '\v', '\f':
|
||||||
|
return true
|
||||||
|
case '\u0085', '\u00A0':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if '\u2000' <= r && r <= '\u200a' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
switch r {
|
||||||
|
case '\u1680', '\u2028', '\u2029', '\u202f', '\u205f', '\u3000':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
9
pkg/commands/exec_live_win.go
Normal file
9
pkg/commands/exec_live_win.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package commands
|
||||||
|
|
||||||
|
// RunCommandWithOutputLiveWrapper runs a command live but because of windows compatibility this command can't be ran there
|
||||||
|
// TODO: Remove this hack and replace it with a proper way to run commands live on windows
|
||||||
|
func RunCommandWithOutputLiveWrapper(c *OSCommand, command string, output func(string) string) error {
|
||||||
|
return c.RunCommand(command)
|
||||||
|
}
|
@ -294,8 +294,13 @@ func (c *GitCommand) AbortMergeBranch() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch fetch git repo
|
// Fetch fetch git repo
|
||||||
func (c *GitCommand) Fetch() error {
|
func (c *GitCommand) Fetch(unamePassQuestion func(string) string, canAskForCredentials bool) error {
|
||||||
return c.OSCommand.RunCommand("git fetch")
|
return c.OSCommand.DetectUnamePass("git fetch", func(question string) string {
|
||||||
|
if canAskForCredentials {
|
||||||
|
return unamePassQuestion(question)
|
||||||
|
}
|
||||||
|
return "\n"
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResetToCommit reset to commit
|
// ResetToCommit reset to commit
|
||||||
@ -373,18 +378,19 @@ func (c *GitCommand) Commit(message string, amend bool) (*exec.Cmd, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pull pulls from repo
|
// Pull pulls from repo
|
||||||
func (c *GitCommand) Pull() error {
|
func (c *GitCommand) Pull(ask func(string) string) error {
|
||||||
return c.OSCommand.RunCommand("git pull --no-edit")
|
return c.OSCommand.DetectUnamePass("git pull --no-edit", ask)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push pushes to a branch
|
// Push pushes to a branch
|
||||||
func (c *GitCommand) Push(branchName string, force bool) error {
|
func (c *GitCommand) Push(branchName string, force bool, ask func(string) string) error {
|
||||||
forceFlag := ""
|
forceFlag := ""
|
||||||
if force {
|
if force {
|
||||||
forceFlag = "--force-with-lease "
|
forceFlag = "--force-with-lease "
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.OSCommand.RunCommand(fmt.Sprintf("git push %s -u origin %s", forceFlag, branchName))
|
cmd := fmt.Sprintf("git push %s -u origin %s", forceFlag, branchName)
|
||||||
|
return c.OSCommand.DetectUnamePass(cmd, ask)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SquashPreviousTwoCommits squashes a commit down to the one below it
|
// SquashPreviousTwoCommits squashes a commit down to the one below it
|
||||||
|
@ -95,7 +95,7 @@ func TestVerifyInGitRepo(t *testing.T) {
|
|||||||
},
|
},
|
||||||
func(err error) {
|
func(err error) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Regexp(t, "fatal: .ot a git repository \\(or any of the parent directories\\): \\.git", err.Error())
|
assert.Regexp(t, `fatal: .ot a git repository \(or any of the parent directories\s?\/?\): \.git`, err.Error())
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -256,7 +256,7 @@ func TestNewGitCommand(t *testing.T) {
|
|||||||
},
|
},
|
||||||
func(gitCmd *GitCommand, err error) {
|
func(gitCmd *GitCommand, err error) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Regexp(t, "fatal: .ot a git repository \\(or any of the parent directories\\): \\.git", err.Error())
|
assert.Regexp(t, `fatal: .ot a git repository ((\(or any of the parent directories\): \.git)|(\(or any parent up to mount point \/\)))`, err.Error())
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1010,7 +1010,7 @@ func TestGitCommandPush(t *testing.T) {
|
|||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
func(err error) {
|
func(err error) {
|
||||||
assert.Nil(t, err)
|
assert.Contains(t, err.Error(), "error: failed to push some refs")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1023,7 +1023,7 @@ func TestGitCommandPush(t *testing.T) {
|
|||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
func(err error) {
|
func(err error) {
|
||||||
assert.Nil(t, err)
|
assert.Contains(t, err.Error(), "error: failed to push some refs")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1031,12 +1031,11 @@ func TestGitCommandPush(t *testing.T) {
|
|||||||
func(cmd string, args ...string) *exec.Cmd {
|
func(cmd string, args ...string) *exec.Cmd {
|
||||||
assert.EqualValues(t, "git", cmd)
|
assert.EqualValues(t, "git", cmd)
|
||||||
assert.EqualValues(t, []string{"push", "-u", "origin", "test"}, args)
|
assert.EqualValues(t, []string{"push", "-u", "origin", "test"}, args)
|
||||||
|
|
||||||
return exec.Command("test")
|
return exec.Command("test")
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
func(err error) {
|
func(err error) {
|
||||||
assert.Error(t, err)
|
assert.Contains(t, err.Error(), "error: failed to push some refs")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -1045,7 +1044,10 @@ func TestGitCommandPush(t *testing.T) {
|
|||||||
t.Run(s.testName, func(t *testing.T) {
|
t.Run(s.testName, func(t *testing.T) {
|
||||||
gitCmd := newDummyGitCommand()
|
gitCmd := newDummyGitCommand()
|
||||||
gitCmd.OSCommand.command = s.command
|
gitCmd.OSCommand.command = s.command
|
||||||
s.test(gitCmd.Push("test", s.forcePush))
|
err := gitCmd.Push("test", s.forcePush, func(passOrUname string) string {
|
||||||
|
return "\n"
|
||||||
|
})
|
||||||
|
s.test(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/config"
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
@ -57,6 +58,36 @@ func (c *OSCommand) RunCommandWithOutput(command string) (string, error) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RunCommandWithOutputLive runs RunCommandWithOutputLiveWrapper
|
||||||
|
func (c *OSCommand) RunCommandWithOutputLive(command string, output func(string) string) error {
|
||||||
|
return RunCommandWithOutputLiveWrapper(c, command, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DetectUnamePass detect a username / password question in a command
|
||||||
|
// ask is a function that gets executen when this function detect you need to fillin a password
|
||||||
|
// The ask argument will be "username" or "password" and expects the user's password or username back
|
||||||
|
func (c *OSCommand) DetectUnamePass(command string, ask func(string) string) error {
|
||||||
|
ttyText := ""
|
||||||
|
errMessage := c.RunCommandWithOutputLive(command, func(word string) string {
|
||||||
|
ttyText = ttyText + " " + word
|
||||||
|
|
||||||
|
prompts := map[string]string{
|
||||||
|
"password": `Password\s*for\s*'.+':`,
|
||||||
|
"username": `Username\s*for\s*'.+':`,
|
||||||
|
}
|
||||||
|
|
||||||
|
for askFor, pattern := range prompts {
|
||||||
|
if match, _ := regexp.MatchString(pattern, ttyText); match {
|
||||||
|
ttyText = ""
|
||||||
|
return ask(askFor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
})
|
||||||
|
return errMessage
|
||||||
|
}
|
||||||
|
|
||||||
// RunCommand runs a command and just returns the error
|
// RunCommand runs a command and just returns the error
|
||||||
func (c *OSCommand) RunCommand(command string) error {
|
func (c *OSCommand) RunCommand(command string) error {
|
||||||
_, err := c.RunCommandWithOutput(command)
|
_, err := c.RunCommandWithOutput(command)
|
||||||
@ -186,7 +217,7 @@ func (c *OSCommand) CreateTempFile(filename, content string) (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := tmpfile.Write([]byte(content)); err != nil {
|
if _, err := tmpfile.WriteString(content); err != nil {
|
||||||
c.Log.Error(err)
|
c.Log.Error(err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ type AppConfig struct {
|
|||||||
BuildSource string `long:"build-source" env:"BUILD_SOURCE" default:""`
|
BuildSource string `long:"build-source" env:"BUILD_SOURCE" default:""`
|
||||||
UserConfig *viper.Viper
|
UserConfig *viper.Viper
|
||||||
AppState *AppState
|
AppState *AppState
|
||||||
|
IsNewRepo bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppConfigurer interface allows individual app config structs to inherit Fields
|
// AppConfigurer interface allows individual app config structs to inherit Fields
|
||||||
@ -36,6 +37,8 @@ type AppConfigurer interface {
|
|||||||
WriteToUserConfig(string, string) error
|
WriteToUserConfig(string, string) error
|
||||||
SaveAppState() error
|
SaveAppState() error
|
||||||
LoadAppState() error
|
LoadAppState() error
|
||||||
|
SetIsNewRepo(bool)
|
||||||
|
GetIsNewRepo() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAppConfig makes a new app config
|
// NewAppConfig makes a new app config
|
||||||
@ -54,6 +57,7 @@ func NewAppConfig(name, version, commit, date string, buildSource string, debugg
|
|||||||
BuildSource: buildSource,
|
BuildSource: buildSource,
|
||||||
UserConfig: userConfig,
|
UserConfig: userConfig,
|
||||||
AppState: &AppState{},
|
AppState: &AppState{},
|
||||||
|
IsNewRepo: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := appConfig.LoadAppState(); err != nil {
|
if err := appConfig.LoadAppState(); err != nil {
|
||||||
@ -63,6 +67,16 @@ func NewAppConfig(name, version, commit, date string, buildSource string, debugg
|
|||||||
return appConfig, nil
|
return appConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetIsNewRepo returns known repo boolean
|
||||||
|
func (c *AppConfig) GetIsNewRepo() bool {
|
||||||
|
return c.IsNewRepo
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetIsNewRepo set if the current repo is known
|
||||||
|
func (c *AppConfig) SetIsNewRepo(toSet bool) {
|
||||||
|
c.IsNewRepo = toSet
|
||||||
|
}
|
||||||
|
|
||||||
// GetDebug returns debug flag
|
// GetDebug returns debug flag
|
||||||
func (c *AppConfig) GetDebug() bool {
|
func (c *AppConfig) GetDebug() bool {
|
||||||
return c.Debug
|
return c.Debug
|
||||||
@ -153,7 +167,7 @@ func prepareConfigFile(filename string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LoadAndMergeFile Loads the config/state file, creating
|
// LoadAndMergeFile Loads the config/state file, creating
|
||||||
// the file as an empty one if it does not exist
|
// the file has an empty one if it does not exist
|
||||||
func LoadAndMergeFile(v *viper.Viper, filename string) error {
|
func LoadAndMergeFile(v *viper.Viper, filename string) error {
|
||||||
configPath, err := prepareConfigFile(filename)
|
configPath, err := prepareConfigFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -161,6 +161,17 @@ func (gui *Gui) handleCreatePullRequestPress(g *gocui.Gui, v *gocui.View) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleGitFetch(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
if err := gui.createMessagePanel(g, v, "", gui.Tr.SLocalize("FetchWait")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
unamePassOpend, err := gui.fetch(g, v, true)
|
||||||
|
gui.HandleCredentialsPopup(g, unamePassOpend, err)
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleForceCheckout(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleForceCheckout(g *gocui.Gui, v *gocui.View) error {
|
||||||
branch := gui.getSelectedBranch()
|
branch := gui.getSelectedBranch()
|
||||||
message := gui.Tr.SLocalize("SureForceCheckout")
|
message := gui.Tr.SLocalize("SureForceCheckout")
|
||||||
@ -223,14 +234,14 @@ func (gui *Gui) deleteBranch(g *gocui.Gui, v *gocui.View, force bool) error {
|
|||||||
|
|
||||||
func (gui *Gui) deleteNamedBranch(g *gocui.Gui, v *gocui.View, selectedBranch *commands.Branch, force bool) error {
|
func (gui *Gui) deleteNamedBranch(g *gocui.Gui, v *gocui.View, selectedBranch *commands.Branch, force bool) error {
|
||||||
title := gui.Tr.SLocalize("DeleteBranch")
|
title := gui.Tr.SLocalize("DeleteBranch")
|
||||||
var messageId string
|
var messageID string
|
||||||
if force {
|
if force {
|
||||||
messageId = "ForceDeleteBranchMessage"
|
messageID = "ForceDeleteBranchMessage"
|
||||||
} else {
|
} else {
|
||||||
messageId = "DeleteBranchMessage"
|
messageID = "DeleteBranchMessage"
|
||||||
}
|
}
|
||||||
message := gui.Tr.TemplateLocalize(
|
message := gui.Tr.TemplateLocalize(
|
||||||
messageId,
|
messageID,
|
||||||
Teml{
|
Teml{
|
||||||
"selectedBranchName": selectedBranch.Name,
|
"selectedBranchName": selectedBranch.Name,
|
||||||
},
|
},
|
||||||
@ -240,9 +251,8 @@ func (gui *Gui) deleteNamedBranch(g *gocui.Gui, v *gocui.View, selectedBranch *c
|
|||||||
errMessage := err.Error()
|
errMessage := err.Error()
|
||||||
if !force && strings.Contains(errMessage, "is not fully merged") {
|
if !force && strings.Contains(errMessage, "is not fully merged") {
|
||||||
return gui.deleteNamedBranch(g, v, selectedBranch, true)
|
return gui.deleteNamedBranch(g, v, selectedBranch, true)
|
||||||
} else {
|
|
||||||
return gui.createErrorPanel(g, errMessage)
|
|
||||||
}
|
}
|
||||||
|
return gui.createErrorPanel(g, errMessage)
|
||||||
}
|
}
|
||||||
return gui.refreshSidePanels(g)
|
return gui.refreshSidePanels(g)
|
||||||
}, nil)
|
}, nil)
|
||||||
|
@ -37,19 +37,24 @@ func (gui *Gui) closeConfirmationPrompt(g *gocui.Gui) error {
|
|||||||
return g.DeleteView("confirmation")
|
return g.DeleteView("confirmation")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) getMessageHeight(message string, width int) int {
|
func (gui *Gui) getMessageHeight(wrap bool, message string, width int) int {
|
||||||
lines := strings.Split(message, "\n")
|
lines := strings.Split(message, "\n")
|
||||||
lineCount := 0
|
lineCount := 0
|
||||||
|
// if we need to wrap, calculate height to fit content within view's width
|
||||||
|
if wrap {
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
lineCount += len(line)/width + 1
|
lineCount += len(line)/width + 1
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
lineCount = len(lines)
|
||||||
|
}
|
||||||
return lineCount
|
return lineCount
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) getConfirmationPanelDimensions(g *gocui.Gui, prompt string) (int, int, int, int) {
|
func (gui *Gui) getConfirmationPanelDimensions(g *gocui.Gui, wrap bool, prompt string) (int, int, int, int) {
|
||||||
width, height := g.Size()
|
width, height := g.Size()
|
||||||
panelWidth := width / 2
|
panelWidth := width / 2
|
||||||
panelHeight := gui.getMessageHeight(prompt, panelWidth)
|
panelHeight := gui.getMessageHeight(wrap, prompt, panelWidth)
|
||||||
return width/2 - panelWidth/2,
|
return width/2 - panelWidth/2,
|
||||||
height/2 - panelHeight/2 - panelHeight%2 - 1,
|
height/2 - panelHeight/2 - panelHeight%2 - 1,
|
||||||
width/2 + panelWidth/2,
|
width/2 + panelWidth/2,
|
||||||
@ -67,7 +72,7 @@ func (gui *Gui) createPromptPanel(g *gocui.Gui, currentView *gocui.View, title s
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) prepareConfirmationPanel(currentView *gocui.View, title, prompt string) (*gocui.View, error) {
|
func (gui *Gui) prepareConfirmationPanel(currentView *gocui.View, title, prompt string) (*gocui.View, error) {
|
||||||
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(gui.g, prompt)
|
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(gui.g, true, prompt)
|
||||||
confirmationView, err := gui.g.SetView("confirmation", x0, y0, x1, y1, 0)
|
confirmationView, err := gui.g.SetView("confirmation", x0, y0, x1, y1, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != gocui.ErrUnknownView {
|
if err != gocui.ErrUnknownView {
|
||||||
@ -84,10 +89,15 @@ func (gui *Gui) prepareConfirmationPanel(currentView *gocui.View, title, prompt
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) onNewPopupPanel() {
|
func (gui *Gui) onNewPopupPanel() {
|
||||||
gui.g.SetViewOnBottom("commitMessage")
|
viewNames = []string{"commitMessage",
|
||||||
gui.g.SetViewOnBottom("menu")
|
"credentials",
|
||||||
|
"menu"}
|
||||||
|
for _, viewName := range viewNames {
|
||||||
|
_, _ = gui.g.SetViewOnBottom(viewName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// it is very important that within this function we never include the original prompt in any error messages, because it may contain e.g. a user password
|
||||||
func (gui *Gui) createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, title, prompt string, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error {
|
func (gui *Gui) createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, title, prompt string, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error {
|
||||||
gui.onNewPopupPanel()
|
gui.onNewPopupPanel()
|
||||||
g.Update(func(g *gocui.Gui) error {
|
g.Update(func(g *gocui.Gui) error {
|
||||||
@ -137,7 +147,13 @@ func (gui *Gui) createMessagePanel(g *gocui.Gui, currentView *gocui.View, title,
|
|||||||
return gui.createConfirmationPanel(g, currentView, title, prompt, nil, nil)
|
return gui.createConfirmationPanel(g, currentView, title, prompt, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) createErrorPanel(g *gocui.Gui, message string) error {
|
// createSpecificErrorPanel allows you to create an error popup, specifying the
|
||||||
|
// view to be focused when the user closes the popup, and a boolean specifying
|
||||||
|
// whether we will log the error. If the message may include a user password,
|
||||||
|
// this function is to be used over the more generic createErrorPanel, with
|
||||||
|
// willLog set to false
|
||||||
|
func (gui *Gui) createSpecificErrorPanel(message string, nextView *gocui.View, willLog bool) error {
|
||||||
|
if willLog {
|
||||||
go func() {
|
go func() {
|
||||||
// when reporting is switched on this log call sometimes introduces
|
// when reporting is switched on this log call sometimes introduces
|
||||||
// a delay on the error panel popping up. Here I'm adding a second wait
|
// a delay on the error panel popping up. Here I'm adding a second wait
|
||||||
@ -145,10 +161,13 @@ func (gui *Gui) createErrorPanel(g *gocui.Gui, message string) error {
|
|||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
gui.Log.Error(message)
|
gui.Log.Error(message)
|
||||||
}()
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
// gui.Log.WithField("staging", "staging").Info("creating confirmation panel")
|
|
||||||
currentView := g.CurrentView()
|
|
||||||
colorFunction := color.New(color.FgRed).SprintFunc()
|
colorFunction := color.New(color.FgRed).SprintFunc()
|
||||||
coloredMessage := colorFunction(strings.TrimSpace(message))
|
coloredMessage := colorFunction(strings.TrimSpace(message))
|
||||||
return gui.createConfirmationPanel(g, currentView, gui.Tr.SLocalize("Error"), coloredMessage, nil, nil)
|
return gui.createConfirmationPanel(gui.g, nextView, gui.Tr.SLocalize("Error"), coloredMessage, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) createErrorPanel(g *gocui.Gui, message string) error {
|
||||||
|
return gui.createSpecificErrorPanel(message, g.CurrentView(), true)
|
||||||
}
|
}
|
||||||
|
104
pkg/gui/credentials_panel.go
Normal file
104
pkg/gui/credentials_panel.go
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
package gui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jesseduffield/gocui"
|
||||||
|
)
|
||||||
|
|
||||||
|
type credentials chan string
|
||||||
|
|
||||||
|
// waitForPassUname wait for a username or password input from the credentials popup
|
||||||
|
func (gui *Gui) waitForPassUname(g *gocui.Gui, currentView *gocui.View, passOrUname string) string {
|
||||||
|
gui.credentials = make(chan string)
|
||||||
|
g.Update(func(g *gocui.Gui) error {
|
||||||
|
credentialsView, _ := g.View("credentials")
|
||||||
|
if passOrUname == "username" {
|
||||||
|
credentialsView.Title = gui.Tr.SLocalize("CredentialsUsername")
|
||||||
|
credentialsView.Mask = 0
|
||||||
|
} else {
|
||||||
|
credentialsView.Title = gui.Tr.SLocalize("CredentialsPassword")
|
||||||
|
credentialsView.Mask = '*'
|
||||||
|
}
|
||||||
|
err := gui.switchFocus(g, currentView, credentialsView)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
gui.RenderCommitLength()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// wait for username/passwords input
|
||||||
|
userInput := <-gui.credentials
|
||||||
|
return userInput + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleSubmitCredential(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
message := gui.trimmedContent(v)
|
||||||
|
gui.credentials <- message
|
||||||
|
err := gui.refreshFiles(g)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Clear()
|
||||||
|
err = v.SetCursor(0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = g.SetViewOnBottom("credentials")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
nextView, err := gui.g.View("confirmation")
|
||||||
|
if err != nil {
|
||||||
|
nextView = gui.getFilesView(g)
|
||||||
|
}
|
||||||
|
err = gui.switchFocus(g, nil, nextView)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return gui.refreshCommits(g)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleCloseCredentialsView(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
_, err := g.SetViewOnBottom("credentials")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
gui.credentials <- ""
|
||||||
|
return gui.switchFocus(g, nil, gui.getFilesView(g))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleCredentialsViewFocused(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
if _, err := g.SetViewOnTop("credentials"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
message := gui.Tr.TemplateLocalize(
|
||||||
|
"CloseConfirm",
|
||||||
|
Teml{
|
||||||
|
"keyBindClose": "esc",
|
||||||
|
"keyBindConfirm": "enter",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return gui.renderString(g, "options", message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleCredentialsPopup handles the views after executing a command that might ask for credentials
|
||||||
|
func (gui *Gui) HandleCredentialsPopup(g *gocui.Gui, popupOpened bool, cmdErr error) {
|
||||||
|
if popupOpened {
|
||||||
|
_, _ = gui.g.SetViewOnBottom("credentials")
|
||||||
|
}
|
||||||
|
if cmdErr != nil {
|
||||||
|
errMessage := cmdErr.Error()
|
||||||
|
if strings.Contains(errMessage, "Invalid username or password") {
|
||||||
|
errMessage = gui.Tr.SLocalize("PassUnameWrong")
|
||||||
|
}
|
||||||
|
// we are not logging this error because it may contain a password
|
||||||
|
_ = gui.createSpecificErrorPanel(errMessage, gui.getFilesView(gui.g), false)
|
||||||
|
} else {
|
||||||
|
_ = gui.closeConfirmationPrompt(g)
|
||||||
|
_ = gui.refreshSidePanels(g)
|
||||||
|
}
|
||||||
|
}
|
@ -26,7 +26,7 @@ func (gui *Gui) getSelectedFile(g *gocui.Gui) (*commands.File, error) {
|
|||||||
return gui.State.Files[selectedLine], nil
|
return gui.State.Files[selectedLine], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View, alreadySelected bool) error {
|
||||||
file, err := gui.getSelectedFile(g)
|
file, err := gui.getSelectedFile(g)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != gui.Errors.ErrNoFiles {
|
if err != gui.Errors.ErrNoFiles {
|
||||||
@ -48,10 +48,18 @@ func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
content := gui.GitCommand.Diff(file, false)
|
content := gui.GitCommand.Diff(file, false)
|
||||||
|
if alreadySelected {
|
||||||
|
g.Update(func(*gocui.Gui) error {
|
||||||
|
return gui.setViewContent(gui.g, gui.getMainView(gui.g), content)
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return gui.renderString(g, "main", content)
|
return gui.renderString(g, "main", content)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) refreshFiles() error {
|
func (gui *Gui) refreshFiles() error {
|
||||||
|
selectedFile, _ := gui.getSelectedFile(gui.g)
|
||||||
|
|
||||||
filesView := gui.getFilesView()
|
filesView := gui.getFilesView()
|
||||||
gui.refreshStateFiles()
|
gui.refreshStateFiles()
|
||||||
|
|
||||||
@ -64,8 +72,10 @@ func (gui *Gui) refreshFiles() error {
|
|||||||
}
|
}
|
||||||
fmt.Fprint(filesView, list)
|
fmt.Fprint(filesView, list)
|
||||||
|
|
||||||
if filesView == gui.g.CurrentView() {
|
if filesView == g.CurrentView() {
|
||||||
return gui.handleFileSelect(gui.g, filesView)
|
newSelectedFile, _ := gui.getSelectedFile(gui.g)
|
||||||
|
alreadySelected := newSelectedFile.Name == selectedFile.Name
|
||||||
|
return gui.handleFileSelect(g, filesView, alreadySelected)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@ -77,20 +87,14 @@ func (gui *Gui) handleFilesNextLine(g *gocui.Gui, v *gocui.View) error {
|
|||||||
panelState := gui.State.Panels.Files
|
panelState := gui.State.Panels.Files
|
||||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Files), false)
|
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Files), false)
|
||||||
|
|
||||||
if err := gui.resetOrigin(gui.getMainView()); err != nil {
|
return gui.handleFileSelect(gui.g, v, false)
|
||||||
return err
|
|
||||||
}
|
|
||||||
return gui.handleFileSelect(gui.g, v)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleFilesPrevLine(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleFilesPrevLine(g *gocui.Gui, v *gocui.View) error {
|
||||||
panelState := gui.State.Panels.Files
|
panelState := gui.State.Panels.Files
|
||||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Files), true)
|
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Files), true)
|
||||||
|
|
||||||
if err := gui.resetOrigin(gui.getMainView()); err != nil {
|
return gui.handleFileSelect(gui.g, v, false)
|
||||||
return err
|
|
||||||
}
|
|
||||||
return gui.handleFileSelect(gui.g, v)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// specific functions
|
// specific functions
|
||||||
@ -169,7 +173,7 @@ func (gui *Gui) handleFilePress(g *gocui.Gui, v *gocui.View) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.handleFileSelect(g, v)
|
return gui.handleFileSelect(g, v, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) allFilesStaged() bool {
|
func (gui *Gui) allFilesStaged() bool {
|
||||||
@ -376,32 +380,32 @@ func (gui *Gui) catSelectedFile(g *gocui.Gui) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) pullFiles(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) pullFiles(g *gocui.Gui, v *gocui.View) error {
|
||||||
gui.createMessagePanel(g, v, "", gui.Tr.SLocalize("PullWait"))
|
if err := gui.createMessagePanel(gui.g, v, "", gui.Tr.SLocalize("PullWait")); err != nil {
|
||||||
go func() {
|
return err
|
||||||
if err := gui.GitCommand.Pull(); err != nil {
|
|
||||||
gui.createErrorPanel(g, err.Error())
|
|
||||||
} else {
|
|
||||||
gui.closeConfirmationPrompt(g)
|
|
||||||
gui.refreshCommits(g)
|
|
||||||
gui.refreshStatus(g)
|
|
||||||
}
|
}
|
||||||
gui.refreshFiles()
|
go func() {
|
||||||
|
unamePassOpend := false
|
||||||
|
err := gui.GitCommand.Pull(func(passOrUname string) string {
|
||||||
|
unamePassOpend = true
|
||||||
|
return gui.waitForPassUname(g, v, passOrUname)
|
||||||
|
})
|
||||||
|
gui.HandleCredentialsPopup(g, unamePassOpend, err)
|
||||||
}()
|
}()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) pushWithForceFlag(currentView *gocui.View, force bool) error {
|
func (gui *Gui) pushWithForceFlag(g *gocui.Gui, v *gocui.View, force bool) error {
|
||||||
if err := gui.createMessagePanel(gui.g, currentView, "", gui.Tr.SLocalize("PushWait")); err != nil {
|
if err := gui.createMessagePanel(g, v, "", gui.Tr.SLocalize("PushWait")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
|
unamePassOpend := false
|
||||||
branchName := gui.State.Branches[0].Name
|
branchName := gui.State.Branches[0].Name
|
||||||
if err := gui.GitCommand.Push(branchName, force); err != nil {
|
err := gui.GitCommand.Push(branchName, force, func(passOrUname string) string {
|
||||||
_ = gui.createErrorPanel(gui.g, err.Error())
|
unamePassOpend = true
|
||||||
} else {
|
return gui.waitForPassUname(g, v, passOrUname)
|
||||||
_ = gui.closeConfirmationPrompt(gui.g)
|
})
|
||||||
_ = gui.refreshSidePanels(gui.g)
|
gui.HandleCredentialsPopup(g, unamePassOpend, err)
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -410,10 +414,10 @@ func (gui *Gui) pushFiles(g *gocui.Gui, v *gocui.View) error {
|
|||||||
// if we have pullables we'll ask if the user wants to force push
|
// if we have pullables we'll ask if the user wants to force push
|
||||||
_, pullables := gui.GitCommand.GetCurrentBranchUpstreamDifferenceCount()
|
_, pullables := gui.GitCommand.GetCurrentBranchUpstreamDifferenceCount()
|
||||||
if pullables == "?" || pullables == "0" {
|
if pullables == "?" || pullables == "0" {
|
||||||
return gui.pushWithForceFlag(v, false)
|
return gui.pushWithForceFlag(g, v, false)
|
||||||
}
|
}
|
||||||
err := gui.createConfirmationPanel(g, nil, gui.Tr.SLocalize("ForcePush"), gui.Tr.SLocalize("ForcePushPrompt"), func(g *gocui.Gui, v *gocui.View) error {
|
err := gui.createConfirmationPanel(g, nil, gui.Tr.SLocalize("ForcePush"), gui.Tr.SLocalize("ForcePushPrompt"), func(g *gocui.Gui, v *gocui.View) error {
|
||||||
return gui.pushWithForceFlag(v, true)
|
return gui.pushWithForceFlag(g, v, true)
|
||||||
}, nil)
|
}, nil)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package gui
|
package gui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
// "io"
|
// "io"
|
||||||
// "io/ioutil"
|
// "io/ioutil"
|
||||||
@ -15,6 +16,7 @@ import (
|
|||||||
|
|
||||||
// "strings"
|
// "strings"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
"github.com/golang-collections/collections/stack"
|
"github.com/golang-collections/collections/stack"
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||||
@ -70,6 +72,8 @@ type Gui struct {
|
|||||||
Errors SentinelErrors
|
Errors SentinelErrors
|
||||||
Updater *updates.Updater
|
Updater *updates.Updater
|
||||||
statusManager *statusManager
|
statusManager *statusManager
|
||||||
|
credentials credentials
|
||||||
|
waitForIntro sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
// for now the staging panel state, unlike the other panel states, is going to be
|
// for now the staging panel state, unlike the other panel states, is going to be
|
||||||
@ -356,6 +360,23 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if check, _ := g.View("credentials"); check == nil {
|
||||||
|
// doesn't matter where this view starts because it will be hidden
|
||||||
|
if credentialsView, err := g.SetView("credentials", 0, 0, width/2, height/2, 0); err != nil {
|
||||||
|
if err != gocui.ErrUnknownView {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err := g.SetViewOnBottom("credentials")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
credentialsView.Title = gui.Tr.SLocalize("CredentialsUsername")
|
||||||
|
credentialsView.FgColor = gocui.ColorWhite
|
||||||
|
credentialsView.Editable = true
|
||||||
|
credentialsView.Editor = gocui.EditorFunc(gui.simpleEditor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if appStatusView, err := g.SetView("appStatus", -1, optionsTop, width, optionsTop+2, 0); err != nil {
|
if appStatusView, err := g.SetView("appStatus", -1, optionsTop, width, optionsTop+2, 0); err != nil {
|
||||||
if err != gocui.ErrUnknownView {
|
if err != gocui.ErrUnknownView {
|
||||||
return err
|
return err
|
||||||
@ -385,6 +406,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
if err := gui.updateRecentRepoList(); err != nil {
|
if err := gui.updateRecentRepoList(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
gui.waitForIntro.Done()
|
||||||
|
|
||||||
if _, err := gui.g.SetCurrentView(filesView.Name()); err != nil {
|
if _, err := gui.g.SetCurrentView(filesView.Name()); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -419,35 +441,54 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
// here is a good place log some stuff
|
// here is a good place log some stuff
|
||||||
// if you download humanlog and do tail -f development.log | humanlog
|
// if you download humanlog and do tail -f development.log | humanlog
|
||||||
// this will let you see these branches as prettified json
|
// this will let you see these branches as prettified json
|
||||||
// gui.Log.Info(utils.AsJson(gui.State.Files))
|
// gui.Log.Info(utils.AsJson(gui.State.Branches[0:4]))
|
||||||
|
|
||||||
return gui.resizeCurrentPopupPanel(g)
|
return gui.resizeCurrentPopupPanel(g)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) promptAnonymousReporting() error {
|
func (gui *Gui) promptAnonymousReporting() error {
|
||||||
return gui.createConfirmationPanel(gui.g, nil, gui.Tr.SLocalize("AnonymousReportingTitle"), gui.Tr.SLocalize("AnonymousReportingPrompt"), func(g *gocui.Gui, v *gocui.View) error {
|
return gui.createConfirmationPanel(gui.g, nil, gui.Tr.SLocalize("AnonymousReportingTitle"), gui.Tr.SLocalize("AnonymousReportingPrompt"), func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
gui.waitForIntro.Done()
|
||||||
return gui.Config.WriteToUserConfig("reporting", "on")
|
return gui.Config.WriteToUserConfig("reporting", "on")
|
||||||
}, func(g *gocui.Gui, v *gocui.View) error {
|
}, func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
gui.waitForIntro.Done()
|
||||||
return gui.Config.WriteToUserConfig("reporting", "off")
|
return gui.Config.WriteToUserConfig("reporting", "off")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) fetch() error {
|
func (gui *Gui) fetch(g *gocui.Gui, v *gocui.View, canAskForCredentials bool) (unamePassOpend bool, err error) {
|
||||||
gui.GitCommand.Fetch()
|
unamePassOpend = false
|
||||||
gui.refreshStatus(gui.g)
|
err = gui.GitCommand.Fetch(func(passOrUname string) string {
|
||||||
|
unamePassOpend = true
|
||||||
|
return gui.waitForPassUname(gui.g, v, passOrUname)
|
||||||
|
}, canAskForCredentials)
|
||||||
|
|
||||||
|
if canAskForCredentials && err != nil && strings.Contains(err.Error(), "exit status 128") {
|
||||||
|
colorFunction := color.New(color.FgRed).SprintFunc()
|
||||||
|
coloredMessage := colorFunction(strings.TrimSpace(gui.Tr.SLocalize("PassUnameWrong")))
|
||||||
|
close := func(g *gocui.Gui, v *gocui.View) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
_ = gui.createConfirmationPanel(g, v, gui.Tr.SLocalize("Error"), coloredMessage, close, close)
|
||||||
|
}
|
||||||
|
|
||||||
func (gui *Gui) updateLoader() error {
|
gui.refreshStatus(g)
|
||||||
if view, _ := gui.g.View("confirmation"); view != nil {
|
return unamePassOpend, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) updateLoader(g *gocui.Gui) error {
|
||||||
|
gui.g.Update(func(g *gocui.Gui) error {
|
||||||
|
if view, _ := g.View("confirmation"); view != nil {
|
||||||
content := gui.trimmedContent(view)
|
content := gui.trimmedContent(view)
|
||||||
if strings.Contains(content, "...") {
|
if strings.Contains(content, "...") {
|
||||||
staticContent := strings.Split(content, "...")[0] + "..."
|
staticContent := strings.Split(content, "...")[0] + "..."
|
||||||
if err := gui.renderString(gui.g, "confirmation", staticContent+" "+utils.Loader()); err != nil {
|
if err := gui.setViewContent(g, view, staticContent+" "+utils.Loader()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -490,10 +531,31 @@ func (gui *Gui) Run() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.goEvery(time.Second*60, gui.fetch)
|
if gui.Config.GetUserConfig().GetString("reporting") == "undetermined" {
|
||||||
gui.goEvery(time.Second*2, gui.refreshFiles)
|
gui.waitForIntro.Add(2)
|
||||||
gui.goEvery(time.Millisecond*50, gui.updateLoader)
|
} else {
|
||||||
gui.goEvery(time.Millisecond*50, gui.renderAppStatus)
|
gui.waitForIntro.Add(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
gui.waitForIntro.Wait()
|
||||||
|
isNew := gui.Config.GetIsNewRepo()
|
||||||
|
if !isNew {
|
||||||
|
time.After(60 * time.Second)
|
||||||
|
}
|
||||||
|
_, err := gui.fetch(g, g.CurrentView(), false)
|
||||||
|
if err != nil && strings.Contains(err.Error(), "exit status 128") && isNew {
|
||||||
|
_ = gui.createConfirmationPanel(g, g.CurrentView(), gui.Tr.SLocalize("NoAutomaticGitFetchTitle"), gui.Tr.SLocalize("NoAutomaticGitFetchBody"), nil, nil)
|
||||||
|
} else {
|
||||||
|
gui.goEvery(g, time.Second*60, func(g *gocui.Gui) error {
|
||||||
|
_, err := gui.fetch(g, g.CurrentView(), false)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
gui.goEvery(g, time.Second*10, gui.refreshFiles)
|
||||||
|
gui.goEvery(g, time.Millisecond*50, gui.updateLoader)
|
||||||
|
gui.goEvery(g, time.Millisecond*50, gui.renderAppStatus)
|
||||||
|
|
||||||
g.SetManagerFunc(gui.layout)
|
g.SetManagerFunc(gui.layout)
|
||||||
|
|
||||||
|
@ -12,7 +12,6 @@ type Binding struct {
|
|||||||
Handler func(*gocui.Gui, *gocui.View) error
|
Handler func(*gocui.Gui, *gocui.View) error
|
||||||
Key interface{} // FIXME: find out how to get `gocui.Key | rune`
|
Key interface{} // FIXME: find out how to get `gocui.Key | rune`
|
||||||
Modifier gocui.Modifier
|
Modifier gocui.Modifier
|
||||||
KeyReadable string
|
|
||||||
Description string
|
Description string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,16 +22,26 @@ func (b *Binding) GetDisplayStrings() []string {
|
|||||||
|
|
||||||
// GetKey is a function.
|
// GetKey is a function.
|
||||||
func (b *Binding) GetKey() string {
|
func (b *Binding) GetKey() string {
|
||||||
r, ok := b.Key.(rune)
|
key := 0
|
||||||
key := ""
|
|
||||||
|
|
||||||
if ok {
|
switch b.Key.(type) {
|
||||||
key = string(r)
|
case rune:
|
||||||
} else if b.KeyReadable != "" {
|
key = int(b.Key.(rune))
|
||||||
key = b.KeyReadable
|
case gocui.Key:
|
||||||
|
key = int(b.Key.(gocui.Key))
|
||||||
}
|
}
|
||||||
|
|
||||||
return key
|
// special keys
|
||||||
|
switch key {
|
||||||
|
case 27:
|
||||||
|
return "esc"
|
||||||
|
case 13:
|
||||||
|
return "enter"
|
||||||
|
case 32:
|
||||||
|
return "space"
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetKeybindings is a function.
|
// GetKeybindings is a function.
|
||||||
@ -144,7 +153,6 @@ func (gui *Gui) GetKeybindings() []*Binding {
|
|||||||
Key: gocui.KeySpace,
|
Key: gocui.KeySpace,
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleFilePress,
|
Handler: gui.handleFilePress,
|
||||||
KeyReadable: "space",
|
|
||||||
Description: gui.Tr.SLocalize("toggleStaged"),
|
Description: gui.Tr.SLocalize("toggleStaged"),
|
||||||
}, {
|
}, {
|
||||||
ViewName: "files",
|
ViewName: "files",
|
||||||
@ -212,7 +220,12 @@ func (gui *Gui) GetKeybindings() []*Binding {
|
|||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleEnterFile,
|
Handler: gui.handleEnterFile,
|
||||||
Description: gui.Tr.SLocalize("StageLines"),
|
Description: gui.Tr.SLocalize("StageLines"),
|
||||||
KeyReadable: "enter",
|
}, {
|
||||||
|
ViewName: "files",
|
||||||
|
Key: 'f',
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: gui.handleGitFetch,
|
||||||
|
Description: gui.Tr.SLocalize("fetch"),
|
||||||
}, {
|
}, {
|
||||||
ViewName: "merging",
|
ViewName: "merging",
|
||||||
Key: gocui.KeyEsc,
|
Key: gocui.KeyEsc,
|
||||||
@ -282,7 +295,6 @@ func (gui *Gui) GetKeybindings() []*Binding {
|
|||||||
Key: gocui.KeySpace,
|
Key: gocui.KeySpace,
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleBranchPress,
|
Handler: gui.handleBranchPress,
|
||||||
KeyReadable: "space",
|
|
||||||
Description: gui.Tr.SLocalize("checkout"),
|
Description: gui.Tr.SLocalize("checkout"),
|
||||||
}, {
|
}, {
|
||||||
ViewName: "branches",
|
ViewName: "branches",
|
||||||
@ -367,7 +379,6 @@ func (gui *Gui) GetKeybindings() []*Binding {
|
|||||||
Key: gocui.KeySpace,
|
Key: gocui.KeySpace,
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleStashApply,
|
Handler: gui.handleStashApply,
|
||||||
KeyReadable: "space",
|
|
||||||
Description: gui.Tr.SLocalize("apply"),
|
Description: gui.Tr.SLocalize("apply"),
|
||||||
}, {
|
}, {
|
||||||
ViewName: "stash",
|
ViewName: "stash",
|
||||||
@ -391,6 +402,16 @@ func (gui *Gui) GetKeybindings() []*Binding {
|
|||||||
Key: gocui.KeyEsc,
|
Key: gocui.KeyEsc,
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleCommitClose,
|
Handler: gui.handleCommitClose,
|
||||||
|
}, {
|
||||||
|
ViewName: "credentials",
|
||||||
|
Key: gocui.KeyEnter,
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: gui.handleSubmitCredential,
|
||||||
|
}, {
|
||||||
|
ViewName: "credentials",
|
||||||
|
Key: gocui.KeyEsc,
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: gui.handleCloseCredentialsView,
|
||||||
}, {
|
}, {
|
||||||
ViewName: "menu",
|
ViewName: "menu",
|
||||||
Key: gocui.KeyEsc,
|
Key: gocui.KeyEsc,
|
||||||
@ -406,7 +427,6 @@ func (gui *Gui) GetKeybindings() []*Binding {
|
|||||||
Key: gocui.KeyEsc,
|
Key: gocui.KeyEsc,
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleStagingEscape,
|
Handler: gui.handleStagingEscape,
|
||||||
KeyReadable: "esc",
|
|
||||||
Description: gui.Tr.SLocalize("EscapeStaging"),
|
Description: gui.Tr.SLocalize("EscapeStaging"),
|
||||||
}, {
|
}, {
|
||||||
ViewName: "staging",
|
ViewName: "staging",
|
||||||
|
@ -55,7 +55,7 @@ func (gui *Gui) createMenu(title string, items interface{}, handlePress func(int
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(gui.g, list)
|
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(gui.g, false, list)
|
||||||
menuView, _ := gui.g.SetView("menu", x0, y0, x1, y1, 0)
|
menuView, _ := gui.g.SetView("menu", x0, y0, x1, y1, 0)
|
||||||
menuView.Title = title
|
menuView.Title = title
|
||||||
menuView.FgColor = gocui.ColorWhite
|
menuView.FgColor = gocui.ColorWhite
|
||||||
|
@ -14,7 +14,7 @@ type recentRepo struct {
|
|||||||
path string
|
path string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDisplayStrings is a function.
|
// GetDisplayStrings returns the path from a recent repo.
|
||||||
func (r *recentRepo) GetDisplayStrings() []string {
|
func (r *recentRepo) GetDisplayStrings() []string {
|
||||||
yellow := color.New(color.FgMagenta)
|
yellow := color.New(color.FgMagenta)
|
||||||
base := filepath.Base(r.path)
|
base := filepath.Base(r.path)
|
||||||
@ -55,16 +55,22 @@ func (gui *Gui) updateRecentRepoList() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
gui.Config.GetAppState().RecentRepos = newRecentReposList(recentRepos, currentRepo)
|
known, recentRepos := newRecentReposList(recentRepos, currentRepo)
|
||||||
|
gui.Config.SetIsNewRepo(known)
|
||||||
|
gui.Config.GetAppState().RecentRepos = recentRepos
|
||||||
return gui.Config.SaveAppState()
|
return gui.Config.SaveAppState()
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRecentReposList(recentRepos []string, currentRepo string) []string {
|
// newRecentReposList returns a new repo list with a new entry but only when it doesn't exist yet
|
||||||
|
func newRecentReposList(recentRepos []string, currentRepo string) (bool, []string) {
|
||||||
|
isNew := true
|
||||||
newRepos := []string{currentRepo}
|
newRepos := []string{currentRepo}
|
||||||
for _, repo := range recentRepos {
|
for _, repo := range recentRepos {
|
||||||
if repo != currentRepo {
|
if repo != currentRepo {
|
||||||
newRepos = append(newRepos, repo)
|
newRepos = append(newRepos, repo)
|
||||||
|
} else {
|
||||||
|
isNew = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return newRepos
|
return isNew, newRepos
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error {
|
|||||||
case "status":
|
case "status":
|
||||||
return gui.handleStatusSelect(g, v)
|
return gui.handleStatusSelect(g, v)
|
||||||
case "files":
|
case "files":
|
||||||
return gui.handleFileSelect(g, v)
|
return gui.handleFileSelect(g, v, false)
|
||||||
case "branches":
|
case "branches":
|
||||||
return gui.handleBranchSelect(g, v)
|
return gui.handleBranchSelect(g, v)
|
||||||
case "commits":
|
case "commits":
|
||||||
@ -101,11 +101,15 @@ func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error {
|
|||||||
return nil
|
return nil
|
||||||
case "commitMessage":
|
case "commitMessage":
|
||||||
return gui.handleCommitFocused(g, v)
|
return gui.handleCommitFocused(g, v)
|
||||||
case "merging":
|
case "credentials":
|
||||||
|
return gui.handleCredentialsViewFocused(g, v)
|
||||||
|
case "main":
|
||||||
// TODO: pull this out into a 'view focused' function
|
// TODO: pull this out into a 'view focused' function
|
||||||
gui.refreshMergePanel(g)
|
gui.refreshMergePanel(g)
|
||||||
v.Highlight = false
|
v.Highlight = false
|
||||||
return nil
|
return nil
|
||||||
|
case "merging":
|
||||||
|
return nil
|
||||||
case "staging":
|
case "staging":
|
||||||
return nil
|
return nil
|
||||||
// return gui.handleStagingSelect(g, v)
|
// return gui.handleStagingSelect(g, v)
|
||||||
@ -238,19 +242,28 @@ func (gui *Gui) focusPoint(cx int, cy int, v *gocui.View) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) cleanString(s string) string {
|
||||||
|
output := string(bom.Clean([]byte(s)))
|
||||||
|
return utils.NormalizeLinefeeds(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) setViewContent(g *gocui.Gui, v *gocui.View, s string) error {
|
||||||
|
v.Clear()
|
||||||
|
fmt.Fprint(v, gui.cleanString(s))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// renderString resets the origin of a view and sets its content
|
||||||
func (gui *Gui) renderString(g *gocui.Gui, viewName, s string) error {
|
func (gui *Gui) renderString(g *gocui.Gui, viewName, s string) error {
|
||||||
g.Update(func(*gocui.Gui) error {
|
g.Update(func(*gocui.Gui) error {
|
||||||
v, err := g.View(viewName)
|
v, err := g.View(viewName)
|
||||||
// just in case the view disappeared as this function was called, we'll
|
|
||||||
// silently return if it's not found
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil // return gracefully if view has been deleted
|
||||||
}
|
}
|
||||||
v.Clear()
|
if err := v.SetOrigin(0, 0); err != nil {
|
||||||
output := string(bom.Clean([]byte(s)))
|
return err
|
||||||
output = utils.NormalizeLinefeeds(output)
|
}
|
||||||
fmt.Fprint(v, output)
|
return gui.setViewContent(gui.g, v, s)
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -321,7 +334,7 @@ func (gui *Gui) currentViewName(g *gocui.Gui) string {
|
|||||||
|
|
||||||
func (gui *Gui) resizeCurrentPopupPanel(g *gocui.Gui) error {
|
func (gui *Gui) resizeCurrentPopupPanel(g *gocui.Gui) error {
|
||||||
v := g.CurrentView()
|
v := g.CurrentView()
|
||||||
if v.Name() == "commitMessage" || v.Name() == "confirmation" {
|
if v.Name() == "commitMessage" || v.Name() == "credentials" || v.Name() == "confirmation" {
|
||||||
return gui.resizePopupPanel(g, v)
|
return gui.resizePopupPanel(g, v)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -331,7 +344,7 @@ func (gui *Gui) resizePopupPanel(g *gocui.Gui, v *gocui.View) error {
|
|||||||
// If the confirmation panel is already displayed, just resize the width,
|
// If the confirmation panel is already displayed, just resize the width,
|
||||||
// otherwise continue
|
// otherwise continue
|
||||||
content := v.Buffer()
|
content := v.Buffer()
|
||||||
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, content)
|
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, v.Wrap, content)
|
||||||
vx0, vy0, vx1, vy1 := v.Dimensions()
|
vx0, vy0, vx1, vy1 := v.Dimensions()
|
||||||
if vx0 == x0 && vy0 == y0 && vx1 == x1 && vy1 == y1 {
|
if vx0 == x0 && vy0 == y0 && vx1 == x1 && vy1 == y1 {
|
||||||
return nil
|
return nil
|
||||||
|
@ -31,6 +31,15 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
|||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "CommitMessage",
|
ID: "CommitMessage",
|
||||||
Other: "Commit bericht",
|
Other: "Commit bericht",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "CredentialsUsername",
|
||||||
|
Other: "Gebruikersnaam",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "CredentialsPassword",
|
||||||
|
Other: "Wachtwoord",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "PassUnameWrong",
|
||||||
|
Other: "Wachtwoord en/of gebruikersnaam verkeert",
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "CommitChanges",
|
ID: "CommitChanges",
|
||||||
Other: "Commit veranderingen",
|
Other: "Commit veranderingen",
|
||||||
@ -129,10 +138,13 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
|||||||
Other: "Dit is geen bestand",
|
Other: "Dit is geen bestand",
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "PullWait",
|
ID: "PullWait",
|
||||||
Other: "Pulling...",
|
Other: "Pullen...",
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "PushWait",
|
ID: "PushWait",
|
||||||
Other: "Pushing...",
|
Other: "Pushen...",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "FetchWait",
|
||||||
|
Other: "Fetchen...",
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "FileNoMergeCons",
|
ID: "FileNoMergeCons",
|
||||||
Other: "Dit bestand heeft geen merge conflicten",
|
Other: "Dit bestand heeft geen merge conflicten",
|
||||||
@ -409,12 +421,21 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
|||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "NoBranchOnRemote",
|
ID: "NoBranchOnRemote",
|
||||||
Other: `Deze branch bestaat niet op de remote. U moet het eerst naar de remote pushen.`,
|
Other: `Deze branch bestaat niet op de remote. U moet het eerst naar de remote pushen.`,
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "fetch",
|
||||||
|
Other: `fetch`,
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "NoAutomaticGitFetchTitle",
|
||||||
|
Other: `Geen automatiese git fetch`,
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "NoAutomaticGitFetchBody",
|
||||||
|
Other: `Lazygit kan niet "git fetch" uitvoeren in een privé repository, gebruik f in het branches paneel om "git fetch" manueel uit te voeren`,
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "StageLines",
|
ID: "StageLines",
|
||||||
Other: `stage individual hunks/lines`,
|
Other: `stage individuele hunks/lijnen`,
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "FileStagingRequirements",
|
ID: "FileStagingRequirements",
|
||||||
Other: `Can only stage individual lines for tracked files with unstaged changes`,
|
Other: `Kan alleen individuele lijnen stagen van getrackte bestanden met onstaged veranderingen`,
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "StagingTitle",
|
ID: "StagingTitle",
|
||||||
Other: `Staging`,
|
Other: `Staging`,
|
||||||
@ -423,16 +444,16 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
|||||||
Other: `stage hunk`,
|
Other: `stage hunk`,
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "StageLine",
|
ID: "StageLine",
|
||||||
Other: `stage line`,
|
Other: `stage lijn`,
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "EscapeStaging",
|
ID: "EscapeStaging",
|
||||||
Other: `return to files panel`,
|
Other: `ga terug naar het bestanden paneel`,
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "CantFindHunks",
|
ID: "CantFindHunks",
|
||||||
Other: `Could not find any hunks in this patch`,
|
Other: `Kan geen hunks vinden in deze patch`,
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "CantFindHunk",
|
ID: "CantFindHunk",
|
||||||
Other: `Could not find hunk`,
|
Other: `Kan geen hunk vinden`,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,15 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
|||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "CommitMessage",
|
ID: "CommitMessage",
|
||||||
Other: "Commit message",
|
Other: "Commit message",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "CredentialsUsername",
|
||||||
|
Other: "Username",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "CredentialsPassword",
|
||||||
|
Other: "Password",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "PassUnameWrong",
|
||||||
|
Other: "Password and/or username wrong",
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "CommitChanges",
|
ID: "CommitChanges",
|
||||||
Other: "commit changes",
|
Other: "commit changes",
|
||||||
@ -141,6 +150,9 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
|||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "PushWait",
|
ID: "PushWait",
|
||||||
Other: "Pushing...",
|
Other: "Pushing...",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "FetchWait",
|
||||||
|
Other: "Fetching...",
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "FileNoMergeCons",
|
ID: "FileNoMergeCons",
|
||||||
Other: "This file has no merge conflicts",
|
Other: "This file has no merge conflicts",
|
||||||
@ -417,6 +429,15 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
|||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "NoBranchOnRemote",
|
ID: "NoBranchOnRemote",
|
||||||
Other: `This branch doesn't exist on remote. You need to push it to remote first.`,
|
Other: `This branch doesn't exist on remote. You need to push it to remote first.`,
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "fetch",
|
||||||
|
Other: `fetch`,
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "NoAutomaticGitFetchTitle",
|
||||||
|
Other: `No automatic git fetch`,
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "NoAutomaticGitFetchBody",
|
||||||
|
Other: `Lazygit can't use "git fetch" in a private repo; use 'f' in the files panel to run "git fetch" manually`,
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "StageLines",
|
ID: "StageLines",
|
||||||
Other: `stage individual hunks/lines`,
|
Other: `stage individual hunks/lines`,
|
||||||
|
@ -29,6 +29,15 @@ func addPolish(i18nObject *i18n.Bundle) error {
|
|||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "CommitMessage",
|
ID: "CommitMessage",
|
||||||
Other: "Wiadomość commita",
|
Other: "Wiadomość commita",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "CredentialsUsername",
|
||||||
|
Other: "Username",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "CredentialsPassword",
|
||||||
|
Other: "Password",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "PassUnameWrong",
|
||||||
|
Other: "Password and/or username wrong",
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "CommitChanges",
|
ID: "CommitChanges",
|
||||||
Other: "commituj zmiany",
|
Other: "commituj zmiany",
|
||||||
@ -122,6 +131,9 @@ func addPolish(i18nObject *i18n.Bundle) error {
|
|||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "PushWait",
|
ID: "PushWait",
|
||||||
Other: "Wypychanie zmian...",
|
Other: "Wypychanie zmian...",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "FetchWait",
|
||||||
|
Other: "Fetching...",
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "FileNoMergeCons",
|
ID: "FileNoMergeCons",
|
||||||
Other: "Ten plik nie powoduje konfliktów scalania",
|
Other: "Ten plik nie powoduje konfliktów scalania",
|
||||||
@ -392,30 +404,39 @@ func addPolish(i18nObject *i18n.Bundle) error {
|
|||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "NoBranchOnRemote",
|
ID: "NoBranchOnRemote",
|
||||||
Other: `Ta gałąź nie istnieje na zdalnym. Najpierw musisz go odepchnąć na odległość.`,
|
Other: `Ta gałąź nie istnieje na zdalnym. Najpierw musisz go odepchnąć na odległość.`,
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "fetch",
|
||||||
|
Other: `fetch`,
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "NoAutomaticGitFetchTitle",
|
||||||
|
Other: `No automatic git fetch`,
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "NoAutomaticGitFetchBody",
|
||||||
|
Other: `Lazygit can't use "git fetch" in a private repo use f in the branches panel to run "git fetch" manually`,
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "StageLines",
|
ID: "StageLines",
|
||||||
Other: `stage individual hunks/lines`,
|
Other: `zatwierdź pojedyncze linie`,
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "FileStagingRequirements",
|
ID: "FileStagingRequirements",
|
||||||
Other: `Can only stage individual lines for tracked files with unstaged changes`,
|
Other: `Można tylko zatwierdzić pojedyncze linie dla śledzonych plików z niezatwierdzonymi zmianami`,
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "StagingTitle",
|
ID: "StagingTitle",
|
||||||
Other: `Staging`,
|
Other: `Zatwierdzanie`,
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "StageHunk",
|
ID: "StageHunk",
|
||||||
Other: `stage hunk`,
|
Other: `zatwierdź kawałek`,
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "StageLine",
|
ID: "StageLine",
|
||||||
Other: `stage line`,
|
Other: `zatwierdź linię`,
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "EscapeStaging",
|
ID: "EscapeStaging",
|
||||||
Other: `return to files panel`,
|
Other: `wróć do panelu plików`,
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "CantFindHunks",
|
ID: "CantFindHunks",
|
||||||
Other: `Could not find any hunks in this patch`,
|
Other: `Nie można znaleźć żadnych kawałków w tej łatce`,
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "CantFindHunk",
|
ID: "CantFindHunk",
|
||||||
Other: `Could not find hunk`,
|
Other: `Nie można znaleźć kawałka`,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -36,25 +36,24 @@ type Updaterer interface {
|
|||||||
Update()
|
Update()
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
const (
|
||||||
projectUrl = "https://github.com/jesseduffield/lazygit"
|
PROJECT_URL = "https://github.com/jesseduffield/lazygit"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewUpdater creates a new updater
|
// NewUpdater creates a new updater
|
||||||
func NewUpdater(log *logrus.Entry, config config.AppConfigurer, osCommand *commands.OSCommand, tr *i18n.Localizer) (*Updater, error) {
|
func NewUpdater(log *logrus.Entry, config config.AppConfigurer, osCommand *commands.OSCommand, tr *i18n.Localizer) (*Updater, error) {
|
||||||
contextLogger := log.WithField("context", "updates")
|
contextLogger := log.WithField("context", "updates")
|
||||||
|
|
||||||
updater := &Updater{
|
return &Updater{
|
||||||
Log: contextLogger,
|
Log: contextLogger,
|
||||||
Config: config,
|
Config: config,
|
||||||
OSCommand: osCommand,
|
OSCommand: osCommand,
|
||||||
Tr: tr,
|
Tr: tr,
|
||||||
}
|
}, nil
|
||||||
return updater, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Updater) getLatestVersionNumber() (string, error) {
|
func (u *Updater) getLatestVersionNumber() (string, error) {
|
||||||
req, err := http.NewRequest("GET", projectUrl+"/releases/latest", nil)
|
req, err := http.NewRequest("GET", PROJECT_URL+"/releases/latest", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -65,17 +64,16 @@ func (u *Updater) getLatestVersionNumber() (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
dec := json.NewDecoder(resp.Body)
|
||||||
|
data := struct {
|
||||||
|
TagName string `json:"tag_name"`
|
||||||
|
}{}
|
||||||
|
if err := dec.Decode(&data); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
byt := []byte(body)
|
return data.TagName, nil
|
||||||
var dat map[string]interface{}
|
|
||||||
if err := json.Unmarshal(byt, &dat); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return dat["tag_name"].(string), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecordLastUpdateCheck records last time an update check was performed
|
// RecordLastUpdateCheck records last time an update check was performed
|
||||||
@ -225,14 +223,14 @@ func (u *Updater) getBinaryUrl(newVersion string) (string, error) {
|
|||||||
}
|
}
|
||||||
url := fmt.Sprintf(
|
url := fmt.Sprintf(
|
||||||
"%s/releases/download/%s/lazygit_%s_%s_%s.%s",
|
"%s/releases/download/%s/lazygit_%s_%s_%s.%s",
|
||||||
projectUrl,
|
PROJECT_URL,
|
||||||
newVersion,
|
newVersion,
|
||||||
newVersion[1:],
|
newVersion[1:],
|
||||||
u.mappedOs(runtime.GOOS),
|
u.mappedOs(runtime.GOOS),
|
||||||
u.mappedArch(runtime.GOARCH),
|
u.mappedArch(runtime.GOARCH),
|
||||||
extension,
|
extension,
|
||||||
)
|
)
|
||||||
u.Log.Info("url for latest release is " + url)
|
u.Log.Info("Url for latest release is " + url)
|
||||||
return url, nil
|
return url, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,7 +249,7 @@ func (u *Updater) update(newVersion string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
u.Log.Info("updating with url " + rawUrl)
|
u.Log.Info("Updating with url " + rawUrl)
|
||||||
return u.downloadAndInstall(rawUrl)
|
return u.downloadAndInstall(rawUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,7 +265,7 @@ func (u *Updater) downloadAndInstall(rawUrl string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(tempDir)
|
defer os.RemoveAll(tempDir)
|
||||||
u.Log.Info("temp directory is " + tempDir)
|
u.Log.Info("Temp directory is " + tempDir)
|
||||||
|
|
||||||
// Get it!
|
// Get it!
|
||||||
if err := g.Get(tempDir, url); err != nil {
|
if err := g.Get(tempDir, url); err != nil {
|
||||||
@ -279,14 +277,14 @@ func (u *Updater) downloadAndInstall(rawUrl string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
u.Log.Info("binary path is " + binaryPath)
|
u.Log.Info("Binary path is " + binaryPath)
|
||||||
|
|
||||||
binaryName := filepath.Base(binaryPath)
|
binaryName := filepath.Base(binaryPath)
|
||||||
u.Log.Info("binary name is " + binaryName)
|
u.Log.Info("Binary name is " + binaryName)
|
||||||
|
|
||||||
// Verify the main file exists
|
// Verify the main file exists
|
||||||
tempPath := filepath.Join(tempDir, binaryName)
|
tempPath := filepath.Join(tempDir, binaryName)
|
||||||
u.Log.Info("temp path to binary is " + tempPath)
|
u.Log.Info("Temp path to binary is " + tempPath)
|
||||||
if _, err := os.Stat(tempPath); err != nil {
|
if _, err := os.Stat(tempPath); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -296,7 +294,7 @@ func (u *Updater) downloadAndInstall(rawUrl string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
u.Log.Info("update complete!")
|
u.Log.Info("Update complete!")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
5
scripts/bump_modules.sh
Executable file
5
scripts/bump_modules.sh
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
GO111MODULE=on
|
||||||
|
mv go.mod /tmp/
|
||||||
|
go mod init
|
63
scripts/generate_cheatsheet.go
Normal file
63
scripts/generate_cheatsheet.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// This "script" generates a file called Keybindings_{{.LANG}}.md
|
||||||
|
// in current working directory.
|
||||||
|
//
|
||||||
|
// The content of this generated file is a keybindings cheatsheet.
|
||||||
|
//
|
||||||
|
// To generate cheatsheet in english run:
|
||||||
|
// LANG=en go run scripts/generate_cheatsheet.go
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/app"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func writeString(file *os.File, str string) {
|
||||||
|
_, err := file.WriteString(str)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTitle(mApp *app.App, viewName string) string {
|
||||||
|
viewTitle := strings.Title(viewName) + "Title"
|
||||||
|
translatedTitle := mApp.Tr.SLocalize(viewTitle)
|
||||||
|
formattedTitle := fmt.Sprintf("\n## %s\n\n", translatedTitle)
|
||||||
|
return formattedTitle
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
mConfig, _ := config.NewAppConfig("", "", "", "", "", new(bool))
|
||||||
|
mApp, _ := app.Setup(mConfig)
|
||||||
|
lang := mApp.Tr.GetLanguage()
|
||||||
|
file, _ := os.Create("Keybindings_" + lang + ".md")
|
||||||
|
current := ""
|
||||||
|
|
||||||
|
writeString(file, fmt.Sprintf("# Lazygit %s\n", mApp.Tr.SLocalize("menu")))
|
||||||
|
writeString(file, getTitle(mApp, "global"))
|
||||||
|
|
||||||
|
writeString(file, "<pre>\n")
|
||||||
|
|
||||||
|
for _, binding := range mApp.Gui.GetKeybindings() {
|
||||||
|
if binding.Description == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if binding.ViewName != current {
|
||||||
|
current = binding.ViewName
|
||||||
|
writeString(file, "</pre>\n")
|
||||||
|
writeString(file, getTitle(mApp, current))
|
||||||
|
writeString(file, "<pre>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
info := fmt.Sprintf(" <kbd>%s</kbd>: %s\n", binding.GetKey(), binding.Description)
|
||||||
|
writeString(file, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
writeString(file, "</pre>\n")
|
||||||
|
}
|
25
test/hooks/pre-push
Normal file
25
test/hooks/pre-push
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# test pre-push hook for testing the lazygit credentials view
|
||||||
|
#
|
||||||
|
# to enable, use:
|
||||||
|
# chmod +x .git/hooks/pre-push
|
||||||
|
#
|
||||||
|
# this will hang if you're using git from the command line, so only enable this
|
||||||
|
# when you are testing the credentials view in lazygit
|
||||||
|
|
||||||
|
exec < /dev/tty
|
||||||
|
|
||||||
|
echo -n "Username for 'github': "
|
||||||
|
read username
|
||||||
|
|
||||||
|
echo -n "Password for 'github': "
|
||||||
|
read password
|
||||||
|
|
||||||
|
if [ "$username" = "username" -a "$password" = "password" ]; then
|
||||||
|
echo "success"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
>&2 echo "incorrect username/password"
|
||||||
|
exit 1
|
10
vendor/github.com/jesseduffield/gocui/view.go
generated
vendored
10
vendor/github.com/jesseduffield/gocui/view.go
generated
vendored
@ -148,7 +148,6 @@ func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
|
|||||||
if x < 0 || x >= maxX || y < 0 || y >= maxY {
|
if x < 0 || x >= maxX || y < 0 || y >= maxY {
|
||||||
return errors.New("invalid point")
|
return errors.New("invalid point")
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ry, rcy int
|
ry, rcy int
|
||||||
err error
|
err error
|
||||||
@ -270,6 +269,12 @@ func (v *View) parseInput(ch rune) []cell {
|
|||||||
if isEscape {
|
if isEscape {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
repeatCount := 1
|
||||||
|
if ch == '\t' {
|
||||||
|
ch = ' '
|
||||||
|
repeatCount = 4
|
||||||
|
}
|
||||||
|
for i := 0; i < repeatCount; i++ {
|
||||||
c := cell{
|
c := cell{
|
||||||
fgColor: v.ei.curFgColor,
|
fgColor: v.ei.curFgColor,
|
||||||
bgColor: v.ei.curBgColor,
|
bgColor: v.ei.curBgColor,
|
||||||
@ -277,6 +282,7 @@ func (v *View) parseInput(ch rune) []cell {
|
|||||||
}
|
}
|
||||||
cells = append(cells, c)
|
cells = append(cells, c)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return cells
|
return cells
|
||||||
}
|
}
|
||||||
@ -533,7 +539,7 @@ func lineWrap(line []cell, columns int) [][]cell {
|
|||||||
n += rw
|
n += rw
|
||||||
if n > columns {
|
if n > columns {
|
||||||
n = rw
|
n = rw
|
||||||
lines = append(lines, line[offset:i-1])
|
lines = append(lines, line[offset:i])
|
||||||
offset = i
|
offset = i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
23
vendor/github.com/jesseduffield/pty/License
generated
vendored
Normal file
23
vendor/github.com/jesseduffield/pty/License
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
Copyright (c) 2011 Keith Rarick
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the
|
||||||
|
Software without restriction, including without limitation
|
||||||
|
the rights to use, copy, modify, merge, publish, distribute,
|
||||||
|
sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall
|
||||||
|
be included in all copies or substantial portions of the
|
||||||
|
Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
||||||
|
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
||||||
|
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
||||||
|
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
16
vendor/github.com/jesseduffield/pty/doc.go
generated
vendored
Normal file
16
vendor/github.com/jesseduffield/pty/doc.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Package pty provides functions for working with Unix terminals.
|
||||||
|
package pty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrUnsupported is returned if a function is not
|
||||||
|
// available on the current platform.
|
||||||
|
var ErrUnsupported = errors.New("unsupported")
|
||||||
|
|
||||||
|
// Opens a pty and its corresponding tty.
|
||||||
|
func Open() (pty, tty *os.File, err error) {
|
||||||
|
return open()
|
||||||
|
}
|
13
vendor/github.com/jesseduffield/pty/ioctl.go
generated
vendored
Normal file
13
vendor/github.com/jesseduffield/pty/ioctl.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
func ioctl(fd, cmd, ptr uintptr) error {
|
||||||
|
_, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr)
|
||||||
|
if e != 0 {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
39
vendor/github.com/jesseduffield/pty/ioctl_bsd.go
generated
vendored
Normal file
39
vendor/github.com/jesseduffield/pty/ioctl_bsd.go
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// +build darwin dragonfly freebsd netbsd openbsd
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
// from <sys/ioccom.h>
|
||||||
|
const (
|
||||||
|
_IOC_VOID uintptr = 0x20000000
|
||||||
|
_IOC_OUT uintptr = 0x40000000
|
||||||
|
_IOC_IN uintptr = 0x80000000
|
||||||
|
_IOC_IN_OUT uintptr = _IOC_OUT | _IOC_IN
|
||||||
|
_IOC_DIRMASK = _IOC_VOID | _IOC_OUT | _IOC_IN
|
||||||
|
|
||||||
|
_IOC_PARAM_SHIFT = 13
|
||||||
|
_IOC_PARAM_MASK = (1 << _IOC_PARAM_SHIFT) - 1
|
||||||
|
)
|
||||||
|
|
||||||
|
func _IOC_PARM_LEN(ioctl uintptr) uintptr {
|
||||||
|
return (ioctl >> 16) & _IOC_PARAM_MASK
|
||||||
|
}
|
||||||
|
|
||||||
|
func _IOC(inout uintptr, group byte, ioctl_num uintptr, param_len uintptr) uintptr {
|
||||||
|
return inout | (param_len&_IOC_PARAM_MASK)<<16 | uintptr(group)<<8 | ioctl_num
|
||||||
|
}
|
||||||
|
|
||||||
|
func _IO(group byte, ioctl_num uintptr) uintptr {
|
||||||
|
return _IOC(_IOC_VOID, group, ioctl_num, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _IOR(group byte, ioctl_num uintptr, param_len uintptr) uintptr {
|
||||||
|
return _IOC(_IOC_OUT, group, ioctl_num, param_len)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _IOW(group byte, ioctl_num uintptr, param_len uintptr) uintptr {
|
||||||
|
return _IOC(_IOC_IN, group, ioctl_num, param_len)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _IOWR(group byte, ioctl_num uintptr, param_len uintptr) uintptr {
|
||||||
|
return _IOC(_IOC_IN_OUT, group, ioctl_num, param_len)
|
||||||
|
}
|
65
vendor/github.com/jesseduffield/pty/pty_darwin.go
generated
vendored
Normal file
65
vendor/github.com/jesseduffield/pty/pty_darwin.go
generated
vendored
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package pty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func open() (pty, tty *os.File, err error) {
|
||||||
|
pFD, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|syscall.O_CLOEXEC, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
p := os.NewFile(uintptr(pFD), "/dev/ptmx")
|
||||||
|
// In case of error after this point, make sure we close the ptmx fd.
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
_ = p.Close() // Best effort.
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
sname, err := ptsname(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := grantpt(p); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := unlockpt(p); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := os.OpenFile(sname, os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return p, t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ptsname(f *os.File) (string, error) {
|
||||||
|
n := make([]byte, _IOC_PARM_LEN(syscall.TIOCPTYGNAME))
|
||||||
|
|
||||||
|
err := ioctl(f.Fd(), syscall.TIOCPTYGNAME, uintptr(unsafe.Pointer(&n[0])))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range n {
|
||||||
|
if c == 0 {
|
||||||
|
return string(n[:i]), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", errors.New("TIOCPTYGNAME string not NUL-terminated")
|
||||||
|
}
|
||||||
|
|
||||||
|
func grantpt(f *os.File) error {
|
||||||
|
return ioctl(f.Fd(), syscall.TIOCPTYGRANT, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unlockpt(f *os.File) error {
|
||||||
|
return ioctl(f.Fd(), syscall.TIOCPTYUNLK, 0)
|
||||||
|
}
|
80
vendor/github.com/jesseduffield/pty/pty_dragonfly.go
generated
vendored
Normal file
80
vendor/github.com/jesseduffield/pty/pty_dragonfly.go
generated
vendored
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package pty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// same code as pty_darwin.go
|
||||||
|
func open() (pty, tty *os.File, err error) {
|
||||||
|
p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
// In case of error after this point, make sure we close the ptmx fd.
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
_ = p.Close() // Best effort.
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
sname, err := ptsname(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := grantpt(p); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := unlockpt(p); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := os.OpenFile(sname, os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return p, t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func grantpt(f *os.File) error {
|
||||||
|
_, err := isptmaster(f.Fd())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func unlockpt(f *os.File) error {
|
||||||
|
_, err := isptmaster(f.Fd())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func isptmaster(fd uintptr) (bool, error) {
|
||||||
|
err := ioctl(fd, syscall.TIOCISPTMASTER, 0)
|
||||||
|
return err == nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
emptyFiodgnameArg fiodgnameArg
|
||||||
|
ioctl_FIODNAME = _IOW('f', 120, unsafe.Sizeof(emptyFiodgnameArg))
|
||||||
|
)
|
||||||
|
|
||||||
|
func ptsname(f *os.File) (string, error) {
|
||||||
|
name := make([]byte, _C_SPECNAMELEN)
|
||||||
|
fa := fiodgnameArg{Name: (*byte)(unsafe.Pointer(&name[0])), Len: _C_SPECNAMELEN, Pad_cgo_0: [4]byte{0, 0, 0, 0}}
|
||||||
|
|
||||||
|
err := ioctl(f.Fd(), ioctl_FIODNAME, uintptr(unsafe.Pointer(&fa)))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range name {
|
||||||
|
if c == 0 {
|
||||||
|
s := "/dev/" + string(name[:i])
|
||||||
|
return strings.Replace(s, "ptm", "pts", -1), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", errors.New("TIOCPTYGNAME string not NUL-terminated")
|
||||||
|
}
|
78
vendor/github.com/jesseduffield/pty/pty_freebsd.go
generated
vendored
Normal file
78
vendor/github.com/jesseduffield/pty/pty_freebsd.go
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package pty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func posixOpenpt(oflag int) (fd int, err error) {
|
||||||
|
r0, _, e1 := syscall.Syscall(syscall.SYS_POSIX_OPENPT, uintptr(oflag), 0, 0)
|
||||||
|
fd = int(r0)
|
||||||
|
if e1 != 0 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return fd, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func open() (pty, tty *os.File, err error) {
|
||||||
|
fd, err := posixOpenpt(syscall.O_RDWR | syscall.O_CLOEXEC)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
p := os.NewFile(uintptr(fd), "/dev/pts")
|
||||||
|
// In case of error after this point, make sure we close the pts fd.
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
_ = p.Close() // Best effort.
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
sname, err := ptsname(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := os.OpenFile("/dev/"+sname, os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return p, t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isptmaster(fd uintptr) (bool, error) {
|
||||||
|
err := ioctl(fd, syscall.TIOCPTMASTER, 0)
|
||||||
|
return err == nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
emptyFiodgnameArg fiodgnameArg
|
||||||
|
ioctlFIODGNAME = _IOW('f', 120, unsafe.Sizeof(emptyFiodgnameArg))
|
||||||
|
)
|
||||||
|
|
||||||
|
func ptsname(f *os.File) (string, error) {
|
||||||
|
master, err := isptmaster(f.Fd())
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !master {
|
||||||
|
return "", syscall.EINVAL
|
||||||
|
}
|
||||||
|
|
||||||
|
const n = _C_SPECNAMELEN + 1
|
||||||
|
var (
|
||||||
|
buf = make([]byte, n)
|
||||||
|
arg = fiodgnameArg{Len: n, Buf: (*byte)(unsafe.Pointer(&buf[0]))}
|
||||||
|
)
|
||||||
|
if err := ioctl(f.Fd(), ioctlFIODGNAME, uintptr(unsafe.Pointer(&arg))); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range buf {
|
||||||
|
if c == 0 {
|
||||||
|
return string(buf[:i]), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", errors.New("FIODGNAME string not NUL-terminated")
|
||||||
|
}
|
51
vendor/github.com/jesseduffield/pty/pty_linux.go
generated
vendored
Normal file
51
vendor/github.com/jesseduffield/pty/pty_linux.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package pty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func open() (pty, tty *os.File, err error) {
|
||||||
|
p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
// In case of error after this point, make sure we close the ptmx fd.
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
_ = p.Close() // Best effort.
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
sname, err := ptsname(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := unlockpt(p); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := os.OpenFile(sname, os.O_RDWR|syscall.O_NOCTTY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return p, t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ptsname(f *os.File) (string, error) {
|
||||||
|
var n _C_uint
|
||||||
|
err := ioctl(f.Fd(), syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n)))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return "/dev/pts/" + strconv.Itoa(int(n)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unlockpt(f *os.File) error {
|
||||||
|
var u _C_int
|
||||||
|
// use TIOCSPTLCK with a zero valued arg to clear the slave pty lock
|
||||||
|
return ioctl(f.Fd(), syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&u)))
|
||||||
|
}
|
33
vendor/github.com/jesseduffield/pty/pty_openbsd.go
generated
vendored
Normal file
33
vendor/github.com/jesseduffield/pty/pty_openbsd.go
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package pty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func open() (pty, tty *os.File, err error) {
|
||||||
|
/*
|
||||||
|
* from ptm(4):
|
||||||
|
* The PTMGET command allocates a free pseudo terminal, changes its
|
||||||
|
* ownership to the caller, revokes the access privileges for all previous
|
||||||
|
* users, opens the file descriptors for the master and slave devices and
|
||||||
|
* returns them to the caller in struct ptmget.
|
||||||
|
*/
|
||||||
|
|
||||||
|
p, err := os.OpenFile("/dev/ptm", os.O_RDWR|syscall.O_CLOEXEC, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
defer p.Close()
|
||||||
|
|
||||||
|
var ptm ptmget
|
||||||
|
if err := ioctl(p.Fd(), uintptr(ioctl_PTMGET), uintptr(unsafe.Pointer(&ptm))); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pty = os.NewFile(uintptr(ptm.Cfd), "/dev/ptm")
|
||||||
|
tty = os.NewFile(uintptr(ptm.Sfd), "/dev/ptm")
|
||||||
|
|
||||||
|
return pty, tty, nil
|
||||||
|
}
|
11
vendor/github.com/jesseduffield/pty/pty_unsupported.go
generated
vendored
Normal file
11
vendor/github.com/jesseduffield/pty/pty_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// +build !linux,!darwin,!freebsd,!dragonfly,!openbsd
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func open() (pty, tty *os.File, err error) {
|
||||||
|
return nil, nil, ErrUnsupported
|
||||||
|
}
|
54
vendor/github.com/jesseduffield/pty/run.go
generated
vendored
Normal file
54
vendor/github.com/jesseduffield/pty/run.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Start assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout,
|
||||||
|
// and c.Stderr, calls c.Start, and returns the File of the tty's
|
||||||
|
// corresponding pty.
|
||||||
|
func Start(c *exec.Cmd) (pty *os.File, err error) {
|
||||||
|
return StartWithSize(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartWithSize assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout,
|
||||||
|
// and c.Stderr, calls c.Start, and returns the File of the tty's
|
||||||
|
// corresponding pty.
|
||||||
|
//
|
||||||
|
// This will resize the pty to the specified size before starting the command
|
||||||
|
func StartWithSize(c *exec.Cmd, sz *Winsize) (pty *os.File, err error) {
|
||||||
|
pty, tty, err := Open()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer tty.Close()
|
||||||
|
if sz != nil {
|
||||||
|
err = Setsize(pty, sz)
|
||||||
|
if err != nil {
|
||||||
|
pty.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.Stdout == nil {
|
||||||
|
c.Stdout = tty
|
||||||
|
}
|
||||||
|
if c.Stderr == nil {
|
||||||
|
c.Stderr = tty
|
||||||
|
}
|
||||||
|
c.Stdin = tty
|
||||||
|
if c.SysProcAttr == nil {
|
||||||
|
c.SysProcAttr = &syscall.SysProcAttr{}
|
||||||
|
}
|
||||||
|
c.SysProcAttr.Setctty = true
|
||||||
|
c.SysProcAttr.Setsid = true
|
||||||
|
err = c.Start()
|
||||||
|
if err != nil {
|
||||||
|
pty.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pty, err
|
||||||
|
}
|
10
vendor/github.com/jesseduffield/pty/types.go
generated
vendored
Normal file
10
vendor/github.com/jesseduffield/pty/types.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// +build ignore
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
type (
|
||||||
|
_C_int C.int
|
||||||
|
_C_uint C.uint
|
||||||
|
)
|
17
vendor/github.com/jesseduffield/pty/types_dragonfly.go
generated
vendored
Normal file
17
vendor/github.com/jesseduffield/pty/types_dragonfly.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// +build ignore
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
/*
|
||||||
|
#define _KERNEL
|
||||||
|
#include <sys/conf.h>
|
||||||
|
#include <sys/param.h>
|
||||||
|
#include <sys/filio.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
const (
|
||||||
|
_C_SPECNAMELEN = C.SPECNAMELEN /* max length of devicename */
|
||||||
|
)
|
||||||
|
|
||||||
|
type fiodgnameArg C.struct_fiodname_args
|
15
vendor/github.com/jesseduffield/pty/types_freebsd.go
generated
vendored
Normal file
15
vendor/github.com/jesseduffield/pty/types_freebsd.go
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// +build ignore
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <sys/param.h>
|
||||||
|
#include <sys/filio.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
const (
|
||||||
|
_C_SPECNAMELEN = C.SPECNAMELEN /* max length of devicename */
|
||||||
|
)
|
||||||
|
|
||||||
|
type fiodgnameArg C.struct_fiodgname_arg
|
14
vendor/github.com/jesseduffield/pty/types_openbsd.go
generated
vendored
Normal file
14
vendor/github.com/jesseduffield/pty/types_openbsd.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// +build ignore
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/tty.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
type ptmget C.struct_ptmget
|
||||||
|
|
||||||
|
var ioctl_PTMGET = C.PTMGET
|
64
vendor/github.com/jesseduffield/pty/util.go
generated
vendored
Normal file
64
vendor/github.com/jesseduffield/pty/util.go
generated
vendored
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InheritSize applies the terminal size of master to slave. This should be run
|
||||||
|
// in a signal handler for syscall.SIGWINCH to automatically resize the slave when
|
||||||
|
// the master receives a window size change notification.
|
||||||
|
func InheritSize(master, slave *os.File) error {
|
||||||
|
size, err := GetsizeFull(master)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = Setsize(slave, size)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setsize resizes t to s.
|
||||||
|
func Setsize(t *os.File, ws *Winsize) error {
|
||||||
|
return windowRectCall(ws, t.Fd(), syscall.TIOCSWINSZ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetsizeFull returns the full terminal size description.
|
||||||
|
func GetsizeFull(t *os.File) (size *Winsize, err error) {
|
||||||
|
var ws Winsize
|
||||||
|
err = windowRectCall(&ws, t.Fd(), syscall.TIOCGWINSZ)
|
||||||
|
return &ws, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getsize returns the number of rows (lines) and cols (positions
|
||||||
|
// in each line) in terminal t.
|
||||||
|
func Getsize(t *os.File) (rows, cols int, err error) {
|
||||||
|
ws, err := GetsizeFull(t)
|
||||||
|
return int(ws.Rows), int(ws.Cols), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Winsize describes the terminal size.
|
||||||
|
type Winsize struct {
|
||||||
|
Rows uint16 // ws_row: Number of rows (in cells)
|
||||||
|
Cols uint16 // ws_col: Number of columns (in cells)
|
||||||
|
X uint16 // ws_xpixel: Width in pixels
|
||||||
|
Y uint16 // ws_ypixel: Height in pixels
|
||||||
|
}
|
||||||
|
|
||||||
|
func windowRectCall(ws *Winsize, fd, a2 uintptr) error {
|
||||||
|
_, _, errno := syscall.Syscall(
|
||||||
|
syscall.SYS_IOCTL,
|
||||||
|
fd,
|
||||||
|
a2,
|
||||||
|
uintptr(unsafe.Pointer(ws)),
|
||||||
|
)
|
||||||
|
if errno != 0 {
|
||||||
|
return syscall.Errno(errno)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
9
vendor/github.com/jesseduffield/pty/ztypes_386.go
generated
vendored
Normal file
9
vendor/github.com/jesseduffield/pty/ztypes_386.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
type (
|
||||||
|
_C_int int32
|
||||||
|
_C_uint uint32
|
||||||
|
)
|
9
vendor/github.com/jesseduffield/pty/ztypes_amd64.go
generated
vendored
Normal file
9
vendor/github.com/jesseduffield/pty/ztypes_amd64.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
type (
|
||||||
|
_C_int int32
|
||||||
|
_C_uint uint32
|
||||||
|
)
|
9
vendor/github.com/jesseduffield/pty/ztypes_arm.go
generated
vendored
Normal file
9
vendor/github.com/jesseduffield/pty/ztypes_arm.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
type (
|
||||||
|
_C_int int32
|
||||||
|
_C_uint uint32
|
||||||
|
)
|
11
vendor/github.com/jesseduffield/pty/ztypes_arm64.go
generated
vendored
Normal file
11
vendor/github.com/jesseduffield/pty/ztypes_arm64.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types.go
|
||||||
|
|
||||||
|
// +build arm64
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
type (
|
||||||
|
_C_int int32
|
||||||
|
_C_uint uint32
|
||||||
|
)
|
14
vendor/github.com/jesseduffield/pty/ztypes_dragonfly_amd64.go
generated
vendored
Normal file
14
vendor/github.com/jesseduffield/pty/ztypes_dragonfly_amd64.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types_dragonfly.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
const (
|
||||||
|
_C_SPECNAMELEN = 0x3f
|
||||||
|
)
|
||||||
|
|
||||||
|
type fiodgnameArg struct {
|
||||||
|
Name *byte
|
||||||
|
Len uint32
|
||||||
|
Pad_cgo_0 [4]byte
|
||||||
|
}
|
13
vendor/github.com/jesseduffield/pty/ztypes_freebsd_386.go
generated
vendored
Normal file
13
vendor/github.com/jesseduffield/pty/ztypes_freebsd_386.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types_freebsd.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
const (
|
||||||
|
_C_SPECNAMELEN = 0x3f
|
||||||
|
)
|
||||||
|
|
||||||
|
type fiodgnameArg struct {
|
||||||
|
Len int32
|
||||||
|
Buf *byte
|
||||||
|
}
|
14
vendor/github.com/jesseduffield/pty/ztypes_freebsd_amd64.go
generated
vendored
Normal file
14
vendor/github.com/jesseduffield/pty/ztypes_freebsd_amd64.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types_freebsd.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
const (
|
||||||
|
_C_SPECNAMELEN = 0x3f
|
||||||
|
)
|
||||||
|
|
||||||
|
type fiodgnameArg struct {
|
||||||
|
Len int32
|
||||||
|
Pad_cgo_0 [4]byte
|
||||||
|
Buf *byte
|
||||||
|
}
|
13
vendor/github.com/jesseduffield/pty/ztypes_freebsd_arm.go
generated
vendored
Normal file
13
vendor/github.com/jesseduffield/pty/ztypes_freebsd_arm.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types_freebsd.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
const (
|
||||||
|
_C_SPECNAMELEN = 0x3f
|
||||||
|
)
|
||||||
|
|
||||||
|
type fiodgnameArg struct {
|
||||||
|
Len int32
|
||||||
|
Buf *byte
|
||||||
|
}
|
12
vendor/github.com/jesseduffield/pty/ztypes_mipsx.go
generated
vendored
Normal file
12
vendor/github.com/jesseduffield/pty/ztypes_mipsx.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types.go
|
||||||
|
|
||||||
|
// +build linux
|
||||||
|
// +build mips mipsle mips64 mips64le
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
type (
|
||||||
|
_C_int int32
|
||||||
|
_C_uint uint32
|
||||||
|
)
|
13
vendor/github.com/jesseduffield/pty/ztypes_openbsd_386.go
generated
vendored
Normal file
13
vendor/github.com/jesseduffield/pty/ztypes_openbsd_386.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types_openbsd.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
type ptmget struct {
|
||||||
|
Cfd int32
|
||||||
|
Sfd int32
|
||||||
|
Cn [16]int8
|
||||||
|
Sn [16]int8
|
||||||
|
}
|
||||||
|
|
||||||
|
var ioctl_PTMGET = 0x40287401
|
13
vendor/github.com/jesseduffield/pty/ztypes_openbsd_amd64.go
generated
vendored
Normal file
13
vendor/github.com/jesseduffield/pty/ztypes_openbsd_amd64.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types_openbsd.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
type ptmget struct {
|
||||||
|
Cfd int32
|
||||||
|
Sfd int32
|
||||||
|
Cn [16]int8
|
||||||
|
Sn [16]int8
|
||||||
|
}
|
||||||
|
|
||||||
|
var ioctl_PTMGET = 0x40287401
|
11
vendor/github.com/jesseduffield/pty/ztypes_ppc64.go
generated
vendored
Normal file
11
vendor/github.com/jesseduffield/pty/ztypes_ppc64.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// +build ppc64
|
||||||
|
|
||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
type (
|
||||||
|
_C_int int32
|
||||||
|
_C_uint uint32
|
||||||
|
)
|
11
vendor/github.com/jesseduffield/pty/ztypes_ppc64le.go
generated
vendored
Normal file
11
vendor/github.com/jesseduffield/pty/ztypes_ppc64le.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// +build ppc64le
|
||||||
|
|
||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
type (
|
||||||
|
_C_int int32
|
||||||
|
_C_uint uint32
|
||||||
|
)
|
11
vendor/github.com/jesseduffield/pty/ztypes_s390x.go
generated
vendored
Normal file
11
vendor/github.com/jesseduffield/pty/ztypes_s390x.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// +build s390x
|
||||||
|
|
||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
type (
|
||||||
|
_C_int int32
|
||||||
|
_C_uint uint32
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user