1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2024-12-02 09:21:40 +02:00

add sub commit context

This commit is contained in:
Jesse Duffield 2020-08-22 08:49:02 +10:00
parent 41df63cdc4
commit 974c6510b8
11 changed files with 296 additions and 33 deletions

View File

@ -37,14 +37,14 @@ type CommitListBuilder struct {
}
// NewCommitListBuilder builds a new commit list builder
func NewCommitListBuilder(log *logrus.Entry, gitCommand *GitCommand, osCommand *OSCommand, tr *i18n.Localizer, cherryPickedCommits []*Commit) (*CommitListBuilder, error) {
func NewCommitListBuilder(log *logrus.Entry, gitCommand *GitCommand, osCommand *OSCommand, tr *i18n.Localizer, cherryPickedCommits []*Commit) *CommitListBuilder {
return &CommitListBuilder{
Log: log,
GitCommand: gitCommand,
OSCommand: osCommand,
Tr: tr,
CherryPickedCommits: cherryPickedCommits,
}, nil
}
}
// extractCommitFromLine takes a line from a git log and extracts the sha, message, date, and tag if present
@ -82,33 +82,40 @@ func (c *CommitListBuilder) extractCommitFromLine(line string) *Commit {
}
type GetCommitsOptions struct {
Limit bool
FilterPath string
Limit bool
FilterPath string
IncludeRebaseCommits bool
RefName string // e.g. "HEAD" or "my_branch"
}
// GetCommits obtains the commits of the current branch
func (c *CommitListBuilder) GetCommits(options GetCommitsOptions) ([]*Commit, error) {
func (c *CommitListBuilder) GetCommits(opts GetCommitsOptions) ([]*Commit, error) {
commits := []*Commit{}
var rebasingCommits []*Commit
rebaseMode, err := c.GitCommand.RebaseMode()
if err != nil {
return nil, err
}
if rebaseMode != "" && options.FilterPath == "" {
// here we want to also prepend the commits that we're in the process of rebasing
rebasingCommits, err = c.getRebasingCommits(rebaseMode)
rebaseMode := ""
if opts.IncludeRebaseCommits {
var err error
rebaseMode, err = c.GitCommand.RebaseMode()
if err != nil {
return nil, err
}
if len(rebasingCommits) > 0 {
commits = append(commits, rebasingCommits...)
if rebaseMode != "" && opts.FilterPath == "" {
// here we want to also prepend the commits that we're in the process of rebasing
rebasingCommits, err = c.getRebasingCommits(rebaseMode)
if err != nil {
return nil, err
}
if len(rebasingCommits) > 0 {
commits = append(commits, rebasingCommits...)
}
}
}
unpushedCommits := c.getUnpushedCommits()
cmd := c.getLogCmd(options)
unpushedCommits := c.getUnpushedCommits(opts.RefName)
cmd := c.getLogCmd(opts)
err = RunLineOutputCmd(cmd, func(line string) (bool, error) {
err := RunLineOutputCmd(cmd, func(line string) (bool, error) {
if strings.Split(line, " ")[0] != "gpg:" {
commit := c.extractCommitFromLine(line)
_, unpushed := unpushedCommits[commit.ShortSha()]
@ -287,9 +294,9 @@ func (c *CommitListBuilder) getMergeBase() (string, error) {
// getUnpushedCommits Returns the sha's of the commits that have not yet been pushed
// to the remote branch of the current branch, a map is returned to ease look up
func (c *CommitListBuilder) getUnpushedCommits() map[string]bool {
func (c *CommitListBuilder) getUnpushedCommits(refName string) map[string]bool {
pushables := map[string]bool{}
o, err := c.OSCommand.RunCommandWithOutput("git rev-list @{u}..HEAD --abbrev-commit --abbrev=8")
o, err := c.OSCommand.RunCommandWithOutput("git rev-list %s@{u}..%s --abbrev-commit --abbrev=8", refName, refName)
if err != nil {
return pushables
}
@ -301,16 +308,16 @@ func (c *CommitListBuilder) getUnpushedCommits() map[string]bool {
}
// getLog gets the git log.
func (c *CommitListBuilder) getLogCmd(options GetCommitsOptions) *exec.Cmd {
func (c *CommitListBuilder) getLogCmd(opts GetCommitsOptions) *exec.Cmd {
limitFlag := ""
if options.Limit {
if opts.Limit {
limitFlag = "-300"
}
filterFlag := ""
if options.FilterPath != "" {
filterFlag = fmt.Sprintf(" --follow -- %s", c.OSCommand.Quote(options.FilterPath))
if opts.FilterPath != "" {
filterFlag = fmt.Sprintf(" --follow -- %s", c.OSCommand.Quote(opts.FilterPath))
}
return c.OSCommand.ExecutableFromString(fmt.Sprintf("git log --oneline --pretty=format:\"%%H%s%%at%s%%aN%s%%d%s%%s\" %s --abbrev=%d --date=unix %s", SEPARATION_CHAR, SEPARATION_CHAR, SEPARATION_CHAR, SEPARATION_CHAR, limitFlag, 20, filterFlag))
return c.OSCommand.ExecutableFromString(fmt.Sprintf("git log %s --oneline --pretty=format:\"%%H%s%%at%s%%aN%s%%d%s%%s\" %s --abbrev=%d --date=unix %s", opts.RefName, SEPARATION_CHAR, SEPARATION_CHAR, SEPARATION_CHAR, SEPARATION_CHAR, limitFlag, 20, filterFlag))
}

View File

@ -55,7 +55,7 @@ func TestCommitListBuilderGetUnpushedCommits(t *testing.T) {
t.Run(s.testName, func(t *testing.T) {
c := NewDummyCommitListBuilder()
c.OSCommand.SetCommand(s.command)
s.test(c.getUnpushedCommits())
s.test(c.getUnpushedCommits("HEAD"))
})
}
}

View File

@ -36,3 +36,7 @@ func (f *File) Names() []string {
func (f *File) Matches(f2 *File) bool {
return utils.StringArraysOverlap(f.Names(), f2.Names())
}
func (f *File) ID() string {
return f.Name
}

View File

@ -98,12 +98,16 @@ func (gui *Gui) refreshCommits() error {
}
func (gui *Gui) refreshCommitsWithLimit() error {
builder, err := commands.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr, gui.State.CherryPickedCommits)
if err != nil {
return err
}
builder := commands.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr, gui.State.CherryPickedCommits)
commits, err := builder.GetCommits(commands.GetCommitsOptions{Limit: gui.State.Panels.Commits.LimitCommits, FilterPath: gui.State.FilterPath})
commits, err := builder.GetCommits(
commands.GetCommitsOptions{
Limit: gui.State.Panels.Commits.LimitCommits,
FilterPath: gui.State.FilterPath,
IncludeRebaseCommits: true,
RefName: "HEAD",
},
)
if err != nil {
return err
}

View File

@ -3,6 +3,7 @@ package gui
import (
"fmt"
"github.com/davecgh/go-spew/spew"
"github.com/jesseduffield/gocui"
)
@ -22,6 +23,7 @@ const (
TAGS_CONTEXT_KEY = "tags"
BRANCH_COMMITS_CONTEXT_KEY = "commits"
REFLOG_COMMITS_CONTEXT_KEY = "reflogCommits"
SUB_COMMITS_CONTEXT_KEY = "subCommits"
COMMIT_FILES_CONTEXT_KEY = "commitFiles"
STASH_CONTEXT_KEY = "stash"
MAIN_NORMAL_CONTEXT_KEY = "normal"
@ -129,6 +131,7 @@ type ContextTree struct {
BranchCommits SimpleContextNode
CommitFiles SimpleContextNode
ReflogCommits SimpleContextNode
SubCommits SimpleContextNode
Stash SimpleContextNode
Normal SimpleContextNode
Staging SimpleContextNode
@ -160,6 +163,7 @@ func (gui *Gui) allContexts() []Context {
gui.Contexts.Staging.Context,
gui.Contexts.Merging.Context,
gui.Contexts.PatchBuilding.Context,
gui.Contexts.SubCommits.Context,
}
}
@ -194,6 +198,9 @@ func (gui *Gui) contextTree() ContextTree {
ReflogCommits: SimpleContextNode{
Context: gui.reflogCommitsListContext(),
},
SubCommits: SimpleContextNode{
Context: gui.subCommitsListContext(),
},
Branches: SimpleContextNode{
Context: gui.branchesListContext(),
},
@ -510,6 +517,31 @@ func (gui *Gui) currentContext() Context {
return gui.State.ContextStack[len(gui.State.ContextStack)-1]
}
func (gui *Gui) currentSideContext() *ListContext {
stack := gui.State.ContextStack
// on startup the stack can be empty so we'll return an empty string in that case
if len(stack) == 0 {
return nil
}
// find the first context in the stack with the type of SIDE_CONTEXT
for i := range stack {
context := stack[len(stack)-1-i]
if context.GetKind() == SIDE_CONTEXT {
gui.Log.Warn(spew.Sdump(context.GetKey()))
return context.(*ListContext)
}
}
return nil
}
func (gui *Gui) defaultSideContext() Context {
return gui.Contexts.Files.Context
}
func (gui *Gui) setInitialViewContexts() {
// arguably we should only have our ViewContextMap and we should do away with
// contexts on views, or vice versa

View File

@ -173,6 +173,13 @@ type reflogCommitPanelState struct {
listPanelState
}
type subCommitPanelState struct {
listPanelState
// e.g. name of branch whose commits we're looking at
refName string
}
type stashPanelState struct {
listPanelState
}
@ -199,6 +206,7 @@ type panelStates struct {
Tags *tagsPanelState
Commits *commitPanelState
ReflogCommits *reflogCommitPanelState
SubCommits *subCommitPanelState
Stash *stashPanelState
Menu *menuPanelState
LineByLine *lineByLinePanelState
@ -237,6 +245,7 @@ type guiState struct {
// if we're not in filtering mode, CommitFiles and FilteredReflogCommits will be
// one and the same
ReflogCommits []*commands.Commit
SubCommits []*commands.Commit
Remotes []*commands.Remote
RemoteBranches []*commands.RemoteBranch
Tags []*commands.Tag
@ -289,13 +298,15 @@ func (gui *Gui) resetState() {
CherryPickedCommits: make([]*commands.Commit, 0),
StashEntries: make([]*commands.StashEntry, 0),
Panels: &panelStates{
// TODO: work out why some of these are -1 and some are 0. Last time I checked there was a good reason but I'm less certain now
Files: &filePanelState{listPanelState{SelectedLineIdx: -1}},
Branches: &branchPanelState{listPanelState{SelectedLineIdx: 0}},
Remotes: &remotePanelState{listPanelState{SelectedLineIdx: 0}},
RemoteBranches: &remoteBranchesState{listPanelState{SelectedLineIdx: -1}},
Tags: &tagsPanelState{listPanelState{SelectedLineIdx: -1}},
Commits: &commitPanelState{listPanelState: listPanelState{SelectedLineIdx: -1}, LimitCommits: true},
ReflogCommits: &reflogCommitPanelState{listPanelState{SelectedLineIdx: 0}}, // TODO: might need to make -1
ReflogCommits: &reflogCommitPanelState{listPanelState{SelectedLineIdx: 0}},
SubCommits: &subCommitPanelState{listPanelState: listPanelState{SelectedLineIdx: 0}, refName: ""},
CommitFiles: &commitFilesPanelState{listPanelState: listPanelState{SelectedLineIdx: -1}, refName: ""},
Stash: &stashPanelState{listPanelState{SelectedLineIdx: -1}},
Menu: &menuPanelState{listPanelState: listPanelState{SelectedLineIdx: 0}, OnPress: nil},

View File

@ -555,6 +555,13 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Handler: gui.handleClipboardCopyBranch,
Description: gui.Tr.SLocalize("copyBranchNameToClipboard"),
},
{
ViewName: "branches",
Contexts: []string{LOCAL_BRANCHES_CONTEXT_KEY},
Key: gui.getKey("universal.goInto"),
Handler: gui.wrappedHandler(gui.handleSwitchToSubCommits),
Description: gui.Tr.SLocalize("viewCommits"),
},
{
ViewName: "branches",
Contexts: []string{TAGS_CONTEXT_KEY},
@ -590,6 +597,13 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Handler: gui.handleCreateResetToTagMenu,
Description: gui.Tr.SLocalize("viewResetOptions"),
},
{
ViewName: "branches",
Contexts: []string{TAGS_CONTEXT_KEY},
Key: gui.getKey("universal.goInto"),
Handler: gui.wrappedHandler(gui.handleSwitchToSubCommits),
Description: gui.Tr.SLocalize("viewCommits"),
},
{
ViewName: "branches",
Key: gui.getKey("universal.nextTab"),
@ -616,6 +630,13 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Handler: gui.handleCreateResetToRemoteBranchMenu,
Description: gui.Tr.SLocalize("viewResetOptions"),
},
{
ViewName: "branches",
Contexts: []string{REMOTE_BRANCHES_CONTEXT_KEY},
Key: gui.getKey("universal.goInto"),
Handler: gui.wrappedHandler(gui.handleSwitchToSubCommits),
Description: gui.Tr.SLocalize("viewCommits"),
},
{
ViewName: "branches",
Contexts: []string{REMOTES_CONTEXT_KEY},
@ -818,6 +839,27 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Handler: gui.handleCreateReflogResetMenu,
Description: gui.Tr.SLocalize("viewResetOptions"),
},
{
ViewName: "branches",
Contexts: []string{SUB_COMMITS_CONTEXT_KEY},
Key: gui.getKey("universal.goInto"),
Handler: gui.wrappedHandler(gui.handleViewSubCommitFiles),
Description: gui.Tr.SLocalize("viewCommitFiles"),
},
{
ViewName: "branches",
Contexts: []string{SUB_COMMITS_CONTEXT_KEY},
Key: gui.getKey("universal.select"),
Handler: gui.handleCheckoutSubCommit,
Description: gui.Tr.SLocalize("checkoutCommit"),
},
{
ViewName: "branches",
Contexts: []string{SUB_COMMITS_CONTEXT_KEY},
Key: gui.getKey("commits.viewResetOptions"),
Handler: gui.wrappedHandler(gui.handleCreateSubCommitResetMenu),
Description: gui.Tr.SLocalize("viewResetOptions"),
},
{
ViewName: "stash",
Key: gui.getKey("universal.goInto"),

View File

@ -338,7 +338,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
func (gui *Gui) onInitialViewsCreation() error {
gui.setInitialViewContexts()
if err := gui.switchContext(gui.Contexts.Files.Context); err != nil {
if err := gui.switchContext(gui.defaultSideContext()); err != nil {
return err
}

View File

@ -5,6 +5,14 @@ import (
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
)
// TODO: if we don't end up using this, delete it
const (
CONTAINS_NOTHING = iota
CONTAINS_COMMITS
CONTAINS_FILES
CONTAINS_BRANCHES
)
type ListContext struct {
ViewName string
ContextKey string
@ -21,12 +29,17 @@ type ListContext struct {
Kind int
ParentContext Context
WindowName string
Contains int
}
type ListItem interface {
ID() string
}
func (lc *ListContext) GetContains() int {
return lc.Contains
}
func (lc *ListContext) SetWindowName(windowName string) {
lc.WindowName = windowName
}
@ -69,7 +82,6 @@ func (lc *ListContext) GetSelectedItem() ListItem {
}
func (lc *ListContext) GetSelectedItemId() string {
item := lc.GetSelectedItem()
if item == nil {
@ -119,6 +131,13 @@ func (lc *ListContext) HandleFocus() error {
return nil
}
view, err := lc.Gui.g.View(lc.ViewName)
if err != nil {
return nil
}
view.FocusPoint(0, lc.GetPanelState().GetSelectedLineIdx())
if lc.Gui.inDiffMode() {
return lc.Gui.renderDiff()
}
@ -248,6 +267,7 @@ func (gui *Gui) menuListContext() *ListContext {
Gui: gui,
RendersToMainView: false,
Kind: PERSISTENT_POPUP,
Contains: CONTAINS_NOTHING,
// no GetDisplayStrings field because we do a custom render on menu creation
}
@ -267,6 +287,7 @@ func (gui *Gui) filesListContext() *ListContext {
GetDisplayStrings: func() [][]string {
return presentation.GetFileListDisplayStrings(gui.State.Files, gui.State.Diff.Ref)
},
Contains: CONTAINS_NOTHING,
}
}
@ -283,6 +304,7 @@ func (gui *Gui) branchesListContext() *ListContext {
GetDisplayStrings: func() [][]string {
return presentation.GetBranchListDisplayStrings(gui.State.Branches, gui.State.ScreenMode != SCREEN_NORMAL, gui.State.Diff.Ref)
},
Contains: CONTAINS_COMMITS,
}
}
@ -300,6 +322,7 @@ func (gui *Gui) remotesListContext() *ListContext {
GetDisplayStrings: func() [][]string {
return presentation.GetRemoteListDisplayStrings(gui.State.Remotes, gui.State.Diff.Ref)
},
Contains: CONTAINS_BRANCHES,
}
}
@ -316,6 +339,7 @@ func (gui *Gui) remoteBranchesListContext() *ListContext {
GetDisplayStrings: func() [][]string {
return presentation.GetRemoteBranchListDisplayStrings(gui.State.RemoteBranches, gui.State.Diff.Ref)
},
Contains: CONTAINS_COMMITS,
}
}
@ -332,6 +356,7 @@ func (gui *Gui) tagsListContext() *ListContext {
GetDisplayStrings: func() [][]string {
return presentation.GetTagListDisplayStrings(gui.State.Tags, gui.State.Diff.Ref)
},
Contains: CONTAINS_COMMITS,
}
}
@ -349,6 +374,7 @@ func (gui *Gui) branchCommitsListContext() *ListContext {
GetDisplayStrings: func() [][]string {
return presentation.GetCommitListDisplayStrings(gui.State.Commits, gui.State.ScreenMode != SCREEN_NORMAL, gui.cherryPickedCommitShaMap(), gui.State.Diff.Ref)
},
Contains: CONTAINS_FILES,
}
}
@ -365,6 +391,24 @@ func (gui *Gui) reflogCommitsListContext() *ListContext {
GetDisplayStrings: func() [][]string {
return presentation.GetReflogCommitListDisplayStrings(gui.State.FilteredReflogCommits, gui.State.ScreenMode != SCREEN_NORMAL, gui.State.Diff.Ref)
},
Contains: CONTAINS_FILES,
}
}
func (gui *Gui) subCommitsListContext() *ListContext {
return &ListContext{
ViewName: "branches",
ContextKey: SUB_COMMITS_CONTEXT_KEY,
GetItemsLength: func() int { return len(gui.State.SubCommits) },
GetPanelState: func() IListPanelState { return gui.State.Panels.SubCommits },
OnFocus: gui.handleSubCommitSelect,
Gui: gui,
RendersToMainView: true,
Kind: SIDE_CONTEXT,
GetDisplayStrings: func() [][]string {
return presentation.GetCommitListDisplayStrings(gui.State.SubCommits, gui.State.ScreenMode != SCREEN_NORMAL, gui.cherryPickedCommitShaMap(), gui.State.Diff.Ref)
},
Contains: CONTAINS_COMMITS,
}
}
@ -381,6 +425,7 @@ func (gui *Gui) stashListContext() *ListContext {
GetDisplayStrings: func() [][]string {
return presentation.GetStashEntryListDisplayStrings(gui.State.StashEntries, gui.State.Diff.Ref)
},
Contains: CONTAINS_FILES,
}
}
@ -398,6 +443,7 @@ func (gui *Gui) commitFilesListContext() *ListContext {
GetDisplayStrings: func() [][]string {
return presentation.GetCommitFileListDisplayStrings(gui.State.CommitFiles, gui.State.Diff.Ref)
},
Contains: CONTAINS_NOTHING,
}
}
@ -411,6 +457,7 @@ func (gui *Gui) getListContexts() []*ListContext {
gui.tagsListContext(),
gui.branchCommitsListContext(),
gui.reflogCommitsListContext(),
gui.subCommitsListContext(),
gui.stashListContext(),
gui.commitFilesListContext(),
}

View File

@ -0,0 +1,113 @@
package gui
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands"
)
// list panel functions
func (gui *Gui) getSelectedSubCommit() *commands.Commit {
selectedLine := gui.State.Panels.SubCommits.SelectedLineIdx
commits := gui.State.SubCommits
if selectedLine == -1 || len(commits) == 0 {
return nil
}
return commits[selectedLine]
}
func (gui *Gui) handleSubCommitSelect() error {
commit := gui.getSelectedSubCommit()
var task updateTask
if commit == nil {
task = gui.createRenderStringTask("No commits")
} else {
cmd := gui.OSCommand.ExecutableFromString(
gui.GitCommand.ShowCmdStr(commit.Sha, gui.State.FilterPath),
)
task = gui.createRunPtyTask(cmd)
}
return gui.refreshMain(refreshMainOpts{
main: &viewUpdateOpts{
title: "Commit",
task: task,
},
})
}
func (gui *Gui) handleCheckoutSubCommit(g *gocui.Gui, v *gocui.View) error {
commit := gui.getSelectedSubCommit()
if commit == nil {
return nil
}
err := gui.ask(askOpts{
returnToView: gui.getCommitsView(),
returnFocusOnClose: true,
title: gui.Tr.SLocalize("checkoutCommit"),
prompt: gui.Tr.SLocalize("SureCheckoutThisCommit"),
handleConfirm: func() error {
return gui.handleCheckoutRef(commit.Sha, handleCheckoutRefOptions{})
},
})
if err != nil {
return err
}
gui.State.Panels.SubCommits.SelectedLineIdx = 0
return nil
}
func (gui *Gui) handleCreateSubCommitResetMenu() error {
commit := gui.getSelectedSubCommit()
return gui.createResetMenu(commit.Sha)
}
func (gui *Gui) handleViewSubCommitFiles() error {
commit := gui.getSelectedSubCommit()
if commit == nil {
return nil
}
return gui.switchToCommitFilesContext(commit.Sha, REF_TYPE_OTHER_COMMIT, gui.Contexts.SubCommits.Context, "branches")
}
func (gui *Gui) switchToSubCommitsContext(refName string) error {
// need to populate my sub commits
builder := commands.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr, gui.State.CherryPickedCommits)
commits, err := builder.GetCommits(
commands.GetCommitsOptions{
Limit: gui.State.Panels.Commits.LimitCommits,
FilterPath: gui.State.FilterPath,
IncludeRebaseCommits: false,
RefName: refName,
},
)
if err != nil {
return err
}
gui.State.SubCommits = commits
gui.State.Panels.SubCommits.refName = refName
gui.State.Panels.SubCommits.SelectedLineIdx = 0
gui.Contexts.SubCommits.Context.SetParentContext(gui.currentSideContext())
return gui.switchContext(gui.Contexts.SubCommits.Context)
}
func (gui *Gui) handleSwitchToSubCommits() error {
currentContext := gui.currentSideContext()
if currentContext == nil {
return nil
}
gui.Log.Warn(currentContext.GetKey())
return gui.switchToSubCommitsContext(currentContext.GetSelectedItemId())
}

View File

@ -1173,6 +1173,9 @@ func addEnglish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "buildingPatch",
Other: "building patch",
}, &i18n.Message{
ID: "viewCommits",
Other: "view commits",
},
)
}