Skip to content

Commit

Permalink
render commit graph
Browse files Browse the repository at this point in the history
  • Loading branch information
jesseduffield committed Nov 4, 2021
1 parent 2fc1498 commit 802cfb1
Show file tree
Hide file tree
Showing 53 changed files with 522 additions and 263 deletions.
2 changes: 1 addition & 1 deletion bump_gocui.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Go's proxy servers are not very up-to-date so that's why we use `GOPROXY=direct`
# We specify the `awesome` branch to avoid the default behaviour of looking for a semver tag.
GOPROXY=direct go get -u github.com/jesseduffield/gocui@awesome && go mod vendor
GOPROXY=direct go get -u github.com/jesseduffield/gocui@awesome && go mod vendor && go mod tidy

# Note to self if you ever want to fork a repo be sure to use this same approach: it's important to use the branch name (e.g. master)
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ require (
github.com/imdario/mergo v0.3.11
github.com/integrii/flaggy v1.4.0
github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4
github.com/jesseduffield/gocui v0.3.1-0.20211031223253-24baf341da75
github.com/jesseduffield/gocui v0.3.1-0.20211102081536-e4eee64f4d13
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
github.com/jesseduffield/yaml v2.1.0+incompatible
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
Expand All @@ -40,7 +40,7 @@ require (
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 // indirect
golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c // indirect
golang.org/x/sys v0.0.0-20211031064116-611d5d643895 // indirect
golang.org/x/sys v0.0.0-20211102061401-a2f17f7b995c // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4 h1:GOQrmaE8i+KEdB8NzAegKYd4tPn/inM0I1uo0NXFerg=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
github.com/jesseduffield/gocui v0.3.1-0.20211031223253-24baf341da75 h1:zu+WBGwscCwu7GEuxANGl8E51HbW6ueqTF1XdAoqnZs=
github.com/jesseduffield/gocui v0.3.1-0.20211031223253-24baf341da75/go.mod h1:znJuCDnF2Ph40YZSlBwdX/4GEofnIoWLGdT4mK5zRAU=
github.com/jesseduffield/gocui v0.3.1-0.20211102081536-e4eee64f4d13 h1:JB1nYX2l3s9aBtw4Ymc7KXp/Hk3IukO4u+APok6WWjo=
github.com/jesseduffield/gocui v0.3.1-0.20211102081536-e4eee64f4d13/go.mod h1:znJuCDnF2Ph40YZSlBwdX/4GEofnIoWLGdT4mK5zRAU=
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e h1:uw/oo+kg7t/oeMs6sqlAwr85ND/9cpO3up3VxphxY0U=
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e/go.mod h1:u60qdFGXRd36jyEXxetz0vQceQIxzI13lIo3EFUDf4I=
github.com/jesseduffield/yaml v2.1.0+incompatible h1:HWQJ1gIv2zHKbDYNp0Jwjlj24K8aqpFHnMCynY1EpmE=
Expand Down Expand Up @@ -178,8 +178,8 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211031064116-611d5d643895 h1:iaNpwpnrgL5jzWS0vCNnfa8HqzxveCFpFx3uC/X4Tps=
golang.org/x/sys v0.0.0-20211031064116-611d5d643895/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211102061401-a2f17f7b995c h1:QOfDMdrf/UwlVR0UBq2Mpr58UzNtvgJRXA4BgPfFACs=
golang.org/x/sys v0.0.0-20211102061401-a2f17f7b995c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
Expand Down
2 changes: 1 addition & 1 deletion pkg/commands/loading_commits.go
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ func (c *CommitListBuilder) getLogCmd(opts GetCommitsOptions) *exec.Cmd {

return c.OSCommand.ExecutableFromString(
fmt.Sprintf(
"git log %s --oneline %s %s --abbrev=%d %s",
"git log --topo-order %s --oneline %s %s --abbrev=%d %s",
c.OSCommand.Quote(opts.RefName),
prettyFormat,
limitFlag,
Expand Down
5 changes: 4 additions & 1 deletion pkg/gui/commits_panel.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import (
"github.com/jesseduffield/lazygit/pkg/utils"
)

// after selecting the 200th commit, we'll load in all the rest
const COMMIT_THRESHOLD = 200

// list panel functions

func (gui *Gui) getSelectedLocalCommit() *models.Commit {
Expand All @@ -23,7 +26,7 @@ func (gui *Gui) getSelectedLocalCommit() *models.Commit {

func (gui *Gui) handleCommitSelect() error {
state := gui.State.Panels.Commits
if state.SelectedLineIdx > 290 && state.LimitCommits {
if state.SelectedLineIdx > COMMIT_THRESHOLD && state.LimitCommits {
state.LimitCommits = false
go utils.Safe(func() {
if err := gui.refreshCommitsWithLimit(); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/gui/gui.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ func (gui *Gui) resetState(filterPath string, reuseState bool) {
Remotes: &remotePanelState{listPanelState{SelectedLineIdx: 0}},
RemoteBranches: &remoteBranchesState{listPanelState{SelectedLineIdx: -1}},
Tags: &tagsPanelState{listPanelState{SelectedLineIdx: -1}},
Commits: &commitPanelState{listPanelState: listPanelState{SelectedLineIdx: -1}, LimitCommits: true},
Commits: &commitPanelState{listPanelState: listPanelState{SelectedLineIdx: 0}, LimitCommits: true},
ReflogCommits: &reflogCommitPanelState{listPanelState{SelectedLineIdx: 0}},
SubCommits: &subCommitPanelState{listPanelState: listPanelState{SelectedLineIdx: 0}, refName: ""},
CommitFiles: &commitFilesPanelState{listPanelState: listPanelState{SelectedLineIdx: -1}, refName: ""},
Expand Down
11 changes: 11 additions & 0 deletions pkg/gui/list_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ type ListContext struct {
// the boolean here tells us whether the item is nil. This is needed because you can't work it out on the calling end once the pointer is wrapped in an interface (unless you want to use reflection)
SelectedItem func() (ListItem, bool)
OnGetPanelState func() IListPanelState
// if this is true, we'll call GetDisplayStrings for just the visible part of the
// view and re-render that. This is useful when you need to render different
// content based on the selection (e.g. for showing the selected commit)
RenderSelection bool

Gui *Gui

Expand Down Expand Up @@ -60,10 +64,17 @@ type ListItem interface {
func (self *ListContext) FocusLine() {
view, err := self.Gui.g.View(self.ViewName)
if err != nil {
// ignoring error for now
return
}

// we need a way of knowing whether we've rendered to the view yet.
view.FocusPoint(0, self.GetPanelState().GetSelectedLineIdx())
if self.RenderSelection {
_, originY := view.Origin()
displayStrings := self.GetDisplayStrings(originY, view.InnerHeight())
self.Gui.renderDisplayStringsAtPos(view, originY, displayStrings)
}
view.Footer = formatListFooter(self.GetPanelState().GetSelectedLineIdx(), self.GetItemsLength())
}

Expand Down
22 changes: 22 additions & 0 deletions pkg/gui/list_context_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,18 +157,29 @@ func (gui *Gui) branchCommitsListContext() IListContext {
OnClickSelectedItem: gui.handleViewCommitFiles,
Gui: gui,
GetDisplayStrings: func(startIdx int, length int) [][]string {
selectedCommitSha := ""
if gui.currentContext().GetKey() == BRANCH_COMMITS_CONTEXT_KEY {
selectedCommit := gui.getSelectedLocalCommit()
if selectedCommit != nil {
selectedCommitSha = selectedCommit.Sha
}
}
return presentation.GetCommitListDisplayStrings(
gui.State.Commits,
gui.State.ScreenMode != SCREEN_NORMAL,
gui.cherryPickedCommitShaMap(),
gui.State.Modes.Diffing.Ref,
parseEmoji,
selectedCommitSha,
startIdx,
length,
)
},
SelectedItem: func() (ListItem, bool) {
item := gui.getSelectedLocalCommit()
return item, item != nil
},
RenderSelection: true,
}
}

Expand Down Expand Up @@ -215,18 +226,29 @@ func (gui *Gui) subCommitsListContext() IListContext {
OnFocus: gui.handleSubCommitSelect,
Gui: gui,
GetDisplayStrings: func(startIdx int, length int) [][]string {
selectedCommitSha := ""
if gui.currentContext().GetKey() == SUB_COMMITS_CONTEXT_KEY {
selectedCommit := gui.getSelectedSubCommit()
if selectedCommit != nil {
selectedCommitSha = selectedCommit.Sha
}
}
return presentation.GetCommitListDisplayStrings(
gui.State.SubCommits,
gui.State.ScreenMode != SCREEN_NORMAL,
gui.cherryPickedCommitShaMap(),
gui.State.Modes.Diffing.Ref,
parseEmoji,
selectedCommitSha,
0,
len(gui.State.SubCommits),
)
},
SelectedItem: func() (ListItem, bool) {
item := gui.getSelectedSubCommit()
return item, item != nil
},
RenderSelection: true,
}
}

Expand Down
164 changes: 98 additions & 66 deletions pkg/gui/presentation/commits.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,81 +2,133 @@ package presentation

import (
"strings"
"sync"

"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/presentation/authors"
"github.com/jesseduffield/lazygit/pkg/gui/presentation/graph"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/theme"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/kyokomi/emoji/v2"
)

func GetCommitListDisplayStrings(commits []*models.Commit, fullDescription bool, cherryPickedCommitShaMap map[string]bool, diffName string, parseEmoji bool) [][]string {
lines := make([][]string, len(commits))
type pipeSetCacheKey struct {
commitSha string
commitCount int
}

var displayFunc func(*models.Commit, map[string]bool, bool, bool) []string
if fullDescription {
displayFunc = getFullDescriptionDisplayStringsForCommit
} else {
displayFunc = getDisplayStringsForCommit
var pipeSetCache = make(map[pipeSetCacheKey][][]*graph.Pipe)
var mutex sync.Mutex

func GetCommitListDisplayStrings(
commits []*models.Commit,
fullDescription bool,
cherryPickedCommitShaMap map[string]bool,
diffName string,
parseEmoji bool,
selectedCommitSha string,
startIdx int,
length int,
) [][]string {
mutex.Lock()
defer mutex.Unlock()

if len(commits) == 0 {
return nil
}

for i := range commits {
diffed := commits[i].Sha == diffName
lines[i] = displayFunc(commits[i], cherryPickedCommitShaMap, diffed, parseEmoji)
// given that our cache key is a commit sha and a commit count, it's very important that we don't actually try to render pipes
// when dealing with things like filtered commits.
cacheKey := pipeSetCacheKey{
commitSha: commits[0].Sha,
commitCount: len(commits),
}

pipeSets, ok := pipeSetCache[cacheKey]
if !ok {
// pipe sets are unique to a commit head. and a commit count. Sometimes we haven't loaded everything for that.
// so let's just cache it based on that.
getStyle := func(commit *models.Commit) style.TextStyle {
return authors.AuthorStyle(commit.Author)
}
pipeSets = graph.GetPipeSets(commits, getStyle)
pipeSetCache[cacheKey] = pipeSets
}

end := startIdx + length
if end > len(commits)-1 {
end = len(commits) - 1
}

filteredPipeSets := pipeSets[startIdx : end+1]
filteredCommits := commits[startIdx : end+1]
graphLines := graph.RenderAux(filteredPipeSets, filteredCommits, selectedCommitSha)

lines := make([][]string, 0, len(graphLines))
for i, commit := range filteredCommits {
lines = append(lines, displayCommit(commit, cherryPickedCommitShaMap, diffName, parseEmoji, graphLines[i], fullDescription))
}
return lines
}

func getFullDescriptionDisplayStringsForCommit(c *models.Commit, cherryPickedCommitShaMap map[string]bool, diffed, parseEmoji bool) []string {
shaColor := theme.DefaultTextColor
switch c.Status {
case "unpushed":
shaColor = style.FgRed
case "pushed":
shaColor = style.FgYellow
case "merged":
shaColor = style.FgGreen
case "rebasing":
shaColor = style.FgBlue
case "reflog":
shaColor = style.FgBlue
}
func displayCommit(
commit *models.Commit,
cherryPickedCommitShaMap map[string]bool,
diffName string,
parseEmoji bool,
graphLine string,
fullDescription bool,
) []string {

if diffed {
shaColor = theme.DiffTerminalColor
} else if cherryPickedCommitShaMap[c.Sha] {
// for some reason, setting the background to blue pads out the other commits
// horizontally. For the sake of accessibility I'm considering this a feature,
// not a bug
shaColor = theme.CherryPickedCommitTextStyle
shaColor := getShaColor(commit, diffName, cherryPickedCommitShaMap)

actionString := ""
if commit.Action != "" {
actionString = actionColorMap(commit.Action).Sprint(commit.Action) + " "
}

tagString := ""
secondColumnString := style.FgBlue.Sprint(utils.UnixToDate(c.UnixTimestamp))
if c.Action != "" {
secondColumnString = actionColorMap(c.Action).Sprint(c.Action)
} else if c.ExtraInfo != "" {
tagString = style.FgMagenta.SetBold().Sprint(c.ExtraInfo) + " "
if fullDescription {
if commit.ExtraInfo != "" {
tagString = style.FgMagenta.SetBold().Sprint(commit.ExtraInfo) + " "
}
} else {
if len(commit.Tags) > 0 {
tagString = theme.DiffTerminalColor.SetBold().Sprint(strings.Join(commit.Tags, " ")) + " "
}
}

name := c.Name
name := commit.Name
if parseEmoji {
name = emoji.Sprint(name)
}

return []string{
shaColor.Sprint(c.ShortSha()),
secondColumnString,
authors.LongAuthor(c.Author),
tagString + theme.DefaultTextColor.Sprint(name),
authorFunc := authors.ShortAuthor
if fullDescription {
authorFunc = authors.LongAuthor
}

cols := make([]string, 0, 5)
cols = append(cols, shaColor.Sprint(commit.ShortSha()))
if fullDescription {
cols = append(cols, style.FgBlue.Sprint(utils.UnixToDate(commit.UnixTimestamp)))
}
cols = append(
cols,
actionString,
authorFunc(commit.Author),
graphLine+tagString+theme.DefaultTextColor.Sprint(name),
)

return cols

}

func getDisplayStringsForCommit(c *models.Commit, cherryPickedCommitShaMap map[string]bool, diffed, parseEmoji bool) []string {
func getShaColor(commit *models.Commit, diffName string, cherryPickedCommitShaMap map[string]bool) style.TextStyle {
diffed := commit.Sha == diffName
shaColor := theme.DefaultTextColor
switch c.Status {
switch commit.Status {
case "unpushed":
shaColor = style.FgRed
case "pushed":
Expand All @@ -91,31 +143,11 @@ func getDisplayStringsForCommit(c *models.Commit, cherryPickedCommitShaMap map[s

if diffed {
shaColor = theme.DiffTerminalColor
} else if cherryPickedCommitShaMap[c.Sha] {
// for some reason, setting the background to blue pads out the other commits
// horizontally. For the sake of accessibility I'm considering this a feature,
// not a bug
} else if cherryPickedCommitShaMap[commit.Sha] {
shaColor = theme.CherryPickedCommitTextStyle
}

actionString := ""
tagString := ""
if c.Action != "" {
actionString = actionColorMap(c.Action).Sprint(utils.WithPadding(c.Action, 7)) + " "
} else if len(c.Tags) > 0 {
tagString = theme.DiffTerminalColor.SetBold().Sprint(strings.Join(c.Tags, " ")) + " "
}

name := c.Name
if parseEmoji {
name = emoji.Sprint(name)
}

return []string{
shaColor.Sprint(c.ShortSha()),
authors.ShortAuthor(c.Author),
actionString + tagString + theme.DefaultTextColor.Sprint(name),
}
return shaColor
}

func actionColorMap(str string) style.TextStyle {
Expand Down
Loading

0 comments on commit 802cfb1

Please sign in to comment.