mirror of
				https://github.com/jesseduffield/lazygit.git
				synced 2025-10-30 23:57:43 +02:00 
			
		
		
		
	fetching branches without checking out
This commit is contained in:
		| @@ -52,6 +52,9 @@ func newLogger(config config.AppConfigurer) *logrus.Entry { | ||||
| 	} else { | ||||
| 		log = newProductionLogger(config) | ||||
| 	} | ||||
|  | ||||
| 	// highly recommended: tail -f development.log | humanlog | ||||
| 	// https://github.com/aybabtme/humanlog | ||||
| 	log.Formatter = &logrus.JSONFormatter{} | ||||
|  | ||||
| 	if config.GetUserConfig().GetString("reporting") == "on" { | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package commands | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/fatih/color" | ||||
| @@ -10,13 +11,21 @@ import ( | ||||
| // Branch : A git branch | ||||
| // duplicating this for now | ||||
| type Branch struct { | ||||
| 	Name    string | ||||
| 	Recency string | ||||
| 	Name      string | ||||
| 	Recency   string | ||||
| 	Pushables string | ||||
| 	Pullables string | ||||
| 	Selected  bool | ||||
| } | ||||
|  | ||||
| // GetDisplayStrings returns the dispaly string of branch | ||||
| 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 | ||||
|   | ||||
| @@ -130,17 +130,6 @@ func (c *GitCommand) GetStashEntryDiff(index int) (string, error) { | ||||
|  | ||||
| // GetStatusFiles git status files | ||||
| 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() | ||||
| 	statusStrings := utils.SplitLines(statusOutput) | ||||
| 	files := []*File{} | ||||
| @@ -165,7 +154,6 @@ func (c *GitCommand) GetStatusFiles() []*File { | ||||
| 		} | ||||
| 		files = append(files, file) | ||||
| 	} | ||||
| 	c.Log.Info(files) // TODO: use a dumper-esque log here | ||||
| 	return files | ||||
| } | ||||
|  | ||||
| @@ -228,14 +216,24 @@ func (c *GitCommand) ResetAndClean() error { | ||||
| 	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 | ||||
| func (c *GitCommand) UpstreamDifferenceCount() (string, string) { | ||||
| 	pushableCount, err := c.OSCommand.RunCommandWithOutput("git rev-list @{u}..HEAD --count") | ||||
| func (c *GitCommand) GetCommitDifferences(from, to string) (string, string) { | ||||
| 	command := "git rev-list %s..%s --count" | ||||
| 	pushableCount, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf(command, to, from)) | ||||
| 	if err != nil { | ||||
| 		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 { | ||||
| 		return "?", "?" | ||||
| 	} | ||||
| @@ -618,3 +616,8 @@ func (c *GitCommand) ApplyPatch(patch string) (string, error) { | ||||
|  | ||||
| 	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)) | ||||
| } | ||||
|   | ||||
| @@ -557,8 +557,8 @@ func TestGitCommandMergeStatusFiles(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // TestGitCommandUpstreamDifferentCount is a function. | ||||
| func TestGitCommandUpstreamDifferentCount(t *testing.T) { | ||||
| // TestGitCommandGetCommitDifferences is a function. | ||||
| func TestGitCommandGetCommitDifferences(t *testing.T) { | ||||
| 	type scenario struct { | ||||
| 		testName string | ||||
| 		command  func(string, ...string) *exec.Cmd | ||||
| @@ -610,7 +610,7 @@ func TestGitCommandUpstreamDifferentCount(t *testing.T) { | ||||
| 		t.Run(s.testName, func(t *testing.T) { | ||||
| 			gitCmd := newDummyGitCommand() | ||||
| 			gitCmd.OSCommand.command = s.command | ||||
| 			s.test(gitCmd.UpstreamDifferenceCount()) | ||||
| 			s.test(gitCmd.GetCommitDifferences("HEAD", "@{u}")) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -7,7 +7,6 @@ import ( | ||||
| 	"github.com/jesseduffield/gocui" | ||||
| 	"github.com/jesseduffield/lazygit/pkg/commands" | ||||
| 	"github.com/jesseduffield/lazygit/pkg/git" | ||||
| 	"github.com/jesseduffield/lazygit/pkg/utils" | ||||
| ) | ||||
|  | ||||
| // 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 { | ||||
| 		return err | ||||
| 	} | ||||
| 	go func() { | ||||
| 		_ = gui.RenderSelectedBranchUpstreamDifferences() | ||||
| 	}() | ||||
| 	go func() { | ||||
| 		graph, err := gui.GitCommand.GetBranchGraph(branch.Name) | ||||
| 		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 | ||||
| } | ||||
|  | ||||
| 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 | ||||
| // be sure there is a state.Branches array to pick the current branch from | ||||
| func (gui *Gui) refreshBranches(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) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| @@ -56,16 +67,13 @@ func (gui *Gui) refreshBranches(g *gocui.Gui) error { | ||||
| 		gui.State.Branches = builder.Build() | ||||
|  | ||||
| 		gui.refreshSelectedLine(&gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches)) | ||||
|  | ||||
| 		v.Clear() | ||||
| 		list, err := utils.RenderList(gui.State.Branches) | ||||
| 		if err != nil { | ||||
| 		if err := gui.resetOrigin(gui.getBranchesView(gui.g)); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := gui.RenderSelectedBranchUpstreamDifferences(); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		fmt.Fprint(v, list) | ||||
|  | ||||
| 		gui.resetOrigin(v) | ||||
| 		return gui.refreshStatus(g) | ||||
| 	}) | ||||
| 	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 { | ||||
| 	panelState := gui.State.Panels.Branches | ||||
| 	gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Branches), false) | ||||
|  | ||||
| 	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 { | ||||
| 			return err | ||||
| 		} | ||||
| 	} else { | ||||
| 		gui.State.Panels.Branches.SelectedLine = 0 | ||||
| 	} | ||||
|  | ||||
| 	return gui.refreshSidePanels(g) | ||||
| } | ||||
|  | ||||
| @@ -213,3 +223,37 @@ func (gui *Gui) handleMerge(g *gocui.Gui, v *gocui.View) error { | ||||
| 	} | ||||
| 	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 | ||||
| } | ||||
|   | ||||
| @@ -24,10 +24,10 @@ func (gui *Gui) handleCommitConfirm(g *gocui.Gui, v *gocui.View) error { | ||||
| 		return gui.Errors.ErrSubProcess | ||||
| 	} | ||||
| 	v.Clear() | ||||
| 	v.SetCursor(0, 0) | ||||
| 	v.SetOrigin(0, 0) | ||||
| 	g.SetViewOnBottom("commitMessage") | ||||
| 	gui.switchFocus(g, v, gui.getFilesView(g)) | ||||
| 	_ = v.SetCursor(0, 0) | ||||
| 	_ = v.SetOrigin(0, 0) | ||||
| 	_, _ = g.SetViewOnBottom("commitMessage") | ||||
| 	_ = gui.switchFocus(g, v, gui.getFilesView(g)) | ||||
| 	return gui.refreshSidePanels(g) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -96,7 +96,8 @@ func (gui *Gui) handleResetToCommit(g *gocui.Gui, commitView *gocui.View) error | ||||
| 			panic(err) | ||||
| 		} | ||||
| 		gui.resetOrigin(commitView) | ||||
| 		return gui.handleCommitSelect(g, nil) | ||||
| 		gui.State.Panels.Commits.SelectedLine = 0 | ||||
| 		return gui.handleCommitSelect(g, commitView) | ||||
| 	}, nil) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -28,7 +28,7 @@ func (gui *Gui) wrappedConfirmationFunction(function func(*gocui.Gui, *gocui.Vie | ||||
| func (gui *Gui) closeConfirmationPrompt(g *gocui.Gui) error { | ||||
| 	view, err := g.View("confirmation") | ||||
| 	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 { | ||||
| 		panic(err) | ||||
| @@ -77,11 +77,10 @@ func (gui *Gui) prepareConfirmationPanel(currentView *gocui.View, title, prompt | ||||
| 		confirmationView.Wrap = true | ||||
| 		confirmationView.FgColor = gocui.ColorWhite | ||||
| 	} | ||||
| 	confirmationView.Clear() | ||||
|  | ||||
| 	if err := gui.switchFocus(gui.g, currentView, confirmationView); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	gui.g.Update(func(g *gocui.Gui) error { | ||||
| 		confirmationView.Clear() | ||||
| 		return gui.switchFocus(gui.g, currentView, confirmationView) | ||||
| 	}) | ||||
| 	return confirmationView, nil | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -64,7 +64,7 @@ func (gui *Gui) refreshFiles(g *gocui.Gui) error { | ||||
| 		fmt.Fprint(filesView, list) | ||||
|  | ||||
| 		if filesView == g.CurrentView() { | ||||
| 			gui.handleFileSelect(g, filesView) | ||||
| 			return gui.handleFileSelect(g, filesView) | ||||
| 		} | ||||
| 		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 { | ||||
| 	// 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" { | ||||
| 		return gui.pushWithForceFlag(v, false) | ||||
| 	} | ||||
|   | ||||
| @@ -72,6 +72,9 @@ type Gui struct { | ||||
| 	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 { | ||||
| 	SelectedLine   int | ||||
| 	StageableLines []int | ||||
| @@ -233,7 +236,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { | ||||
| 		} | ||||
| 		return nil | ||||
| 	} else { | ||||
| 		g.SetViewOnBottom("limit") | ||||
| 		_, _ = g.SetViewOnBottom("limit") | ||||
| 	} | ||||
|  | ||||
| 	g.DeleteView("limit") | ||||
| @@ -364,14 +367,14 @@ func (gui *Gui) layout(g *gocui.Gui) error { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		gui.g.SetCurrentView(filesView.Name()) | ||||
|  | ||||
| 		gui.refreshSidePanels(gui.g) | ||||
| 		if gui.g.CurrentView().Name() != "menu" { | ||||
| 			if err := gui.renderGlobalOptions(g); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		if _, err := gui.g.SetCurrentView(filesView.Name()); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		if err := gui.refreshSidePanels(gui.g); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		if err := gui.switchFocus(g, nil, filesView); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| @@ -394,8 +397,10 @@ func (gui *Gui) layout(g *gocui.Gui) error { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// TODO: comment-out | ||||
| 	gui.Log.Info(utils.AsJson(gui.State)) | ||||
| 	// here is a good place log some stuff | ||||
| 	// 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) | ||||
| } | ||||
| @@ -435,8 +440,8 @@ func (gui *Gui) renderAppStatus(g *gocui.Gui) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (gui *Gui) renderGlobalOptions(g *gocui.Gui) error { | ||||
| 	return gui.renderOptionsMap(g, map[string]string{ | ||||
| func (gui *Gui) renderGlobalOptions() error { | ||||
| 	return gui.renderOptionsMap(map[string]string{ | ||||
| 		"PgUp/PgDn": gui.Tr.SLocalize("scroll"), | ||||
| 		"← → ↑ ↓":   gui.Tr.SLocalize("navigate"), | ||||
| 		"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*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.renderAppStatus) | ||||
|  | ||||
|   | ||||
| @@ -219,12 +219,7 @@ func (gui *Gui) GetKeybindings() []*Binding { | ||||
| 			Handler:     gui.handleSwitchToStagingPanel, | ||||
| 			Description: gui.Tr.SLocalize("StageLines"), | ||||
| 			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", | ||||
| 			Key:      gocui.KeyEsc, | ||||
| 			Modifier: gocui.ModNone, | ||||
| @@ -327,12 +322,13 @@ func (gui *Gui) GetKeybindings() []*Binding { | ||||
| 			Modifier:    gocui.ModNone, | ||||
| 			Handler:     gui.handleMerge, | ||||
| 			Description: gui.Tr.SLocalize("mergeIntoCurrentBranch"), | ||||
| 		}, | ||||
| 		{ViewName: "branches", Key: 'k', Modifier: gocui.ModNone, Handler: gui.handleBranchesPrevLine}, | ||||
| 		{ViewName: "branches", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: gui.handleBranchesPrevLine}, | ||||
| 		{ViewName: "branches", Key: 'j', Modifier: gocui.ModNone, Handler: gui.handleBranchesNextLine}, | ||||
| 		{ViewName: "branches", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: gui.handleBranchesNextLine}, | ||||
| 		{ | ||||
| 		}, { | ||||
| 			ViewName:    "branches", | ||||
| 			Key:         'f', | ||||
| 			Modifier:    gocui.ModNone, | ||||
| 			Handler:     gui.handleFastForward, | ||||
| 			Description: gui.Tr.SLocalize("FastForward"), | ||||
| 		}, { | ||||
| 			ViewName:    "commits", | ||||
| 			Key:         's', | ||||
| 			Modifier:    gocui.ModNone, | ||||
| @@ -362,12 +358,7 @@ func (gui *Gui) GetKeybindings() []*Binding { | ||||
| 			Modifier:    gocui.ModNone, | ||||
| 			Handler:     gui.handleCommitFixup, | ||||
| 			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", | ||||
| 			Key:         gocui.KeySpace, | ||||
| 			Modifier:    gocui.ModNone, | ||||
| @@ -386,12 +377,7 @@ func (gui *Gui) GetKeybindings() []*Binding { | ||||
| 			Modifier:    gocui.ModNone, | ||||
| 			Handler:     gui.handleStashDrop, | ||||
| 			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", | ||||
| 			Key:      gocui.KeyEnter, | ||||
| 			Modifier: gocui.ModNone, | ||||
| @@ -411,11 +397,7 @@ func (gui *Gui) GetKeybindings() []*Binding { | ||||
| 			Key:      'q', | ||||
| 			Modifier: gocui.ModNone, | ||||
| 			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", | ||||
| 			Key:         gocui.KeyEsc, | ||||
| 			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 | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -25,22 +25,18 @@ func (gui *Gui) handleMenuPrevLine(g *gocui.Gui, v *gocui.View) error { | ||||
| 	panelState := gui.State.Panels.Menu | ||||
| 	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) | ||||
| } | ||||
|  | ||||
| // specific functions | ||||
|  | ||||
| func (gui *Gui) renderMenuOptions(g *gocui.Gui) error { | ||||
| func (gui *Gui) renderMenuOptions() error { | ||||
| 	optionsMap := map[string]string{ | ||||
| 		"esc/q": gui.Tr.SLocalize("close"), | ||||
| 		"↑ ↓":   gui.Tr.SLocalize("navigate"), | ||||
| 		"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 { | ||||
| @@ -68,10 +64,6 @@ func (gui *Gui) createMenu(items interface{}, handlePress func(int) error) error | ||||
| 	fmt.Fprint(menuView, list) | ||||
| 	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 { | ||||
| 		selectedLine := gui.State.Panels.Menu.SelectedLine | ||||
| 		return handlePress(selectedLine) | ||||
|   | ||||
| @@ -194,9 +194,6 @@ func (gui *Gui) refreshMergePanel(g *gocui.Gui) error { | ||||
| 		gui.State.ConflictIndex = len(gui.State.Conflicts) - 1 | ||||
| 	} | ||||
| 	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) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| @@ -233,8 +230,8 @@ func (gui *Gui) switchToMerging(g *gocui.Gui) error { | ||||
| 	return gui.refreshMergePanel(g) | ||||
| } | ||||
|  | ||||
| func (gui *Gui) renderMergeOptions(g *gocui.Gui) error { | ||||
| 	return gui.renderOptionsMap(g, map[string]string{ | ||||
| func (gui *Gui) renderMergeOptions() error { | ||||
| 	return gui.renderOptionsMap(map[string]string{ | ||||
| 		"↑ ↓":   gui.Tr.SLocalize("selectHunk"), | ||||
| 		"← →":   gui.Tr.SLocalize("navigateConflicts"), | ||||
| 		"space": gui.Tr.SLocalize("pickHunk"), | ||||
|   | ||||
| @@ -30,7 +30,7 @@ func (gui *Gui) handleStashEntrySelect(g *gocui.Gui, v *gocui.View) error { | ||||
| 	go func() { | ||||
| 		// doing this asynchronously cos it can take time | ||||
| 		diff, _ := gui.GitCommand.GetStashEntryDiff(stashEntry.Index) | ||||
| 		gui.renderString(g, "main", diff) | ||||
| 		_ = gui.renderString(g, "main", diff) | ||||
| 	}() | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
| @@ -19,7 +19,7 @@ func (gui *Gui) refreshStatus(g *gocui.Gui) error { | ||||
| 	// contents end up cleared | ||||
| 	g.Update(func(*gocui.Gui) error { | ||||
| 		v.Clear() | ||||
| 		pushables, pullables := gui.GitCommand.UpstreamDifferenceCount() | ||||
| 		pushables, pullables := gui.GitCommand.GetCurrentBranchUpstreamDifferenceCount() | ||||
| 		fmt.Fprint(v, "↑"+pushables+"↓"+pullables) | ||||
| 		branches := gui.State.Branches | ||||
| 		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 { | ||||
| 	blue := color.New(color.FgBlue) | ||||
|  | ||||
| 	dashboardString := strings.Join( | ||||
| 		[]string{ | ||||
| 			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", | ||||
| 			"Tutorial: https://youtu.be/VDXvbHZYeKY", | ||||
| 			"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") | ||||
|  | ||||
| 	return gui.renderString(g, "main", dashboardString) | ||||
|   | ||||
| @@ -13,11 +13,16 @@ import ( | ||||
| var cyclableViews = []string{"status", "files", "branches", "commits", "stash"} | ||||
|  | ||||
| func (gui *Gui) refreshSidePanels(g *gocui.Gui) error { | ||||
| 	gui.refreshBranches(g) | ||||
| 	gui.refreshFiles(g) | ||||
| 	gui.refreshCommits(g) | ||||
| 	gui.refreshStashEntries(g) | ||||
| 	return nil | ||||
| 	if err := gui.refreshBranches(g); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := gui.refreshFiles(g); err != 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 { | ||||
| @@ -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 { | ||||
| 	switch v.Name() { | ||||
| 	case "menu": | ||||
| 		return nil | ||||
| 		return gui.handleMenuSelect(g, v) | ||||
| 	case "status": | ||||
| 		return gui.handleStatusSelect(g, v) | ||||
| 	case "files": | ||||
| @@ -160,6 +165,10 @@ func (gui *Gui) switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error { | ||||
|  | ||||
| 	g.Cursor = newView.Editable | ||||
|  | ||||
| 	if err := gui.renderPanelOptions(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return gui.newLineFocused(g, newView) | ||||
| } | ||||
|  | ||||
| @@ -240,8 +249,8 @@ func (gui *Gui) optionsMapToString(optionsMap map[string]string) string { | ||||
| 	return strings.Join(optionsArray, ", ") | ||||
| } | ||||
|  | ||||
| func (gui *Gui) renderOptionsMap(g *gocui.Gui, optionsMap map[string]string) error { | ||||
| 	return gui.renderString(g, "options", gui.optionsMapToString(optionsMap)) | ||||
| func (gui *Gui) renderOptionsMap(optionsMap map[string]string) error { | ||||
| 	return gui.renderString(gui.g, "options", gui.optionsMapToString(optionsMap)) | ||||
| } | ||||
|  | ||||
| // TODO: refactor properly | ||||
| @@ -312,22 +321,6 @@ func (gui *Gui) resizePopupPanel(g *gocui.Gui, v *gocui.View) error { | ||||
| 	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 | ||||
| func (gui *Gui) generalFocusLine(lineNumber int, bottomLine int, v *gocui.View) error { | ||||
| 	_, height := v.Size() | ||||
| @@ -367,3 +360,28 @@ func (gui *Gui) refreshSelectedLine(line *int, total int) { | ||||
| 		*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() | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -435,6 +435,12 @@ func addEnglish(i18nObject *i18n.Bundle) error { | ||||
| 		}, &i18n.Message{ | ||||
| 			ID:    "CantFindHunk", | ||||
| 			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}} ...", | ||||
| 		}, | ||||
| 	) | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user