1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-03-19 21:28:28 +02:00

fetching branches without checking out

This commit is contained in:
Jesse Duffield 2018-12-07 18:52:31 +11:00
parent ca3afa2a39
commit ff856b7630
18 changed files with 224 additions and 132 deletions

View File

@ -52,6 +52,9 @@ func newLogger(config config.AppConfigurer) *logrus.Entry {
} else { } else {
log = newProductionLogger(config) log = newProductionLogger(config)
} }
// highly recommended: tail -f development.log | humanlog
// https://github.com/aybabtme/humanlog
log.Formatter = &logrus.JSONFormatter{} log.Formatter = &logrus.JSONFormatter{}
if config.GetUserConfig().GetString("reporting") == "on" { if config.GetUserConfig().GetString("reporting") == "on" {

View File

@ -1,6 +1,7 @@
package commands package commands
import ( import (
"fmt"
"strings" "strings"
"github.com/fatih/color" "github.com/fatih/color"
@ -10,13 +11,21 @@ import (
// Branch : A git branch // Branch : A git branch
// duplicating this for now // duplicating this for now
type Branch struct { type Branch struct {
Name string Name string
Recency string Recency string
Pushables string
Pullables string
Selected bool
} }
// GetDisplayStrings returns the dispaly string of branch // GetDisplayStrings returns the dispaly string of branch
func (b *Branch) GetDisplayStrings() []string { func (b *Branch) GetDisplayStrings() []string {
return []string{b.Recency, utils.ColoredString(b.Name, b.GetColor())} displayName := utils.ColoredString(b.Name, b.GetColor())
if b.Selected && b.Pushables != "" && b.Pullables != "" {
displayName = fmt.Sprintf("↑%s↓%s %s", b.Pushables, b.Pullables, displayName)
}
return []string{b.Recency, displayName}
} }
// GetColor branch color // GetColor branch color

View File

@ -130,17 +130,6 @@ func (c *GitCommand) GetStashEntryDiff(index int) (string, error) {
// GetStatusFiles git status files // GetStatusFiles git status files
func (c *GitCommand) GetStatusFiles() []*File { func (c *GitCommand) GetStatusFiles() []*File {
// files := []*File{}
// for i := 0; i < 100; i++ {
// files = append(files, &File{
// Name: strconv.Itoa(i),
// DisplayString: strconv.Itoa(i),
// Type: "file",
// })
// }
// return files
statusOutput, _ := c.GitStatus() statusOutput, _ := c.GitStatus()
statusStrings := utils.SplitLines(statusOutput) statusStrings := utils.SplitLines(statusOutput)
files := []*File{} files := []*File{}
@ -165,7 +154,6 @@ func (c *GitCommand) GetStatusFiles() []*File {
} }
files = append(files, file) files = append(files, file)
} }
c.Log.Info(files) // TODO: use a dumper-esque log here
return files return files
} }
@ -228,14 +216,24 @@ func (c *GitCommand) ResetAndClean() error {
return c.OSCommand.RunCommand("git clean -fd") return c.OSCommand.RunCommand("git clean -fd")
} }
// UpstreamDifferenceCount checks how many pushables/pullables there are for the func (c *GitCommand) GetCurrentBranchUpstreamDifferenceCount() (string, string) {
return c.GetCommitDifferences("HEAD", "@{u}")
}
func (c *GitCommand) GetBranchUpstreamDifferenceCount(branchName string) (string, string) {
upstream := "origin" // hardcoded for now
return c.GetCommitDifferences(branchName, fmt.Sprintf("%s/%s", upstream, branchName))
}
// GetCommitDifferences checks how many pushables/pullables there are for the
// current branch // current branch
func (c *GitCommand) UpstreamDifferenceCount() (string, string) { func (c *GitCommand) GetCommitDifferences(from, to string) (string, string) {
pushableCount, err := c.OSCommand.RunCommandWithOutput("git rev-list @{u}..HEAD --count") command := "git rev-list %s..%s --count"
pushableCount, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf(command, to, from))
if err != nil { if err != nil {
return "?", "?" return "?", "?"
} }
pullableCount, err := c.OSCommand.RunCommandWithOutput("git rev-list HEAD..@{u} --count") pullableCount, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf(command, from, to))
if err != nil { if err != nil {
return "?", "?" return "?", "?"
} }
@ -618,3 +616,8 @@ func (c *GitCommand) ApplyPatch(patch string) (string, error) {
return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git apply --cached %s", filename)) return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git apply --cached %s", filename))
} }
func (c *GitCommand) FastForward(branchName string) error {
upstream := "origin" // hardcoding for now
return c.OSCommand.RunCommand(fmt.Sprintf("git fetch %s %s:%s", upstream, branchName, branchName))
}

View File

@ -557,8 +557,8 @@ func TestGitCommandMergeStatusFiles(t *testing.T) {
} }
} }
// TestGitCommandUpstreamDifferentCount is a function. // TestGitCommandGetCommitDifferences is a function.
func TestGitCommandUpstreamDifferentCount(t *testing.T) { func TestGitCommandGetCommitDifferences(t *testing.T) {
type scenario struct { type scenario struct {
testName string testName string
command func(string, ...string) *exec.Cmd command func(string, ...string) *exec.Cmd
@ -610,7 +610,7 @@ func TestGitCommandUpstreamDifferentCount(t *testing.T) {
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
gitCmd := newDummyGitCommand() gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = s.command gitCmd.OSCommand.command = s.command
s.test(gitCmd.UpstreamDifferenceCount()) s.test(gitCmd.GetCommitDifferences("HEAD", "@{u}"))
}) })
} }
} }

View File

@ -7,7 +7,6 @@ import (
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/git" "github.com/jesseduffield/lazygit/pkg/git"
"github.com/jesseduffield/lazygit/pkg/utils"
) )
// list panel functions // list panel functions
@ -31,6 +30,9 @@ func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error {
if err := gui.focusPoint(0, gui.State.Panels.Branches.SelectedLine, v); err != nil { if err := gui.focusPoint(0, gui.State.Panels.Branches.SelectedLine, v); err != nil {
return err return err
} }
go func() {
_ = gui.RenderSelectedBranchUpstreamDifferences()
}()
go func() { go func() {
graph, err := gui.GitCommand.GetBranchGraph(branch.Name) graph, err := gui.GitCommand.GetBranchGraph(branch.Name)
if err != nil && strings.HasPrefix(graph, "fatal: ambiguous argument") { if err != nil && strings.HasPrefix(graph, "fatal: ambiguous argument") {
@ -41,14 +43,23 @@ func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error {
return nil return nil
} }
func (gui *Gui) RenderSelectedBranchUpstreamDifferences() error {
// here we tell the selected branch that it is selected.
// this is necessary for showing stats on a branch that is selected, because
// the displaystring function doesn't have access to gui state to tell if it's selected
for i, branch := range gui.State.Branches {
branch.Selected = i == gui.State.Panels.Branches.SelectedLine
}
branch := gui.getSelectedBranch()
branch.Pushables, branch.Pullables = gui.GitCommand.GetBranchUpstreamDifferenceCount(branch.Name)
return gui.renderListPanel(gui.getBranchesView(gui.g), gui.State.Branches)
}
// 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 {
g.Update(func(g *gocui.Gui) error { g.Update(func(g *gocui.Gui) error {
v, err := g.View("branches")
if err != nil {
panic(err)
}
builder, err := git.NewBranchListBuilder(gui.Log, gui.GitCommand) builder, err := git.NewBranchListBuilder(gui.Log, gui.GitCommand)
if err != nil { if err != nil {
return err return err
@ -56,16 +67,13 @@ 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)) gui.refreshSelectedLine(&gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches))
if err := gui.resetOrigin(gui.getBranchesView(gui.g)); err != nil {
v.Clear() return err
list, err := utils.RenderList(gui.State.Branches) }
if err != nil { if err := gui.RenderSelectedBranchUpstreamDifferences(); err != nil {
return err return err
} }
fmt.Fprint(v, list)
gui.resetOrigin(v)
return gui.refreshStatus(g) return gui.refreshStatus(g)
}) })
return nil return nil
@ -74,7 +82,6 @@ func (gui *Gui) refreshBranches(g *gocui.Gui) error {
func (gui *Gui) handleBranchesNextLine(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleBranchesNextLine(g *gocui.Gui, v *gocui.View) error {
panelState := gui.State.Panels.Branches panelState := gui.State.Panels.Branches
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Branches), false) gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Branches), false)
return gui.handleBranchSelect(gui.g, v) return gui.handleBranchSelect(gui.g, v)
} }
@ -99,7 +106,10 @@ func (gui *Gui) handleBranchPress(g *gocui.Gui, v *gocui.View) error {
if err := gui.createErrorPanel(g, err.Error()); err != nil { if err := gui.createErrorPanel(g, err.Error()); err != nil {
return err return err
} }
} else {
gui.State.Panels.Branches.SelectedLine = 0
} }
return gui.refreshSidePanels(g) return gui.refreshSidePanels(g)
} }
@ -213,3 +223,37 @@ func (gui *Gui) handleMerge(g *gocui.Gui, v *gocui.View) error {
} }
return nil return nil
} }
func (gui *Gui) handleFastForward(g *gocui.Gui, v *gocui.View) error {
branch := gui.getSelectedBranch()
if branch == nil {
return nil
}
if branch.Pushables == "" {
return nil
}
if branch.Pushables == "?" {
return gui.createErrorPanel(gui.g, "Cannot fast-forward a branch with no upstream")
}
if branch.Pushables != "0" {
return gui.createErrorPanel(gui.g, "Cannot fast-forward a branch with commits to push")
}
upstream := "origin" // hardcoding for now
message := gui.Tr.TemplateLocalize(
"Fetching",
Teml{
"from": fmt.Sprintf("%s/%s", upstream, branch.Name),
"to": branch.Name,
},
)
go func() {
_ = gui.createMessagePanel(gui.g, v, "", message)
if err := gui.GitCommand.FastForward(branch.Name); err != nil {
_ = gui.createErrorPanel(gui.g, err.Error())
} else {
_ = gui.closeConfirmationPrompt(gui.g)
_ = gui.RenderSelectedBranchUpstreamDifferences()
}
}()
return nil
}

View File

@ -24,10 +24,10 @@ func (gui *Gui) handleCommitConfirm(g *gocui.Gui, v *gocui.View) error {
return gui.Errors.ErrSubProcess return gui.Errors.ErrSubProcess
} }
v.Clear() v.Clear()
v.SetCursor(0, 0) _ = v.SetCursor(0, 0)
v.SetOrigin(0, 0) _ = v.SetOrigin(0, 0)
g.SetViewOnBottom("commitMessage") _, _ = g.SetViewOnBottom("commitMessage")
gui.switchFocus(g, v, gui.getFilesView(g)) _ = gui.switchFocus(g, v, gui.getFilesView(g))
return gui.refreshSidePanels(g) return gui.refreshSidePanels(g)
} }

View File

@ -96,7 +96,8 @@ func (gui *Gui) handleResetToCommit(g *gocui.Gui, commitView *gocui.View) error
panic(err) panic(err)
} }
gui.resetOrigin(commitView) gui.resetOrigin(commitView)
return gui.handleCommitSelect(g, nil) gui.State.Panels.Commits.SelectedLine = 0
return gui.handleCommitSelect(g, commitView)
}, nil) }, nil)
} }

View File

@ -28,7 +28,7 @@ func (gui *Gui) wrappedConfirmationFunction(function func(*gocui.Gui, *gocui.Vie
func (gui *Gui) closeConfirmationPrompt(g *gocui.Gui) error { func (gui *Gui) closeConfirmationPrompt(g *gocui.Gui) error {
view, err := g.View("confirmation") view, err := g.View("confirmation")
if err != nil { if err != nil {
panic(err) return nil // if it's already been closed we can just return
} }
if err := gui.returnFocus(g, view); err != nil { if err := gui.returnFocus(g, view); err != nil {
panic(err) panic(err)
@ -77,11 +77,10 @@ func (gui *Gui) prepareConfirmationPanel(currentView *gocui.View, title, prompt
confirmationView.Wrap = true confirmationView.Wrap = true
confirmationView.FgColor = gocui.ColorWhite confirmationView.FgColor = gocui.ColorWhite
} }
confirmationView.Clear() gui.g.Update(func(g *gocui.Gui) error {
confirmationView.Clear()
if err := gui.switchFocus(gui.g, currentView, confirmationView); err != nil { return gui.switchFocus(gui.g, currentView, confirmationView)
return nil, err })
}
return confirmationView, nil return confirmationView, nil
} }

View File

@ -64,7 +64,7 @@ func (gui *Gui) refreshFiles(g *gocui.Gui) error {
fmt.Fprint(filesView, list) fmt.Fprint(filesView, list)
if filesView == g.CurrentView() { if filesView == g.CurrentView() {
gui.handleFileSelect(g, filesView) return gui.handleFileSelect(g, filesView)
} }
return nil return nil
}) })
@ -411,7 +411,7 @@ func (gui *Gui) pushWithForceFlag(currentView *gocui.View, force bool) error {
func (gui *Gui) pushFiles(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) pushFiles(g *gocui.Gui, v *gocui.View) error {
// if we have pullables we'll ask if the user wants to force push // if we have pullables we'll ask if the user wants to force push
_, pullables := gui.GitCommand.UpstreamDifferenceCount() _, pullables := gui.GitCommand.GetCurrentBranchUpstreamDifferenceCount()
if pullables == "?" || pullables == "0" { if pullables == "?" || pullables == "0" {
return gui.pushWithForceFlag(v, false) return gui.pushWithForceFlag(v, false)
} }

View File

@ -72,6 +72,9 @@ type Gui struct {
statusManager *statusManager statusManager *statusManager
} }
// for now the staging panel state, unlike the other panel states, is going to be
// non-mutative, so that we don't accidentally end up
// with mismatches of data. We might change this in the future
type stagingPanelState struct { type stagingPanelState struct {
SelectedLine int SelectedLine int
StageableLines []int StageableLines []int
@ -233,7 +236,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
} }
return nil return nil
} else { } else {
g.SetViewOnBottom("limit") _, _ = g.SetViewOnBottom("limit")
} }
g.DeleteView("limit") g.DeleteView("limit")
@ -364,14 +367,14 @@ func (gui *Gui) layout(g *gocui.Gui) error {
return err return err
} }
gui.g.SetCurrentView(filesView.Name()) if _, err := gui.g.SetCurrentView(filesView.Name()); err != nil {
return err
gui.refreshSidePanels(gui.g)
if gui.g.CurrentView().Name() != "menu" {
if err := gui.renderGlobalOptions(g); err != nil {
return err
}
} }
if err := gui.refreshSidePanels(gui.g); err != nil {
return err
}
if err := gui.switchFocus(g, nil, filesView); err != nil { if err := gui.switchFocus(g, nil, filesView); err != nil {
return err return err
} }
@ -394,8 +397,10 @@ func (gui *Gui) layout(g *gocui.Gui) error {
} }
} }
// TODO: comment-out // here is a good place log some stuff
gui.Log.Info(utils.AsJson(gui.State)) // if you download humanlog and do tail -f development.log | humanlog
// this will let you see these branches as prettified json
// gui.Log.Info(utils.AsJson(gui.State.Branches[0:4]))
return gui.resizeCurrentPopupPanel(g) return gui.resizeCurrentPopupPanel(g)
} }
@ -435,8 +440,8 @@ func (gui *Gui) renderAppStatus(g *gocui.Gui) error {
return nil return nil
} }
func (gui *Gui) renderGlobalOptions(g *gocui.Gui) error { func (gui *Gui) renderGlobalOptions() error {
return gui.renderOptionsMap(g, map[string]string{ return gui.renderOptionsMap(map[string]string{
"PgUp/PgDn": gui.Tr.SLocalize("scroll"), "PgUp/PgDn": gui.Tr.SLocalize("scroll"),
"← → ↑ ↓": gui.Tr.SLocalize("navigate"), "← → ↑ ↓": gui.Tr.SLocalize("navigate"),
"esc/q": gui.Tr.SLocalize("close"), "esc/q": gui.Tr.SLocalize("close"),
@ -467,7 +472,7 @@ func (gui *Gui) Run() error {
} }
gui.goEvery(g, time.Second*60, gui.fetch) gui.goEvery(g, time.Second*60, gui.fetch)
// gui.goEvery(g, time.Second*2, gui.refreshFiles) // TODO: comment back in gui.goEvery(g, time.Second*2, gui.refreshFiles)
gui.goEvery(g, time.Millisecond*50, gui.updateLoader) gui.goEvery(g, time.Millisecond*50, gui.updateLoader)
gui.goEvery(g, time.Millisecond*50, gui.renderAppStatus) gui.goEvery(g, time.Millisecond*50, gui.renderAppStatus)

View File

@ -219,12 +219,7 @@ func (gui *Gui) GetKeybindings() []*Binding {
Handler: gui.handleSwitchToStagingPanel, Handler: gui.handleSwitchToStagingPanel,
Description: gui.Tr.SLocalize("StageLines"), Description: gui.Tr.SLocalize("StageLines"),
KeyReadable: "enter", KeyReadable: "enter",
}, }, {
{ViewName: "files", Key: 'k', Modifier: gocui.ModNone, Handler: gui.handleFilesPrevLine},
{ViewName: "files", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: gui.handleFilesPrevLine},
{ViewName: "files", Key: 'j', Modifier: gocui.ModNone, Handler: gui.handleFilesNextLine},
{ViewName: "files", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: gui.handleFilesNextLine},
{
ViewName: "main", ViewName: "main",
Key: gocui.KeyEsc, Key: gocui.KeyEsc,
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
@ -327,12 +322,13 @@ func (gui *Gui) GetKeybindings() []*Binding {
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: gui.handleMerge, Handler: gui.handleMerge,
Description: gui.Tr.SLocalize("mergeIntoCurrentBranch"), Description: gui.Tr.SLocalize("mergeIntoCurrentBranch"),
}, }, {
{ViewName: "branches", Key: 'k', Modifier: gocui.ModNone, Handler: gui.handleBranchesPrevLine}, ViewName: "branches",
{ViewName: "branches", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: gui.handleBranchesPrevLine}, Key: 'f',
{ViewName: "branches", Key: 'j', Modifier: gocui.ModNone, Handler: gui.handleBranchesNextLine}, Modifier: gocui.ModNone,
{ViewName: "branches", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: gui.handleBranchesNextLine}, Handler: gui.handleFastForward,
{ Description: gui.Tr.SLocalize("FastForward"),
}, {
ViewName: "commits", ViewName: "commits",
Key: 's', Key: 's',
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
@ -362,12 +358,7 @@ func (gui *Gui) GetKeybindings() []*Binding {
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: gui.handleCommitFixup, Handler: gui.handleCommitFixup,
Description: gui.Tr.SLocalize("fixupCommit"), Description: gui.Tr.SLocalize("fixupCommit"),
}, }, {
{ViewName: "commits", Key: 'k', Modifier: gocui.ModNone, Handler: gui.handleCommitsPrevLine},
{ViewName: "commits", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: gui.handleCommitsPrevLine},
{ViewName: "commits", Key: 'j', Modifier: gocui.ModNone, Handler: gui.handleCommitsNextLine},
{ViewName: "commits", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: gui.handleCommitsNextLine},
{
ViewName: "stash", ViewName: "stash",
Key: gocui.KeySpace, Key: gocui.KeySpace,
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
@ -386,12 +377,7 @@ func (gui *Gui) GetKeybindings() []*Binding {
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: gui.handleStashDrop, Handler: gui.handleStashDrop,
Description: gui.Tr.SLocalize("drop"), Description: gui.Tr.SLocalize("drop"),
}, }, {
{ViewName: "stash", Key: 'k', Modifier: gocui.ModNone, Handler: gui.handleStashPrevLine},
{ViewName: "stash", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: gui.handleStashPrevLine},
{ViewName: "stash", Key: 'j', Modifier: gocui.ModNone, Handler: gui.handleStashNextLine},
{ViewName: "stash", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: gui.handleStashNextLine},
{
ViewName: "commitMessage", ViewName: "commitMessage",
Key: gocui.KeyEnter, Key: gocui.KeyEnter,
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
@ -411,11 +397,7 @@ func (gui *Gui) GetKeybindings() []*Binding {
Key: 'q', Key: 'q',
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: gui.handleMenuClose, Handler: gui.handleMenuClose,
}, }, {
{ViewName: "menu", Key: 'k', Modifier: gocui.ModNone, Handler: gui.handleMenuPrevLine},
{ViewName: "menu", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: gui.handleMenuPrevLine},
{ViewName: "menu", Key: 'j', Modifier: gocui.ModNone, Handler: gui.handleMenuNextLine},
{ViewName: "menu", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: gui.handleMenuNextLine}, {
ViewName: "staging", ViewName: "staging",
Key: gocui.KeyEsc, Key: gocui.KeyEsc,
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
@ -487,6 +469,26 @@ func (gui *Gui) GetKeybindings() []*Binding {
}...) }...)
} }
listPanelMap := map[string]struct {
prevLine func(*gocui.Gui, *gocui.View) error
nextLine func(*gocui.Gui, *gocui.View) error
}{
"menu": {prevLine: gui.handleMenuPrevLine, nextLine: gui.handleMenuNextLine},
"files": {prevLine: gui.handleFilesPrevLine, nextLine: gui.handleFilesNextLine},
"branches": {prevLine: gui.handleBranchesPrevLine, nextLine: gui.handleBranchesNextLine},
"commits": {prevLine: gui.handleCommitsPrevLine, nextLine: gui.handleCommitsNextLine},
"stash": {prevLine: gui.handleStashPrevLine, nextLine: gui.handleStashNextLine},
}
for viewName, functions := range listPanelMap {
bindings = append(bindings, []*Binding{
{ViewName: viewName, Key: 'k', Modifier: gocui.ModNone, Handler: functions.prevLine},
{ViewName: viewName, Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: functions.prevLine},
{ViewName: viewName, Key: 'j', Modifier: gocui.ModNone, Handler: functions.nextLine},
{ViewName: viewName, Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: functions.nextLine},
}...)
}
return bindings return bindings
} }

View File

@ -25,22 +25,18 @@ func (gui *Gui) handleMenuPrevLine(g *gocui.Gui, v *gocui.View) error {
panelState := gui.State.Panels.Menu panelState := gui.State.Panels.Menu
gui.changeSelectedLine(&panelState.SelectedLine, v.LinesHeight(), true) gui.changeSelectedLine(&panelState.SelectedLine, v.LinesHeight(), true)
if err := gui.focusPoint(0, gui.State.Panels.Commits.SelectedLine, v); err != nil {
return err
}
return gui.handleMenuSelect(g, v) return gui.handleMenuSelect(g, v)
} }
// specific functions // specific functions
func (gui *Gui) renderMenuOptions(g *gocui.Gui) error { func (gui *Gui) renderMenuOptions() error {
optionsMap := map[string]string{ optionsMap := map[string]string{
"esc/q": gui.Tr.SLocalize("close"), "esc/q": gui.Tr.SLocalize("close"),
"↑ ↓": gui.Tr.SLocalize("navigate"), "↑ ↓": gui.Tr.SLocalize("navigate"),
"space": gui.Tr.SLocalize("execute"), "space": gui.Tr.SLocalize("execute"),
} }
return gui.renderOptionsMap(g, optionsMap) return gui.renderOptionsMap(optionsMap)
} }
func (gui *Gui) handleMenuClose(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleMenuClose(g *gocui.Gui, v *gocui.View) error {
@ -68,10 +64,6 @@ func (gui *Gui) createMenu(items interface{}, handlePress func(int) error) error
fmt.Fprint(menuView, list) fmt.Fprint(menuView, list)
gui.State.Panels.Menu.SelectedLine = 0 gui.State.Panels.Menu.SelectedLine = 0
if err := gui.renderMenuOptions(gui.g); err != nil {
return err
}
wrappedHandlePress := func(g *gocui.Gui, v *gocui.View) error { wrappedHandlePress := func(g *gocui.Gui, v *gocui.View) error {
selectedLine := gui.State.Panels.Menu.SelectedLine selectedLine := gui.State.Panels.Menu.SelectedLine
return handlePress(selectedLine) return handlePress(selectedLine)

View File

@ -194,9 +194,6 @@ func (gui *Gui) refreshMergePanel(g *gocui.Gui) error {
gui.State.ConflictIndex = len(gui.State.Conflicts) - 1 gui.State.ConflictIndex = len(gui.State.Conflicts) - 1
} }
hasFocus := gui.currentViewName(g) == "main" hasFocus := gui.currentViewName(g) == "main"
if hasFocus {
gui.renderMergeOptions(g)
}
content, err := gui.coloredConflictFile(cat, gui.State.Conflicts, gui.State.ConflictIndex, gui.State.ConflictTop, hasFocus) content, err := gui.coloredConflictFile(cat, gui.State.Conflicts, gui.State.ConflictIndex, gui.State.ConflictTop, hasFocus)
if err != nil { if err != nil {
return err return err
@ -233,8 +230,8 @@ func (gui *Gui) switchToMerging(g *gocui.Gui) error {
return gui.refreshMergePanel(g) return gui.refreshMergePanel(g)
} }
func (gui *Gui) renderMergeOptions(g *gocui.Gui) error { func (gui *Gui) renderMergeOptions() error {
return gui.renderOptionsMap(g, map[string]string{ return gui.renderOptionsMap(map[string]string{
"↑ ↓": gui.Tr.SLocalize("selectHunk"), "↑ ↓": gui.Tr.SLocalize("selectHunk"),
"← →": gui.Tr.SLocalize("navigateConflicts"), "← →": gui.Tr.SLocalize("navigateConflicts"),
"space": gui.Tr.SLocalize("pickHunk"), "space": gui.Tr.SLocalize("pickHunk"),

View File

@ -30,7 +30,7 @@ func (gui *Gui) handleStashEntrySelect(g *gocui.Gui, v *gocui.View) error {
go func() { go func() {
// doing this asynchronously cos it can take time // doing this asynchronously cos it can take time
diff, _ := gui.GitCommand.GetStashEntryDiff(stashEntry.Index) diff, _ := gui.GitCommand.GetStashEntryDiff(stashEntry.Index)
gui.renderString(g, "main", diff) _ = gui.renderString(g, "main", diff)
}() }()
return nil return nil
} }

View File

@ -19,7 +19,7 @@ func (gui *Gui) refreshStatus(g *gocui.Gui) error {
// contents end up cleared // contents end up cleared
g.Update(func(*gocui.Gui) error { g.Update(func(*gocui.Gui) error {
v.Clear() v.Clear()
pushables, pullables := gui.GitCommand.UpstreamDifferenceCount() pushables, pullables := gui.GitCommand.GetCurrentBranchUpstreamDifferenceCount()
fmt.Fprint(v, "↑"+pushables+"↓"+pullables) fmt.Fprint(v, "↑"+pushables+"↓"+pullables)
branches := gui.State.Branches branches := gui.State.Branches
if err := gui.updateHasMergeConflictStatus(); err != nil { if err := gui.updateHasMergeConflictStatus(); err != nil {
@ -48,6 +48,8 @@ func (gui *Gui) handleCheckForUpdate(g *gocui.Gui, v *gocui.View) error {
} }
func (gui *Gui) handleStatusSelect(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleStatusSelect(g *gocui.Gui, v *gocui.View) error {
blue := color.New(color.FgBlue)
dashboardString := strings.Join( dashboardString := strings.Join(
[]string{ []string{
lazygitTitle(), lazygitTitle(),
@ -56,7 +58,7 @@ func (gui *Gui) handleStatusSelect(g *gocui.Gui, v *gocui.View) error {
"Config Options: https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md", "Config Options: https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md",
"Tutorial: https://youtu.be/VDXvbHZYeKY", "Tutorial: https://youtu.be/VDXvbHZYeKY",
"Raise an Issue: https://github.com/jesseduffield/lazygit/issues", "Raise an Issue: https://github.com/jesseduffield/lazygit/issues",
"Buy Jesse a coffee: https://donorbox.org/lazygit", blue.Sprint("Buy Jesse a coffee: https://donorbox.org/lazygit"), // caffeine ain't free
}, "\n\n") }, "\n\n")
return gui.renderString(g, "main", dashboardString) return gui.renderString(g, "main", dashboardString)

View File

@ -13,11 +13,16 @@ import (
var cyclableViews = []string{"status", "files", "branches", "commits", "stash"} var cyclableViews = []string{"status", "files", "branches", "commits", "stash"}
func (gui *Gui) refreshSidePanels(g *gocui.Gui) error { func (gui *Gui) refreshSidePanels(g *gocui.Gui) error {
gui.refreshBranches(g) if err := gui.refreshBranches(g); err != nil {
gui.refreshFiles(g) return err
gui.refreshCommits(g) }
gui.refreshStashEntries(g) if err := gui.refreshFiles(g); err != nil {
return nil return err
}
if err := gui.refreshCommits(g); err != nil {
return err
}
return gui.refreshStashEntries(g)
} }
func (gui *Gui) nextView(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) nextView(g *gocui.Gui, v *gocui.View) error {
@ -81,7 +86,7 @@ func (gui *Gui) previousView(g *gocui.Gui, v *gocui.View) error {
func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error {
switch v.Name() { switch v.Name() {
case "menu": case "menu":
return nil return gui.handleMenuSelect(g, v)
case "status": case "status":
return gui.handleStatusSelect(g, v) return gui.handleStatusSelect(g, v)
case "files": case "files":
@ -160,6 +165,10 @@ func (gui *Gui) switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error {
g.Cursor = newView.Editable g.Cursor = newView.Editable
if err := gui.renderPanelOptions(); err != nil {
return err
}
return gui.newLineFocused(g, newView) return gui.newLineFocused(g, newView)
} }
@ -240,8 +249,8 @@ func (gui *Gui) optionsMapToString(optionsMap map[string]string) string {
return strings.Join(optionsArray, ", ") return strings.Join(optionsArray, ", ")
} }
func (gui *Gui) renderOptionsMap(g *gocui.Gui, optionsMap map[string]string) error { func (gui *Gui) renderOptionsMap(optionsMap map[string]string) error {
return gui.renderString(g, "options", gui.optionsMapToString(optionsMap)) return gui.renderString(gui.g, "options", gui.optionsMapToString(optionsMap))
} }
// TODO: refactor properly // TODO: refactor properly
@ -312,22 +321,6 @@ func (gui *Gui) resizePopupPanel(g *gocui.Gui, v *gocui.View) error {
return err return err
} }
// focusLine focuses and selects the given line
func (gui *Gui) focusLine(lineNumber int, v *gocui.View) error {
_, height := v.Size()
overScroll := lineNumber - height + 1
if overScroll < 0 {
overScroll = 0
}
if err := v.SetOrigin(0, overScroll); err != nil {
return err
}
if err := v.SetCursor(0, lineNumber-overScroll); err != nil {
return err
}
return nil
}
// generalFocusLine takes a lineNumber to focus, and a bottomLine to ensure we can see // generalFocusLine takes a lineNumber to focus, and a bottomLine to ensure we can see
func (gui *Gui) generalFocusLine(lineNumber int, bottomLine int, v *gocui.View) error { func (gui *Gui) generalFocusLine(lineNumber int, bottomLine int, v *gocui.View) error {
_, height := v.Size() _, height := v.Size()
@ -367,3 +360,28 @@ func (gui *Gui) refreshSelectedLine(line *int, total int) {
*line = total - 1 *line = total - 1
} }
} }
func (gui *Gui) renderListPanel(v *gocui.View, items interface{}) error {
gui.g.Update(func(g *gocui.Gui) error {
list, err := utils.RenderList(items)
if err != nil {
return gui.createErrorPanel(gui.g, err.Error())
}
v.Clear()
fmt.Fprint(v, list)
return nil
})
return nil
}
func (gui *Gui) renderPanelOptions() error {
currentView := gui.g.CurrentView()
switch currentView.Name() {
case "menu":
return gui.renderMenuOptions()
case "main":
return gui.renderMergeOptions()
default:
return gui.renderGlobalOptions()
}
}

View File

@ -435,6 +435,12 @@ func addEnglish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{ }, &i18n.Message{
ID: "CantFindHunk", ID: "CantFindHunk",
Other: `Could not find hunk`, Other: `Could not find hunk`,
}, &i18n.Message{
ID: "FastForward",
Other: `fast-forward this branch from its upstream`,
}, &i18n.Message{
ID: "Fetching",
Other: "fetching and fast-forwarding {{.from}} -> {{.to}} ...",
}, },
) )
} }

View File

@ -517,3 +517,14 @@ func TestPrevIndex(t *testing.T) {
}) })
} }
} }
func TestAsJson(t *testing.T) {
type myStruct struct {
a string
}
output := AsJson(&myStruct{a: "foo"})
// no idea why this is returning empty hashes but it's works in the app ¯\_(ツ)_/¯
assert.EqualValues(t, "{}", output)
}