mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-10-05 21:46:49 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2df78b257b | ||
|
|
85017d43a7 | ||
|
|
33fbe2d5c5 | ||
|
|
890fd2a511 | ||
|
|
16a08da10d | ||
|
|
0a04dacb06 | ||
|
|
150aa76923 | ||
|
|
03d838980a | ||
|
|
05c179bfd1 | ||
|
|
b19866545e | ||
|
|
a2077bec48 | ||
|
|
cb68452afd | ||
|
|
57c0fc24de | ||
|
|
2feb187a6c | ||
|
|
cb9aa60830 | ||
|
|
25177dfd2b | ||
|
|
f864eb1e65 | ||
|
|
65f3073e7e | ||
|
|
ff75984796 | ||
|
|
089c55a0b3 | ||
|
|
620e49b8e3 | ||
|
|
3b7e4cf2e6 | ||
|
|
73ad5ec13c | ||
|
|
7296a6bff4 | ||
|
|
5aed1c0499 | ||
|
|
03ceb0b1ba | ||
|
|
3ea79ef4e1 | ||
|
|
fdb2880075 | ||
|
|
404001be8a | ||
|
|
15c64d4bd8 | ||
|
|
331616a5e8 | ||
|
|
82b11bafef |
@@ -11,7 +11,7 @@ require (
|
|||||||
github.com/golang/protobuf v1.3.2 // indirect
|
github.com/golang/protobuf v1.3.2 // indirect
|
||||||
github.com/google/go-cmp v0.3.1 // indirect
|
github.com/google/go-cmp v0.3.1 // indirect
|
||||||
github.com/integrii/flaggy v1.3.0
|
github.com/integrii/flaggy v1.3.0
|
||||||
github.com/jesseduffield/gocui v0.3.1-0.20191110053728-01cdcccd0508
|
github.com/jesseduffield/gocui v0.3.1-0.20191116013947-b13bda319532
|
||||||
github.com/jesseduffield/pty v1.2.1
|
github.com/jesseduffield/pty v1.2.1
|
||||||
github.com/jesseduffield/rollrus v0.0.0-20190701125922-dd028cb0bfd7
|
github.com/jesseduffield/rollrus v0.0.0-20190701125922-dd028cb0bfd7
|
||||||
github.com/jesseduffield/termbox-go v0.0.0-20190630083001-9dd53af7214e // indirect
|
github.com/jesseduffield/termbox-go v0.0.0-20190630083001-9dd53af7214e // indirect
|
||||||
|
|||||||
@@ -75,8 +75,8 @@ github.com/integrii/flaggy v1.3.0 h1:8I5Qqz22C6+EwUqJuaN5ITh77obI8VSg6RwYLe2VB7o
|
|||||||
github.com/integrii/flaggy v1.3.0/go.mod h1:tnTxHeTJbah0gQ6/K0RW0J7fMUBk9MCF5blhm43LNpI=
|
github.com/integrii/flaggy v1.3.0/go.mod h1:tnTxHeTJbah0gQ6/K0RW0J7fMUBk9MCF5blhm43LNpI=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||||
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/gocui v0.3.1-0.20191110053728-01cdcccd0508 h1:8CPQLUe+0QXxnbnUfaMxh1UGxg3rYCqCvbxecC9rrIY=
|
github.com/jesseduffield/gocui v0.3.1-0.20191116013947-b13bda319532 h1:V1Lk2rm5/p27NjnlF2ezzkxDaisHNcveMNueSD7RYgs=
|
||||||
github.com/jesseduffield/gocui v0.3.1-0.20191110053728-01cdcccd0508/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
github.com/jesseduffield/gocui v0.3.1-0.20191116013947-b13bda319532/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||||
github.com/jesseduffield/pty v1.2.1 h1:7xYBiwNH0PpWqC8JmvrPq1a/ksNqyCavzWu9pbBGYWI=
|
github.com/jesseduffield/pty v1.2.1 h1:7xYBiwNH0PpWqC8JmvrPq1a/ksNqyCavzWu9pbBGYWI=
|
||||||
github.com/jesseduffield/pty v1.2.1/go.mod h1:7jlS40+UhOqkZJDIG1B/H21xnuET/+fvbbnHCa8wSIo=
|
github.com/jesseduffield/pty v1.2.1/go.mod h1:7jlS40+UhOqkZJDIG1B/H21xnuET/+fvbbnHCa8wSIo=
|
||||||
github.com/jesseduffield/roll v0.0.0-20190629104057-695be2e62b00 h1:+JaOkfBNYQYlGD7dgru8mCwYNEc5tRRI8mThlVANhSM=
|
github.com/jesseduffield/roll v0.0.0-20190629104057-695be2e62b00 h1:+JaOkfBNYQYlGD7dgru8mCwYNEc5tRRI8mThlVANhSM=
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ type Branch struct {
|
|||||||
|
|
||||||
// GetDisplayStrings returns the display string of branch
|
// GetDisplayStrings returns the display string of branch
|
||||||
func (b *Branch) GetDisplayStrings(isFocused bool) []string {
|
func (b *Branch) GetDisplayStrings(isFocused bool) []string {
|
||||||
displayName := utils.ColoredString(b.Name, b.GetColor())
|
displayName := utils.ColoredString(b.Name, GetBranchColor(b.Name))
|
||||||
if isFocused && b.Selected && b.Pushables != "" && b.Pullables != "" {
|
if isFocused && b.Selected && b.Pushables != "" && b.Pullables != "" {
|
||||||
displayName = fmt.Sprintf("%s ↑%s↓%s", displayName, b.Pushables, b.Pullables)
|
displayName = fmt.Sprintf("%s ↑%s↓%s", displayName, b.Pushables, b.Pullables)
|
||||||
}
|
}
|
||||||
@@ -30,9 +30,11 @@ func (b *Branch) GetDisplayStrings(isFocused bool) []string {
|
|||||||
return []string{b.Recency, displayName}
|
return []string{b.Recency, displayName}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetColor branch color
|
// GetBranchColor branch color
|
||||||
func (b *Branch) GetColor() color.Attribute {
|
func GetBranchColor(name string) color.Attribute {
|
||||||
switch b.getType() {
|
branchType := strings.Split(name, "/")[0]
|
||||||
|
|
||||||
|
switch branchType {
|
||||||
case "feature":
|
case "feature":
|
||||||
return color.FgGreen
|
return color.FgGreen
|
||||||
case "bugfix":
|
case "bugfix":
|
||||||
@@ -43,8 +45,3 @@ func (b *Branch) GetColor() color.Attribute {
|
|||||||
return theme.DefaultTextColor
|
return theme.DefaultTextColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// expected to return feature/bugfix/hotfix or blank string
|
|
||||||
func (b *Branch) getType() string {
|
|
||||||
return strings.Split(b.Name, "/")[0]
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -47,7 +47,9 @@ func (b *BranchListBuilder) obtainCurrentBranch() *Branch {
|
|||||||
|
|
||||||
func (b *BranchListBuilder) obtainReflogBranches() []*Branch {
|
func (b *BranchListBuilder) obtainReflogBranches() []*Branch {
|
||||||
branches := make([]*Branch, 0)
|
branches := make([]*Branch, 0)
|
||||||
rawString, err := b.GitCommand.OSCommand.RunCommandWithOutput("git reflog -n100 --pretty='%cr|%gs' --grep-reflog='checkout: moving' HEAD")
|
// if we directly put this string in RunCommandWithOutput the compiler complains because it thinks it's a format string
|
||||||
|
unescaped := "git reflog -n100 --pretty='%cr|%gs' --grep-reflog='checkout: moving' HEAD"
|
||||||
|
rawString, err := b.GitCommand.OSCommand.RunCommandWithOutput(unescaped)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return branches
|
return branches
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
@@ -14,6 +16,7 @@ type Commit struct {
|
|||||||
DisplayString string
|
DisplayString string
|
||||||
Action string // one of "", "pick", "edit", "squash", "reword", "drop", "fixup"
|
Action string // one of "", "pick", "edit", "squash", "reword", "drop", "fixup"
|
||||||
Copied bool // to know if this commit is ready to be cherry-picked somewhere
|
Copied bool // to know if this commit is ready to be cherry-picked somewhere
|
||||||
|
Tags []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDisplayStrings is a function.
|
// GetDisplayStrings is a function.
|
||||||
@@ -52,9 +55,12 @@ func (c *Commit) GetDisplayStrings(isFocused bool) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
actionString := ""
|
actionString := ""
|
||||||
|
tagString := ""
|
||||||
if c.Action != "" {
|
if c.Action != "" {
|
||||||
actionString = cyan.Sprint(utils.WithPadding(c.Action, 7)) + " "
|
actionString = cyan.Sprint(utils.WithPadding(c.Action, 7)) + " "
|
||||||
|
} else if len(c.Tags) > 0 {
|
||||||
|
tagString = utils.ColoredString(strings.Join(c.Tags, " "), color.FgMagenta) + " "
|
||||||
}
|
}
|
||||||
|
|
||||||
return []string{shaColor.Sprint(c.Sha), actionString + defaultColor.Sprint(c.Name)}
|
return []string{shaColor.Sprint(c.Sha), actionString + tagString + defaultColor.Sprint(c.Name)}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ func (c *CommitListBuilder) GetCommits() ([]*Commit, error) {
|
|||||||
Name: strings.Join(splitLine[1:], " "),
|
Name: strings.Join(splitLine[1:], " "),
|
||||||
Status: status,
|
Status: status,
|
||||||
DisplayString: strings.Join(splitLine, " "),
|
DisplayString: strings.Join(splitLine, " "),
|
||||||
|
// TODO: add tags here
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if rebaseMode != "" {
|
if rebaseMode != "" {
|
||||||
@@ -261,7 +262,7 @@ func (c *CommitListBuilder) getMergeBase() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// swallowing error because it's not a big deal; probably because there are no commits yet
|
// swallowing error because it's not a big deal; probably because there are no commits yet
|
||||||
output, _ := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git merge-base HEAD %s", baseBranch))
|
output, _ := c.OSCommand.RunCommandWithOutput("git merge-base HEAD %s", baseBranch)
|
||||||
return output, nil
|
return output, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -289,6 +289,10 @@ func TestCommitListBuilderGetCommits(t *testing.T) {
|
|||||||
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
|
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
|
||||||
// here's where we are returning the error
|
// here's where we are returning the error
|
||||||
return exec.Command("test")
|
return exec.Command("test")
|
||||||
|
case "branch":
|
||||||
|
assert.EqualValues(t, []string{"branch", "--contains"}, args)
|
||||||
|
// here too
|
||||||
|
return exec.Command("test")
|
||||||
case "rev-parse":
|
case "rev-parse":
|
||||||
assert.EqualValues(t, []string{"rev-parse", "--short", "HEAD"}, args)
|
assert.EqualValues(t, []string{"rev-parse", "--short", "HEAD"}, args)
|
||||||
// here too
|
// here too
|
||||||
|
|||||||
+99
-51
@@ -6,6 +6,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -21,7 +22,13 @@ import (
|
|||||||
gogit "gopkg.in/src-d/go-git.v4"
|
gogit "gopkg.in/src-d/go-git.v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
func verifyInGitRepo(runCmd func(string) error) error {
|
// this takes something like:
|
||||||
|
// * (HEAD detached at 264fc6f5)
|
||||||
|
// remotes
|
||||||
|
// and returns '264fc6f5' as the second match
|
||||||
|
const CurrentBranchNameRegex = `(?m)^\*.*?([^ ]*?)\)?$`
|
||||||
|
|
||||||
|
func verifyInGitRepo(runCmd func(string, ...interface{}) error) error {
|
||||||
return runCmd("git status")
|
return runCmd("git status")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +157,9 @@ func findDotGitDir(stat func(string) (os.FileInfo, error), readFile func(filenam
|
|||||||
|
|
||||||
// GetStashEntries stash entries
|
// GetStashEntries stash entries
|
||||||
func (c *GitCommand) GetStashEntries() []*StashEntry {
|
func (c *GitCommand) GetStashEntries() []*StashEntry {
|
||||||
rawString, _ := c.OSCommand.RunCommandWithOutput("git stash list --pretty='%gs'")
|
// if we directly put this string in RunCommandWithOutput the compiler complains because it thinks it's a format string
|
||||||
|
unescaped := "git stash list --pretty='%gs'"
|
||||||
|
rawString, _ := c.OSCommand.RunCommandWithOutput(unescaped)
|
||||||
stashEntries := []*StashEntry{}
|
stashEntries := []*StashEntry{}
|
||||||
for i, line := range utils.SplitLines(rawString) {
|
for i, line := range utils.SplitLines(rawString) {
|
||||||
stashEntries = append(stashEntries, stashEntryFromLine(line, i))
|
stashEntries = append(stashEntries, stashEntryFromLine(line, i))
|
||||||
@@ -168,7 +177,7 @@ func stashEntryFromLine(line string, index int) *StashEntry {
|
|||||||
|
|
||||||
// GetStashEntryDiff stash diff
|
// GetStashEntryDiff stash diff
|
||||||
func (c *GitCommand) GetStashEntryDiff(index int) (string, error) {
|
func (c *GitCommand) GetStashEntryDiff(index int) (string, error) {
|
||||||
return c.OSCommand.RunCommandWithOutput("git stash show -p --color stash@{" + fmt.Sprint(index) + "}")
|
return c.OSCommand.RunCommandWithOutput("git stash show -p --color stash@{%d}", index)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStatusFiles git status files
|
// GetStatusFiles git status files
|
||||||
@@ -206,13 +215,13 @@ func (c *GitCommand) GetStatusFiles() []*File {
|
|||||||
|
|
||||||
// StashDo modify stash
|
// StashDo modify stash
|
||||||
func (c *GitCommand) StashDo(index int, method string) error {
|
func (c *GitCommand) StashDo(index int, method string) error {
|
||||||
return c.OSCommand.RunCommand(fmt.Sprintf("git stash %s stash@{%d}", method, index))
|
return c.OSCommand.RunCommand("git stash %s stash@{%d}", method, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StashSave save stash
|
// StashSave save stash
|
||||||
// TODO: before calling this, check if there is anything to save
|
// TODO: before calling this, check if there is anything to save
|
||||||
func (c *GitCommand) StashSave(message string) error {
|
func (c *GitCommand) StashSave(message string) error {
|
||||||
return c.OSCommand.RunCommand(fmt.Sprintf("git stash save %s", c.OSCommand.Quote(message)))
|
return c.OSCommand.RunCommand("git stash save %s", c.OSCommand.Quote(message))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MergeStatusFiles merge status files
|
// MergeStatusFiles merge status files
|
||||||
@@ -264,23 +273,22 @@ func (c *GitCommand) ResetAndClean() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *GitCommand) GetCurrentBranchUpstreamDifferenceCount() (string, string) {
|
func (c *GitCommand) GetCurrentBranchUpstreamDifferenceCount() (string, string) {
|
||||||
return c.GetCommitDifferences("HEAD", "@{u}")
|
return c.GetCommitDifferences("HEAD", "HEAD@{u}")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GitCommand) GetBranchUpstreamDifferenceCount(branchName string) (string, string) {
|
func (c *GitCommand) GetBranchUpstreamDifferenceCount(branchName string) (string, string) {
|
||||||
upstream := "origin" // hardcoded for now
|
return c.GetCommitDifferences(branchName, branchName+"@{u}")
|
||||||
return c.GetCommitDifferences(branchName, fmt.Sprintf("%s/%s", upstream, branchName))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCommitDifferences checks how many pushables/pullables there are for the
|
// GetCommitDifferences checks how many pushables/pullables there are for the
|
||||||
// current branch
|
// current branch
|
||||||
func (c *GitCommand) GetCommitDifferences(from, to string) (string, string) {
|
func (c *GitCommand) GetCommitDifferences(from, to string) (string, string) {
|
||||||
command := "git rev-list %s..%s --count"
|
command := "git rev-list %s..%s --count"
|
||||||
pushableCount, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf(command, to, from))
|
pushableCount, err := c.OSCommand.RunCommandWithOutput(command, to, from)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "?", "?"
|
return "?", "?"
|
||||||
}
|
}
|
||||||
pullableCount, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf(command, from, to))
|
pullableCount, err := c.OSCommand.RunCommandWithOutput(command, from, to)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "?", "?"
|
return "?", "?"
|
||||||
}
|
}
|
||||||
@@ -289,7 +297,7 @@ func (c *GitCommand) GetCommitDifferences(from, to string) (string, string) {
|
|||||||
|
|
||||||
// RenameCommit renames the topmost commit with the given name
|
// RenameCommit renames the topmost commit with the given name
|
||||||
func (c *GitCommand) RenameCommit(name string) error {
|
func (c *GitCommand) RenameCommit(name string) error {
|
||||||
return c.OSCommand.RunCommand(fmt.Sprintf("git commit --allow-empty --amend -m %s", c.OSCommand.Quote(name)))
|
return c.OSCommand.RunCommand("git commit --allow-empty --amend -m %s", c.OSCommand.Quote(name))
|
||||||
}
|
}
|
||||||
|
|
||||||
// RebaseBranch interactive rebases onto a branch
|
// RebaseBranch interactive rebases onto a branch
|
||||||
@@ -314,22 +322,25 @@ func (c *GitCommand) Fetch(unamePassQuestion func(string) string, canAskForCrede
|
|||||||
|
|
||||||
// ResetToCommit reset to commit
|
// ResetToCommit reset to commit
|
||||||
func (c *GitCommand) ResetToCommit(sha string, strength string) error {
|
func (c *GitCommand) ResetToCommit(sha string, strength string) error {
|
||||||
return c.OSCommand.RunCommand(fmt.Sprintf("git reset --%s %s", strength, sha))
|
return c.OSCommand.RunCommand("git reset --%s %s", strength, sha)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBranch create new branch
|
// NewBranch create new branch
|
||||||
func (c *GitCommand) NewBranch(name string) error {
|
func (c *GitCommand) NewBranch(name string) error {
|
||||||
return c.OSCommand.RunCommand(fmt.Sprintf("git checkout -b %s", name))
|
return c.OSCommand.RunCommand("git checkout -b %s", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CurrentBranchName is a function.
|
// CurrentBranchName is a function.
|
||||||
func (c *GitCommand) CurrentBranchName() (string, error) {
|
func (c *GitCommand) CurrentBranchName() (string, error) {
|
||||||
branchName, err := c.OSCommand.RunCommandWithOutput("git symbolic-ref --short HEAD")
|
branchName, err := c.OSCommand.RunCommandWithOutput("git symbolic-ref --short HEAD")
|
||||||
if err != nil {
|
if err != nil || branchName == "HEAD\n" {
|
||||||
branchName, err = c.OSCommand.RunCommandWithOutput("git rev-parse --short HEAD")
|
output, err := c.OSCommand.RunCommandWithOutput("git branch --contains")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
re := regexp.MustCompile(CurrentBranchNameRegex)
|
||||||
|
match := re.FindStringSubmatch(output)
|
||||||
|
branchName = match[1]
|
||||||
}
|
}
|
||||||
return utils.TrimTrailingNewline(branchName), nil
|
return utils.TrimTrailingNewline(branchName), nil
|
||||||
}
|
}
|
||||||
@@ -342,7 +353,7 @@ func (c *GitCommand) DeleteBranch(branch string, force bool) error {
|
|||||||
command = "git branch -D"
|
command = "git branch -D"
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.OSCommand.RunCommand(fmt.Sprintf("%s %s", command, branch))
|
return c.OSCommand.RunCommand("%s %s", command, branch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListStash list stash
|
// ListStash list stash
|
||||||
@@ -352,7 +363,7 @@ func (c *GitCommand) ListStash() (string, error) {
|
|||||||
|
|
||||||
// Merge merge
|
// Merge merge
|
||||||
func (c *GitCommand) Merge(branchName string) error {
|
func (c *GitCommand) Merge(branchName string) error {
|
||||||
return c.OSCommand.RunCommand(fmt.Sprintf("git merge --no-edit %s", branchName))
|
return c.OSCommand.RunCommand("git merge --no-edit %s", branchName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AbortMerge abort merge
|
// AbortMerge abort merge
|
||||||
@@ -409,18 +420,18 @@ func (c *GitCommand) Push(branchName string, force bool, upstream string, ask fu
|
|||||||
setUpstreamArg = "--set-upstream " + upstream
|
setUpstreamArg = "--set-upstream " + upstream
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := fmt.Sprintf("git push %s %s", forceFlag, setUpstreamArg)
|
cmd := fmt.Sprintf("git push --follow-tags %s %s", forceFlag, setUpstreamArg)
|
||||||
return c.OSCommand.DetectUnamePass(cmd, ask)
|
return c.OSCommand.DetectUnamePass(cmd, ask)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CatFile obtains the content of a file
|
// CatFile obtains the content of a file
|
||||||
func (c *GitCommand) CatFile(fileName string) (string, error) {
|
func (c *GitCommand) CatFile(fileName string) (string, error) {
|
||||||
return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("cat %s", c.OSCommand.Quote(fileName)))
|
return c.OSCommand.RunCommandWithOutput("cat %s", c.OSCommand.Quote(fileName))
|
||||||
}
|
}
|
||||||
|
|
||||||
// StageFile stages a file
|
// StageFile stages a file
|
||||||
func (c *GitCommand) StageFile(fileName string) error {
|
func (c *GitCommand) StageFile(fileName string) error {
|
||||||
return c.OSCommand.RunCommand(fmt.Sprintf("git add %s", c.OSCommand.Quote(fileName)))
|
return c.OSCommand.RunCommand("git add %s", c.OSCommand.Quote(fileName))
|
||||||
}
|
}
|
||||||
|
|
||||||
// StageAll stages all files
|
// StageAll stages all files
|
||||||
@@ -443,7 +454,7 @@ func (c *GitCommand) UnStageFile(fileName string, tracked bool) error {
|
|||||||
// renamed files look like "file1 -> file2"
|
// renamed files look like "file1 -> file2"
|
||||||
fileNames := strings.Split(fileName, " -> ")
|
fileNames := strings.Split(fileName, " -> ")
|
||||||
for _, name := range fileNames {
|
for _, name := range fileNames {
|
||||||
if err := c.OSCommand.RunCommand(fmt.Sprintf(command, c.OSCommand.Quote(name))); err != nil {
|
if err := c.OSCommand.RunCommand(command, c.OSCommand.Quote(name)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -487,7 +498,7 @@ func (c *GitCommand) DiscardAllFileChanges(file *File) error {
|
|||||||
// if the file isn't tracked, we assume you want to delete it
|
// if the file isn't tracked, we assume you want to delete it
|
||||||
quotedFileName := c.OSCommand.Quote(file.Name)
|
quotedFileName := c.OSCommand.Quote(file.Name)
|
||||||
if file.HasStagedChanges || file.HasMergeConflicts {
|
if file.HasStagedChanges || file.HasMergeConflicts {
|
||||||
if err := c.OSCommand.RunCommand(fmt.Sprintf("git reset -- %s", quotedFileName)); err != nil {
|
if err := c.OSCommand.RunCommand("git reset -- %s", quotedFileName); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -501,7 +512,7 @@ func (c *GitCommand) DiscardAllFileChanges(file *File) error {
|
|||||||
// DiscardUnstagedFileChanges directly
|
// DiscardUnstagedFileChanges directly
|
||||||
func (c *GitCommand) DiscardUnstagedFileChanges(file *File) error {
|
func (c *GitCommand) DiscardUnstagedFileChanges(file *File) error {
|
||||||
quotedFileName := c.OSCommand.Quote(file.Name)
|
quotedFileName := c.OSCommand.Quote(file.Name)
|
||||||
return c.OSCommand.RunCommand(fmt.Sprintf("git checkout -- %s", quotedFileName))
|
return c.OSCommand.RunCommand("git checkout -- %s", quotedFileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checkout checks out a branch, with --force if you set the force arg to true
|
// Checkout checks out a branch, with --force if you set the force arg to true
|
||||||
@@ -510,7 +521,7 @@ func (c *GitCommand) Checkout(branch string, force bool) error {
|
|||||||
if force {
|
if force {
|
||||||
forceArg = "--force "
|
forceArg = "--force "
|
||||||
}
|
}
|
||||||
return c.OSCommand.RunCommand(fmt.Sprintf("git checkout %s %s", forceArg, branch))
|
return c.OSCommand.RunCommand("git checkout %s %s", forceArg, branch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrepareCommitSubProcess prepares a subprocess for `git commit`
|
// PrepareCommitSubProcess prepares a subprocess for `git commit`
|
||||||
@@ -527,11 +538,11 @@ func (c *GitCommand) PrepareCommitAmendSubProcess() *exec.Cmd {
|
|||||||
// Currently it limits the result to 100 commits, but when we get async stuff
|
// Currently it limits the result to 100 commits, but when we get async stuff
|
||||||
// working we can do lazy loading
|
// working we can do lazy loading
|
||||||
func (c *GitCommand) GetBranchGraph(branchName string) (string, error) {
|
func (c *GitCommand) GetBranchGraph(branchName string) (string, error) {
|
||||||
return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git log --graph --color --abbrev-commit --decorate --date=relative --pretty=medium -100 %s", branchName))
|
return c.OSCommand.RunCommandWithOutput("git log --graph --color --abbrev-commit --decorate --date=relative --pretty=medium -100 %s", branchName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GitCommand) GetUpstreamForBranch(branchName string) (string, error) {
|
func (c *GitCommand) GetUpstreamForBranch(branchName string) (string, error) {
|
||||||
output, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git rev-parse --abbrev-ref --symbolic-full-name %s@{u}", branchName))
|
output, err := c.OSCommand.RunCommandWithOutput("git rev-parse --abbrev-ref --symbolic-full-name %s@{u}", branchName)
|
||||||
return strings.TrimSpace(output), err
|
return strings.TrimSpace(output), err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -542,13 +553,13 @@ func (c *GitCommand) Ignore(filename string) error {
|
|||||||
|
|
||||||
// Show shows the diff of a commit
|
// Show shows the diff of a commit
|
||||||
func (c *GitCommand) Show(sha string) (string, error) {
|
func (c *GitCommand) Show(sha string) (string, error) {
|
||||||
show, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git show --color --no-renames %s", sha))
|
show, err := c.OSCommand.RunCommandWithOutput("git show --color --no-renames %s", sha)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// if this is a merge commit, we need to go a step further and get the diff between the two branches we merged
|
// if this is a merge commit, we need to go a step further and get the diff between the two branches we merged
|
||||||
revList, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git rev-list -1 --merges %s^...%s", sha, sha))
|
revList, err := c.OSCommand.RunCommandWithOutput("git rev-list -1 --merges %s^...%s", sha, sha)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// turns out we get an error here when it's the first commit. We'll just return the original show
|
// turns out we get an error here when it's the first commit. We'll just return the original show
|
||||||
return show, nil
|
return show, nil
|
||||||
@@ -570,7 +581,7 @@ func (c *GitCommand) Show(sha string) (string, error) {
|
|||||||
return show, nil
|
return show, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
mergeDiff, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git diff --color %s...%s", secondLineWords[1], secondLineWords[2]))
|
mergeDiff, err := c.OSCommand.RunCommandWithOutput("git diff --color %s...%s", secondLineWords[1], secondLineWords[2])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -585,10 +596,10 @@ func (c *GitCommand) GetRemoteURL() string {
|
|||||||
|
|
||||||
// CheckRemoteBranchExists Returns remote branch
|
// CheckRemoteBranchExists Returns remote branch
|
||||||
func (c *GitCommand) CheckRemoteBranchExists(branch *Branch) bool {
|
func (c *GitCommand) CheckRemoteBranchExists(branch *Branch) bool {
|
||||||
_, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf(
|
_, err := c.OSCommand.RunCommandWithOutput(
|
||||||
"git show-ref --verify -- refs/remotes/origin/%s",
|
"git show-ref --verify -- refs/remotes/origin/%s",
|
||||||
branch.Name,
|
branch.Name,
|
||||||
))
|
)
|
||||||
|
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
@@ -610,10 +621,8 @@ func (c *GitCommand) Diff(file *File, plain bool, cached bool) string {
|
|||||||
colorArg = ""
|
colorArg = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
command := fmt.Sprintf("git diff %s %s %s %s", colorArg, cachedArg, trackedArg, fileName)
|
|
||||||
|
|
||||||
// for now we assume an error means the file was deleted
|
// for now we assume an error means the file was deleted
|
||||||
s, _ := c.OSCommand.RunCommandWithOutput(command)
|
s, _ := c.OSCommand.RunCommandWithOutput("git diff %s %s %s %s", colorArg, cachedArg, trackedArg, fileName)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -629,12 +638,11 @@ func (c *GitCommand) ApplyPatch(patch string, flags ...string) error {
|
|||||||
flagStr += " --" + flag
|
flagStr += " --" + flag
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.OSCommand.RunCommand(fmt.Sprintf("git apply %s %s", flagStr, c.OSCommand.Quote(filepath)))
|
return c.OSCommand.RunCommand("git apply %s %s", flagStr, c.OSCommand.Quote(filepath))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GitCommand) FastForward(branchName string) error {
|
func (c *GitCommand) FastForward(branchName string, remoteName string, remoteBranchName string) error {
|
||||||
upstream := "origin" // hardcoding for now
|
return c.OSCommand.RunCommand("git fetch %s %s:%s", remoteName, remoteBranchName, branchName)
|
||||||
return c.OSCommand.RunCommand(fmt.Sprintf("git fetch %s %s:%s", upstream, branchName, branchName))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GitCommand) RunSkipEditorCommand(command string) error {
|
func (c *GitCommand) RunSkipEditorCommand(command string) error {
|
||||||
@@ -854,7 +862,7 @@ func (c *GitCommand) MoveTodoDown(index int) error {
|
|||||||
|
|
||||||
// Revert reverts the selected commit by sha
|
// Revert reverts the selected commit by sha
|
||||||
func (c *GitCommand) Revert(sha string) error {
|
func (c *GitCommand) Revert(sha string) error {
|
||||||
return c.OSCommand.RunCommand(fmt.Sprintf("git revert %s", sha))
|
return c.OSCommand.RunCommand("git revert %s", sha)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CherryPickCommits begins an interactive rebase with the given shas being cherry picked onto HEAD
|
// CherryPickCommits begins an interactive rebase with the given shas being cherry picked onto HEAD
|
||||||
@@ -874,8 +882,7 @@ func (c *GitCommand) CherryPickCommits(commits []*Commit) error {
|
|||||||
|
|
||||||
// GetCommitFiles get the specified commit files
|
// GetCommitFiles get the specified commit files
|
||||||
func (c *GitCommand) GetCommitFiles(commitSha string, patchManager *PatchManager) ([]*CommitFile, error) {
|
func (c *GitCommand) GetCommitFiles(commitSha string, patchManager *PatchManager) ([]*CommitFile, error) {
|
||||||
cmd := fmt.Sprintf("git show --pretty= --name-only --no-renames %s", commitSha)
|
files, err := c.OSCommand.RunCommandWithOutput("git show --pretty= --name-only --no-renames %s", commitSha)
|
||||||
files, err := c.OSCommand.RunCommandWithOutput(cmd)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -905,14 +912,12 @@ func (c *GitCommand) ShowCommitFile(commitSha, fileName string, plain bool) (str
|
|||||||
if plain {
|
if plain {
|
||||||
colorArg = ""
|
colorArg = ""
|
||||||
}
|
}
|
||||||
cmd := fmt.Sprintf("git show --no-renames %s %s -- %s", colorArg, commitSha, fileName)
|
return c.OSCommand.RunCommandWithOutput("git show --no-renames %s %s -- %s", colorArg, commitSha, fileName)
|
||||||
return c.OSCommand.RunCommandWithOutput(cmd)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckoutFile checks out the file for the given commit
|
// CheckoutFile checks out the file for the given commit
|
||||||
func (c *GitCommand) CheckoutFile(commitSha, fileName string) error {
|
func (c *GitCommand) CheckoutFile(commitSha, fileName string) error {
|
||||||
cmd := fmt.Sprintf("git checkout %s %s", commitSha, fileName)
|
return c.OSCommand.RunCommand("git checkout %s %s", commitSha, fileName)
|
||||||
return c.OSCommand.RunCommand(cmd)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DiscardOldFileChanges discards changes to a file from an old commit
|
// DiscardOldFileChanges discards changes to a file from an old commit
|
||||||
@@ -922,7 +927,7 @@ func (c *GitCommand) DiscardOldFileChanges(commits []*Commit, commitIndex int, f
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check if file exists in previous commit (this command returns an error if the file doesn't exist)
|
// check if file exists in previous commit (this command returns an error if the file doesn't exist)
|
||||||
if err := c.OSCommand.RunCommand(fmt.Sprintf("git cat-file -e HEAD^:%s", fileName)); err != nil {
|
if err := c.OSCommand.RunCommand("git cat-file -e HEAD^:%s", fileName); err != nil {
|
||||||
if err := c.OSCommand.Remove(fileName); err != nil {
|
if err := c.OSCommand.Remove(fileName); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -968,14 +973,12 @@ func (c *GitCommand) ResetSoftHead() error {
|
|||||||
|
|
||||||
// DiffCommits show diff between commits
|
// DiffCommits show diff between commits
|
||||||
func (c *GitCommand) DiffCommits(sha1, sha2 string) (string, error) {
|
func (c *GitCommand) DiffCommits(sha1, sha2 string) (string, error) {
|
||||||
cmd := fmt.Sprintf("git diff --color %s %s", sha1, sha2)
|
return c.OSCommand.RunCommandWithOutput("git diff --color %s %s", sha1, sha2)
|
||||||
return c.OSCommand.RunCommandWithOutput(cmd)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateFixupCommit creates a commit that fixes up a previous commit
|
// CreateFixupCommit creates a commit that fixes up a previous commit
|
||||||
func (c *GitCommand) CreateFixupCommit(sha string) error {
|
func (c *GitCommand) CreateFixupCommit(sha string) error {
|
||||||
cmd := fmt.Sprintf("git commit --fixup=%s", sha)
|
return c.OSCommand.RunCommand("git commit --fixup=%s", sha)
|
||||||
return c.OSCommand.RunCommand(cmd)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SquashAllAboveFixupCommits squashes all fixup! commits above the given one
|
// SquashAllAboveFixupCommits squashes all fixup! commits above the given one
|
||||||
@@ -1059,5 +1062,50 @@ func (c *GitCommand) BeginInteractiveRebaseForCommit(commits []*Commit, commitIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *GitCommand) SetUpstreamBranch(upstream string) error {
|
func (c *GitCommand) SetUpstreamBranch(upstream string) error {
|
||||||
return c.OSCommand.RunCommand(fmt.Sprintf("git branch -u %s", upstream))
|
return c.OSCommand.RunCommand("git branch -u %s", upstream)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GitCommand) AddRemote(name string, url string) error {
|
||||||
|
return c.OSCommand.RunCommand("git remote add %s %s", name, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GitCommand) RemoveRemote(name string) error {
|
||||||
|
return c.OSCommand.RunCommand("git remote remove %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GitCommand) IsHeadDetached() bool {
|
||||||
|
err := c.OSCommand.RunCommand("git symbolic-ref -q HEAD")
|
||||||
|
return err != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GitCommand) DeleteRemoteBranch(remoteName string, branchName string) error {
|
||||||
|
return c.OSCommand.RunCommand("git push %s --delete %s", remoteName, branchName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GitCommand) SetBranchUpstream(remoteName string, remoteBranchName string, branchName string) error {
|
||||||
|
return c.OSCommand.RunCommand("git branch --set-upstream-to=%s/%s %s", remoteName, remoteBranchName, branchName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GitCommand) RenameRemote(oldRemoteName string, newRemoteName string) error {
|
||||||
|
return c.OSCommand.RunCommand("git remote rename %s %s", oldRemoteName, newRemoteName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GitCommand) UpdateRemoteUrl(remoteName string, updatedUrl string) error {
|
||||||
|
return c.OSCommand.RunCommand("git remote set-url %s %s", remoteName, updatedUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GitCommand) CreateLightweightTag(tagName string, commitSha string) error {
|
||||||
|
return c.OSCommand.RunCommand("git tag %s %s", tagName, commitSha)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GitCommand) ShowTag(tagName string) (string, error) {
|
||||||
|
return c.OSCommand.RunCommandWithOutput("git tag -n99 %s", tagName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GitCommand) DeleteTag(tagName string) error {
|
||||||
|
return c.OSCommand.RunCommand("git tag -d %s", tagName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GitCommand) PushTag(remoteName string, tagName string) error {
|
||||||
|
return c.OSCommand.RunCommand("git push %s %s", remoteName, tagName)
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-10
@@ -58,14 +58,14 @@ func (f fileInfoMock) Sys() interface{} {
|
|||||||
func TestVerifyInGitRepo(t *testing.T) {
|
func TestVerifyInGitRepo(t *testing.T) {
|
||||||
type scenario struct {
|
type scenario struct {
|
||||||
testName string
|
testName string
|
||||||
runCmd func(string) error
|
runCmd func(string, ...interface{}) error
|
||||||
test func(error)
|
test func(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
scenarios := []scenario{
|
scenarios := []scenario{
|
||||||
{
|
{
|
||||||
"Valid git repository",
|
"Valid git repository",
|
||||||
func(string) error {
|
func(string, ...interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
func(err error) {
|
func(err error) {
|
||||||
@@ -74,7 +74,7 @@ func TestVerifyInGitRepo(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Not a valid git repository",
|
"Not a valid git repository",
|
||||||
func(string) error {
|
func(string, ...interface{}) error {
|
||||||
return fmt.Errorf("fatal: Not a git repository (or any of the parent directories): .git")
|
return fmt.Errorf("fatal: Not a git repository (or any of the parent directories): .git")
|
||||||
},
|
},
|
||||||
func(err error) {
|
func(err error) {
|
||||||
@@ -990,7 +990,7 @@ func TestGitCommandPush(t *testing.T) {
|
|||||||
"Push with force disabled",
|
"Push with force disabled",
|
||||||
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"}, args)
|
assert.EqualValues(t, []string{"push", "--follow-tags"}, args)
|
||||||
|
|
||||||
return exec.Command("echo")
|
return exec.Command("echo")
|
||||||
},
|
},
|
||||||
@@ -1003,7 +1003,7 @@ func TestGitCommandPush(t *testing.T) {
|
|||||||
"Push with force enabled",
|
"Push with force enabled",
|
||||||
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", "--force-with-lease"}, args)
|
assert.EqualValues(t, []string{"push", "--follow-tags", "--force-with-lease"}, args)
|
||||||
|
|
||||||
return exec.Command("echo")
|
return exec.Command("echo")
|
||||||
},
|
},
|
||||||
@@ -1016,7 +1016,7 @@ func TestGitCommandPush(t *testing.T) {
|
|||||||
"Push with an error occurring",
|
"Push with an error occurring",
|
||||||
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"}, args)
|
assert.EqualValues(t, []string{"push", "--follow-tags"}, args)
|
||||||
return exec.Command("test")
|
return exec.Command("test")
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
@@ -1639,7 +1639,7 @@ func TestGitCommandCurrentBranchName(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"falls back to git rev-parse if symbolic-ref fails",
|
"falls back to git `git branch --contains` if symbolic-ref fails",
|
||||||
func(cmd string, args ...string) *exec.Cmd {
|
func(cmd string, args ...string) *exec.Cmd {
|
||||||
assert.EqualValues(t, "git", cmd)
|
assert.EqualValues(t, "git", cmd)
|
||||||
|
|
||||||
@@ -1647,9 +1647,9 @@ func TestGitCommandCurrentBranchName(t *testing.T) {
|
|||||||
case "symbolic-ref":
|
case "symbolic-ref":
|
||||||
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
|
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
|
||||||
return exec.Command("test")
|
return exec.Command("test")
|
||||||
case "rev-parse":
|
case "branch":
|
||||||
assert.EqualValues(t, []string{"rev-parse", "--short", "HEAD"}, args)
|
assert.EqualValues(t, []string{"branch", "--contains"}, args)
|
||||||
return exec.Command("echo", "master")
|
return exec.Command("echo", "* master")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *GitCommand) GetRemotes() ([]*Remote, error) {
|
||||||
|
// get remote branches
|
||||||
|
remoteBranchesStr, err := c.OSCommand.RunCommandWithOutput("git for-each-ref --format='%%(refname:strip=2)' refs/remotes")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
goGitRemotes, err := c.Repo.Remotes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// first step is to get our remotes from go-git
|
||||||
|
remotes := make([]*Remote, len(goGitRemotes))
|
||||||
|
for i, goGitRemote := range goGitRemotes {
|
||||||
|
remoteName := goGitRemote.Config().Name
|
||||||
|
|
||||||
|
re := regexp.MustCompile(fmt.Sprintf("%s\\/(.*)", remoteName))
|
||||||
|
matches := re.FindAllStringSubmatch(remoteBranchesStr, -1)
|
||||||
|
branches := make([]*RemoteBranch, len(matches))
|
||||||
|
for j, match := range matches {
|
||||||
|
branches[j] = &RemoteBranch{
|
||||||
|
Name: match[1],
|
||||||
|
RemoteName: remoteName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remotes[i] = &Remote{
|
||||||
|
Name: goGitRemote.Config().Name,
|
||||||
|
Urls: goGitRemote.Config().URLs,
|
||||||
|
Branches: branches,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now lets sort our remotes by name alphabetically
|
||||||
|
sort.Slice(remotes, func(i, j int) bool {
|
||||||
|
// we want origin at the top because we'll be most likely to want it
|
||||||
|
if remotes[i].Name == "origin" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if remotes[j].Name == "origin" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return strings.ToLower(remotes[i].Name) < strings.ToLower(remotes[j].Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
return remotes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
const semverRegex = `v?((\d+\.?)+)([^\d]?.*)`
|
||||||
|
|
||||||
|
func (c *GitCommand) GetTags() ([]*Tag, error) {
|
||||||
|
// get remote branches
|
||||||
|
remoteBranchesStr, err := c.OSCommand.RunCommandWithOutput(`git tag --list`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
content := utils.TrimTrailingNewline(remoteBranchesStr)
|
||||||
|
if content == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
split := strings.Split(content, "\n")
|
||||||
|
|
||||||
|
// first step is to get our remotes from go-git
|
||||||
|
tags := make([]*Tag, len(split))
|
||||||
|
for i, tagName := range split {
|
||||||
|
|
||||||
|
tags[i] = &Tag{
|
||||||
|
Name: tagName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now lets sort our tags by name numerically
|
||||||
|
re := regexp.MustCompile(semverRegex)
|
||||||
|
|
||||||
|
// the reason this is complicated is because we're both sorting alphabetically
|
||||||
|
// and when we're dealing with semver strings
|
||||||
|
sort.Slice(tags, func(i, j int) bool {
|
||||||
|
a := tags[i].Name
|
||||||
|
b := tags[j].Name
|
||||||
|
|
||||||
|
matchA := re.FindStringSubmatch(a)
|
||||||
|
matchB := re.FindStringSubmatch(b)
|
||||||
|
|
||||||
|
if len(matchA) > 0 && len(matchB) > 0 {
|
||||||
|
numbersA := strings.Split(matchA[1], ".")
|
||||||
|
numbersB := strings.Split(matchB[1], ".")
|
||||||
|
k := 0
|
||||||
|
for {
|
||||||
|
if len(numbersA) == k && len(numbersB) == k {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if len(numbersA) == k {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if len(numbersB) == k {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if mustConvertToInt(numbersA[k]) < mustConvertToInt(numbersB[k]) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if mustConvertToInt(numbersA[k]) > mustConvertToInt(numbersB[k]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
k++
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.ToLower(matchA[3]) < strings.ToLower(matchB[3])
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.ToLower(a) < strings.ToLower(b)
|
||||||
|
})
|
||||||
|
|
||||||
|
return tags, nil
|
||||||
|
}
|
||||||
+10
-3
@@ -1,6 +1,7 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -58,7 +59,13 @@ func (c *OSCommand) SetCommand(cmd func(string, ...string) *exec.Cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RunCommandWithOutput wrapper around commands returning their output and error
|
// RunCommandWithOutput wrapper around commands returning their output and error
|
||||||
func (c *OSCommand) RunCommandWithOutput(command string) (string, error) {
|
// NOTE: because this takes a format string followed by format args, you'll need
|
||||||
|
// to escape any percentage signs via '%%'.
|
||||||
|
func (c *OSCommand) RunCommandWithOutput(formatString string, formatArgs ...interface{}) (string, error) {
|
||||||
|
command := formatString
|
||||||
|
if formatArgs != nil {
|
||||||
|
command = fmt.Sprintf(formatString, formatArgs...)
|
||||||
|
}
|
||||||
c.Log.WithField("command", command).Info("RunCommand")
|
c.Log.WithField("command", command).Info("RunCommand")
|
||||||
cmd := c.ExecutableFromString(command)
|
cmd := c.ExecutableFromString(command)
|
||||||
return sanitisedCommandOutput(cmd.CombinedOutput())
|
return sanitisedCommandOutput(cmd.CombinedOutput())
|
||||||
@@ -114,8 +121,8 @@ func (c *OSCommand) DetectUnamePass(command string, ask func(string) string) err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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(formatString string, formatArgs ...interface{}) error {
|
||||||
_, err := c.RunCommandWithOutput(command)
|
_, err := c.RunCommandWithOutput(formatString, formatArgs...)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Remote : A git remote
|
||||||
|
type Remote struct {
|
||||||
|
Name string
|
||||||
|
Urls []string
|
||||||
|
Selected bool
|
||||||
|
Branches []*RemoteBranch
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDisplayStrings returns the display string of a remote
|
||||||
|
func (r *Remote) GetDisplayStrings(isFocused bool) []string {
|
||||||
|
|
||||||
|
branchCount := len(r.Branches)
|
||||||
|
|
||||||
|
return []string{r.Name, utils.ColoredString(fmt.Sprintf("%d branches", branchCount), color.FgBlue)}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Remote Branch : A git remote branch
|
||||||
|
type RemoteBranch struct {
|
||||||
|
Name string
|
||||||
|
Selected bool
|
||||||
|
RemoteName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDisplayStrings returns the display string of branch
|
||||||
|
func (b *RemoteBranch) GetDisplayStrings(isFocused bool) []string {
|
||||||
|
displayName := utils.ColoredString(b.Name, GetBranchColor(b.Name))
|
||||||
|
|
||||||
|
return []string{displayName}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
// Tag : A git tag
|
||||||
|
type Tag struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDisplayStrings returns the display string of a remote
|
||||||
|
func (r *Tag) GetDisplayStrings(isFocused bool) []string {
|
||||||
|
return []string{r.Name}
|
||||||
|
}
|
||||||
+120
-56
@@ -21,14 +21,6 @@ func (gui *Gui) getSelectedBranch() *commands.Branch {
|
|||||||
return gui.State.Branches[selectedLine]
|
return gui.State.Branches[selectedLine]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleBranchesClick(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
itemCount := len(gui.State.Branches)
|
|
||||||
handleSelect := gui.handleBranchSelect
|
|
||||||
selectedLine := &gui.State.Panels.Branches.SelectedLine
|
|
||||||
|
|
||||||
return gui.handleClick(v, itemCount, selectedLine, handleSelect)
|
|
||||||
}
|
|
||||||
|
|
||||||
// may want to standardise how these select methods work
|
// may want to standardise how these select methods work
|
||||||
func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error {
|
||||||
if gui.popupPanelFocused() {
|
if gui.popupPanelFocused() {
|
||||||
@@ -56,7 +48,7 @@ func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error {
|
|||||||
}()
|
}()
|
||||||
go func() {
|
go func() {
|
||||||
upstream, _ := gui.GitCommand.GetUpstreamForBranch(branch.Name)
|
upstream, _ := gui.GitCommand.GetUpstreamForBranch(branch.Name)
|
||||||
if strings.Contains(upstream, "no upstream configured for branch") {
|
if strings.Contains(upstream, "no upstream configured for branch") || strings.Contains(upstream, "unknown revision or path not in the working tree") {
|
||||||
upstream = gui.Tr.SLocalize("notTrackingRemote")
|
upstream = gui.Tr.SLocalize("notTrackingRemote")
|
||||||
}
|
}
|
||||||
graph, err := gui.GitCommand.GetBranchGraph(branch.Name)
|
graph, err := gui.GitCommand.GetBranchGraph(branch.Name)
|
||||||
@@ -84,6 +76,14 @@ func (gui *Gui) RenderSelectedBranchUpstreamDifferences() error {
|
|||||||
// gui.refreshStatus is called at the end of this because that's when we can
|
// gui.refreshStatus is called at the end of this because that's when we can
|
||||||
// be sure there is a state.Branches array to pick the current branch from
|
// be sure there is a state.Branches array to pick the current branch from
|
||||||
func (gui *Gui) refreshBranches(g *gocui.Gui) error {
|
func (gui *Gui) refreshBranches(g *gocui.Gui) error {
|
||||||
|
if err := gui.refreshRemotes(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gui.refreshTags(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
g.Update(func(g *gocui.Gui) error {
|
g.Update(func(g *gocui.Gui) error {
|
||||||
builder, err := commands.NewBranchListBuilder(gui.Log, gui.GitCommand)
|
builder, err := commands.NewBranchListBuilder(gui.Log, gui.GitCommand)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -91,9 +91,12 @@ func (gui *Gui) refreshBranches(g *gocui.Gui) error {
|
|||||||
}
|
}
|
||||||
gui.State.Branches = builder.Build()
|
gui.State.Branches = builder.Build()
|
||||||
|
|
||||||
gui.refreshSelectedLine(&gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches))
|
// TODO: if we're in the remotes view and we've just deleted a remote we need to refresh accordingly
|
||||||
if err := gui.RenderSelectedBranchUpstreamDifferences(); err != nil {
|
if gui.getBranchesView().Context == "local-branches" {
|
||||||
return err
|
gui.refreshSelectedLine(&gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches))
|
||||||
|
if err := gui.RenderSelectedBranchUpstreamDifferences(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.refreshStatus(g)
|
return gui.refreshStatus(g)
|
||||||
@@ -101,32 +104,18 @@ func (gui *Gui) refreshBranches(g *gocui.Gui) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleBranchesNextLine(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) renderLocalBranchesWithSelection() error {
|
||||||
if gui.popupPanelFocused() {
|
branchesView := gui.getBranchesView()
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
panelState := gui.State.Panels.Branches
|
gui.refreshSelectedLine(&gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches))
|
||||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Branches), false)
|
if err := gui.renderListPanel(branchesView, gui.State.Branches); err != nil {
|
||||||
|
|
||||||
if err := gui.resetOrigin(gui.getMainView()); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return gui.handleBranchSelect(gui.g, v)
|
if err := gui.handleBranchSelect(gui.g, branchesView); err != nil {
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) handleBranchesPrevLine(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
if gui.popupPanelFocused() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
panelState := gui.State.Panels.Branches
|
|
||||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Branches), true)
|
|
||||||
|
|
||||||
if err := gui.resetOrigin(gui.getMainView()); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return gui.handleBranchSelect(gui.g, v)
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// specific functions
|
// specific functions
|
||||||
@@ -219,8 +208,16 @@ func (gui *Gui) handleCheckoutByName(g *gocui.Gui, v *gocui.View) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) getCheckedOutBranch() *commands.Branch {
|
||||||
|
if len(gui.State.Branches) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return gui.State.Branches[0]
|
||||||
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleNewBranch(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleNewBranch(g *gocui.Gui, v *gocui.View) error {
|
||||||
branch := gui.State.Branches[0]
|
branch := gui.getCheckedOutBranch()
|
||||||
message := gui.Tr.TemplateLocalize(
|
message := gui.Tr.TemplateLocalize(
|
||||||
"NewBranchNameBranchOff",
|
"NewBranchNameBranchOff",
|
||||||
Teml{
|
Teml{
|
||||||
@@ -250,7 +247,7 @@ func (gui *Gui) deleteBranch(g *gocui.Gui, v *gocui.View, force bool) error {
|
|||||||
if selectedBranch == nil {
|
if selectedBranch == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
checkedOutBranch := gui.State.Branches[0]
|
checkedOutBranch := gui.getCheckedOutBranch()
|
||||||
if checkedOutBranch.Name == selectedBranch.Name {
|
if checkedOutBranch.Name == selectedBranch.Name {
|
||||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("CantDeleteCheckOutBranch"))
|
return gui.createErrorPanel(g, gui.Tr.SLocalize("CantDeleteCheckOutBranch"))
|
||||||
}
|
}
|
||||||
@@ -283,44 +280,54 @@ func (gui *Gui) deleteNamedBranch(g *gocui.Gui, v *gocui.View, selectedBranch *c
|
|||||||
}, nil)
|
}, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleMerge(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) mergeBranchIntoCheckedOutBranch(branchName string) error {
|
||||||
checkedOutBranch := gui.State.Branches[0].Name
|
if gui.GitCommand.IsHeadDetached() {
|
||||||
selectedBranch := gui.getSelectedBranch().Name
|
return gui.createErrorPanel(gui.g, "Cannot merge branch in detached head state. You might have checked out a commit directly or a remote branch, in which case you should checkout the local branch you want to be on")
|
||||||
if checkedOutBranch == selectedBranch {
|
}
|
||||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("CantMergeBranchIntoItself"))
|
checkedOutBranchName := gui.getCheckedOutBranch().Name
|
||||||
|
if checkedOutBranchName == branchName {
|
||||||
|
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("CantMergeBranchIntoItself"))
|
||||||
}
|
}
|
||||||
prompt := gui.Tr.TemplateLocalize(
|
prompt := gui.Tr.TemplateLocalize(
|
||||||
"ConfirmMerge",
|
"ConfirmMerge",
|
||||||
Teml{
|
Teml{
|
||||||
"checkedOutBranch": checkedOutBranch,
|
"checkedOutBranch": checkedOutBranchName,
|
||||||
"selectedBranch": selectedBranch,
|
"selectedBranch": branchName,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("MergingTitle"), prompt,
|
return gui.createConfirmationPanel(gui.g, gui.getBranchesView(), true, gui.Tr.SLocalize("MergingTitle"), prompt,
|
||||||
func(g *gocui.Gui, v *gocui.View) error {
|
func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
|
||||||
err := gui.GitCommand.Merge(selectedBranch)
|
err := gui.GitCommand.Merge(branchName)
|
||||||
return gui.handleGenericMergeCommandResult(err)
|
return gui.handleGenericMergeCommandResult(err)
|
||||||
}, nil)
|
}, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleRebase(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleMerge(g *gocui.Gui, v *gocui.View) error {
|
||||||
checkedOutBranch := gui.State.Branches[0].Name
|
selectedBranchName := gui.getSelectedBranch().Name
|
||||||
selectedBranch := gui.getSelectedBranch().Name
|
return gui.mergeBranchIntoCheckedOutBranch(selectedBranchName)
|
||||||
if selectedBranch == checkedOutBranch {
|
}
|
||||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("CantRebaseOntoSelf"))
|
|
||||||
|
func (gui *Gui) handleRebaseOntoLocalBranch(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
selectedBranchName := gui.getSelectedBranch().Name
|
||||||
|
return gui.handleRebaseOntoBranch(selectedBranchName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleRebaseOntoBranch(selectedBranchName string) error {
|
||||||
|
checkedOutBranch := gui.getCheckedOutBranch().Name
|
||||||
|
if selectedBranchName == checkedOutBranch {
|
||||||
|
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("CantRebaseOntoSelf"))
|
||||||
}
|
}
|
||||||
prompt := gui.Tr.TemplateLocalize(
|
prompt := gui.Tr.TemplateLocalize(
|
||||||
"ConfirmRebase",
|
"ConfirmRebase",
|
||||||
Teml{
|
Teml{
|
||||||
"checkedOutBranch": checkedOutBranch,
|
"checkedOutBranch": checkedOutBranch,
|
||||||
"selectedBranch": selectedBranch,
|
"selectedBranch": selectedBranchName,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("RebasingTitle"), prompt,
|
return gui.createConfirmationPanel(gui.g, gui.getBranchesView(), true, gui.Tr.SLocalize("RebasingTitle"), prompt,
|
||||||
func(g *gocui.Gui, v *gocui.View) error {
|
func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
err := gui.GitCommand.RebaseBranch(selectedBranchName)
|
||||||
err := gui.GitCommand.RebaseBranch(selectedBranch)
|
|
||||||
return gui.handleGenericMergeCommandResult(err)
|
return gui.handleGenericMergeCommandResult(err)
|
||||||
}, nil)
|
}, nil)
|
||||||
}
|
}
|
||||||
@@ -339,17 +346,27 @@ func (gui *Gui) handleFastForward(g *gocui.Gui, v *gocui.View) error {
|
|||||||
if branch.Pushables != "0" {
|
if branch.Pushables != "0" {
|
||||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("FwdCommitsToPush"))
|
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("FwdCommitsToPush"))
|
||||||
}
|
}
|
||||||
upstream := "origin" // hardcoding for now
|
|
||||||
|
upstream, err := gui.GitCommand.GetUpstreamForBranch(branch.Name)
|
||||||
|
if err != nil {
|
||||||
|
return gui.createErrorPanel(gui.g, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
split := strings.Split(upstream, "/")
|
||||||
|
remoteName := split[0]
|
||||||
|
remoteBranchName := strings.Join(split[1:], "/")
|
||||||
|
|
||||||
message := gui.Tr.TemplateLocalize(
|
message := gui.Tr.TemplateLocalize(
|
||||||
"Fetching",
|
"Fetching",
|
||||||
Teml{
|
Teml{
|
||||||
"from": fmt.Sprintf("%s/%s", upstream, branch.Name),
|
"from": fmt.Sprintf("%s/%s", remoteName, remoteBranchName),
|
||||||
"to": branch.Name,
|
"to": branch.Name,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
go func() {
|
go func() {
|
||||||
_ = gui.createLoaderPanel(gui.g, v, message)
|
_ = gui.createLoaderPanel(gui.g, v, message)
|
||||||
if err := gui.GitCommand.FastForward(branch.Name); err != nil {
|
|
||||||
|
if err := gui.GitCommand.FastForward(branch.Name, remoteName, remoteBranchName); err != nil {
|
||||||
_ = gui.createErrorPanel(gui.g, err.Error())
|
_ = gui.createErrorPanel(gui.g, err.Error())
|
||||||
} else {
|
} else {
|
||||||
_ = gui.closeConfirmationPrompt(gui.g, true)
|
_ = gui.closeConfirmationPrompt(gui.g, true)
|
||||||
@@ -358,3 +375,50 @@ func (gui *Gui) handleFastForward(g *gocui.Gui, v *gocui.View) error {
|
|||||||
}()
|
}()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) onBranchesTabClick(tabIndex int) error {
|
||||||
|
contexts := []string{"local-branches", "remotes", "tags"}
|
||||||
|
branchesView := gui.getBranchesView()
|
||||||
|
branchesView.TabIndex = tabIndex
|
||||||
|
|
||||||
|
return gui.switchBranchesPanelContext(contexts[tabIndex])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) switchBranchesPanelContext(context string) error {
|
||||||
|
branchesView := gui.getBranchesView()
|
||||||
|
branchesView.Context = context
|
||||||
|
|
||||||
|
contextTabIndexMap := map[string]int{
|
||||||
|
"local-branches": 0,
|
||||||
|
"remotes": 1,
|
||||||
|
"remote-branches": 1,
|
||||||
|
"tags": 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
branchesView.TabIndex = contextTabIndexMap[context]
|
||||||
|
|
||||||
|
switch context {
|
||||||
|
case "local-branches":
|
||||||
|
return gui.renderLocalBranchesWithSelection()
|
||||||
|
case "remotes":
|
||||||
|
return gui.renderRemotesWithSelection()
|
||||||
|
case "remote-branches":
|
||||||
|
return gui.renderRemoteBranchesWithSelection()
|
||||||
|
case "tags":
|
||||||
|
return gui.renderTagsWithSelection()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleNextBranchesTab(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
return gui.onBranchesTabClick(
|
||||||
|
utils.ModuloWithWrap(v.TabIndex+1, len(v.Tabs)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handlePrevBranchesTab(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
return gui.onBranchesTabClick(
|
||||||
|
utils.ModuloWithWrap(v.TabIndex-1, len(v.Tabs)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -50,26 +50,8 @@ func (gui *Gui) handleCommitFileSelect(g *gocui.Gui, v *gocui.View) error {
|
|||||||
return gui.renderString(g, "main", commitText)
|
return gui.renderString(g, "main", commitText)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleCommitFilesNextLine(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
panelState := gui.State.Panels.CommitFiles
|
|
||||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.CommitFiles), false)
|
|
||||||
|
|
||||||
return gui.handleCommitFileSelect(gui.g, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) handleCommitFilesPrevLine(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
panelState := gui.State.Panels.CommitFiles
|
|
||||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.CommitFiles), true)
|
|
||||||
|
|
||||||
return gui.handleCommitFileSelect(gui.g, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) handleSwitchToCommitsPanel(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleSwitchToCommitsPanel(g *gocui.Gui, v *gocui.View) error {
|
||||||
commitsView, err := g.View("commits")
|
return gui.switchFocus(g, v, gui.getCommitsView())
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return gui.switchFocus(g, v, commitsView)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleCheckoutCommitFile(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleCheckoutCommitFile(g *gocui.Gui, v *gocui.View) error {
|
||||||
@@ -208,7 +190,7 @@ func (gui *Gui) enterCommitFile(selectedLineIdx int) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gui.changeContext("patch-building"); err != nil {
|
if err := gui.changeMainViewsContext("patch-building"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := gui.switchFocus(gui.g, gui.getCommitFilesView(), gui.getMainView()); err != nil {
|
if err := gui.switchFocus(gui.g, gui.getCommitFilesView(), gui.getMainView()); err != nil {
|
||||||
|
|||||||
+28
-50
@@ -23,27 +23,6 @@ func (gui *Gui) getSelectedCommit(g *gocui.Gui) *commands.Commit {
|
|||||||
return gui.State.Commits[selectedLine]
|
return gui.State.Commits[selectedLine]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleCommitsClick(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
if gui.popupPanelFocused() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
prevSelectedLineIdx := gui.State.Panels.Commits.SelectedLine
|
|
||||||
newSelectedLineIdx := v.SelectedLineIdx()
|
|
||||||
|
|
||||||
if newSelectedLineIdx > len(gui.State.Commits)-1 {
|
|
||||||
return gui.handleCommitSelect(gui.g, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
gui.State.Panels.Commits.SelectedLine = newSelectedLineIdx
|
|
||||||
|
|
||||||
if prevSelectedLineIdx == newSelectedLineIdx && gui.currentViewName() == v.Name() {
|
|
||||||
return gui.handleSwitchToCommitFilesPanel(gui.g, v)
|
|
||||||
} else {
|
|
||||||
return gui.handleCommitSelect(gui.g, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error {
|
||||||
if gui.popupPanelFocused() {
|
if gui.popupPanelFocused() {
|
||||||
return nil
|
return nil
|
||||||
@@ -111,7 +90,7 @@ func (gui *Gui) refreshCommits(g *gocui.Gui) error {
|
|||||||
if g.CurrentView() == v {
|
if g.CurrentView() == v {
|
||||||
gui.handleCommitSelect(g, v)
|
gui.handleCommitSelect(g, v)
|
||||||
}
|
}
|
||||||
if g.CurrentView() == gui.getCommitFilesView() || (g.CurrentView() == gui.getMainView() || gui.State.Context == "patch-building") {
|
if g.CurrentView() == gui.getCommitFilesView() || (g.CurrentView() == gui.getMainView() || gui.State.MainContext == "patch-building") {
|
||||||
return gui.refreshCommitFilesView()
|
return gui.refreshCommitFilesView()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -119,34 +98,6 @@ func (gui *Gui) refreshCommits(g *gocui.Gui) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleCommitsNextLine(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
if gui.popupPanelFocused() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
panelState := gui.State.Panels.Commits
|
|
||||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Commits), false)
|
|
||||||
|
|
||||||
if err := gui.resetOrigin(gui.getMainView()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return gui.handleCommitSelect(gui.g, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) handleCommitsPrevLine(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
if gui.popupPanelFocused() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
panelState := gui.State.Panels.Commits
|
|
||||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Commits), true)
|
|
||||||
|
|
||||||
if err := gui.resetOrigin(gui.getMainView()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return gui.handleCommitSelect(gui.g, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// specific functions
|
// specific functions
|
||||||
|
|
||||||
func (gui *Gui) handleResetToCommit(g *gocui.Gui, commitView *gocui.View) error {
|
func (gui *Gui) handleResetToCommit(g *gocui.Gui, commitView *gocui.View) error {
|
||||||
@@ -633,3 +584,30 @@ func (gui *Gui) handleCreateCommitResetMenu(g *gocui.Gui, v *gocui.View) error {
|
|||||||
|
|
||||||
return gui.createMenu(fmt.Sprintf("%s %s", gui.Tr.SLocalize("resetTo"), commit.Sha), options, len(options), handleMenuPress)
|
return gui.createMenu(fmt.Sprintf("%s %s", gui.Tr.SLocalize("resetTo"), commit.Sha), options, len(options), handleMenuPress)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleTagCommit(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
// TODO: bring up menu asking if you want to make a lightweight or annotated tag
|
||||||
|
// if annotated, switch to a subprocess to create the message
|
||||||
|
|
||||||
|
commit := gui.getSelectedCommit(g)
|
||||||
|
if commit == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return gui.handleCreateLightweightTag(commit.Sha)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleCreateLightweightTag(commitSha string) error {
|
||||||
|
return gui.createPromptPanel(gui.g, gui.getCommitsView(), gui.Tr.SLocalize("TagNameTitle"), "", func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
if err := gui.GitCommand.CreateLightweightTag(v.Buffer(), commitSha); err != nil {
|
||||||
|
return gui.createErrorPanel(g, err.Error())
|
||||||
|
}
|
||||||
|
if err := gui.refreshCommits(g); err != nil {
|
||||||
|
return gui.createErrorPanel(g, err.Error())
|
||||||
|
}
|
||||||
|
if err := gui.refreshTags(); err != nil {
|
||||||
|
return gui.createErrorPanel(g, err.Error())
|
||||||
|
}
|
||||||
|
return gui.handleCommitSelect(g, v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,11 +17,13 @@ import (
|
|||||||
|
|
||||||
func (gui *Gui) wrappedConfirmationFunction(function func(*gocui.Gui, *gocui.View) error, returnFocusOnClose bool) func(*gocui.Gui, *gocui.View) error {
|
func (gui *Gui) wrappedConfirmationFunction(function func(*gocui.Gui, *gocui.View) error, returnFocusOnClose bool) func(*gocui.Gui, *gocui.View) error {
|
||||||
return func(g *gocui.Gui, v *gocui.View) error {
|
return func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
|
||||||
if function != nil {
|
if function != nil {
|
||||||
if err := function(g, v); err != nil {
|
if err := function(g, v); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.closeConfirmationPrompt(g, returnFocusOnClose)
|
return gui.closeConfirmationPrompt(g, returnFocusOnClose)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -31,6 +33,7 @@ func (gui *Gui) closeConfirmationPrompt(g *gocui.Gui, returnFocusOnClose bool) e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil // if it's already been closed we can just return
|
return nil // if it's already been closed we can just return
|
||||||
}
|
}
|
||||||
|
view.Editable = false
|
||||||
if returnFocusOnClose {
|
if returnFocusOnClose {
|
||||||
if err := gui.returnFocus(g, view); err != nil {
|
if err := gui.returnFocus(g, view); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -64,20 +67,6 @@ func (gui *Gui) getConfirmationPanelDimensions(g *gocui.Gui, wrap bool, prompt s
|
|||||||
height/2 + panelHeight/2
|
height/2 + panelHeight/2
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, initialContent string, handleConfirm func(*gocui.Gui, *gocui.View) error) error {
|
|
||||||
gui.onNewPopupPanel()
|
|
||||||
confirmationView, err := gui.prepareConfirmationPanel(currentView, title, initialContent, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
confirmationView.Editable = true
|
|
||||||
if err := gui.renderString(g, "confirmation", initialContent); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// in the future we might want to give createPromptPanel the returnFocusOnClose arg too, but for now we're always setting it to true
|
|
||||||
return gui.setKeyBindings(g, handleConfirm, nil, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) prepareConfirmationPanel(currentView *gocui.View, title, prompt string, hasLoader bool) (*gocui.View, error) {
|
func (gui *Gui) prepareConfirmationPanel(currentView *gocui.View, title, prompt string, hasLoader bool) (*gocui.View, error) {
|
||||||
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(gui.g, true, 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)
|
||||||
@@ -105,35 +94,31 @@ func (gui *Gui) onNewPopupPanel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) createLoaderPanel(g *gocui.Gui, currentView *gocui.View, prompt string) error {
|
func (gui *Gui) createPopupPanel(g *gocui.Gui, currentView *gocui.View, title, prompt string, hasLoader bool, returnFocusOnClose bool, editable bool, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error {
|
||||||
return gui.createPopupPanel(g, currentView, "", prompt, true, true, nil, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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, returnFocusOnClose bool, title, prompt string, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error {
|
|
||||||
return gui.createPopupPanel(g, currentView, title, prompt, false, returnFocusOnClose, handleConfirm, handleClose)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) createPopupPanel(g *gocui.Gui, currentView *gocui.View, title, prompt string, hasLoader bool, returnFocusOnClose bool, 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 {
|
||||||
// delete the existing confirmation panel if it exists
|
// delete the existing confirmation panel if it exists
|
||||||
if view, _ := g.View("confirmation"); view != nil {
|
if view, _ := g.View("confirmation"); view != nil {
|
||||||
if err := gui.closeConfirmationPrompt(g, true); err != nil {
|
if err := gui.closeConfirmationPrompt(g, true); err != nil {
|
||||||
errMessage := gui.Tr.TemplateLocalize(
|
gui.Log.Error(err)
|
||||||
"CantCloseConfirmationPrompt",
|
|
||||||
Teml{
|
|
||||||
"error": err.Error(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
gui.Log.Error(errMessage)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
confirmationView, err := gui.prepareConfirmationPanel(currentView, title, prompt, hasLoader)
|
confirmationView, err := gui.prepareConfirmationPanel(currentView, title, prompt, hasLoader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
confirmationView.Editable = false
|
confirmationView.Editable = editable
|
||||||
|
if editable {
|
||||||
|
go func() {
|
||||||
|
// TODO: remove this wait (right now if you remove it the EditGotoToEndOfLine method doesn't seem to work)
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
gui.g.Update(func(g *gocui.Gui) error {
|
||||||
|
confirmationView.EditGotoToEndOfLine()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
if err := gui.renderString(g, "confirmation", prompt); err != nil {
|
if err := gui.renderString(g, "confirmation", prompt); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -142,6 +127,19 @@ func (gui *Gui) createPopupPanel(g *gocui.Gui, currentView *gocui.View, title, p
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) createLoaderPanel(g *gocui.Gui, currentView *gocui.View, prompt string) error {
|
||||||
|
return gui.createPopupPanel(g, currentView, "", prompt, true, true, false, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, returnFocusOnClose bool, title, prompt string, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error {
|
||||||
|
return gui.createPopupPanel(g, currentView, title, prompt, false, returnFocusOnClose, false, handleConfirm, handleClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, initialContent string, handleConfirm func(*gocui.Gui, *gocui.View) error) error {
|
||||||
|
return gui.createPopupPanel(gui.g, currentView, title, initialContent, false, true, true, handleConfirm, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func (gui *Gui) setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error, returnFocusOnClose bool) error {
|
func (gui *Gui) setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error, returnFocusOnClose bool) error {
|
||||||
actions := gui.Tr.TemplateLocalize(
|
actions := gui.Tr.TemplateLocalize(
|
||||||
"CloseConfirm",
|
"CloseConfirm",
|
||||||
@@ -153,14 +151,14 @@ func (gui *Gui) setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*go
|
|||||||
if err := gui.renderString(g, "options", actions); err != nil {
|
if err := gui.renderString(g, "options", actions); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := g.SetKeybinding("confirmation", gocui.KeyEnter, gocui.ModNone, gui.wrappedConfirmationFunction(handleConfirm, returnFocusOnClose)); err != nil {
|
if err := g.SetKeybinding("confirmation", nil, gocui.KeyEnter, gocui.ModNone, gui.wrappedConfirmationFunction(handleConfirm, returnFocusOnClose)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return g.SetKeybinding("confirmation", gocui.KeyEsc, gocui.ModNone, gui.wrappedConfirmationFunction(handleClose, returnFocusOnClose))
|
return g.SetKeybinding("confirmation", nil, gocui.KeyEsc, gocui.ModNone, gui.wrappedConfirmationFunction(handleClose, returnFocusOnClose))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) createMessagePanel(g *gocui.Gui, currentView *gocui.View, title, prompt string) error {
|
func (gui *Gui) createMessagePanel(g *gocui.Gui, currentView *gocui.View, title, prompt string) error {
|
||||||
return gui.createPopupPanel(g, currentView, title, prompt, false, true, nil, nil)
|
return gui.createPopupPanel(g, currentView, title, prompt, false, true, false, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// createSpecificErrorPanel allows you to create an error popup, specifying the
|
// createSpecificErrorPanel allows you to create an error popup, specifying the
|
||||||
|
|||||||
+11
-36
@@ -1,45 +1,20 @@
|
|||||||
package gui
|
package gui
|
||||||
|
|
||||||
func (gui *Gui) changeContext(context string) error {
|
// changeContext is a helper function for when we want to change a 'main' context
|
||||||
oldContext := gui.State.Context
|
// which currently just means a context that affects both the main and secondary views
|
||||||
|
// other views can have their context changed directly but this function helps
|
||||||
if gui.State.Context == context {
|
// keep the main and secondary views in sync
|
||||||
|
func (gui *Gui) changeMainViewsContext(context string) error {
|
||||||
|
if gui.State.MainContext == context {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
contextMap := gui.GetContextMap()
|
switch context {
|
||||||
|
case "normal", "patch-building", "staging", "merging":
|
||||||
oldBindings := contextMap[oldContext]
|
gui.getMainView().Context = context
|
||||||
for _, binding := range oldBindings {
|
gui.getSecondaryView().Context = context
|
||||||
if err := gui.g.DeleteKeybinding(binding.ViewName, binding.Key, binding.Modifier); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bindings := contextMap[context]
|
gui.State.MainContext = context
|
||||||
for _, binding := range bindings {
|
|
||||||
if err := gui.g.SetKeybinding(binding.ViewName, binding.Key, binding.Modifier, binding.Handler); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gui.State.Context = context
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) setInitialContext() error {
|
|
||||||
contextMap := gui.GetContextMap()
|
|
||||||
|
|
||||||
initialContext := "normal"
|
|
||||||
|
|
||||||
bindings := contextMap[initialContext]
|
|
||||||
for _, binding := range bindings {
|
|
||||||
if err := gui.g.SetKeybinding(binding.ViewName, binding.Key, binding.Modifier, binding.Handler); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gui.State.Context = initialContext
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-56
@@ -27,41 +27,24 @@ 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) handleFilesClick(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View) error {
|
||||||
if gui.popupPanelFocused() {
|
return gui.selectFile(false)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
prevSelectedLineIdx := gui.State.Panels.Files.SelectedLine
|
|
||||||
newSelectedLineIdx := v.SelectedLineIdx()
|
|
||||||
|
|
||||||
if newSelectedLineIdx > len(gui.State.Files)-1 {
|
|
||||||
return gui.handleFileSelect(gui.g, v, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
gui.State.Panels.Files.SelectedLine = newSelectedLineIdx
|
|
||||||
|
|
||||||
if prevSelectedLineIdx == newSelectedLineIdx && gui.currentViewName() == v.Name() {
|
|
||||||
return gui.handleFilePress(gui.g, v)
|
|
||||||
} else {
|
|
||||||
return gui.handleFileSelect(gui.g, v, true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View, alreadySelected bool) error {
|
func (gui *Gui) selectFile(alreadySelected bool) error {
|
||||||
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
|
if _, err := gui.g.SetCurrentView("files"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := gui.getSelectedFile(g)
|
file, err := gui.getSelectedFile(gui.g)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != gui.Errors.ErrNoFiles {
|
if err != gui.Errors.ErrNoFiles {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return gui.renderString(g, "main", gui.Tr.SLocalize("NoChangedFiles"))
|
return gui.renderString(gui.g, "main", gui.Tr.SLocalize("NoChangedFiles"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gui.focusPoint(0, gui.State.Panels.Files.SelectedLine, len(gui.State.Files), v); err != nil {
|
if err := gui.focusPoint(0, gui.State.Panels.Files.SelectedLine, len(gui.State.Files), gui.getFilesView()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +73,7 @@ func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View, alreadySelected bo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if alreadySelected {
|
if alreadySelected {
|
||||||
g.Update(func(*gocui.Gui) error {
|
gui.g.Update(func(*gocui.Gui) error {
|
||||||
if err := gui.setViewContent(gui.g, gui.getSecondaryView(), contentCached); err != nil {
|
if err := gui.setViewContent(gui.g, gui.getSecondaryView(), contentCached); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -98,10 +81,10 @@ func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View, alreadySelected bo
|
|||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err := gui.renderString(g, "secondary", contentCached); err != nil {
|
if err := gui.renderString(gui.g, "secondary", contentCached); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return gui.renderString(g, "main", leftContent)
|
return gui.renderString(gui.g, "main", leftContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) refreshFiles() error {
|
func (gui *Gui) refreshFiles() error {
|
||||||
@@ -133,10 +116,10 @@ func (gui *Gui) refreshFiles() error {
|
|||||||
}
|
}
|
||||||
fmt.Fprint(filesView, list)
|
fmt.Fprint(filesView, list)
|
||||||
|
|
||||||
if g.CurrentView() == filesView || (g.CurrentView() == gui.getMainView() && gui.State.Context == "merging") {
|
if g.CurrentView() == filesView || (g.CurrentView() == gui.getMainView() && g.CurrentView().Context == "merging") {
|
||||||
newSelectedFile, _ := gui.getSelectedFile(gui.g)
|
newSelectedFile, _ := gui.getSelectedFile(gui.g)
|
||||||
alreadySelected := newSelectedFile.Name == selectedFile.Name
|
alreadySelected := newSelectedFile.Name == selectedFile.Name
|
||||||
return gui.handleFileSelect(g, filesView, alreadySelected)
|
return gui.selectFile(alreadySelected)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -144,28 +127,6 @@ func (gui *Gui) refreshFiles() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleFilesNextLine(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
if gui.popupPanelFocused() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
panelState := gui.State.Panels.Files
|
|
||||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Files), false)
|
|
||||||
|
|
||||||
return gui.handleFileSelect(gui.g, v, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) handleFilesPrevLine(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
if gui.popupPanelFocused() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
panelState := gui.State.Panels.Files
|
|
||||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Files), true)
|
|
||||||
|
|
||||||
return gui.handleFileSelect(gui.g, v, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// specific functions
|
// specific functions
|
||||||
|
|
||||||
func (gui *Gui) stagedFiles() []*commands.File {
|
func (gui *Gui) stagedFiles() []*commands.File {
|
||||||
@@ -216,7 +177,7 @@ func (gui *Gui) enterFile(forceSecondaryFocused bool, selectedLineIdx int) error
|
|||||||
if file.HasMergeConflicts {
|
if file.HasMergeConflicts {
|
||||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("FileStagingRequirements"))
|
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("FileStagingRequirements"))
|
||||||
}
|
}
|
||||||
if err := gui.changeContext("staging"); err != nil {
|
if err := gui.changeMainViewsContext("staging"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := gui.switchFocus(gui.g, gui.getFilesView(), gui.getMainView()); err != nil {
|
if err := gui.switchFocus(gui.g, gui.getFilesView(), gui.getMainView()); err != nil {
|
||||||
@@ -248,7 +209,7 @@ func (gui *Gui) handleFilePress(g *gocui.Gui, v *gocui.View) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.handleFileSelect(g, v, true)
|
return gui.selectFile(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) allFilesStaged() bool {
|
func (gui *Gui) allFilesStaged() bool {
|
||||||
@@ -275,7 +236,7 @@ func (gui *Gui) handleStageAll(g *gocui.Gui, v *gocui.View) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.handleFileSelect(g, v, false)
|
return gui.handleFileSelect(gui.g, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleIgnoreFile(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleIgnoreFile(g *gocui.Gui, v *gocui.View) error {
|
||||||
@@ -469,7 +430,7 @@ func (gui *Gui) pushWithForceFlag(g *gocui.Gui, v *gocui.View, force bool, upstr
|
|||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
unamePassOpend := false
|
unamePassOpend := false
|
||||||
branchName := gui.State.Branches[0].Name
|
branchName := gui.getCheckedOutBranch().Name
|
||||||
err := gui.GitCommand.Push(branchName, force, upstream, func(passOrUname string) string {
|
err := gui.GitCommand.Push(branchName, force, upstream, func(passOrUname string) string {
|
||||||
unamePassOpend = true
|
unamePassOpend = true
|
||||||
return gui.waitForPassUname(g, v, passOrUname)
|
return gui.waitForPassUname(g, v, passOrUname)
|
||||||
@@ -510,7 +471,7 @@ func (gui *Gui) handleSwitchToMerge(g *gocui.Gui, v *gocui.View) error {
|
|||||||
if !file.HasInlineMergeConflicts {
|
if !file.HasInlineMergeConflicts {
|
||||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("FileNoMergeCons"))
|
return gui.createErrorPanel(g, gui.Tr.SLocalize("FileNoMergeCons"))
|
||||||
}
|
}
|
||||||
if err := gui.changeContext("merging"); err != nil {
|
if err := gui.changeMainViewsContext("merging"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := gui.switchFocus(g, v, gui.getMainView()); err != nil {
|
if err := gui.switchFocus(g, v, gui.getMainView()); err != nil {
|
||||||
|
|||||||
+74
-31
@@ -107,10 +107,23 @@ type filePanelState struct {
|
|||||||
SelectedLine int
|
SelectedLine int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: consider splitting this out into the window and the branches view
|
||||||
type branchPanelState struct {
|
type branchPanelState struct {
|
||||||
SelectedLine int
|
SelectedLine int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type remotePanelState struct {
|
||||||
|
SelectedLine int
|
||||||
|
}
|
||||||
|
|
||||||
|
type remoteBranchesState struct {
|
||||||
|
SelectedLine int
|
||||||
|
}
|
||||||
|
|
||||||
|
type tagsPanelState struct {
|
||||||
|
SelectedLine int
|
||||||
|
}
|
||||||
|
|
||||||
type commitPanelState struct {
|
type commitPanelState struct {
|
||||||
SelectedLine int
|
SelectedLine int
|
||||||
SpecificDiffMode bool
|
SpecificDiffMode bool
|
||||||
@@ -135,15 +148,18 @@ type statusPanelState struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type panelStates struct {
|
type panelStates struct {
|
||||||
Files *filePanelState
|
Files *filePanelState
|
||||||
Branches *branchPanelState
|
Branches *branchPanelState
|
||||||
Commits *commitPanelState
|
Remotes *remotePanelState
|
||||||
Stash *stashPanelState
|
RemoteBranches *remoteBranchesState
|
||||||
Menu *menuPanelState
|
Tags *tagsPanelState
|
||||||
LineByLine *lineByLinePanelState
|
Commits *commitPanelState
|
||||||
Merging *mergingPanelState
|
Stash *stashPanelState
|
||||||
CommitFiles *commitFilesPanelState
|
Menu *menuPanelState
|
||||||
Status *statusPanelState
|
LineByLine *lineByLinePanelState
|
||||||
|
Merging *mergingPanelState
|
||||||
|
CommitFiles *commitFilesPanelState
|
||||||
|
Status *statusPanelState
|
||||||
}
|
}
|
||||||
|
|
||||||
type guiState struct {
|
type guiState struct {
|
||||||
@@ -153,13 +169,16 @@ type guiState struct {
|
|||||||
StashEntries []*commands.StashEntry
|
StashEntries []*commands.StashEntry
|
||||||
CommitFiles []*commands.CommitFile
|
CommitFiles []*commands.CommitFile
|
||||||
DiffEntries []*commands.Commit
|
DiffEntries []*commands.Commit
|
||||||
|
Remotes []*commands.Remote
|
||||||
|
RemoteBranches []*commands.RemoteBranch
|
||||||
|
Tags []*commands.Tag
|
||||||
MenuItemCount int // can't store the actual list because it's of interface{} type
|
MenuItemCount int // can't store the actual list because it's of interface{} type
|
||||||
PreviousView string
|
PreviousView string
|
||||||
Platform commands.Platform
|
Platform commands.Platform
|
||||||
Updating bool
|
Updating bool
|
||||||
Panels *panelStates
|
Panels *panelStates
|
||||||
WorkingTreeState string // one of "merging", "rebasing", "normal"
|
WorkingTreeState string // one of "merging", "rebasing", "normal"
|
||||||
Context string // important not to set this value directly but to use gui.changeContext("new context")
|
MainContext string // used to keep the main and secondary views' contexts in sync
|
||||||
CherryPickedCommits []*commands.Commit
|
CherryPickedCommits []*commands.Commit
|
||||||
SplitMainPanel bool
|
SplitMainPanel bool
|
||||||
RetainOriginalDir bool
|
RetainOriginalDir bool
|
||||||
@@ -181,12 +200,15 @@ func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *comma
|
|||||||
DiffEntries: make([]*commands.Commit, 0),
|
DiffEntries: make([]*commands.Commit, 0),
|
||||||
Platform: *oSCommand.Platform,
|
Platform: *oSCommand.Platform,
|
||||||
Panels: &panelStates{
|
Panels: &panelStates{
|
||||||
Files: &filePanelState{SelectedLine: -1},
|
Files: &filePanelState{SelectedLine: -1},
|
||||||
Branches: &branchPanelState{SelectedLine: 0},
|
Branches: &branchPanelState{SelectedLine: 0},
|
||||||
Commits: &commitPanelState{SelectedLine: -1},
|
Remotes: &remotePanelState{SelectedLine: 0},
|
||||||
CommitFiles: &commitFilesPanelState{SelectedLine: -1},
|
RemoteBranches: &remoteBranchesState{SelectedLine: -1},
|
||||||
Stash: &stashPanelState{SelectedLine: -1},
|
Tags: &tagsPanelState{SelectedLine: -1},
|
||||||
Menu: &menuPanelState{SelectedLine: 0},
|
Commits: &commitPanelState{SelectedLine: -1},
|
||||||
|
CommitFiles: &commitFilesPanelState{SelectedLine: -1},
|
||||||
|
Stash: &stashPanelState{SelectedLine: -1},
|
||||||
|
Menu: &menuPanelState{SelectedLine: 0},
|
||||||
Merging: &mergingPanelState{
|
Merging: &mergingPanelState{
|
||||||
ConflictIndex: 0,
|
ConflictIndex: 0,
|
||||||
ConflictTop: true,
|
ConflictTop: true,
|
||||||
@@ -299,18 +321,20 @@ func (gui *Gui) onFocusLost(v *gocui.View, newView *gocui.View) error {
|
|||||||
}
|
}
|
||||||
switch v.Name() {
|
switch v.Name() {
|
||||||
case "branches":
|
case "branches":
|
||||||
// This stops the branches panel from showing the upstream/downstream changes to the selected branch, when it loses focus
|
if v.Context == "local-branches" {
|
||||||
// inside renderListPanel it checks to see if the panel has focus
|
// This stops the branches panel from showing the upstream/downstream changes to the selected branch, when it loses focus
|
||||||
if err := gui.renderListPanel(gui.getBranchesView(), gui.State.Branches); err != nil {
|
// inside renderListPanel it checks to see if the panel has focus
|
||||||
return err
|
if err := gui.renderListPanel(gui.getBranchesView(), gui.State.Branches); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case "main":
|
case "main":
|
||||||
// if we have lost focus to a first-class panel, we need to do some cleanup
|
// if we have lost focus to a first-class panel, we need to do some cleanup
|
||||||
if err := gui.changeContext("normal"); err != nil {
|
if err := gui.changeMainViewsContext("normal"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case "commitFiles":
|
case "commitFiles":
|
||||||
if gui.State.Context != "patch-building" {
|
if gui.State.MainContext != "patch-building" {
|
||||||
if _, err := gui.g.SetViewOnBottom(v.Name()); err != nil {
|
if _, err := gui.g.SetViewOnBottom(v.Name()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -480,6 +504,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
branchesView.Title = gui.Tr.SLocalize("BranchesTitle")
|
branchesView.Title = gui.Tr.SLocalize("BranchesTitle")
|
||||||
|
branchesView.Tabs = []string{"Local Branches", "Remotes", "Tags"}
|
||||||
branchesView.FgColor = textColor
|
branchesView.FgColor = textColor
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -571,7 +596,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// doing this here because it'll only happen once
|
// doing this here because it'll only happen once
|
||||||
if err := gui.loadNewRepo(); err != nil {
|
if err := gui.onInitialViewsCreation(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -589,22 +614,30 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
type listViewState struct {
|
type listViewState struct {
|
||||||
selectedLine int
|
selectedLine int
|
||||||
lineCount int
|
lineCount int
|
||||||
|
view *gocui.View
|
||||||
|
context string
|
||||||
}
|
}
|
||||||
|
|
||||||
listViews := map[*gocui.View]listViewState{
|
listViews := []listViewState{
|
||||||
filesView: {selectedLine: gui.State.Panels.Files.SelectedLine, lineCount: len(gui.State.Files)},
|
{view: filesView, context: "", selectedLine: gui.State.Panels.Files.SelectedLine, lineCount: len(gui.State.Files)},
|
||||||
branchesView: {selectedLine: gui.State.Panels.Branches.SelectedLine, lineCount: len(gui.State.Branches)},
|
{view: branchesView, context: "local-branches", selectedLine: gui.State.Panels.Branches.SelectedLine, lineCount: len(gui.State.Branches)},
|
||||||
commitsView: {selectedLine: gui.State.Panels.Commits.SelectedLine, lineCount: len(gui.State.Commits)},
|
{view: branchesView, context: "remotes", selectedLine: gui.State.Panels.Remotes.SelectedLine, lineCount: len(gui.State.Remotes)},
|
||||||
stashView: {selectedLine: gui.State.Panels.Stash.SelectedLine, lineCount: len(gui.State.StashEntries)},
|
{view: branchesView, context: "remote-branches", selectedLine: gui.State.Panels.RemoteBranches.SelectedLine, lineCount: len(gui.State.Remotes)},
|
||||||
|
{view: commitsView, context: "", selectedLine: gui.State.Panels.Commits.SelectedLine, lineCount: len(gui.State.Commits)},
|
||||||
|
{view: stashView, context: "", selectedLine: gui.State.Panels.Stash.SelectedLine, lineCount: len(gui.State.StashEntries)},
|
||||||
}
|
}
|
||||||
|
|
||||||
// menu view might not exist so we check to be safe
|
// menu view might not exist so we check to be safe
|
||||||
if menuView, err := gui.g.View("menu"); err == nil {
|
if menuView, err := gui.g.View("menu"); err == nil {
|
||||||
listViews[menuView] = listViewState{selectedLine: gui.State.Panels.Menu.SelectedLine, lineCount: gui.State.MenuItemCount}
|
listViews = append(listViews, listViewState{view: menuView, context: "", selectedLine: gui.State.Panels.Menu.SelectedLine, lineCount: gui.State.MenuItemCount})
|
||||||
}
|
}
|
||||||
for view, state := range listViews {
|
for _, listView := range listViews {
|
||||||
|
// ignore views where the context doesn't match up with the selected line we're trying to focus
|
||||||
|
if listView.context != "" && (listView.view.Context != listView.context) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
// check if the selected line is now out of view and if so refocus it
|
// check if the selected line is now out of view and if so refocus it
|
||||||
if err := gui.focusPoint(0, state.selectedLine, state.lineCount, view); err != nil {
|
if err := gui.focusPoint(0, listView.selectedLine, listView.lineCount, listView.view); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -616,6 +649,16 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
return gui.resizeCurrentPopupPanel(g)
|
return gui.resizeCurrentPopupPanel(g)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) onInitialViewsCreation() error {
|
||||||
|
if err := gui.changeMainViewsContext("normal"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
gui.getBranchesView().Context = "local-branches"
|
||||||
|
|
||||||
|
return gui.loadNewRepo()
|
||||||
|
}
|
||||||
|
|
||||||
func (gui *Gui) loadNewRepo() error {
|
func (gui *Gui) loadNewRepo() error {
|
||||||
gui.Updater.CheckForNewUpdate(gui.onBackgroundUpdateCheckFinish, false)
|
gui.Updater.CheckForNewUpdate(gui.onBackgroundUpdateCheckFinish, false)
|
||||||
if err := gui.updateRecentRepoList(); err != nil {
|
if err := gui.updateRecentRepoList(); err != nil {
|
||||||
|
|||||||
+660
-453
File diff suppressed because it is too large
Load Diff
@@ -226,7 +226,7 @@ func (gui *Gui) refreshMainView() error {
|
|||||||
var includedLineIndices []int
|
var includedLineIndices []int
|
||||||
// I'd prefer not to have knowledge of contexts using this file but I'm not sure
|
// I'd prefer not to have knowledge of contexts using this file but I'm not sure
|
||||||
// how to get around this
|
// how to get around this
|
||||||
if gui.State.Context == "patch-building" {
|
if gui.State.MainContext == "patch-building" {
|
||||||
filename := gui.State.CommitFiles[gui.State.Panels.CommitFiles.SelectedLine].Name
|
filename := gui.State.CommitFiles[gui.State.Panels.CommitFiles.SelectedLine].Name
|
||||||
includedLineIndices = gui.GitCommand.PatchManager.GetFileIncLineIndices(filename)
|
includedLineIndices = gui.GitCommand.PatchManager.GetFileIncLineIndices(filename)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
package gui
|
||||||
|
|
||||||
|
import "github.com/jesseduffield/gocui"
|
||||||
|
|
||||||
|
type listView struct {
|
||||||
|
viewName string
|
||||||
|
context string
|
||||||
|
getItemsLength func() int
|
||||||
|
getSelectedLineIdxPtr func() *int
|
||||||
|
handleFocus func(g *gocui.Gui, v *gocui.View) error
|
||||||
|
handleItemSelect func(g *gocui.Gui, v *gocui.View) error
|
||||||
|
handleClickSelectedItem func(g *gocui.Gui, v *gocui.View) error
|
||||||
|
gui *Gui
|
||||||
|
rendersToMainView bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lv *listView) handlePrevLine(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
return lv.handleLineChange(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lv *listView) handleNextLine(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
return lv.handleLineChange(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lv *listView) handleLineChange(change int) error {
|
||||||
|
if !lv.gui.isPopupPanel(lv.viewName) && lv.gui.popupPanelFocused() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lv.gui.changeSelectedLine(lv.getSelectedLineIdxPtr(), lv.getItemsLength(), change)
|
||||||
|
|
||||||
|
if lv.rendersToMainView {
|
||||||
|
if err := lv.gui.resetOrigin(lv.gui.getMainView()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
view, err := lv.gui.g.View(lv.viewName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return lv.handleItemSelect(lv.gui.g, view)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lv *listView) handleClick(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
if !lv.gui.isPopupPanel(lv.viewName) && lv.gui.popupPanelFocused() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedLineIdxPtr := lv.getSelectedLineIdxPtr()
|
||||||
|
prevSelectedLineIdx := *selectedLineIdxPtr
|
||||||
|
newSelectedLineIdx := v.SelectedLineIdx()
|
||||||
|
|
||||||
|
if newSelectedLineIdx > lv.getItemsLength()-1 {
|
||||||
|
return lv.handleFocus(lv.gui.g, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
*selectedLineIdxPtr = newSelectedLineIdx
|
||||||
|
|
||||||
|
if prevSelectedLineIdx == newSelectedLineIdx && lv.gui.currentViewName() == lv.viewName && lv.handleClickSelectedItem != nil {
|
||||||
|
return lv.handleClickSelectedItem(lv.gui.g, v)
|
||||||
|
}
|
||||||
|
return lv.handleItemSelect(lv.gui.g, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) getListViews() []*listView {
|
||||||
|
return []*listView{
|
||||||
|
{
|
||||||
|
viewName: "menu",
|
||||||
|
getItemsLength: func() int { return gui.getMenuView().LinesHeight() },
|
||||||
|
getSelectedLineIdxPtr: func() *int { return &gui.State.Panels.Menu.SelectedLine },
|
||||||
|
handleFocus: gui.handleMenuSelect,
|
||||||
|
handleItemSelect: gui.handleMenuSelect,
|
||||||
|
// need to add a layer of indirection here because the callback changes during runtime
|
||||||
|
handleClickSelectedItem: gui.wrappedHandler(func() error { return gui.State.Panels.Menu.OnPress(gui.g, nil) }),
|
||||||
|
gui: gui,
|
||||||
|
rendersToMainView: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewName: "files",
|
||||||
|
getItemsLength: func() int { return len(gui.State.Files) },
|
||||||
|
getSelectedLineIdxPtr: func() *int { return &gui.State.Panels.Files.SelectedLine },
|
||||||
|
handleFocus: gui.wrappedHandler(func() error { return gui.selectFile(true) }),
|
||||||
|
handleItemSelect: gui.wrappedHandler(func() error { return gui.selectFile(true) }),
|
||||||
|
handleClickSelectedItem: gui.handleFilePress,
|
||||||
|
gui: gui,
|
||||||
|
rendersToMainView: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewName: "branches",
|
||||||
|
context: "local-branches",
|
||||||
|
getItemsLength: func() int { return len(gui.State.Branches) },
|
||||||
|
getSelectedLineIdxPtr: func() *int { return &gui.State.Panels.Branches.SelectedLine },
|
||||||
|
handleFocus: gui.handleBranchSelect,
|
||||||
|
handleItemSelect: gui.handleBranchSelect,
|
||||||
|
gui: gui,
|
||||||
|
rendersToMainView: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewName: "branches",
|
||||||
|
context: "remotes",
|
||||||
|
getItemsLength: func() int { return len(gui.State.Remotes) },
|
||||||
|
getSelectedLineIdxPtr: func() *int { return &gui.State.Panels.Remotes.SelectedLine },
|
||||||
|
handleFocus: gui.wrappedHandler(gui.renderRemotesWithSelection),
|
||||||
|
handleItemSelect: gui.handleRemoteSelect,
|
||||||
|
handleClickSelectedItem: gui.handleRemoteEnter,
|
||||||
|
gui: gui,
|
||||||
|
rendersToMainView: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewName: "branches",
|
||||||
|
context: "remote-branches",
|
||||||
|
getItemsLength: func() int { return len(gui.State.RemoteBranches) },
|
||||||
|
getSelectedLineIdxPtr: func() *int { return &gui.State.Panels.RemoteBranches.SelectedLine },
|
||||||
|
handleFocus: gui.handleRemoteBranchSelect,
|
||||||
|
handleItemSelect: gui.handleRemoteBranchSelect,
|
||||||
|
gui: gui,
|
||||||
|
rendersToMainView: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewName: "branches",
|
||||||
|
context: "tags",
|
||||||
|
getItemsLength: func() int { return len(gui.State.Tags) },
|
||||||
|
getSelectedLineIdxPtr: func() *int { return &gui.State.Panels.Tags.SelectedLine },
|
||||||
|
handleFocus: gui.handleTagSelect,
|
||||||
|
handleItemSelect: gui.handleTagSelect,
|
||||||
|
gui: gui,
|
||||||
|
rendersToMainView: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewName: "commits",
|
||||||
|
getItemsLength: func() int { return len(gui.State.Commits) },
|
||||||
|
getSelectedLineIdxPtr: func() *int { return &gui.State.Panels.Commits.SelectedLine },
|
||||||
|
handleFocus: gui.handleCommitSelect,
|
||||||
|
handleItemSelect: gui.handleCommitSelect,
|
||||||
|
handleClickSelectedItem: gui.handleSwitchToCommitFilesPanel,
|
||||||
|
gui: gui,
|
||||||
|
rendersToMainView: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewName: "stash",
|
||||||
|
getItemsLength: func() int { return len(gui.State.StashEntries) },
|
||||||
|
getSelectedLineIdxPtr: func() *int { return &gui.State.Panels.Stash.SelectedLine },
|
||||||
|
handleFocus: gui.handleStashEntrySelect,
|
||||||
|
handleItemSelect: gui.handleStashEntrySelect,
|
||||||
|
gui: gui,
|
||||||
|
rendersToMainView: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewName: "commitFiles",
|
||||||
|
getItemsLength: func() int { return len(gui.State.CommitFiles) },
|
||||||
|
getSelectedLineIdxPtr: func() *int { return &gui.State.Panels.CommitFiles.SelectedLine },
|
||||||
|
handleFocus: gui.handleCommitFileSelect,
|
||||||
|
handleItemSelect: gui.handleCommitFileSelect,
|
||||||
|
gui: gui,
|
||||||
|
rendersToMainView: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-27
@@ -14,20 +14,6 @@ func (gui *Gui) handleMenuSelect(g *gocui.Gui, v *gocui.View) error {
|
|||||||
return gui.focusPoint(0, gui.State.Panels.Menu.SelectedLine, gui.State.MenuItemCount, v)
|
return gui.focusPoint(0, gui.State.Panels.Menu.SelectedLine, gui.State.MenuItemCount, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleMenuNextLine(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
panelState := gui.State.Panels.Menu
|
|
||||||
gui.changeSelectedLine(&panelState.SelectedLine, v.LinesHeight(), false)
|
|
||||||
|
|
||||||
return gui.handleMenuSelect(g, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) handleMenuPrevLine(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
panelState := gui.State.Panels.Menu
|
|
||||||
gui.changeSelectedLine(&panelState.SelectedLine, v.LinesHeight(), true)
|
|
||||||
|
|
||||||
return gui.handleMenuSelect(g, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// specific functions
|
// specific functions
|
||||||
|
|
||||||
func (gui *Gui) renderMenuOptions() error {
|
func (gui *Gui) renderMenuOptions() error {
|
||||||
@@ -87,7 +73,7 @@ func (gui *Gui) createMenu(title string, items interface{}, itemCount int, handl
|
|||||||
for _, key := range []gocui.Key{gocui.KeySpace, gocui.KeyEnter, 'y'} {
|
for _, key := range []gocui.Key{gocui.KeySpace, gocui.KeyEnter, 'y'} {
|
||||||
_ = gui.g.DeleteKeybinding("menu", key, gocui.ModNone)
|
_ = gui.g.DeleteKeybinding("menu", key, gocui.ModNone)
|
||||||
|
|
||||||
if err := gui.g.SetKeybinding("menu", key, gocui.ModNone, wrappedHandlePress); err != nil {
|
if err := gui.g.SetKeybinding("menu", nil, key, gocui.ModNone, wrappedHandlePress); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,15 +89,3 @@ func (gui *Gui) createMenu(title string, items interface{}, itemCount int, handl
|
|||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleMenuClick(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
itemCount := gui.State.MenuItemCount
|
|
||||||
handleSelect := gui.handleMenuSelect
|
|
||||||
selectedLine := &gui.State.Panels.Menu.SelectedLine
|
|
||||||
|
|
||||||
if err := gui.handleClick(v, itemCount, selectedLine, handleSelect); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return gui.State.Panels.Menu.OnPress(g, v)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
|
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (gui *Gui) getBindings(v *gocui.View) []*Binding {
|
func (gui *Gui) getBindings(v *gocui.View) []*Binding {
|
||||||
@@ -13,7 +14,7 @@ func (gui *Gui) getBindings(v *gocui.View) []*Binding {
|
|||||||
bindingsGlobal, bindingsPanel []*Binding
|
bindingsGlobal, bindingsPanel []*Binding
|
||||||
)
|
)
|
||||||
|
|
||||||
bindings := gui.GetCurrentKeybindings()
|
bindings := gui.GetInitialKeybindings()
|
||||||
|
|
||||||
for _, binding := range bindings {
|
for _, binding := range bindings {
|
||||||
if binding.GetKey() != "" && binding.Description != "" {
|
if binding.GetKey() != "" && binding.Description != "" {
|
||||||
@@ -21,7 +22,9 @@ func (gui *Gui) getBindings(v *gocui.View) []*Binding {
|
|||||||
case "":
|
case "":
|
||||||
bindingsGlobal = append(bindingsGlobal, binding)
|
bindingsGlobal = append(bindingsGlobal, binding)
|
||||||
case v.Name():
|
case v.Name():
|
||||||
bindingsPanel = append(bindingsPanel, binding)
|
if len(binding.Contexts) == 0 || utils.IncludesString(binding.Contexts, v.Context) {
|
||||||
|
bindingsPanel = append(bindingsPanel, binding)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ func (gui *Gui) handleRemoveSelectionFromPatch(g *gocui.Gui, v *gocui.View) erro
|
|||||||
|
|
||||||
func (gui *Gui) handleEscapePatchBuildingPanel(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleEscapePatchBuildingPanel(g *gocui.Gui, v *gocui.View) error {
|
||||||
gui.State.Panels.LineByLine = nil
|
gui.State.Panels.LineByLine = nil
|
||||||
gui.changeContext("normal")
|
gui.changeMainViewsContext("normal")
|
||||||
|
|
||||||
if gui.GitCommand.PatchManager.IsEmpty() {
|
if gui.GitCommand.PatchManager.IsEmpty() {
|
||||||
gui.GitCommand.PatchManager.Reset()
|
gui.GitCommand.PatchManager.Reset()
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ func (gui *Gui) validateNormalWorkingTreeState() (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) returnFocusFromLineByLinePanelIfNecessary() error {
|
func (gui *Gui) returnFocusFromLineByLinePanelIfNecessary() error {
|
||||||
if gui.State.Context == "patch-building" {
|
if gui.State.MainContext == "patch-building" {
|
||||||
return gui.handleEscapePatchBuildingPanel(gui.g, nil)
|
return gui.handleEscapePatchBuildingPanel(gui.g, nil)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package gui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
|
"github.com/jesseduffield/gocui"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// list panel functions
|
||||||
|
|
||||||
|
func (gui *Gui) getSelectedRemoteBranch() *commands.RemoteBranch {
|
||||||
|
selectedLine := gui.State.Panels.RemoteBranches.SelectedLine
|
||||||
|
if selectedLine == -1 || len(gui.State.RemoteBranches) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return gui.State.RemoteBranches[selectedLine]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleRemoteBranchSelect(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
if gui.popupPanelFocused() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
gui.State.SplitMainPanel = false
|
||||||
|
|
||||||
|
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
gui.getMainView().Title = "Remote Branch"
|
||||||
|
|
||||||
|
remote := gui.getSelectedRemote()
|
||||||
|
remoteBranch := gui.getSelectedRemoteBranch()
|
||||||
|
if remoteBranch == nil {
|
||||||
|
return gui.renderString(g, "main", "No branches for this remote")
|
||||||
|
}
|
||||||
|
|
||||||
|
gui.focusPoint(0, gui.State.Panels.Menu.SelectedLine, gui.State.MenuItemCount, v)
|
||||||
|
if err := gui.focusPoint(0, gui.State.Panels.RemoteBranches.SelectedLine, len(gui.State.RemoteBranches), v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
graph, err := gui.GitCommand.GetBranchGraph(fmt.Sprintf("%s/%s", remote.Name, remoteBranch.Name))
|
||||||
|
if err != nil && strings.HasPrefix(graph, "fatal: ambiguous argument") {
|
||||||
|
graph = gui.Tr.SLocalize("NoTrackingThisBranch")
|
||||||
|
}
|
||||||
|
_ = gui.renderString(g, "main", fmt.Sprintf("%s/%s\n\n%s", utils.ColoredString(remote.Name, color.FgRed), utils.ColoredString(remoteBranch.Name, color.FgGreen), graph))
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleRemoteBranchesEscape(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
return gui.switchBranchesPanelContext("remotes")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) renderRemoteBranchesWithSelection() error {
|
||||||
|
branchesView := gui.getBranchesView()
|
||||||
|
|
||||||
|
gui.refreshSelectedLine(&gui.State.Panels.RemoteBranches.SelectedLine, len(gui.State.RemoteBranches))
|
||||||
|
if err := gui.renderListPanel(branchesView, gui.State.RemoteBranches); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := gui.handleRemoteBranchSelect(gui.g, branchesView); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleCheckoutRemoteBranch(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
remoteBranch := gui.getSelectedRemoteBranch()
|
||||||
|
if remoteBranch == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := gui.handleCheckoutBranch(remoteBranch.RemoteName + "/" + remoteBranch.Name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return gui.switchBranchesPanelContext("local-branches")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleMergeRemoteBranch(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
selectedBranchName := gui.getSelectedRemoteBranch().Name
|
||||||
|
return gui.mergeBranchIntoCheckedOutBranch(selectedBranchName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleDeleteRemoteBranch(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
remoteBranch := gui.getSelectedRemoteBranch()
|
||||||
|
if remoteBranch == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
message := fmt.Sprintf("%s '%s/%s'?", gui.Tr.SLocalize("DeleteRemoteBranchMessage"), remoteBranch.RemoteName, remoteBranch.Name)
|
||||||
|
return gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("DeleteRemoteBranch"), message, func(*gocui.Gui, *gocui.View) error {
|
||||||
|
return gui.WithWaitingStatus(gui.Tr.SLocalize("DeletingStatus"), func() error {
|
||||||
|
if err := gui.GitCommand.DeleteRemoteBranch(remoteBranch.RemoteName, remoteBranch.Name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return gui.refreshRemotes()
|
||||||
|
})
|
||||||
|
}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleRebaseOntoRemoteBranch(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
selectedBranchName := gui.getSelectedRemoteBranch().Name
|
||||||
|
return gui.handleRebaseOntoBranch(selectedBranchName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleSetBranchUpstream(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
selectedBranch := gui.getSelectedRemoteBranch()
|
||||||
|
checkedOutBranch := gui.getCheckedOutBranch()
|
||||||
|
|
||||||
|
message := gui.Tr.TemplateLocalize(
|
||||||
|
"SetUpstreamMessage",
|
||||||
|
Teml{
|
||||||
|
"checkedOut": checkedOutBranch.Name,
|
||||||
|
"selected": selectedBranch.RemoteName + "/" + selectedBranch.Name,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("SetUpstreamTitle"), message, func(*gocui.Gui, *gocui.View) error {
|
||||||
|
if err := gui.GitCommand.SetBranchUpstream(selectedBranch.RemoteName, selectedBranch.Name, checkedOutBranch.Name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return gui.refreshSidePanels(gui.g)
|
||||||
|
}, nil)
|
||||||
|
}
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
package gui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
|
"github.com/jesseduffield/gocui"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// list panel functions
|
||||||
|
|
||||||
|
func (gui *Gui) getSelectedRemote() *commands.Remote {
|
||||||
|
selectedLine := gui.State.Panels.Remotes.SelectedLine
|
||||||
|
if selectedLine == -1 || len(gui.State.Remotes) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return gui.State.Remotes[selectedLine]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleRemoteSelect(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
if gui.popupPanelFocused() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
gui.State.SplitMainPanel = false
|
||||||
|
|
||||||
|
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
gui.getMainView().Title = "Remote"
|
||||||
|
|
||||||
|
remote := gui.getSelectedRemote()
|
||||||
|
if remote == nil {
|
||||||
|
return gui.renderString(g, "main", "No remotes")
|
||||||
|
}
|
||||||
|
if err := gui.focusPoint(0, gui.State.Panels.Remotes.SelectedLine, len(gui.State.Remotes), v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return gui.renderString(g, "main", fmt.Sprintf("%s\nUrls:\n%s", utils.ColoredString(remote.Name, color.FgGreen), strings.Join(remote.Urls, "\n")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) refreshRemotes() error {
|
||||||
|
prevSelectedRemote := gui.getSelectedRemote()
|
||||||
|
|
||||||
|
remotes, err := gui.GitCommand.GetRemotes()
|
||||||
|
if err != nil {
|
||||||
|
return gui.createErrorPanel(gui.g, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
gui.State.Remotes = remotes
|
||||||
|
|
||||||
|
// we need to ensure our selected remote branches aren't now outdated
|
||||||
|
if prevSelectedRemote != nil && gui.State.RemoteBranches != nil {
|
||||||
|
// find remote now
|
||||||
|
for _, remote := range remotes {
|
||||||
|
if remote.Name == prevSelectedRemote.Name {
|
||||||
|
gui.State.RemoteBranches = remote.Branches
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: see if this works for deleting remote branches
|
||||||
|
switch gui.getBranchesView().Context {
|
||||||
|
case "remotes":
|
||||||
|
return gui.renderRemotesWithSelection()
|
||||||
|
case "remote-branches":
|
||||||
|
return gui.renderRemoteBranchesWithSelection()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) renderRemotesWithSelection() error {
|
||||||
|
branchesView := gui.getBranchesView()
|
||||||
|
|
||||||
|
gui.refreshSelectedLine(&gui.State.Panels.Remotes.SelectedLine, len(gui.State.Remotes))
|
||||||
|
if err := gui.renderListPanel(branchesView, gui.State.Remotes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := gui.handleRemoteSelect(gui.g, branchesView); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleRemoteEnter(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
// naive implementation: get the branches and render them to the list, change the context
|
||||||
|
remote := gui.getSelectedRemote()
|
||||||
|
if remote == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
gui.State.RemoteBranches = remote.Branches
|
||||||
|
|
||||||
|
newSelectedLine := 0
|
||||||
|
if len(remote.Branches) == 0 {
|
||||||
|
newSelectedLine = -1
|
||||||
|
}
|
||||||
|
gui.State.Panels.RemoteBranches.SelectedLine = newSelectedLine
|
||||||
|
|
||||||
|
return gui.switchBranchesPanelContext("remote-branches")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleAddRemote(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
branchesView := gui.getBranchesView()
|
||||||
|
return gui.createPromptPanel(g, branchesView, gui.Tr.SLocalize("newRemoteName"), "", func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
remoteName := gui.trimmedContent(v)
|
||||||
|
return gui.createPromptPanel(g, branchesView, gui.Tr.SLocalize("newRemoteUrl"), "", func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
remoteUrl := gui.trimmedContent(v)
|
||||||
|
if err := gui.GitCommand.AddRemote(remoteName, remoteUrl); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return gui.refreshRemotes()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleRemoveRemote(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
remote := gui.getSelectedRemote()
|
||||||
|
if remote == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("removeRemote"), gui.Tr.SLocalize("removeRemotePrompt")+" '"+remote.Name+"'?", func(*gocui.Gui, *gocui.View) error {
|
||||||
|
if err := gui.GitCommand.RemoveRemote(remote.Name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return gui.refreshRemotes()
|
||||||
|
|
||||||
|
}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleEditRemote(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
branchesView := gui.getBranchesView()
|
||||||
|
remote := gui.getSelectedRemote()
|
||||||
|
if remote == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
editNameMessage := gui.Tr.TemplateLocalize(
|
||||||
|
"editRemoteName",
|
||||||
|
Teml{
|
||||||
|
"remoteName": remote.Name,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return gui.createPromptPanel(g, branchesView, editNameMessage, "", func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
updatedRemoteName := gui.trimmedContent(v)
|
||||||
|
|
||||||
|
if updatedRemoteName != remote.Name {
|
||||||
|
if err := gui.GitCommand.RenameRemote(remote.Name, updatedRemoteName); err != nil {
|
||||||
|
return gui.createErrorPanel(gui.g, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editUrlMessage := gui.Tr.TemplateLocalize(
|
||||||
|
"editRemoteUrl",
|
||||||
|
Teml{
|
||||||
|
"remoteName": updatedRemoteName,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return gui.createPromptPanel(g, branchesView, editUrlMessage, "", func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
updatedRemoteUrl := gui.trimmedContent(v)
|
||||||
|
if err := gui.GitCommand.UpdateRemoteUrl(updatedRemoteName, updatedRemoteUrl); err != nil {
|
||||||
|
return gui.createErrorPanel(gui.g, err.Error())
|
||||||
|
}
|
||||||
|
return gui.refreshRemotes()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -71,34 +71,6 @@ func (gui *Gui) refreshStashEntries(g *gocui.Gui) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleStashNextLine(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
if gui.popupPanelFocused() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
panelState := gui.State.Panels.Stash
|
|
||||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.StashEntries), false)
|
|
||||||
|
|
||||||
if err := gui.resetOrigin(gui.getMainView()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return gui.handleStashEntrySelect(gui.g, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) handleStashPrevLine(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
if gui.popupPanelFocused() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
panelState := gui.State.Panels.Stash
|
|
||||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.StashEntries), true)
|
|
||||||
|
|
||||||
if err := gui.resetOrigin(gui.getMainView()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return gui.handleStashEntrySelect(gui.g, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// specific functions
|
// specific functions
|
||||||
|
|
||||||
func (gui *Gui) handleStashApply(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleStashApply(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ func (gui *Gui) refreshStatus(g *gocui.Gui) error {
|
|||||||
|
|
||||||
if len(branches) > 0 {
|
if len(branches) > 0 {
|
||||||
branch := branches[0]
|
branch := branches[0]
|
||||||
name := utils.ColoredString(branch.Name, branch.GetColor())
|
name := utils.ColoredString(branch.Name, commands.GetBranchColor(branch.Name))
|
||||||
repoName := utils.GetCurrentRepoName()
|
repoName := utils.GetCurrentRepoName()
|
||||||
status += fmt.Sprintf(" %s → %s", repoName, name)
|
status += fmt.Sprintf(" %s → %s", repoName, name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
package gui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/jesseduffield/gocui"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||||
|
)
|
||||||
|
|
||||||
|
// list panel functions
|
||||||
|
|
||||||
|
func (gui *Gui) getSelectedTag() *commands.Tag {
|
||||||
|
selectedLine := gui.State.Panels.Tags.SelectedLine
|
||||||
|
if selectedLine == -1 || len(gui.State.Tags) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return gui.State.Tags[selectedLine]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleTagSelect(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
if gui.popupPanelFocused() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
gui.State.SplitMainPanel = false
|
||||||
|
|
||||||
|
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
gui.getMainView().Title = "Tag"
|
||||||
|
|
||||||
|
tag := gui.getSelectedTag()
|
||||||
|
if tag == nil {
|
||||||
|
return gui.renderString(g, "main", "No tags")
|
||||||
|
}
|
||||||
|
if err := gui.focusPoint(0, gui.State.Panels.Tags.SelectedLine, len(gui.State.Tags), v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
show, err := gui.GitCommand.ShowTag(tag.Name)
|
||||||
|
if err != nil {
|
||||||
|
show = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
graph, err := gui.GitCommand.GetBranchGraph(tag.Name)
|
||||||
|
if err != nil {
|
||||||
|
graph = "No graph for tag " + tag.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = gui.renderString(g, "main", fmt.Sprintf("%s\n%s", show, graph))
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) refreshTags() error {
|
||||||
|
tags, err := gui.GitCommand.GetTags()
|
||||||
|
if err != nil {
|
||||||
|
return gui.createErrorPanel(gui.g, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
gui.State.Tags = tags
|
||||||
|
|
||||||
|
if gui.getBranchesView().Context == "tags" {
|
||||||
|
gui.renderTagsWithSelection()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) renderTagsWithSelection() error {
|
||||||
|
branchesView := gui.getBranchesView()
|
||||||
|
|
||||||
|
gui.refreshSelectedLine(&gui.State.Panels.Tags.SelectedLine, len(gui.State.Tags))
|
||||||
|
if err := gui.renderListPanel(branchesView, gui.State.Tags); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := gui.handleTagSelect(gui.g, branchesView); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleCheckoutTag(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
tag := gui.getSelectedTag()
|
||||||
|
if tag == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := gui.handleCheckoutBranch(tag.Name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return gui.switchBranchesPanelContext("local-branches")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleDeleteTag(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
tag := gui.getSelectedTag()
|
||||||
|
if tag == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt := gui.Tr.TemplateLocalize(
|
||||||
|
"DeleteTagPrompt",
|
||||||
|
Teml{
|
||||||
|
"tagName": tag.Name,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return gui.createConfirmationPanel(gui.g, v, true, gui.Tr.SLocalize("DeleteTagTitle"), prompt, func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
if err := gui.GitCommand.DeleteTag(tag.Name); err != nil {
|
||||||
|
return gui.createErrorPanel(gui.g, err.Error())
|
||||||
|
}
|
||||||
|
return gui.refreshTags()
|
||||||
|
}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handlePushTag(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
tag := gui.getSelectedTag()
|
||||||
|
if tag == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
title := gui.Tr.TemplateLocalize(
|
||||||
|
"PushTagTitle",
|
||||||
|
Teml{
|
||||||
|
"tagName": tag.Name,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return gui.createPromptPanel(gui.g, v, title, "origin", func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
if err := gui.GitCommand.PushTag(v.Buffer(), tag.Name); err != nil {
|
||||||
|
return gui.createErrorPanel(gui.g, err.Error())
|
||||||
|
}
|
||||||
|
return gui.refreshTags()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleCreateTag(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
return gui.createPromptPanel(gui.g, v, gui.Tr.SLocalize("CreateTagTitle"), "", func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
// leaving commit SHA blank so that we're just creating the tag for the current commit
|
||||||
|
if err := gui.GitCommand.CreateLightweightTag(v.Buffer(), ""); err != nil {
|
||||||
|
return gui.createErrorPanel(gui.g, err.Error())
|
||||||
|
}
|
||||||
|
return gui.refreshTags()
|
||||||
|
})
|
||||||
|
}
|
||||||
+41
-16
@@ -5,6 +5,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-errors/errors"
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
"github.com/spkg/bom"
|
"github.com/spkg/bom"
|
||||||
@@ -101,9 +102,21 @@ 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, false)
|
return gui.handleFileSelect(g, v)
|
||||||
case "branches":
|
case "branches":
|
||||||
return gui.handleBranchSelect(g, v)
|
branchesView := gui.getBranchesView()
|
||||||
|
switch branchesView.Context {
|
||||||
|
case "local-branches":
|
||||||
|
return gui.handleBranchSelect(g, v)
|
||||||
|
case "remotes":
|
||||||
|
return gui.handleRemoteSelect(g, v)
|
||||||
|
case "remote-branches":
|
||||||
|
return gui.handleRemoteBranchSelect(g, v)
|
||||||
|
case "tags":
|
||||||
|
return gui.handleTagSelect(g, v)
|
||||||
|
default:
|
||||||
|
return errors.New("unknown branches panel context: " + branchesView.Context)
|
||||||
|
}
|
||||||
case "commits":
|
case "commits":
|
||||||
return gui.handleCommitSelect(g, v)
|
return gui.handleCommitSelect(g, v)
|
||||||
case "commitFiles":
|
case "commitFiles":
|
||||||
@@ -117,7 +130,7 @@ func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error {
|
|||||||
case "credentials":
|
case "credentials":
|
||||||
return gui.handleCredentialsViewFocused(g, v)
|
return gui.handleCredentialsViewFocused(g, v)
|
||||||
case "main":
|
case "main":
|
||||||
if gui.State.Context == "merging" {
|
if gui.State.MainContext == "merging" {
|
||||||
return gui.refreshMergePanel()
|
return gui.refreshMergePanel()
|
||||||
}
|
}
|
||||||
v.Highlight = false
|
v.Highlight = false
|
||||||
@@ -315,6 +328,11 @@ func (gui *Gui) getCommitFilesView() *gocui.View {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) getMenuView() *gocui.View {
|
||||||
|
v, _ := gui.g.View("menu")
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
func (gui *Gui) trimmedContent(v *gocui.View) string {
|
func (gui *Gui) trimmedContent(v *gocui.View) string {
|
||||||
return strings.TrimSpace(v.Buffer())
|
return strings.TrimSpace(v.Buffer())
|
||||||
}
|
}
|
||||||
@@ -362,19 +380,17 @@ func (gui *Gui) generalFocusLine(lineNumber int, bottomLine int, v *gocui.View)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) changeSelectedLine(line *int, total int, up bool) {
|
func (gui *Gui) changeSelectedLine(line *int, total int, change int) {
|
||||||
if up {
|
// TODO: find out why we're doing this
|
||||||
if *line == -1 || *line == 0 {
|
if *line == -1 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if *line+change < 0 {
|
||||||
*line--
|
*line = 0
|
||||||
|
} else if *line+change >= total {
|
||||||
|
*line = total - 1
|
||||||
} else {
|
} else {
|
||||||
if *line == -1 || *line == total-1 {
|
*line += change
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
*line++
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,7 +422,7 @@ func (gui *Gui) renderPanelOptions() error {
|
|||||||
case "menu":
|
case "menu":
|
||||||
return gui.renderMenuOptions()
|
return gui.renderMenuOptions()
|
||||||
case "main":
|
case "main":
|
||||||
if gui.State.Context == "merging" {
|
if gui.State.MainContext == "merging" {
|
||||||
return gui.renderMergeOptions()
|
return gui.renderMergeOptions()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -449,3 +465,12 @@ func (gui *Gui) handleClick(v *gocui.View, itemCount int, selectedLine *int, han
|
|||||||
|
|
||||||
return handleSelect(gui.g, v)
|
return handleSelect(gui.g, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// often gocui wants functions in the form `func(g *gocui.Gui, v *gocui.View) error`
|
||||||
|
// but sometimes we just have a function that returns an error, so this is a
|
||||||
|
// convenience wrapper to give gocui what it wants.
|
||||||
|
func (gui *Gui) wrappedHandler(f func() error) func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
return func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+3
-3
@@ -337,9 +337,6 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
|||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "newFocusedViewIs",
|
ID: "newFocusedViewIs",
|
||||||
Other: "nieuw gefocussed weergave is {{.newFocusedView}}",
|
Other: "nieuw gefocussed weergave is {{.newFocusedView}}",
|
||||||
}, &i18n.Message{
|
|
||||||
ID: "CantCloseConfirmationPrompt",
|
|
||||||
Other: "Kon de bevestiging prompt niet sluiten: {{.error}}",
|
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "MergeAborted",
|
ID: "MergeAborted",
|
||||||
Other: "Merge afgebroken",
|
Other: "Merge afgebroken",
|
||||||
@@ -760,6 +757,9 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
|||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "EnterUpstream",
|
ID: "EnterUpstream",
|
||||||
Other: `Enter upstream as '<remote> <branchname>'`,
|
Other: `Enter upstream as '<remote> <branchname>'`,
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "ReturnToRemotesList",
|
||||||
|
Other: `return to remotes list`,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+70
-4
@@ -221,7 +221,7 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
|||||||
Other: "{{.selectedBranchName}} is not fully merged. Are you sure you want to delete it?",
|
Other: "{{.selectedBranchName}} is not fully merged. Are you sure you want to delete it?",
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "rebaseBranch",
|
ID: "rebaseBranch",
|
||||||
Other: "rebase branch",
|
Other: "rebase checked-out branch onto this branch",
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "CantRebaseOntoSelf",
|
ID: "CantRebaseOntoSelf",
|
||||||
Other: "You cannot rebase a branch onto itself",
|
Other: "You cannot rebase a branch onto itself",
|
||||||
@@ -399,9 +399,6 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
|||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "newFocusedViewIs",
|
ID: "newFocusedViewIs",
|
||||||
Other: "new focused view is {{.newFocusedView}}",
|
Other: "new focused view is {{.newFocusedView}}",
|
||||||
}, &i18n.Message{
|
|
||||||
ID: "CantCloseConfirmationPrompt",
|
|
||||||
Other: "Could not close confirmation prompt: {{.error}}",
|
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "NoChangedFiles",
|
ID: "NoChangedFiles",
|
||||||
Other: "No changed files",
|
Other: "No changed files",
|
||||||
@@ -846,6 +843,75 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
|||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "notTrackingRemote",
|
ID: "notTrackingRemote",
|
||||||
Other: "(not tracking any remote)",
|
Other: "(not tracking any remote)",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "ReturnToRemotesList",
|
||||||
|
Other: `return to remotes list`,
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "addNewRemote",
|
||||||
|
Other: `add new remote`,
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "newRemoteName",
|
||||||
|
Other: `New remote name:`,
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "newRemoteUrl",
|
||||||
|
Other: `New remote url:`,
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "editRemoteName",
|
||||||
|
Other: `Enter updated remote name for {{ .remoteName }}:`,
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "editRemoteUrl",
|
||||||
|
Other: `Enter updated remote url for {{ .remoteName }}:`,
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "removeRemote",
|
||||||
|
Other: `remove remote`,
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "removeRemotePrompt",
|
||||||
|
Other: "Are you sure you want to remove remote",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "DeleteRemoteBranch",
|
||||||
|
Other: "Delete Remote Branch",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "DeleteRemoteBranchMessage",
|
||||||
|
Other: "Are you sure you want to delete remote branch",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "setUpstream",
|
||||||
|
Other: "set as upstream of checked-out branch",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "SetUpstreamTitle",
|
||||||
|
Other: "Set upstream branch",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "SetUpstreamMessage",
|
||||||
|
Other: "Are you sure you want to set the upstream branch of '{{.checkedOut}}' to '{{.selected}}'",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "editRemote",
|
||||||
|
Other: "edit remote",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "tagCommit",
|
||||||
|
Other: "tag commit",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "TagNameTitle",
|
||||||
|
Other: "Tag name:",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "deleteTag",
|
||||||
|
Other: "delete tag",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "DeleteTagTitle",
|
||||||
|
Other: "Delete tag",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "DeleteTagPrompt",
|
||||||
|
Other: "Are you sure you want to delete tag '{{.tagName}}'?",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "PushTagTitle",
|
||||||
|
Other: "remote to push tag '{{.tagName}}' to:",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "pushTags",
|
||||||
|
Other: "push tags",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "createTag",
|
||||||
|
Other: "create tag",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "CreateTagTitle",
|
||||||
|
Other: "Tag name:",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -329,9 +329,6 @@ func addPolish(i18nObject *i18n.Bundle) error {
|
|||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "newFocusedViewIs",
|
ID: "newFocusedViewIs",
|
||||||
Other: "nowy skupiony widok to {{.newFocusedView}}",
|
Other: "nowy skupiony widok to {{.newFocusedView}}",
|
||||||
}, &i18n.Message{
|
|
||||||
ID: "CantCloseConfirmationPrompt",
|
|
||||||
Other: "Nie można zamknąć monitu potwierdzenia: {{.error}}",
|
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "MergeAborted",
|
ID: "MergeAborted",
|
||||||
Other: "Scalanie anulowane",
|
Other: "Scalanie anulowane",
|
||||||
@@ -743,6 +740,9 @@ func addPolish(i18nObject *i18n.Bundle) error {
|
|||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "EnterUpstream",
|
ID: "EnterUpstream",
|
||||||
Other: `Enter upstream as '<remote> <branchname>'`,
|
Other: `Enter upstream as '<remote> <branchname>'`,
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "ReturnToRemotesList",
|
||||||
|
Other: `return to remotes list`,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -299,3 +299,14 @@ func DifferenceInt(a, b []int) []int {
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// used to keep a number n between 0 and max, allowing for wraparounds
|
||||||
|
func ModuloWithWrap(n, max int) int {
|
||||||
|
if n >= max {
|
||||||
|
return n % max
|
||||||
|
} else if n < 0 {
|
||||||
|
return max + n
|
||||||
|
} else {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+2
-2
@@ -295,14 +295,14 @@ func (g *Gui) CurrentView() *View {
|
|||||||
// SetKeybinding creates a new keybinding. If viewname equals to ""
|
// SetKeybinding creates a new keybinding. If viewname equals to ""
|
||||||
// (empty string) then the keybinding will apply to all views. key must
|
// (empty string) then the keybinding will apply to all views. key must
|
||||||
// be a rune or a Key.
|
// be a rune or a Key.
|
||||||
func (g *Gui) SetKeybinding(viewname string, key interface{}, mod Modifier, handler func(*Gui, *View) error) error {
|
func (g *Gui) SetKeybinding(viewname string, contexts []string, key interface{}, mod Modifier, handler func(*Gui, *View) error) error {
|
||||||
var kb *keybinding
|
var kb *keybinding
|
||||||
|
|
||||||
k, ch, err := getKey(key)
|
k, ch, err := getKey(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
kb = newKeybinding(viewname, k, ch, mod, handler)
|
kb = newKeybinding(viewname, contexts, k, ch, mod, handler)
|
||||||
g.keybindings = append(g.keybindings, kb)
|
g.keybindings = append(g.keybindings, kb)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-3
@@ -9,6 +9,7 @@ import "github.com/jesseduffield/termbox-go"
|
|||||||
// Keybidings are used to link a given key-press event with a handler.
|
// Keybidings are used to link a given key-press event with a handler.
|
||||||
type keybinding struct {
|
type keybinding struct {
|
||||||
viewName string
|
viewName string
|
||||||
|
contexts []string
|
||||||
key Key
|
key Key
|
||||||
ch rune
|
ch rune
|
||||||
mod Modifier
|
mod Modifier
|
||||||
@@ -16,9 +17,10 @@ type keybinding struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newKeybinding returns a new Keybinding object.
|
// newKeybinding returns a new Keybinding object.
|
||||||
func newKeybinding(viewname string, key Key, ch rune, mod Modifier, handler func(*Gui, *View) error) (kb *keybinding) {
|
func newKeybinding(viewname string, contexts []string, key Key, ch rune, mod Modifier, handler func(*Gui, *View) error) (kb *keybinding) {
|
||||||
kb = &keybinding{
|
kb = &keybinding{
|
||||||
viewName: viewname,
|
viewName: viewname,
|
||||||
|
contexts: contexts,
|
||||||
key: key,
|
key: key,
|
||||||
ch: ch,
|
ch: ch,
|
||||||
mod: mod,
|
mod: mod,
|
||||||
@@ -32,7 +34,7 @@ func (kb *keybinding) matchKeypress(key Key, ch rune, mod Modifier) bool {
|
|||||||
return kb.key == key && kb.ch == ch && kb.mod == mod
|
return kb.key == key && kb.ch == ch && kb.mod == mod
|
||||||
}
|
}
|
||||||
|
|
||||||
// matchView returns if the keybinding matches the current view.
|
// matchView returns if the keybinding matches the current view (and the view's context)
|
||||||
func (kb *keybinding) matchView(v *View) bool {
|
func (kb *keybinding) matchView(v *View) bool {
|
||||||
// if the user is typing in a field, ignore char keys
|
// if the user is typing in a field, ignore char keys
|
||||||
if v == nil {
|
if v == nil {
|
||||||
@@ -41,7 +43,19 @@ func (kb *keybinding) matchView(v *View) bool {
|
|||||||
if v.Editable == true && kb.ch != 0 {
|
if v.Editable == true && kb.ch != 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return kb.viewName == v.name
|
if kb.viewName != v.name {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// if the keybinding doesn't specify contexts, it applies for all contexts
|
||||||
|
if len(kb.contexts) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, context := range kb.contexts {
|
||||||
|
if context == v.Context {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key represents special keys or keys combinations.
|
// Key represents special keys or keys combinations.
|
||||||
|
|||||||
+2
@@ -103,6 +103,8 @@ type View struct {
|
|||||||
|
|
||||||
// ParentView is the view which catches events bubbled up from the given view if there's no matching handler
|
// ParentView is the view which catches events bubbled up from the given view if there's no matching handler
|
||||||
ParentView *View
|
ParentView *View
|
||||||
|
|
||||||
|
Context string // this is for assigning keybindings to a view only in certain contexts
|
||||||
}
|
}
|
||||||
|
|
||||||
type viewLine struct {
|
type viewLine struct {
|
||||||
|
|||||||
Vendored
+1
-1
@@ -32,7 +32,7 @@ github.com/hashicorp/hcl/json/token
|
|||||||
github.com/integrii/flaggy
|
github.com/integrii/flaggy
|
||||||
# github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99
|
# github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99
|
||||||
github.com/jbenet/go-context/io
|
github.com/jbenet/go-context/io
|
||||||
# github.com/jesseduffield/gocui v0.3.1-0.20191110053728-01cdcccd0508
|
# github.com/jesseduffield/gocui v0.3.1-0.20191116013947-b13bda319532
|
||||||
github.com/jesseduffield/gocui
|
github.com/jesseduffield/gocui
|
||||||
# github.com/jesseduffield/pty v1.2.1
|
# github.com/jesseduffield/pty v1.2.1
|
||||||
github.com/jesseduffield/pty
|
github.com/jesseduffield/pty
|
||||||
|
|||||||
Reference in New Issue
Block a user