Store Commit.Hash by pointer (kept in a pool of hashes)

This in itself is not an improvement, because hashes are unique (they are shared
between real commits and rebase todos, but there are so few of those that it
doesn't matter). However, it becomes an improvement once we also store parent
hashes in the same pool; but the real motivation for this change is to also
reuse the hash pointers in Pipe objects later in the branch. This will be a big
win because in a merge-heavy git repo there are many more Pipe instances than
commits.
This commit is contained in:
Stefan Haller 2025-04-19 16:29:36 +02:00
parent 1037371a44
commit e27bc15bbd
15 changed files with 119 additions and 74 deletions

View file

@ -66,6 +66,7 @@ type GetCommitsOptions struct {
// If non-empty, show divergence from this ref (left-right log) // If non-empty, show divergence from this ref (left-right log)
RefToShowDivergenceFrom string RefToShowDivergenceFrom string
MainBranches *MainBranches MainBranches *MainBranches
HashPool *utils.StringPool
} }
// GetCommits obtains the commits of the current branch // GetCommits obtains the commits of the current branch
@ -74,7 +75,7 @@ func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit,
if opts.IncludeRebaseCommits && opts.FilterPath == "" { if opts.IncludeRebaseCommits && opts.FilterPath == "" {
var err error var err error
commits, err = self.MergeRebasingCommits(commits) commits, err = self.MergeRebasingCommits(opts.HashPool, commits)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -89,7 +90,7 @@ func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit,
defer wg.Done() defer wg.Done()
logErr = self.getLogCmd(opts).RunAndProcessLines(func(line string) (bool, error) { logErr = self.getLogCmd(opts).RunAndProcessLines(func(line string) (bool, error) {
commit := self.extractCommitFromLine(line, opts.RefToShowDivergenceFrom != "") commit := self.extractCommitFromLine(opts.HashPool, line, opts.RefToShowDivergenceFrom != "")
commits = append(commits, commit) commits = append(commits, commit)
return false, nil return false, nil
}) })
@ -159,7 +160,7 @@ func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit,
return commits, nil return commits, nil
} }
func (self *CommitLoader) MergeRebasingCommits(commits []*models.Commit) ([]*models.Commit, error) { func (self *CommitLoader) MergeRebasingCommits(hashPool *utils.StringPool, commits []*models.Commit) ([]*models.Commit, error) {
// chances are we have as many commits as last time so we'll set the capacity to be the old length // chances are we have as many commits as last time so we'll set the capacity to be the old length
result := make([]*models.Commit, 0, len(commits)) result := make([]*models.Commit, 0, len(commits))
for i, commit := range commits { for i, commit := range commits {
@ -172,7 +173,7 @@ func (self *CommitLoader) MergeRebasingCommits(commits []*models.Commit) ([]*mod
workingTreeState := self.getWorkingTreeState() workingTreeState := self.getWorkingTreeState()
addConflictedRebasingCommit := true addConflictedRebasingCommit := true
if workingTreeState.CherryPicking || workingTreeState.Reverting { if workingTreeState.CherryPicking || workingTreeState.Reverting {
sequencerCommits, err := self.getHydratedSequencerCommits(workingTreeState) sequencerCommits, err := self.getHydratedSequencerCommits(hashPool, workingTreeState)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -181,7 +182,7 @@ func (self *CommitLoader) MergeRebasingCommits(commits []*models.Commit) ([]*mod
} }
if workingTreeState.Rebasing { if workingTreeState.Rebasing {
rebasingCommits, err := self.getHydratedRebasingCommits(addConflictedRebasingCommit) rebasingCommits, err := self.getHydratedRebasingCommits(hashPool, addConflictedRebasingCommit)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -196,7 +197,7 @@ func (self *CommitLoader) MergeRebasingCommits(commits []*models.Commit) ([]*mod
// then puts them into a commit object // then puts them into a commit object
// example input: // example input:
// 8ad01fe32fcc20f07bc6693f87aa4977c327f1e1|10 hours ago|Jesse Duffield| (HEAD -> master, tag: v0.15.2)|refresh commits when adding a tag // 8ad01fe32fcc20f07bc6693f87aa4977c327f1e1|10 hours ago|Jesse Duffield| (HEAD -> master, tag: v0.15.2)|refresh commits when adding a tag
func (self *CommitLoader) extractCommitFromLine(line string, showDivergence bool) *models.Commit { func (self *CommitLoader) extractCommitFromLine(hashPool *utils.StringPool, line string, showDivergence bool) *models.Commit {
split := strings.SplitN(line, "\x00", 8) split := strings.SplitN(line, "\x00", 8)
hash := split[0] hash := split[0]
@ -234,7 +235,7 @@ func (self *CommitLoader) extractCommitFromLine(line string, showDivergence bool
parents = strings.Split(parentHashes, " ") parents = strings.Split(parentHashes, " ")
} }
return models.NewCommit(models.NewCommitOpts{ return models.NewCommit(hashPool, models.NewCommitOpts{
Hash: hash, Hash: hash,
Name: message, Name: message,
Tags: tags, Tags: tags,
@ -247,13 +248,13 @@ func (self *CommitLoader) extractCommitFromLine(line string, showDivergence bool
}) })
} }
func (self *CommitLoader) getHydratedRebasingCommits(addConflictingCommit bool) ([]*models.Commit, error) { func (self *CommitLoader) getHydratedRebasingCommits(hashPool *utils.StringPool, addConflictingCommit bool) ([]*models.Commit, error) {
todoFileHasShortHashes := self.version.IsOlderThan(2, 25, 2) todoFileHasShortHashes := self.version.IsOlderThan(2, 25, 2)
return self.getHydratedTodoCommits(self.getRebasingCommits(addConflictingCommit), todoFileHasShortHashes) return self.getHydratedTodoCommits(hashPool, self.getRebasingCommits(hashPool, addConflictingCommit), todoFileHasShortHashes)
} }
func (self *CommitLoader) getHydratedSequencerCommits(workingTreeState models.WorkingTreeState) ([]*models.Commit, error) { func (self *CommitLoader) getHydratedSequencerCommits(hashPool *utils.StringPool, workingTreeState models.WorkingTreeState) ([]*models.Commit, error) {
commits := self.getSequencerCommits() commits := self.getSequencerCommits(hashPool)
if len(commits) > 0 { if len(commits) > 0 {
// If we have any commits in .git/sequencer/todo, then the last one of // If we have any commits in .git/sequencer/todo, then the last one of
// those is the conflicting one. // those is the conflicting one.
@ -262,16 +263,16 @@ func (self *CommitLoader) getHydratedSequencerCommits(workingTreeState models.Wo
// For single-commit cherry-picks and reverts, git apparently doesn't // For single-commit cherry-picks and reverts, git apparently doesn't
// use the sequencer; in that case, CHERRY_PICK_HEAD or REVERT_HEAD is // use the sequencer; in that case, CHERRY_PICK_HEAD or REVERT_HEAD is
// our conflicting commit, so synthesize it here. // our conflicting commit, so synthesize it here.
conflicedCommit := self.getConflictedSequencerCommit(workingTreeState) conflicedCommit := self.getConflictedSequencerCommit(hashPool, workingTreeState)
if conflicedCommit != nil { if conflicedCommit != nil {
commits = append(commits, conflicedCommit) commits = append(commits, conflicedCommit)
} }
} }
return self.getHydratedTodoCommits(commits, true) return self.getHydratedTodoCommits(hashPool, commits, true)
} }
func (self *CommitLoader) getHydratedTodoCommits(todoCommits []*models.Commit, todoFileHasShortHashes bool) ([]*models.Commit, error) { func (self *CommitLoader) getHydratedTodoCommits(hashPool *utils.StringPool, todoCommits []*models.Commit, todoFileHasShortHashes bool) ([]*models.Commit, error) {
if len(todoCommits) == 0 { if len(todoCommits) == 0 {
return nil, nil return nil, nil
} }
@ -292,7 +293,7 @@ func (self *CommitLoader) getHydratedTodoCommits(todoCommits []*models.Commit, t
fullCommits := map[string]*models.Commit{} fullCommits := map[string]*models.Commit{}
err := cmdObj.RunAndProcessLines(func(line string) (bool, error) { err := cmdObj.RunAndProcessLines(func(line string) (bool, error) {
commit := self.extractCommitFromLine(line, false) commit := self.extractCommitFromLine(hashPool, line, false)
fullCommits[commit.Hash()] = commit fullCommits[commit.Hash()] = commit
return false, nil return false, nil
}) })
@ -331,7 +332,7 @@ func (self *CommitLoader) getHydratedTodoCommits(todoCommits []*models.Commit, t
// git-rebase-todo example: // git-rebase-todo example:
// pick ac446ae94ee560bdb8d1d057278657b251aaef17 ac446ae // pick ac446ae94ee560bdb8d1d057278657b251aaef17 ac446ae
// pick afb893148791a2fbd8091aeb81deba4930c73031 afb8931 // pick afb893148791a2fbd8091aeb81deba4930c73031 afb8931
func (self *CommitLoader) getRebasingCommits(addConflictingCommit bool) []*models.Commit { func (self *CommitLoader) getRebasingCommits(hashPool *utils.StringPool, addConflictingCommit bool) []*models.Commit {
bytesContent, err := self.readFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo")) bytesContent, err := self.readFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo"))
if err != nil { if err != nil {
self.Log.Error(fmt.Sprintf("error occurred reading git-rebase-todo: %s", err.Error())) self.Log.Error(fmt.Sprintf("error occurred reading git-rebase-todo: %s", err.Error()))
@ -350,7 +351,7 @@ func (self *CommitLoader) getRebasingCommits(addConflictingCommit bool) []*model
// See if the current commit couldn't be applied because it conflicted; if // See if the current commit couldn't be applied because it conflicted; if
// so, add a fake entry for it // so, add a fake entry for it
if addConflictingCommit { if addConflictingCommit {
if conflictedCommit := self.getConflictedCommit(todos); conflictedCommit != nil { if conflictedCommit := self.getConflictedCommit(hashPool, todos); conflictedCommit != nil {
commits = append(commits, conflictedCommit) commits = append(commits, conflictedCommit)
} }
} }
@ -364,7 +365,7 @@ func (self *CommitLoader) getRebasingCommits(addConflictingCommit bool) []*model
// Command does not have a commit associated, skip // Command does not have a commit associated, skip
continue continue
} }
commits = utils.Prepend(commits, models.NewCommit(models.NewCommitOpts{ commits = utils.Prepend(commits, models.NewCommit(hashPool, models.NewCommitOpts{
Hash: t.Commit, Hash: t.Commit,
Name: t.Msg, Name: t.Msg,
Status: models.StatusRebasing, Status: models.StatusRebasing,
@ -375,7 +376,7 @@ func (self *CommitLoader) getRebasingCommits(addConflictingCommit bool) []*model
return commits return commits
} }
func (self *CommitLoader) getConflictedCommit(todos []todo.Todo) *models.Commit { func (self *CommitLoader) getConflictedCommit(hashPool *utils.StringPool, todos []todo.Todo) *models.Commit {
bytesContent, err := self.readFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/done")) bytesContent, err := self.readFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/done"))
if err != nil { if err != nil {
self.Log.Error(fmt.Sprintf("error occurred reading rebase-merge/done: %s", err.Error())) self.Log.Error(fmt.Sprintf("error occurred reading rebase-merge/done: %s", err.Error()))
@ -391,10 +392,10 @@ func (self *CommitLoader) getConflictedCommit(todos []todo.Todo) *models.Commit
amendFileExists, _ := self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/amend")) amendFileExists, _ := self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/amend"))
messageFileExists, _ := self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/message")) messageFileExists, _ := self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/message"))
return self.getConflictedCommitImpl(todos, doneTodos, amendFileExists, messageFileExists) return self.getConflictedCommitImpl(hashPool, todos, doneTodos, amendFileExists, messageFileExists)
} }
func (self *CommitLoader) getConflictedCommitImpl(todos []todo.Todo, doneTodos []todo.Todo, amendFileExists bool, messageFileExists bool) *models.Commit { func (self *CommitLoader) getConflictedCommitImpl(hashPool *utils.StringPool, todos []todo.Todo, doneTodos []todo.Todo, amendFileExists bool, messageFileExists bool) *models.Commit {
// Should never be possible, but just to be safe: // Should never be possible, but just to be safe:
if len(doneTodos) == 0 { if len(doneTodos) == 0 {
self.Log.Error("no done entries in rebase-merge/done file") self.Log.Error("no done entries in rebase-merge/done file")
@ -465,14 +466,14 @@ func (self *CommitLoader) getConflictedCommitImpl(todos []todo.Todo, doneTodos [
// Any other todo that has a commit associated with it must have failed with // Any other todo that has a commit associated with it must have failed with
// a conflict, otherwise we wouldn't have stopped the rebase: // a conflict, otherwise we wouldn't have stopped the rebase:
return models.NewCommit(models.NewCommitOpts{ return models.NewCommit(hashPool, models.NewCommitOpts{
Hash: lastTodo.Commit, Hash: lastTodo.Commit,
Action: lastTodo.Command, Action: lastTodo.Command,
Status: models.StatusConflicted, Status: models.StatusConflicted,
}) })
} }
func (self *CommitLoader) getSequencerCommits() []*models.Commit { func (self *CommitLoader) getSequencerCommits(hashPool *utils.StringPool) []*models.Commit {
bytesContent, err := self.readFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "sequencer/todo")) bytesContent, err := self.readFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "sequencer/todo"))
if err != nil { if err != nil {
self.Log.Error(fmt.Sprintf("error occurred reading sequencer/todo: %s", err.Error())) self.Log.Error(fmt.Sprintf("error occurred reading sequencer/todo: %s", err.Error()))
@ -493,7 +494,7 @@ func (self *CommitLoader) getSequencerCommits() []*models.Commit {
// Command does not have a commit associated, skip // Command does not have a commit associated, skip
continue continue
} }
commits = utils.Prepend(commits, models.NewCommit(models.NewCommitOpts{ commits = utils.Prepend(commits, models.NewCommit(hashPool, models.NewCommitOpts{
Hash: t.Commit, Hash: t.Commit,
Name: t.Msg, Name: t.Msg,
Status: models.StatusCherryPickingOrReverting, Status: models.StatusCherryPickingOrReverting,
@ -504,7 +505,7 @@ func (self *CommitLoader) getSequencerCommits() []*models.Commit {
return commits return commits
} }
func (self *CommitLoader) getConflictedSequencerCommit(workingTreeState models.WorkingTreeState) *models.Commit { func (self *CommitLoader) getConflictedSequencerCommit(hashPool *utils.StringPool, workingTreeState models.WorkingTreeState) *models.Commit {
var shaFile string var shaFile string
var action todo.TodoCommand var action todo.TodoCommand
if workingTreeState.CherryPicking { if workingTreeState.CherryPicking {
@ -526,7 +527,7 @@ func (self *CommitLoader) getConflictedSequencerCommit(workingTreeState models.W
if len(lines) == 0 { if len(lines) == 0 {
return nil return nil
} }
return models.NewCommit(models.NewCommitOpts{ return models.NewCommit(hashPool, models.NewCommitOpts{
Hash: lines[0], Hash: lines[0],
Status: models.StatusConflicted, Status: models.StatusConflicted,
Action: action, Action: action,

View file

@ -314,13 +314,16 @@ func TestGetCommits(t *testing.T) {
}, },
} }
hashPool := &utils.StringPool{}
common.UserConfig().Git.MainBranches = scenario.mainBranches common.UserConfig().Git.MainBranches = scenario.mainBranches
opts := scenario.opts opts := scenario.opts
opts.MainBranches = NewMainBranches(common, cmd) opts.MainBranches = NewMainBranches(common, cmd)
opts.HashPool = hashPool
commits, err := builder.GetCommits(opts) commits, err := builder.GetCommits(opts)
expectedCommits := lo.Map(scenario.expectedCommitOpts, expectedCommits := lo.Map(scenario.expectedCommitOpts,
func(opts models.NewCommitOpts, _ int) *models.Commit { return models.NewCommit(opts) }) func(opts models.NewCommitOpts, _ int) *models.Commit { return models.NewCommit(hashPool, opts) })
assert.Equal(t, expectedCommits, commits) assert.Equal(t, expectedCommits, commits)
assert.Equal(t, scenario.expectedError, err) assert.Equal(t, scenario.expectedError, err)
@ -330,6 +333,8 @@ func TestGetCommits(t *testing.T) {
} }
func TestCommitLoader_getConflictedCommitImpl(t *testing.T) { func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
hashPool := &utils.StringPool{}
scenarios := []struct { scenarios := []struct {
testName string testName string
todos []todo.Todo todos []todo.Todo
@ -359,7 +364,7 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
}, },
}, },
amendFileExists: false, amendFileExists: false,
expectedResult: models.NewCommit(models.NewCommitOpts{ expectedResult: models.NewCommit(hashPool, models.NewCommitOpts{
Hash: "fa1afe1", Hash: "fa1afe1",
Action: todo.Pick, Action: todo.Pick,
Status: models.StatusConflicted, Status: models.StatusConflicted,
@ -460,7 +465,7 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
}, },
}, },
amendFileExists: false, amendFileExists: false,
expectedResult: models.NewCommit(models.NewCommitOpts{ expectedResult: models.NewCommit(hashPool, models.NewCommitOpts{
Hash: "fa1afe1", Hash: "fa1afe1",
Action: todo.Pick, Action: todo.Pick,
Status: models.StatusConflicted, Status: models.StatusConflicted,
@ -489,7 +494,7 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
}, },
amendFileExists: false, amendFileExists: false,
messageFileExists: true, messageFileExists: true,
expectedResult: models.NewCommit(models.NewCommitOpts{ expectedResult: models.NewCommit(hashPool, models.NewCommitOpts{
Hash: "fa1afe1", Hash: "fa1afe1",
Action: todo.Edit, Action: todo.Edit,
Status: models.StatusConflicted, Status: models.StatusConflicted,
@ -526,7 +531,7 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
}, },
} }
hash := builder.getConflictedCommitImpl(scenario.todos, scenario.doneTodos, scenario.amendFileExists, scenario.messageFileExists) hash := builder.getConflictedCommitImpl(hashPool, scenario.todos, scenario.doneTodos, scenario.amendFileExists, scenario.messageFileExists)
assert.Equal(t, scenario.expectedResult, hash) assert.Equal(t, scenario.expectedResult, hash)
}) })
} }
@ -573,11 +578,13 @@ func TestCommitLoader_setCommitMergedStatuses(t *testing.T) {
for _, scenario := range scenarios { for _, scenario := range scenarios {
t.Run(scenario.testName, func(t *testing.T) { t.Run(scenario.testName, func(t *testing.T) {
hashPool := &utils.StringPool{}
commits := lo.Map(scenario.commitOpts, commits := lo.Map(scenario.commitOpts,
func(opts models.NewCommitOpts, _ int) *models.Commit { return models.NewCommit(opts) }) func(opts models.NewCommitOpts, _ int) *models.Commit { return models.NewCommit(hashPool, opts) })
setCommitMergedStatuses(scenario.ancestor, commits) setCommitMergedStatuses(scenario.ancestor, commits)
expectedCommits := lo.Map(scenario.expectedCommitOpts, expectedCommits := lo.Map(scenario.expectedCommitOpts,
func(opts models.NewCommitOpts, _ int) *models.Commit { return models.NewCommit(opts) }) func(opts models.NewCommitOpts, _ int) *models.Commit { return models.NewCommit(hashPool, opts) })
assert.Equal(t, expectedCommits, commits) assert.Equal(t, expectedCommits, commits)
}) })
} }

View file

@ -10,6 +10,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/git_config" "github.com/jesseduffield/lazygit/pkg/commands/git_config"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo" "github.com/samber/lo"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -158,8 +159,9 @@ func TestRebaseDiscardOldFileChanges(t *testing.T) {
gitConfig: git_config.NewFakeGitConfig(s.gitConfigMockResponses), gitConfig: git_config.NewFakeGitConfig(s.gitConfigMockResponses),
}) })
hashPool := &utils.StringPool{}
commits := lo.Map(s.commitOpts, commits := lo.Map(s.commitOpts,
func(opts models.NewCommitOpts, _ int) *models.Commit { return models.NewCommit(opts) }) func(opts models.NewCommitOpts, _ int) *models.Commit { return models.NewCommit(hashPool, opts) })
s.test(instance.DiscardOldFileChanges(commits, s.commitIndex, s.fileName)) s.test(instance.DiscardOldFileChanges(commits, s.commitIndex, s.fileName))
s.runner.CheckForMissingCalls() s.runner.CheckForMissingCalls()

View file

@ -7,6 +7,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/utils"
) )
type ReflogCommitLoader struct { type ReflogCommitLoader struct {
@ -23,7 +24,7 @@ func NewReflogCommitLoader(common *common.Common, cmd oscommands.ICmdObjBuilder)
// GetReflogCommits only returns the new reflog commits since the given lastReflogCommit // GetReflogCommits only returns the new reflog commits since the given lastReflogCommit
// if none is passed (i.e. it's value is nil) then we get all the reflog commits // if none is passed (i.e. it's value is nil) then we get all the reflog commits
func (self *ReflogCommitLoader) GetReflogCommits(lastReflogCommit *models.Commit, filterPath string, filterAuthor string) ([]*models.Commit, bool, error) { func (self *ReflogCommitLoader) GetReflogCommits(hashPool *utils.StringPool, lastReflogCommit *models.Commit, filterPath string, filterAuthor string) ([]*models.Commit, bool, error) {
commits := make([]*models.Commit, 0) commits := make([]*models.Commit, 0)
cmdArgs := NewGitCmd("log"). cmdArgs := NewGitCmd("log").
@ -39,7 +40,7 @@ func (self *ReflogCommitLoader) GetReflogCommits(lastReflogCommit *models.Commit
onlyObtainedNewReflogCommits := false onlyObtainedNewReflogCommits := false
err := cmdObj.RunAndProcessLines(func(line string) (bool, error) { err := cmdObj.RunAndProcessLines(func(line string) (bool, error) {
commit, ok := self.parseLine(line) commit, ok := self.parseLine(hashPool, line)
if !ok { if !ok {
return false, nil return false, nil
} }
@ -68,7 +69,7 @@ func (self *ReflogCommitLoader) sameReflogCommit(a *models.Commit, b *models.Com
return a.Hash() == b.Hash() && a.UnixTimestamp == b.UnixTimestamp && a.Name == b.Name return a.Hash() == b.Hash() && a.UnixTimestamp == b.UnixTimestamp && a.Name == b.Name
} }
func (self *ReflogCommitLoader) parseLine(line string) (*models.Commit, bool) { func (self *ReflogCommitLoader) parseLine(hashPool *utils.StringPool, line string) (*models.Commit, bool) {
fields := strings.SplitN(line, "\x00", 4) fields := strings.SplitN(line, "\x00", 4)
if len(fields) <= 3 { if len(fields) <= 3 {
return nil, false return nil, false
@ -82,7 +83,7 @@ func (self *ReflogCommitLoader) parseLine(line string) (*models.Commit, bool) {
parents = strings.Split(parentHashes, " ") parents = strings.Split(parentHashes, " ")
} }
return models.NewCommit(models.NewCommitOpts{ return models.NewCommit(hashPool, models.NewCommitOpts{
Hash: fields[0], Hash: fields[0],
Name: fields[2], Name: fields[2],
UnixTimestamp: int64(unixTimestamp), UnixTimestamp: int64(unixTimestamp),

View file

@ -32,6 +32,8 @@ func TestGetReflogCommits(t *testing.T) {
expectedError error expectedError error
} }
hashPool := &utils.StringPool{}
scenarios := []scenario{ scenarios := []scenario{
{ {
testName: "no reflog entries", testName: "no reflog entries",
@ -94,7 +96,7 @@ func TestGetReflogCommits(t *testing.T) {
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-c", "log.showSignature=false", "log", "-g", "--abbrev=40", "--format=%h%x00%ct%x00%gs%x00%p"}, reflogOutput, nil), ExpectGitArgs([]string{"-c", "log.showSignature=false", "log", "-g", "--abbrev=40", "--format=%h%x00%ct%x00%gs%x00%p"}, reflogOutput, nil),
lastReflogCommit: models.NewCommit(models.NewCommitOpts{ lastReflogCommit: models.NewCommit(hashPool, models.NewCommitOpts{
Hash: "c3c4b66b64c97ffeecde", Hash: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from B to A", Name: "checkout: moving from B to A",
Status: models.StatusReflog, Status: models.StatusReflog,
@ -118,7 +120,7 @@ func TestGetReflogCommits(t *testing.T) {
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-c", "log.showSignature=false", "log", "-g", "--abbrev=40", "--format=%h%x00%ct%x00%gs%x00%p", "--follow", "--", "path"}, reflogOutput, nil), ExpectGitArgs([]string{"-c", "log.showSignature=false", "log", "-g", "--abbrev=40", "--format=%h%x00%ct%x00%gs%x00%p", "--follow", "--", "path"}, reflogOutput, nil),
lastReflogCommit: models.NewCommit(models.NewCommitOpts{ lastReflogCommit: models.NewCommit(hashPool, models.NewCommitOpts{
Hash: "c3c4b66b64c97ffeecde", Hash: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from B to A", Name: "checkout: moving from B to A",
Status: models.StatusReflog, Status: models.StatusReflog,
@ -143,7 +145,7 @@ func TestGetReflogCommits(t *testing.T) {
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-c", "log.showSignature=false", "log", "-g", "--abbrev=40", "--format=%h%x00%ct%x00%gs%x00%p", "--author=John Doe <john@doe.com>"}, reflogOutput, nil), ExpectGitArgs([]string{"-c", "log.showSignature=false", "log", "-g", "--abbrev=40", "--format=%h%x00%ct%x00%gs%x00%p", "--author=John Doe <john@doe.com>"}, reflogOutput, nil),
lastReflogCommit: models.NewCommit(models.NewCommitOpts{ lastReflogCommit: models.NewCommit(hashPool, models.NewCommitOpts{
Hash: "c3c4b66b64c97ffeecde", Hash: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from B to A", Name: "checkout: moving from B to A",
Status: models.StatusReflog, Status: models.StatusReflog,
@ -183,14 +185,14 @@ func TestGetReflogCommits(t *testing.T) {
cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner), cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner),
} }
commits, onlyObtainednew, err := builder.GetReflogCommits(scenario.lastReflogCommit, scenario.filterPath, scenario.filterAuthor) commits, onlyObtainednew, err := builder.GetReflogCommits(hashPool, scenario.lastReflogCommit, scenario.filterPath, scenario.filterAuthor)
assert.Equal(t, scenario.expectedOnlyObtainedNew, onlyObtainednew) assert.Equal(t, scenario.expectedOnlyObtainedNew, onlyObtainednew)
assert.Equal(t, scenario.expectedError, err) assert.Equal(t, scenario.expectedError, err)
t.Logf("actual commits: \n%s", litter.Sdump(commits)) t.Logf("actual commits: \n%s", litter.Sdump(commits))
var expectedCommits []*models.Commit var expectedCommits []*models.Commit
if scenario.expectedCommitOpts != nil { if scenario.expectedCommitOpts != nil {
expectedCommits = lo.Map(scenario.expectedCommitOpts, expectedCommits = lo.Map(scenario.expectedCommitOpts,
func(opts models.NewCommitOpts, _ int) *models.Commit { return models.NewCommit(opts) }) func(opts models.NewCommitOpts, _ int) *models.Commit { return models.NewCommit(hashPool, opts) })
} }
assert.Equal(t, expectedCommits, commits) assert.Equal(t, expectedCommits, commits)

View file

@ -42,7 +42,7 @@ const (
// Commit : A git commit // Commit : A git commit
type Commit struct { type Commit struct {
hash string hash *string
Name string Name string
Status CommitStatus Status CommitStatus
Action todo.TodoCommand Action todo.TodoCommand
@ -71,9 +71,9 @@ type NewCommitOpts struct {
Parents []string Parents []string
} }
func NewCommit(opts NewCommitOpts) *Commit { func NewCommit(hashPool *utils.StringPool, opts NewCommitOpts) *Commit {
return &Commit{ return &Commit{
hash: opts.Hash, hash: hashPool.Add(opts.Hash),
Name: opts.Name, Name: opts.Name,
Status: opts.Status, Status: opts.Status,
Action: opts.Action, Action: opts.Action,
@ -88,7 +88,7 @@ func NewCommit(opts NewCommitOpts) *Commit {
} }
func (c *Commit) Hash() string { func (c *Commit) Hash() string {
return c.hash return *c.hash
} }
func (c *Commit) ShortHash() string { func (c *Commit) ShortHash() string {

View file

@ -332,6 +332,7 @@ func (self *RefreshHelper) refreshCommitsWithLimit() error {
RefForPushedStatus: checkedOutBranchName, RefForPushedStatus: checkedOutBranchName,
All: self.c.Contexts().LocalCommits.GetShowWholeGitGraph(), All: self.c.Contexts().LocalCommits.GetShowWholeGitGraph(),
MainBranches: self.c.Model().MainBranches, MainBranches: self.c.Model().MainBranches,
HashPool: self.c.Model().HashPool,
}, },
) )
if err != nil { if err != nil {
@ -360,6 +361,7 @@ func (self *RefreshHelper) refreshSubCommitsWithLimit() error {
RefToShowDivergenceFrom: self.c.Contexts().SubCommits.GetRefToShowDivergenceFrom(), RefToShowDivergenceFrom: self.c.Contexts().SubCommits.GetRefToShowDivergenceFrom(),
RefForPushedStatus: self.c.Contexts().SubCommits.GetRef().FullRefName(), RefForPushedStatus: self.c.Contexts().SubCommits.GetRef().FullRefName(),
MainBranches: self.c.Model().MainBranches, MainBranches: self.c.Model().MainBranches,
HashPool: self.c.Model().HashPool,
}, },
) )
if err != nil { if err != nil {
@ -406,7 +408,7 @@ func (self *RefreshHelper) refreshRebaseCommits() error {
self.c.Mutexes().LocalCommitsMutex.Lock() self.c.Mutexes().LocalCommitsMutex.Lock()
defer self.c.Mutexes().LocalCommitsMutex.Unlock() defer self.c.Mutexes().LocalCommitsMutex.Unlock()
updatedCommits, err := self.c.Git().Loaders.CommitLoader.MergeRebasingCommits(self.c.Model().Commits) updatedCommits, err := self.c.Git().Loaders.CommitLoader.MergeRebasingCommits(self.c.Model().HashPool, self.c.Model().Commits)
if err != nil { if err != nil {
return err return err
} }
@ -455,7 +457,7 @@ func (self *RefreshHelper) refreshBranches(refreshWorktrees bool, keepBranchSele
// which allows us to order them correctly. So if we're filtering we'll just // which allows us to order them correctly. So if we're filtering we'll just
// manually load all the reflog commits here // manually load all the reflog commits here
var err error var err error
reflogCommits, _, err = self.c.Git().Loaders.ReflogCommitLoader.GetReflogCommits(nil, "", "") reflogCommits, _, err = self.c.Git().Loaders.ReflogCommitLoader.GetReflogCommits(self.c.Model().HashPool, nil, "", "")
if err != nil { if err != nil {
self.c.Log.Error(err) self.c.Log.Error(err)
} }
@ -624,7 +626,7 @@ func (self *RefreshHelper) refreshReflogCommits() error {
refresh := func(stateCommits *[]*models.Commit, filterPath string, filterAuthor string) error { refresh := func(stateCommits *[]*models.Commit, filterPath string, filterAuthor string) error {
commits, onlyObtainedNewReflogCommits, err := self.c.Git().Loaders.ReflogCommitLoader. commits, onlyObtainedNewReflogCommits, err := self.c.Git().Loaders.ReflogCommitLoader.
GetReflogCommits(lastReflogCommit, filterPath, filterAuthor) GetReflogCommits(self.c.Model().HashPool, lastReflogCommit, filterPath, filterAuthor)
if err != nil { if err != nil {
return err return err
} }

View file

@ -45,6 +45,7 @@ func (self *SubCommitsHelper) ViewSubCommits(opts ViewSubCommitsOpts) error {
RefForPushedStatus: opts.Ref.FullRefName(), RefForPushedStatus: opts.Ref.FullRefName(),
RefToShowDivergenceFrom: opts.RefToShowDivergenceFrom, RefToShowDivergenceFrom: opts.RefToShowDivergenceFrom,
MainBranches: self.c.Model().MainBranches, MainBranches: self.c.Model().MainBranches,
HashPool: self.c.Model().HashPool,
}, },
) )
if err != nil { if err != nil {

View file

@ -564,7 +564,7 @@ func (self *LocalCommitsController) startInteractiveRebaseWithEdit(
for _, c := range commitsToEdit[:len(commitsToEdit)-1] { for _, c := range commitsToEdit[:len(commitsToEdit)-1] {
// Merge commits can't be set to "edit", so just skip them // Merge commits can't be set to "edit", so just skip them
if !c.IsMerge() { if !c.IsMerge() {
todos = append(todos, models.NewCommit(models.NewCommitOpts{Hash: c.Hash(), Action: todo.Pick})) todos = append(todos, models.NewCommit(self.c.Model().HashPool, models.NewCommitOpts{Hash: c.Hash(), Action: todo.Pick}))
} }
} }
if len(todos) > 0 { if len(todos) > 0 {

View file

@ -558,6 +558,7 @@ func (gui *Gui) resetState(startArgs appTypes.StartArgs) types.Context {
FilesTrie: patricia.NewTrie(), FilesTrie: patricia.NewTrie(),
Authors: map[string]*models.Author{}, Authors: map[string]*models.Author{},
MainBranches: git_commands.NewMainBranches(gui.c.Common, gui.os.Cmd), MainBranches: git_commands.NewMainBranches(gui.c.Common, gui.os.Cmd),
HashPool: &utils.StringPool{},
}, },
Modes: &types.Modes{ Modes: &types.Modes{
Filtering: filtering.New(startArgs.FilterPath, ""), Filtering: filtering.New(startArgs.FilterPath, ""),

View file

@ -544,8 +544,10 @@ func TestGetCommitListDisplayStrings(t *testing.T) {
for _, s := range scenarios { for _, s := range scenarios {
if !focusing || s.focus { if !focusing || s.focus {
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
hashPool := &utils.StringPool{}
commits := lo.Map(s.commitOpts, commits := lo.Map(s.commitOpts,
func(opts models.NewCommitOpts, _ int) *models.Commit { return models.NewCommit(opts) }) func(opts models.NewCommitOpts, _ int) *models.Commit { return models.NewCommit(hashPool, opts) })
result := GetCommitListDisplayStrings( result := GetCommitListDisplayStrings(
common, common,

View file

@ -149,8 +149,10 @@ func TestRenderCommitFileTree(t *testing.T) {
for _, s := range scenarios { for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) { t.Run(s.name, func(t *testing.T) {
hashPool := &utils.StringPool{}
viewModel := filetree.NewCommitFileTreeViewModel(func() []*models.CommitFile { return s.files }, utils.NewDummyLog(), true) viewModel := filetree.NewCommitFileTreeViewModel(func() []*models.CommitFile { return s.files }, utils.NewDummyLog(), true)
viewModel.SetRef(&models.Commit{}) viewModel.SetRef(models.NewCommit(hashPool, models.NewCommitOpts{Hash: "1234"}))
viewModel.SetTree() viewModel.SetTree()
for _, path := range s.collapsedPaths { for _, path := range s.collapsedPaths {
viewModel.ToggleCollapsed(path) viewModel.ToggleCollapsed(path)

View file

@ -219,9 +219,11 @@ func TestRenderCommitGraph(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
hashPool := &utils.StringPool{}
getStyle := func(c *models.Commit) style.TextStyle { return style.FgDefault } getStyle := func(c *models.Commit) style.TextStyle { return style.FgDefault }
commits := lo.Map(test.commitOpts, commits := lo.Map(test.commitOpts,
func(opts models.NewCommitOpts, _ int) *models.Commit { return models.NewCommit(opts) }) func(opts models.NewCommitOpts, _ int) *models.Commit { return models.NewCommit(hashPool, opts) })
lines := RenderCommitGraph(commits, "blah", getStyle) lines := RenderCommitGraph(commits, "blah", getStyle)
trimmedExpectedOutput := "" trimmedExpectedOutput := ""
@ -254,6 +256,8 @@ func TestRenderPipeSet(t *testing.T) {
magenta := style.FgMagenta magenta := style.FgMagenta
nothing := style.Nothing nothing := style.Nothing
hashPool := &utils.StringPool{}
tests := []struct { tests := []struct {
name string name string
pipes []*Pipe pipes []*Pipe
@ -268,7 +272,7 @@ func TestRenderPipeSet(t *testing.T) {
{fromPos: 0, toPos: 0, fromHash: "a", toHash: "b", kind: TERMINATES, style: cyan}, {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: "b", toHash: "c", kind: STARTS, style: green},
}, },
prevCommit: models.NewCommit(models.NewCommitOpts{Hash: "a"}), prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a"}),
expectedStr: "◯", expectedStr: "◯",
expectedStyles: []style.TextStyle{green}, expectedStyles: []style.TextStyle{green},
}, },
@ -278,7 +282,7 @@ func TestRenderPipeSet(t *testing.T) {
{fromPos: 0, toPos: 0, fromHash: "a", toHash: "selected", kind: TERMINATES, style: cyan}, {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: "selected", toHash: "c", kind: STARTS, style: green},
}, },
prevCommit: models.NewCommit(models.NewCommitOpts{Hash: "a"}), prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a"}),
expectedStr: "◯", expectedStr: "◯",
expectedStyles: []style.TextStyle{highlightStyle}, expectedStyles: []style.TextStyle{highlightStyle},
}, },
@ -290,7 +294,7 @@ func TestRenderPipeSet(t *testing.T) {
{fromPos: 0, toPos: 0, fromHash: "selected", toHash: "d", kind: STARTS, style: green}, {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: 1, fromHash: "selected", toHash: "e", kind: STARTS, style: green},
}, },
prevCommit: models.NewCommit(models.NewCommitOpts{Hash: "a"}), prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a"}),
expectedStr: "⏣─╮", expectedStr: "⏣─╮",
expectedStyles: []style.TextStyle{ expectedStyles: []style.TextStyle{
highlightStyle, highlightStyle, highlightStyle, highlightStyle, highlightStyle, highlightStyle,
@ -304,7 +308,7 @@ func TestRenderPipeSet(t *testing.T) {
{fromPos: 0, toPos: 0, fromHash: "b", toHash: "d", kind: STARTS, style: green}, {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: 1, fromHash: "b", toHash: "e", kind: STARTS, style: green},
}, },
prevCommit: models.NewCommit(models.NewCommitOpts{Hash: "a"}), prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a"}),
expectedStr: "⏣─│", expectedStr: "⏣─│",
expectedStyles: []style.TextStyle{ expectedStyles: []style.TextStyle{
green, green, magenta, green, green, magenta,
@ -319,7 +323,7 @@ func TestRenderPipeSet(t *testing.T) {
{fromPos: 3, toPos: 0, fromHash: "e1", toHash: "a2", kind: TERMINATES, style: green}, {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: 2, fromHash: "a2", toHash: "c3", kind: STARTS, style: yellow},
}, },
prevCommit: models.NewCommit(models.NewCommitOpts{Hash: "a1"}), prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a1"}),
expectedStr: "⏣─│─┬─╯", expectedStr: "⏣─│─┬─╯",
expectedStyles: []style.TextStyle{ expectedStyles: []style.TextStyle{
yellow, yellow, magenta, yellow, yellow, green, green, yellow, yellow, magenta, yellow, yellow, green, green,
@ -334,7 +338,7 @@ func TestRenderPipeSet(t *testing.T) {
{fromPos: 3, toPos: 0, fromHash: "e1", toHash: "selected", kind: TERMINATES, style: green}, {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: 2, fromHash: "selected", toHash: "c3", kind: STARTS, style: yellow},
}, },
prevCommit: models.NewCommit(models.NewCommitOpts{Hash: "a1"}), prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a1"}),
expectedStr: "⏣───╮ ╯", expectedStr: "⏣───╮ ╯",
expectedStyles: []style.TextStyle{ expectedStyles: []style.TextStyle{
highlightStyle, highlightStyle, highlightStyle, highlightStyle, highlightStyle, nothing, green, highlightStyle, highlightStyle, highlightStyle, highlightStyle, highlightStyle, nothing, green,
@ -348,7 +352,7 @@ func TestRenderPipeSet(t *testing.T) {
{fromPos: 1, toPos: 0, fromHash: "b1", toHash: "a2", kind: TERMINATES, style: magenta}, {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: 2, toPos: 0, fromHash: "c1", toHash: "a2", kind: TERMINATES, style: green},
}, },
prevCommit: models.NewCommit(models.NewCommitOpts{Hash: "a1"}), prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a1"}),
expectedStr: "◯─┴─╯", expectedStr: "◯─┴─╯",
expectedStyles: []style.TextStyle{ expectedStyles: []style.TextStyle{
yellow, magenta, magenta, green, green, yellow, magenta, magenta, green, green,
@ -363,7 +367,7 @@ func TestRenderPipeSet(t *testing.T) {
{fromPos: 1, toPos: 1, fromHash: "b1", toHash: "b3", kind: CONTINUES, style: magenta}, {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: 2, toPos: 2, fromHash: "c1", toHash: "c3", kind: CONTINUES, style: green},
}, },
prevCommit: models.NewCommit(models.NewCommitOpts{Hash: "a1"}), prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a1"}),
expectedStr: "⏣─│─│─╮", expectedStr: "⏣─│─│─╮",
expectedStyles: []style.TextStyle{ expectedStyles: []style.TextStyle{
yellow, yellow, magenta, yellow, green, yellow, yellow, yellow, yellow, magenta, yellow, green, yellow, yellow,
@ -378,7 +382,7 @@ func TestRenderPipeSet(t *testing.T) {
{fromPos: 1, toPos: 1, fromHash: "b1", toHash: "a2", kind: CONTINUES, style: green}, {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: 2, toPos: 0, fromHash: "c1", toHash: "a2", kind: TERMINATES, style: magenta},
}, },
prevCommit: models.NewCommit(models.NewCommitOpts{Hash: "a1"}), prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a1"}),
expectedStr: "⏣─│─╯", expectedStr: "⏣─│─╯",
expectedStyles: []style.TextStyle{ expectedStyles: []style.TextStyle{
yellow, yellow, green, magenta, magenta, yellow, yellow, green, magenta, magenta,
@ -393,7 +397,7 @@ func TestRenderPipeSet(t *testing.T) {
{fromPos: 2, toPos: 2, fromHash: "c1", toHash: "c3", kind: CONTINUES, style: green}, {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: 3, toPos: 0, fromHash: "d1", toHash: "a2", kind: TERMINATES, style: magenta},
}, },
prevCommit: models.NewCommit(models.NewCommitOpts{Hash: "a1"}), prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a1"}),
expectedStr: "⏣─┬─│─╯", expectedStr: "⏣─┬─│─╯",
expectedStyles: []style.TextStyle{ expectedStyles: []style.TextStyle{
yellow, yellow, yellow, magenta, green, magenta, magenta, yellow, yellow, yellow, magenta, green, magenta, magenta,
@ -405,7 +409,7 @@ func TestRenderPipeSet(t *testing.T) {
{fromPos: 0, toPos: 0, fromHash: "selected", toHash: "a2", kind: TERMINATES, style: red}, {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: "a2", toHash: "a3", kind: STARTS, style: yellow},
}, },
prevCommit: models.NewCommit(models.NewCommitOpts{Hash: "selected"}), prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "selected"}),
expectedStr: "◯", expectedStr: "◯",
expectedStyles: []style.TextStyle{ expectedStyles: []style.TextStyle{
yellow, yellow,
@ -417,7 +421,7 @@ func TestRenderPipeSet(t *testing.T) {
{fromPos: 0, toPos: 0, fromHash: "selected", toHash: "a2", kind: TERMINATES, style: red}, {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: 1, toPos: 1, fromHash: "selected", toHash: "b3", kind: CONTINUES, style: red},
}, },
prevCommit: models.NewCommit(models.NewCommitOpts{Hash: "selected"}), prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "selected"}),
expectedStr: "◯ │", expectedStr: "◯ │",
expectedStyles: []style.TextStyle{ expectedStyles: []style.TextStyle{
highlightStyle, nothing, highlightStyle, highlightStyle, nothing, highlightStyle,
@ -430,7 +434,7 @@ func TestRenderPipeSet(t *testing.T) {
{fromPos: 1, toPos: 1, fromHash: "z1", toHash: "z3", kind: CONTINUES, style: green}, {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: 2, toPos: 2, fromHash: "selected", toHash: "b3", kind: CONTINUES, style: red},
}, },
prevCommit: models.NewCommit(models.NewCommitOpts{Hash: "selected"}), prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "selected"}),
expectedStr: "◯ │ │", expectedStr: "◯ │ │",
expectedStyles: []style.TextStyle{ expectedStyles: []style.TextStyle{
highlightStyle, nothing, green, nothing, highlightStyle, highlightStyle, nothing, green, nothing, highlightStyle,
@ -444,7 +448,7 @@ func TestRenderPipeSet(t *testing.T) {
{fromPos: 0, toPos: 1, fromHash: "a2", toHash: "b3", 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: 1, toPos: 0, fromHash: "selected", toHash: "a2", kind: TERMINATES, style: yellow},
}, },
prevCommit: models.NewCommit(models.NewCommitOpts{Hash: "selected"}), prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "selected"}),
expectedStr: "⏣─╯", expectedStr: "⏣─╯",
expectedStyles: []style.TextStyle{ expectedStyles: []style.TextStyle{
highlightStyle, highlightStyle, highlightStyle, highlightStyle, highlightStyle, highlightStyle,
@ -477,6 +481,8 @@ func TestRenderPipeSet(t *testing.T) {
} }
func TestGetNextPipes(t *testing.T) { func TestGetNextPipes(t *testing.T) {
hashPool := &utils.StringPool{}
tests := []struct { tests := []struct {
prevPipes []*Pipe prevPipes []*Pipe
commit *models.Commit commit *models.Commit
@ -486,7 +492,7 @@ func TestGetNextPipes(t *testing.T) {
prevPipes: []*Pipe{ prevPipes: []*Pipe{
{fromPos: 0, toPos: 0, fromHash: "a", toHash: "b", kind: STARTS, style: style.FgDefault}, {fromPos: 0, toPos: 0, fromHash: "a", toHash: "b", kind: STARTS, style: style.FgDefault},
}, },
commit: models.NewCommit(models.NewCommitOpts{ commit: models.NewCommit(hashPool, models.NewCommitOpts{
Hash: "b", Hash: "b",
Parents: []string{"c"}, Parents: []string{"c"},
}), }),
@ -501,7 +507,7 @@ func TestGetNextPipes(t *testing.T) {
{fromPos: 0, toPos: 0, fromHash: "b", toHash: "c", kind: STARTS, 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: 1, fromHash: "b", toHash: "d", kind: STARTS, style: style.FgDefault},
}, },
commit: models.NewCommit(models.NewCommitOpts{ commit: models.NewCommit(hashPool, models.NewCommitOpts{
Hash: "d", Hash: "d",
Parents: []string{"e"}, Parents: []string{"e"},
}), }),
@ -515,7 +521,7 @@ func TestGetNextPipes(t *testing.T) {
prevPipes: []*Pipe{ prevPipes: []*Pipe{
{fromPos: 0, toPos: 0, fromHash: "a", toHash: "root", kind: TERMINATES, style: style.FgDefault}, {fromPos: 0, toPos: 0, fromHash: "a", toHash: "root", kind: TERMINATES, style: style.FgDefault},
}, },
commit: models.NewCommit(models.NewCommitOpts{ commit: models.NewCommit(hashPool, models.NewCommitOpts{
Hash: "root", Hash: "root",
Parents: []string{}, Parents: []string{},
}), }),
@ -557,8 +563,10 @@ func BenchmarkRenderCommitGraph(b *testing.B) {
} }
func generateCommits(count int) []*models.Commit { func generateCommits(count int) []*models.Commit {
hashPool := &utils.StringPool{}
rnd := rand.New(rand.NewSource(1234)) rnd := rand.New(rand.NewSource(1234))
pool := []*models.Commit{models.NewCommit(models.NewCommitOpts{Hash: "a", AuthorName: "A"})} pool := []*models.Commit{models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a", AuthorName: "A"})}
commits := make([]*models.Commit, 0, count) commits := make([]*models.Commit, 0, count)
authorPool := []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"} authorPool := []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}
for len(commits) < count { for len(commits) < count {
@ -575,7 +583,7 @@ func generateCommits(count int) []*models.Commit {
if reuseParent { if reuseParent {
newParent = pool[j] newParent = pool[j]
} else { } else {
newParent = models.NewCommit(models.NewCommitOpts{ newParent = models.NewCommit(hashPool, models.NewCommitOpts{
Hash: fmt.Sprintf("%s%d", currentCommit.Hash(), j), Hash: fmt.Sprintf("%s%d", currentCommit.Hash(), j),
AuthorName: authorPool[rnd.Intn(len(authorPool))], AuthorName: authorPool[rnd.Intn(len(authorPool))],
}) })
@ -584,7 +592,7 @@ func generateCommits(count int) []*models.Commit {
parentHashes = append(parentHashes, newParent.Hash()) parentHashes = append(parentHashes, newParent.Hash())
} }
changedCommit := models.NewCommit(models.NewCommitOpts{ changedCommit := models.NewCommit(hashPool, models.NewCommitOpts{
Hash: currentCommit.Hash(), Hash: currentCommit.Hash(),
AuthorName: currentCommit.AuthorName, AuthorName: currentCommit.AuthorName,
Parents: parentHashes, Parents: parentHashes,

View file

@ -305,6 +305,8 @@ type Model struct {
FilesTrie *patricia.Trie FilesTrie *patricia.Trie
Authors map[string]*models.Author Authors map[string]*models.Author
HashPool *utils.StringPool
} }
// if you add a new mutex here be sure to instantiate it. We're using pointers to // if you add a new mutex here be sure to instantiate it. We're using pointers to

14
pkg/utils/string_pool.go Normal file
View file

@ -0,0 +1,14 @@
package utils
import "sync"
// A simple string pool implementation that can help reduce memory usage for
// cases where the same string is used multiple times.
type StringPool struct {
sync.Map
}
func (self *StringPool) Add(s string) *string {
poolEntry, _ := self.LoadOrStore(s, &s)
return poolEntry.(*string)
}