diff --git a/pkg/commands/models/commit.go b/pkg/commands/models/commit.go index 37a9be28d..eca66b3ac 100644 --- a/pkg/commands/models/commit.go +++ b/pkg/commands/models/commit.go @@ -93,6 +93,10 @@ func (c *Commit) Hash() string { return *c.hash } +func (c *Commit) HashPtr() *string { + return c.hash +} + func (c *Commit) ShortHash() string { return utils.ShortHash(c.Hash()) } @@ -120,6 +124,10 @@ func (c *Commit) Parents() []string { return lo.Map(c.parents, func(s *string, _ int) string { return *s }) } +func (c *Commit) ParentPtrs() []*string { + return c.parents +} + func (c *Commit) IsFirstCommit() bool { return len(c.parents) == 0 } diff --git a/pkg/gui/context/local_commits_context.go b/pkg/gui/context/local_commits_context.go index dfb270d1e..6f92b34fa 100644 --- a/pkg/gui/context/local_commits_context.go +++ b/pkg/gui/context/local_commits_context.go @@ -32,12 +32,12 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext { ) getDisplayStrings := func(startIdx int, endIdx int) [][]string { - selectedCommitHash := "" + var selectedCommitHashPtr *string if c.Context().Current().GetKey() == LOCAL_COMMITS_CONTEXT_KEY { selectedCommit := viewModel.GetSelected() if selectedCommit != nil { - selectedCommitHash = selectedCommit.Hash() + selectedCommitHashPtr = selectedCommit.HashPtr() } } @@ -57,7 +57,7 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext { c.UserConfig().Gui.ShortTimeFormat, time.Now(), c.UserConfig().Git.ParseEmoji, - selectedCommitHash, + selectedCommitHashPtr, startIdx, endIdx, shouldShowGraph(c), diff --git a/pkg/gui/context/sub_commits_context.go b/pkg/gui/context/sub_commits_context.go index 120e605b3..650a577c1 100644 --- a/pkg/gui/context/sub_commits_context.go +++ b/pkg/gui/context/sub_commits_context.go @@ -46,11 +46,11 @@ func NewSubCommitsContext( return [][]string{} } - selectedCommitHash := "" + var selectedCommitHashPtr *string if c.Context().Current().GetKey() == SUB_COMMITS_CONTEXT_KEY { selectedCommit := viewModel.GetSelected() if selectedCommit != nil { - selectedCommitHash = selectedCommit.Hash() + selectedCommitHashPtr = selectedCommit.HashPtr() } } branches := []*models.Branch{} @@ -72,7 +72,7 @@ func NewSubCommitsContext( c.UserConfig().Gui.ShortTimeFormat, time.Now(), c.UserConfig().Git.ParseEmoji, - selectedCommitHash, + selectedCommitHashPtr, startIdx, endIdx, shouldShowGraph(c), diff --git a/pkg/gui/presentation/commits.go b/pkg/gui/presentation/commits.go index ec4a8425d..7e6e0e838 100644 --- a/pkg/gui/presentation/commits.go +++ b/pkg/gui/presentation/commits.go @@ -51,7 +51,7 @@ func GetCommitListDisplayStrings( shortTimeFormat string, now time.Time, parseEmoji bool, - selectedCommitHash string, + selectedCommitHashPtr *string, startIdx int, endIdx int, showGraph bool, @@ -102,7 +102,7 @@ func GetCommitListDisplayStrings( graphLines := graph.RenderAux( graphPipeSets, graphCommits, - selectedCommitHash, + selectedCommitHashPtr, ) allGraphLines = append(allGraphLines, graphLines...) } @@ -119,7 +119,7 @@ func GetCommitListDisplayStrings( graphLines := graph.RenderAux( graphPipeSets, graphCommits, - selectedCommitHash, + selectedCommitHashPtr, ) allGraphLines = append(allGraphLines, graphLines...) } @@ -140,7 +140,7 @@ func GetCommitListDisplayStrings( graphLines := graph.RenderAux( graphPipeSets, graphCommits, - selectedCommitHash, + selectedCommitHashPtr, ) getGraphLine = func(idx int) string { if idx >= graphOffset { diff --git a/pkg/gui/presentation/commits_test.go b/pkg/gui/presentation/commits_test.go index 5729e819e..595c3d750 100644 --- a/pkg/gui/presentation/commits_test.go +++ b/pkg/gui/presentation/commits_test.go @@ -36,7 +36,7 @@ func TestGetCommitListDisplayStrings(t *testing.T) { shortTimeFormat string now time.Time parseEmoji bool - selectedCommitHash string + selectedCommitHashPtr *string startIdx int endIdx int showGraph bool @@ -563,7 +563,7 @@ func TestGetCommitListDisplayStrings(t *testing.T) { s.shortTimeFormat, s.now, s.parseEmoji, - s.selectedCommitHash, + s.selectedCommitHashPtr, s.startIdx, s.endIdx, s.showGraph, diff --git a/pkg/gui/presentation/graph/graph.go b/pkg/gui/presentation/graph/graph.go index de46193ed..80f6188db 100644 --- a/pkg/gui/presentation/graph/graph.go +++ b/pkg/gui/presentation/graph/graph.go @@ -25,13 +25,17 @@ const ( type Pipe struct { fromPos int toPos int - fromHash string - toHash string + fromHash *string + toHash *string kind PipeKind style style.TextStyle } -var highlightStyle = style.FgLightWhite.SetBold() +var ( + highlightStyle = style.FgLightWhite.SetBold() + EmptyTreeCommitHash = models.EmptyTreeCommitHash + StartCommitHash = "START" +) func (self Pipe) left() int { return min(self.fromPos, self.toPos) @@ -41,13 +45,13 @@ func (self Pipe) right() int { return max(self.fromPos, self.toPos) } -func RenderCommitGraph(commits []*models.Commit, selectedCommitHash string, getStyle func(c *models.Commit) style.TextStyle) []string { +func RenderCommitGraph(commits []*models.Commit, selectedCommitHashPtr *string, getStyle func(c *models.Commit) style.TextStyle) []string { pipeSets := GetPipeSets(commits, getStyle) if len(pipeSets) == 0 { return nil } - lines := RenderAux(pipeSets, commits, selectedCommitHash) + lines := RenderAux(pipeSets, commits, selectedCommitHashPtr) return lines } @@ -57,7 +61,7 @@ func GetPipeSets(commits []*models.Commit, getStyle func(c *models.Commit) style return nil } - pipes := []*Pipe{{fromPos: 0, toPos: 0, fromHash: "START", toHash: commits[0].Hash(), kind: STARTS, style: style.FgDefault}} + pipes := []*Pipe{{fromPos: 0, toPos: 0, fromHash: &StartCommitHash, toHash: commits[0].HashPtr(), kind: STARTS, style: style.FgDefault}} return lo.Map(commits, func(commit *models.Commit, _ int) []*Pipe { pipes = getNextPipes(pipes, commit, getStyle) @@ -65,7 +69,7 @@ func GetPipeSets(commits []*models.Commit, getStyle func(c *models.Commit) style }) } -func RenderAux(pipeSets [][]*Pipe, commits []*models.Commit, selectedCommitHash string) []string { +func RenderAux(pipeSets [][]*Pipe, commits []*models.Commit, selectedCommitHashPtr *string) []string { maxProcs := runtime.GOMAXPROCS(0) // splitting up the rendering of the graph into multiple goroutines allows us to render the graph in parallel @@ -89,7 +93,7 @@ func RenderAux(pipeSets [][]*Pipe, commits []*models.Commit, selectedCommitHash if k > 0 { prevCommit = commits[k-1] } - line := renderPipeSet(pipeSet, selectedCommitHash, prevCommit) + line := renderPipeSet(pipeSet, selectedCommitHashPtr, prevCommit) innerLines = append(innerLines, line) } chunks[i] = innerLines @@ -116,12 +120,12 @@ func getNextPipes(prevPipes []*Pipe, commit *models.Commit, getStyle func(c *mod return pipe.kind != TERMINATES }) - newPipes := make([]*Pipe, 0, len(currentPipes)+len(commit.Parents())) + newPipes := make([]*Pipe, 0, len(currentPipes)+len(commit.ParentPtrs())) // start by assuming that we've got a brand new commit not related to any preceding commit. // (this only happens when we're doing `git log --all`). These will be tacked onto the far end. pos := maxPos + 1 for _, pipe := range currentPipes { - if equalHashes(pipe.toHash, commit.Hash()) { + if equalHashes(pipe.toHash, commit.HashPtr()) { // turns out this commit does have a descendant so we'll place it right under the first instance pos = pipe.toPos break @@ -133,16 +137,16 @@ func getNextPipes(prevPipes []*Pipe, commit *models.Commit, getStyle func(c *mod // a traversed spot is one where a current pipe is starting on, ending on, or passing through traversedSpots := set.New[int]() - var toHash string + var toHash *string if commit.IsFirstCommit() { - toHash = models.EmptyTreeCommitHash + toHash = &EmptyTreeCommitHash } else { - toHash = commit.Parents()[0] + toHash = commit.ParentPtrs()[0] } newPipes = append(newPipes, &Pipe{ fromPos: pos, toPos: pos, - fromHash: commit.Hash(), + fromHash: commit.HashPtr(), toHash: toHash, kind: STARTS, style: getStyle(commit), @@ -150,7 +154,7 @@ func getNextPipes(prevPipes []*Pipe, commit *models.Commit, getStyle func(c *mod traversedSpotsForContinuingPipes := set.New[int]() for _, pipe := range currentPipes { - if !equalHashes(pipe.toHash, commit.Hash()) { + if !equalHashes(pipe.toHash, commit.HashPtr()) { traversedSpotsForContinuingPipes.Add(pipe.toPos) } } @@ -189,7 +193,7 @@ func getNextPipes(prevPipes []*Pipe, commit *models.Commit, getStyle func(c *mod } for _, pipe := range currentPipes { - if equalHashes(pipe.toHash, commit.Hash()) { + if equalHashes(pipe.toHash, commit.HashPtr()) { // terminating here newPipes = append(newPipes, &Pipe{ fromPos: pipe.toPos, @@ -216,13 +220,13 @@ func getNextPipes(prevPipes []*Pipe, commit *models.Commit, getStyle func(c *mod } if commit.IsMerge() { - for _, parent := range commit.Parents()[1:] { + for _, parent := range commit.ParentPtrs()[1:] { availablePos := getNextAvailablePosForNewPipe() // need to act as if continuing pipes are going to continue on the same line. newPipes = append(newPipes, &Pipe{ fromPos: pos, toPos: availablePos, - fromHash: commit.Hash(), + fromHash: commit.HashPtr(), toHash: parent, kind: STARTS, style: getStyle(commit), @@ -233,7 +237,7 @@ func getNextPipes(prevPipes []*Pipe, commit *models.Commit, getStyle func(c *mod } for _, pipe := range currentPipes { - if !equalHashes(pipe.toHash, commit.Hash()) && pipe.toPos > pos { + if !equalHashes(pipe.toHash, commit.HashPtr()) && pipe.toPos > pos { // continuing on, potentially moving left to fill in a blank spot last := pipe.toPos for i := pipe.toPos; i > pos; i-- { @@ -268,7 +272,7 @@ func getNextPipes(prevPipes []*Pipe, commit *models.Commit, getStyle func(c *mod func renderPipeSet( pipes []*Pipe, - selectedCommitHash string, + selectedCommitHashPtr *string, prevCommit *models.Commit, ) string { maxPos := 0 @@ -315,10 +319,10 @@ func renderPipeSet( // we don't want to highlight two commits if they're contiguous. We only want // to highlight multiple things if there's an actual visible pipe involved. highlight := true - if prevCommit != nil && equalHashes(prevCommit.Hash(), selectedCommitHash) { + if prevCommit != nil && equalHashes(prevCommit.HashPtr(), selectedCommitHashPtr) { highlight = false for _, pipe := range pipes { - if equalHashes(pipe.fromHash, selectedCommitHash) && (pipe.kind != TERMINATES || pipe.fromPos != pipe.toPos) { + if equalHashes(pipe.fromHash, selectedCommitHashPtr) && (pipe.kind != TERMINATES || pipe.fromPos != pipe.toPos) { highlight = true } } @@ -327,7 +331,7 @@ func renderPipeSet( // so we have our commit pos again, now it's time to build the cells. // we'll handle the one that's sourced from our selected commit last so that it can override the other cells. selectedPipes, nonSelectedPipes := utils.Partition(pipes, func(pipe *Pipe) bool { - return highlight && equalHashes(pipe.fromHash, selectedCommitHash) + return highlight && equalHashes(pipe.fromHash, selectedCommitHashPtr) }) for _, pipe := range nonSelectedPipes { @@ -370,13 +374,13 @@ func renderPipeSet( return writer.String() } -func equalHashes(a, b string) bool { - // if our selectedCommitHash is an empty string we treat that as meaning there is no selected commit hash - if a == "" || b == "" { +func equalHashes(a, b *string) bool { + // if our selectedCommitHashPtr is nil, there is no selected commit + if a == nil || b == nil { return false } - length := min(len(a), len(b)) + length := min(len(*a), len(*b)) // parent hashes are only stored up to 20 characters for some reason so we'll truncate to that for comparison - return a[:length] == b[:length] + return (*a)[:length] == (*b)[:length] } diff --git a/pkg/gui/presentation/graph/graph_test.go b/pkg/gui/presentation/graph/graph_test.go index 17914d8d5..25d529bbc 100644 --- a/pkg/gui/presentation/graph/graph_test.go +++ b/pkg/gui/presentation/graph/graph_test.go @@ -224,7 +224,7 @@ func TestRenderCommitGraph(t *testing.T) { getStyle := func(c *models.Commit) style.TextStyle { return style.FgDefault } commits := lo.Map(test.commitOpts, func(opts models.NewCommitOpts, _ int) *models.Commit { return models.NewCommit(hashPool, opts) }) - lines := RenderCommitGraph(commits, "blah", getStyle) + lines := RenderCommitGraph(commits, hashPool.Add("blah"), getStyle) trimmedExpectedOutput := "" for _, line := range strings.Split(strings.TrimPrefix(test.expectedOutput, "\n"), "\n") { @@ -257,6 +257,7 @@ func TestRenderPipeSet(t *testing.T) { nothing := style.Nothing hashPool := &utils.StringPool{} + pool := func(s string) *string { return hashPool.Add(s) } tests := []struct { name string @@ -269,8 +270,8 @@ func TestRenderPipeSet(t *testing.T) { { name: "single cell", pipes: []*Pipe{ - {fromPos: 0, toPos: 0, fromHash: "a", toHash: "b", kind: TERMINATES, style: cyan}, - {fromPos: 0, toPos: 0, fromHash: "b", toHash: "c", kind: STARTS, style: green}, + {fromPos: 0, toPos: 0, fromHash: pool("a"), toHash: pool("b"), kind: TERMINATES, style: cyan}, + {fromPos: 0, toPos: 0, fromHash: pool("b"), toHash: pool("c"), kind: STARTS, style: green}, }, prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a"}), expectedStr: "◯", @@ -279,8 +280,8 @@ func TestRenderPipeSet(t *testing.T) { { name: "single cell, selected", pipes: []*Pipe{ - {fromPos: 0, toPos: 0, fromHash: "a", toHash: "selected", kind: TERMINATES, style: cyan}, - {fromPos: 0, toPos: 0, fromHash: "selected", toHash: "c", kind: STARTS, style: green}, + {fromPos: 0, toPos: 0, fromHash: pool("a"), toHash: pool("selected"), kind: TERMINATES, style: cyan}, + {fromPos: 0, toPos: 0, fromHash: pool("selected"), toHash: pool("c"), kind: STARTS, style: green}, }, prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a"}), expectedStr: "◯", @@ -289,10 +290,10 @@ func TestRenderPipeSet(t *testing.T) { { name: "terminating hook and starting hook, selected", pipes: []*Pipe{ - {fromPos: 0, toPos: 0, fromHash: "a", toHash: "selected", kind: TERMINATES, style: cyan}, - {fromPos: 1, toPos: 0, fromHash: "c", toHash: "selected", kind: TERMINATES, style: yellow}, - {fromPos: 0, toPos: 0, fromHash: "selected", toHash: "d", kind: STARTS, style: green}, - {fromPos: 0, toPos: 1, fromHash: "selected", toHash: "e", kind: STARTS, style: green}, + {fromPos: 0, toPos: 0, fromHash: pool("a"), toHash: pool("selected"), kind: TERMINATES, style: cyan}, + {fromPos: 1, toPos: 0, fromHash: pool("c"), toHash: pool("selected"), kind: TERMINATES, style: yellow}, + {fromPos: 0, toPos: 0, fromHash: pool("selected"), toHash: pool("d"), kind: STARTS, style: green}, + {fromPos: 0, toPos: 1, fromHash: pool("selected"), toHash: pool("e"), kind: STARTS, style: green}, }, prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a"}), expectedStr: "⏣─╮", @@ -303,10 +304,10 @@ func TestRenderPipeSet(t *testing.T) { { name: "terminating hook and starting hook, prioritise the terminating one", pipes: []*Pipe{ - {fromPos: 0, toPos: 0, fromHash: "a", toHash: "b", kind: TERMINATES, style: red}, - {fromPos: 1, toPos: 0, fromHash: "c", toHash: "b", kind: TERMINATES, style: magenta}, - {fromPos: 0, toPos: 0, fromHash: "b", toHash: "d", kind: STARTS, style: green}, - {fromPos: 0, toPos: 1, fromHash: "b", toHash: "e", kind: STARTS, style: green}, + {fromPos: 0, toPos: 0, fromHash: pool("a"), toHash: pool("b"), kind: TERMINATES, style: red}, + {fromPos: 1, toPos: 0, fromHash: pool("c"), toHash: pool("b"), kind: TERMINATES, style: magenta}, + {fromPos: 0, toPos: 0, fromHash: pool("b"), toHash: pool("d"), kind: STARTS, style: green}, + {fromPos: 0, toPos: 1, fromHash: pool("b"), toHash: pool("e"), kind: STARTS, style: green}, }, prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a"}), expectedStr: "⏣─│", @@ -317,11 +318,11 @@ func TestRenderPipeSet(t *testing.T) { { name: "starting and terminating pipe sharing some space", pipes: []*Pipe{ - {fromPos: 0, toPos: 0, fromHash: "a1", toHash: "a2", kind: TERMINATES, style: red}, - {fromPos: 0, toPos: 0, fromHash: "a2", toHash: "a3", kind: STARTS, style: yellow}, - {fromPos: 1, toPos: 1, fromHash: "b1", toHash: "b2", kind: CONTINUES, style: magenta}, - {fromPos: 3, toPos: 0, fromHash: "e1", toHash: "a2", kind: TERMINATES, style: green}, - {fromPos: 0, toPos: 2, fromHash: "a2", toHash: "c3", kind: STARTS, style: yellow}, + {fromPos: 0, toPos: 0, fromHash: pool("a1"), toHash: pool("a2"), kind: TERMINATES, style: red}, + {fromPos: 0, toPos: 0, fromHash: pool("a2"), toHash: pool("a3"), kind: STARTS, style: yellow}, + {fromPos: 1, toPos: 1, fromHash: pool("b1"), toHash: pool("b2"), kind: CONTINUES, style: magenta}, + {fromPos: 3, toPos: 0, fromHash: pool("e1"), toHash: pool("a2"), kind: TERMINATES, style: green}, + {fromPos: 0, toPos: 2, fromHash: pool("a2"), toHash: pool("c3"), kind: STARTS, style: yellow}, }, prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a1"}), expectedStr: "⏣─│─┬─╯", @@ -332,11 +333,11 @@ func TestRenderPipeSet(t *testing.T) { { name: "starting and terminating pipe sharing some space, with selection", pipes: []*Pipe{ - {fromPos: 0, toPos: 0, fromHash: "a1", toHash: "selected", kind: TERMINATES, style: red}, - {fromPos: 0, toPos: 0, fromHash: "selected", toHash: "a3", kind: STARTS, style: yellow}, - {fromPos: 1, toPos: 1, fromHash: "b1", toHash: "b2", kind: CONTINUES, style: magenta}, - {fromPos: 3, toPos: 0, fromHash: "e1", toHash: "selected", kind: TERMINATES, style: green}, - {fromPos: 0, toPos: 2, fromHash: "selected", toHash: "c3", kind: STARTS, style: yellow}, + {fromPos: 0, toPos: 0, fromHash: pool("a1"), toHash: pool("selected"), kind: TERMINATES, style: red}, + {fromPos: 0, toPos: 0, fromHash: pool("selected"), toHash: pool("a3"), kind: STARTS, style: yellow}, + {fromPos: 1, toPos: 1, fromHash: pool("b1"), toHash: pool("b2"), kind: CONTINUES, style: magenta}, + {fromPos: 3, toPos: 0, fromHash: pool("e1"), toHash: pool("selected"), kind: TERMINATES, style: green}, + {fromPos: 0, toPos: 2, fromHash: pool("selected"), toHash: pool("c3"), kind: STARTS, style: yellow}, }, prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a1"}), expectedStr: "⏣───╮ ╯", @@ -347,10 +348,10 @@ func TestRenderPipeSet(t *testing.T) { { name: "many terminating pipes", pipes: []*Pipe{ - {fromPos: 0, toPos: 0, fromHash: "a1", toHash: "a2", kind: TERMINATES, style: red}, - {fromPos: 0, toPos: 0, fromHash: "a2", toHash: "a3", kind: STARTS, style: yellow}, - {fromPos: 1, toPos: 0, fromHash: "b1", toHash: "a2", kind: TERMINATES, style: magenta}, - {fromPos: 2, toPos: 0, fromHash: "c1", toHash: "a2", kind: TERMINATES, style: green}, + {fromPos: 0, toPos: 0, fromHash: pool("a1"), toHash: pool("a2"), kind: TERMINATES, style: red}, + {fromPos: 0, toPos: 0, fromHash: pool("a2"), toHash: pool("a3"), kind: STARTS, style: yellow}, + {fromPos: 1, toPos: 0, fromHash: pool("b1"), toHash: pool("a2"), kind: TERMINATES, style: magenta}, + {fromPos: 2, toPos: 0, fromHash: pool("c1"), toHash: pool("a2"), kind: TERMINATES, style: green}, }, prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a1"}), expectedStr: "◯─┴─╯", @@ -361,11 +362,11 @@ func TestRenderPipeSet(t *testing.T) { { name: "starting pipe passing through", pipes: []*Pipe{ - {fromPos: 0, toPos: 0, fromHash: "a1", toHash: "a2", kind: TERMINATES, style: red}, - {fromPos: 0, toPos: 0, fromHash: "a2", toHash: "a3", kind: STARTS, style: yellow}, - {fromPos: 0, toPos: 3, fromHash: "a2", toHash: "d3", kind: STARTS, style: yellow}, - {fromPos: 1, toPos: 1, fromHash: "b1", toHash: "b3", kind: CONTINUES, style: magenta}, - {fromPos: 2, toPos: 2, fromHash: "c1", toHash: "c3", kind: CONTINUES, style: green}, + {fromPos: 0, toPos: 0, fromHash: pool("a1"), toHash: pool("a2"), kind: TERMINATES, style: red}, + {fromPos: 0, toPos: 0, fromHash: pool("a2"), toHash: pool("a3"), kind: STARTS, style: yellow}, + {fromPos: 0, toPos: 3, fromHash: pool("a2"), toHash: pool("d3"), kind: STARTS, style: yellow}, + {fromPos: 1, toPos: 1, fromHash: pool("b1"), toHash: pool("b3"), kind: CONTINUES, style: magenta}, + {fromPos: 2, toPos: 2, fromHash: pool("c1"), toHash: pool("c3"), kind: CONTINUES, style: green}, }, prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a1"}), expectedStr: "⏣─│─│─╮", @@ -376,11 +377,11 @@ func TestRenderPipeSet(t *testing.T) { { name: "starting and terminating path crossing continuing path", pipes: []*Pipe{ - {fromPos: 0, toPos: 0, fromHash: "a1", toHash: "a2", kind: TERMINATES, style: red}, - {fromPos: 0, toPos: 0, fromHash: "a2", toHash: "a3", kind: STARTS, style: yellow}, - {fromPos: 0, toPos: 1, fromHash: "a2", toHash: "b3", kind: STARTS, style: yellow}, - {fromPos: 1, toPos: 1, fromHash: "b1", toHash: "a2", kind: CONTINUES, style: green}, - {fromPos: 2, toPos: 0, fromHash: "c1", toHash: "a2", kind: TERMINATES, style: magenta}, + {fromPos: 0, toPos: 0, fromHash: pool("a1"), toHash: pool("a2"), kind: TERMINATES, style: red}, + {fromPos: 0, toPos: 0, fromHash: pool("a2"), toHash: pool("a3"), kind: STARTS, style: yellow}, + {fromPos: 0, toPos: 1, fromHash: pool("a2"), toHash: pool("b3"), kind: STARTS, style: yellow}, + {fromPos: 1, toPos: 1, fromHash: pool("b1"), toHash: pool("a2"), kind: CONTINUES, style: green}, + {fromPos: 2, toPos: 0, fromHash: pool("c1"), toHash: pool("a2"), kind: TERMINATES, style: magenta}, }, prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a1"}), expectedStr: "⏣─│─╯", @@ -391,11 +392,11 @@ func TestRenderPipeSet(t *testing.T) { { name: "another clash of starting and terminating paths", pipes: []*Pipe{ - {fromPos: 0, toPos: 0, fromHash: "a1", toHash: "a2", kind: TERMINATES, style: red}, - {fromPos: 0, toPos: 0, fromHash: "a2", toHash: "a3", kind: STARTS, style: yellow}, - {fromPos: 0, toPos: 1, fromHash: "a2", toHash: "b3", kind: STARTS, style: yellow}, - {fromPos: 2, toPos: 2, fromHash: "c1", toHash: "c3", kind: CONTINUES, style: green}, - {fromPos: 3, toPos: 0, fromHash: "d1", toHash: "a2", kind: TERMINATES, style: magenta}, + {fromPos: 0, toPos: 0, fromHash: pool("a1"), toHash: pool("a2"), kind: TERMINATES, style: red}, + {fromPos: 0, toPos: 0, fromHash: pool("a2"), toHash: pool("a3"), kind: STARTS, style: yellow}, + {fromPos: 0, toPos: 1, fromHash: pool("a2"), toHash: pool("b3"), kind: STARTS, style: yellow}, + {fromPos: 2, toPos: 2, fromHash: pool("c1"), toHash: pool("c3"), kind: CONTINUES, style: green}, + {fromPos: 3, toPos: 0, fromHash: pool("d1"), toHash: pool("a2"), kind: TERMINATES, style: magenta}, }, prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a1"}), expectedStr: "⏣─┬─│─╯", @@ -406,8 +407,8 @@ func TestRenderPipeSet(t *testing.T) { { name: "commit whose previous commit is selected", pipes: []*Pipe{ - {fromPos: 0, toPos: 0, fromHash: "selected", toHash: "a2", kind: TERMINATES, style: red}, - {fromPos: 0, toPos: 0, fromHash: "a2", toHash: "a3", kind: STARTS, style: yellow}, + {fromPos: 0, toPos: 0, fromHash: pool("selected"), toHash: pool("a2"), kind: TERMINATES, style: red}, + {fromPos: 0, toPos: 0, fromHash: pool("a2"), toHash: pool("a3"), kind: STARTS, style: yellow}, }, prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "selected"}), expectedStr: "◯", @@ -418,8 +419,8 @@ func TestRenderPipeSet(t *testing.T) { { name: "commit whose previous commit is selected and is a merge commit", pipes: []*Pipe{ - {fromPos: 0, toPos: 0, fromHash: "selected", toHash: "a2", kind: TERMINATES, style: red}, - {fromPos: 1, toPos: 1, fromHash: "selected", toHash: "b3", kind: CONTINUES, style: red}, + {fromPos: 0, toPos: 0, fromHash: pool("selected"), toHash: pool("a2"), kind: TERMINATES, style: red}, + {fromPos: 1, toPos: 1, fromHash: pool("selected"), toHash: pool("b3"), kind: CONTINUES, style: red}, }, prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "selected"}), expectedStr: "◯ │", @@ -430,9 +431,9 @@ func TestRenderPipeSet(t *testing.T) { { name: "commit whose previous commit is selected and is a merge commit, with continuing pipe inbetween", pipes: []*Pipe{ - {fromPos: 0, toPos: 0, fromHash: "selected", toHash: "a2", kind: TERMINATES, style: red}, - {fromPos: 1, toPos: 1, fromHash: "z1", toHash: "z3", kind: CONTINUES, style: green}, - {fromPos: 2, toPos: 2, fromHash: "selected", toHash: "b3", kind: CONTINUES, style: red}, + {fromPos: 0, toPos: 0, fromHash: pool("selected"), toHash: pool("a2"), kind: TERMINATES, style: red}, + {fromPos: 1, toPos: 1, fromHash: pool("z1"), toHash: pool("z3"), kind: CONTINUES, style: green}, + {fromPos: 2, toPos: 2, fromHash: pool("selected"), toHash: pool("b3"), kind: CONTINUES, style: red}, }, prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "selected"}), expectedStr: "◯ │ │", @@ -443,10 +444,10 @@ func TestRenderPipeSet(t *testing.T) { { name: "when previous commit is selected, not a merge commit, and spawns a continuing pipe", pipes: []*Pipe{ - {fromPos: 0, toPos: 0, fromHash: "a1", toHash: "a2", kind: TERMINATES, style: red}, - {fromPos: 0, toPos: 0, fromHash: "a2", toHash: "a3", kind: STARTS, style: green}, - {fromPos: 0, toPos: 1, fromHash: "a2", toHash: "b3", kind: STARTS, style: green}, - {fromPos: 1, toPos: 0, fromHash: "selected", toHash: "a2", kind: TERMINATES, style: yellow}, + {fromPos: 0, toPos: 0, fromHash: pool("a1"), toHash: pool("a2"), kind: TERMINATES, style: red}, + {fromPos: 0, toPos: 0, fromHash: pool("a2"), toHash: pool("a3"), kind: STARTS, style: green}, + {fromPos: 0, toPos: 1, fromHash: pool("a2"), toHash: pool("b3"), kind: STARTS, style: green}, + {fromPos: 1, toPos: 0, fromHash: pool("selected"), toHash: pool("a2"), kind: TERMINATES, style: yellow}, }, prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "selected"}), expectedStr: "⏣─╯", @@ -461,7 +462,7 @@ func TestRenderPipeSet(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - actualStr := renderPipeSet(test.pipes, "selected", test.prevCommit) + actualStr := renderPipeSet(test.pipes, pool("selected"), test.prevCommit) t.Log("actual cells:") t.Log(actualStr) expectedStr := "" @@ -482,6 +483,7 @@ func TestRenderPipeSet(t *testing.T) { func TestGetNextPipes(t *testing.T) { hashPool := &utils.StringPool{} + pool := func(s string) *string { return hashPool.Add(s) } tests := []struct { prevPipes []*Pipe @@ -490,43 +492,43 @@ func TestGetNextPipes(t *testing.T) { }{ { prevPipes: []*Pipe{ - {fromPos: 0, toPos: 0, fromHash: "a", toHash: "b", kind: STARTS, style: style.FgDefault}, + {fromPos: 0, toPos: 0, fromHash: pool("a"), toHash: pool("b"), kind: STARTS, style: style.FgDefault}, }, commit: models.NewCommit(hashPool, models.NewCommitOpts{ Hash: "b", Parents: []string{"c"}, }), expected: []*Pipe{ - {fromPos: 0, toPos: 0, fromHash: "a", toHash: "b", kind: TERMINATES, style: style.FgDefault}, - {fromPos: 0, toPos: 0, fromHash: "b", toHash: "c", kind: STARTS, style: style.FgDefault}, + {fromPos: 0, toPos: 0, fromHash: pool("a"), toHash: pool("b"), kind: TERMINATES, style: style.FgDefault}, + {fromPos: 0, toPos: 0, fromHash: pool("b"), toHash: pool("c"), kind: STARTS, style: style.FgDefault}, }, }, { prevPipes: []*Pipe{ - {fromPos: 0, toPos: 0, fromHash: "a", toHash: "b", kind: TERMINATES, style: style.FgDefault}, - {fromPos: 0, toPos: 0, fromHash: "b", toHash: "c", kind: STARTS, style: style.FgDefault}, - {fromPos: 0, toPos: 1, fromHash: "b", toHash: "d", kind: STARTS, style: style.FgDefault}, + {fromPos: 0, toPos: 0, fromHash: pool("a"), toHash: pool("b"), kind: TERMINATES, style: style.FgDefault}, + {fromPos: 0, toPos: 0, fromHash: pool("b"), toHash: pool("c"), kind: STARTS, style: style.FgDefault}, + {fromPos: 0, toPos: 1, fromHash: pool("b"), toHash: pool("d"), kind: STARTS, style: style.FgDefault}, }, commit: models.NewCommit(hashPool, models.NewCommitOpts{ Hash: "d", Parents: []string{"e"}, }), expected: []*Pipe{ - {fromPos: 0, toPos: 0, fromHash: "b", toHash: "c", kind: CONTINUES, style: style.FgDefault}, - {fromPos: 1, toPos: 1, fromHash: "b", toHash: "d", kind: TERMINATES, style: style.FgDefault}, - {fromPos: 1, toPos: 1, fromHash: "d", toHash: "e", kind: STARTS, style: style.FgDefault}, + {fromPos: 0, toPos: 0, fromHash: pool("b"), toHash: pool("c"), kind: CONTINUES, style: style.FgDefault}, + {fromPos: 1, toPos: 1, fromHash: pool("b"), toHash: pool("d"), kind: TERMINATES, style: style.FgDefault}, + {fromPos: 1, toPos: 1, fromHash: pool("d"), toHash: pool("e"), kind: STARTS, style: style.FgDefault}, }, }, { prevPipes: []*Pipe{ - {fromPos: 0, toPos: 0, fromHash: "a", toHash: "root", kind: TERMINATES, style: style.FgDefault}, + {fromPos: 0, toPos: 0, fromHash: pool("a"), toHash: pool("root"), kind: TERMINATES, style: style.FgDefault}, }, commit: models.NewCommit(hashPool, models.NewCommitOpts{ Hash: "root", Parents: []string{}, }), expected: []*Pipe{ - {fromPos: 1, toPos: 1, fromHash: "root", toHash: models.EmptyTreeCommitHash, kind: STARTS, style: style.FgDefault}, + {fromPos: 1, toPos: 1, fromHash: pool("root"), toHash: pool(models.EmptyTreeCommitHash), kind: STARTS, style: style.FgDefault}, }, }, } @@ -538,8 +540,8 @@ func TestGetNextPipes(t *testing.T) { getStyle := func(c *models.Commit) style.TextStyle { return style.FgDefault } pipes := getNextPipes(test.prevPipes, test.commit, getStyle) // rendering cells so that it's easier to see what went wrong - actualStr := renderPipeSet(pipes, "selected", nil) - expectedStr := renderPipeSet(test.expected, "selected", nil) + actualStr := renderPipeSet(pipes, pool("selected"), nil) + expectedStr := renderPipeSet(test.expected, pool("selected"), nil) t.Log("expected cells:") t.Log(expectedStr) t.Log("actual cells:") @@ -552,19 +554,19 @@ func BenchmarkRenderCommitGraph(b *testing.B) { oldColorLevel := color.ForceSetColorLevel(terminfo.ColorLevelMillions) defer color.ForceSetColorLevel(oldColorLevel) - commits := generateCommits(50) + hashPool := &utils.StringPool{} + + commits := generateCommits(hashPool, 50) getStyle := func(commit *models.Commit) style.TextStyle { return authors.AuthorStyle(commit.AuthorName) } b.ResetTimer() for b.Loop() { - RenderCommitGraph(commits, "selected", getStyle) + RenderCommitGraph(commits, hashPool.Add("selected"), getStyle) } } -func generateCommits(count int) []*models.Commit { - hashPool := &utils.StringPool{} - +func generateCommits(hashPool *utils.StringPool, count int) []*models.Commit { rnd := rand.New(rand.NewSource(1234)) pool := []*models.Commit{models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a", AuthorName: "A"})} commits := make([]*models.Commit, 0, count)