Skip to content

Commit

Permalink
Enable the commit graph in the divergence view (#3537)
Browse files Browse the repository at this point in the history
- **PR Description**

In the "View divergence from upstream" view we have so far disabled the
commit graph because it was too difficult to implement properly. I
really miss it though, so here's a PR that enables it there, too.

For feature branches it is not essential, because these usually don't
contain merges and the graph is a trivial line. However, for the master
branch against its upstream it is useful too see how many PRs were
merged since you last fetched it, and the graph helps a lot with that.
Also, when we implement #3536 it will be very useful there, too.
  • Loading branch information
stefanhaller committed Apr 30, 2024
2 parents 00c55d5 + b767357 commit af0897f
Show file tree
Hide file tree
Showing 3 changed files with 252 additions and 19 deletions.
2 changes: 1 addition & 1 deletion pkg/gui/context/sub_commits_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func NewSubCommitsContext(
endIdx,
// Don't show the graph in the left/right view; we'd like to, but
// it's too complicated:
shouldShowGraph(c) && viewModel.GetRefToShowDivergenceFrom() == "",
shouldShowGraph(c),
git_commands.NewNullBisectInfo(),
false,
)
Expand Down
90 changes: 72 additions & 18 deletions pkg/gui/presentation/commits.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
type pipeSetCacheKey struct {
commitHash string
commitCount int
divergence models.Divergence
}

var (
Expand Down Expand Up @@ -78,24 +79,76 @@ func GetCommitListDisplayStrings(
// function expects to be passed the index of the commit in terms of the `commits` slice
var getGraphLine func(int) string
if showGraph {
// this is where the graph begins (may be beyond the TODO commits depending on startIdx,
// but we'll never include TODO commits as part of the graph because it'll be messy)
graphOffset := max(startIdx, rebaseOffset)

pipeSets := loadPipesets(commits[rebaseOffset:])
pipeSetOffset := max(startIdx-rebaseOffset, 0)
graphPipeSets := pipeSets[pipeSetOffset:max(endIdx-rebaseOffset, 0)]
graphCommits := commits[graphOffset:endIdx]
graphLines := graph.RenderAux(
graphPipeSets,
graphCommits,
selectedCommitHash,
)
getGraphLine = func(idx int) string {
if idx >= graphOffset {
return graphLines[idx-graphOffset]
} else {
return ""
if len(commits) > 0 && commits[0].Divergence != models.DivergenceNone {
// Showing a divergence log; we know we don't have any rebasing
// commits in this case. But we need to render separate graphs for
// the Local and Remote sections.
allGraphLines := []string{}

_, localSectionStart, found := lo.FindIndexOf(
commits, func(c *models.Commit) bool { return c.Divergence == models.DivergenceLeft })
if !found {
localSectionStart = len(commits)
}

if localSectionStart > 0 {
// we have some remote commits
pipeSets := loadPipesets(commits[:localSectionStart])
if startIdx < localSectionStart {
// some of the remote commits are visible
start := startIdx
end := min(endIdx, localSectionStart)
graphPipeSets := pipeSets[start:end]
graphCommits := commits[start:end]
graphLines := graph.RenderAux(
graphPipeSets,
graphCommits,
selectedCommitHash,
)
allGraphLines = append(allGraphLines, graphLines...)
}
}
if localSectionStart < len(commits) {
// we have some local commits
pipeSets := loadPipesets(commits[localSectionStart:])
if localSectionStart < endIdx {
// some of the local commits are visible
graphOffset := max(startIdx, localSectionStart)
pipeSetOffset := max(startIdx-localSectionStart, 0)
graphPipeSets := pipeSets[pipeSetOffset : endIdx-localSectionStart]
graphCommits := commits[graphOffset:endIdx]
graphLines := graph.RenderAux(
graphPipeSets,
graphCommits,
selectedCommitHash,
)
allGraphLines = append(allGraphLines, graphLines...)
}
}

getGraphLine = func(idx int) string {
return allGraphLines[idx-startIdx]
}
} else {
// this is where the graph begins (may be beyond the TODO commits depending on startIdx,
// but we'll never include TODO commits as part of the graph because it'll be messy)
graphOffset := max(startIdx, rebaseOffset)

pipeSets := loadPipesets(commits[rebaseOffset:])
pipeSetOffset := max(startIdx-rebaseOffset, 0)
graphPipeSets := pipeSets[pipeSetOffset:max(endIdx-rebaseOffset, 0)]
graphCommits := commits[graphOffset:endIdx]
graphLines := graph.RenderAux(
graphPipeSets,
graphCommits,
selectedCommitHash,
)
getGraphLine = func(idx int) string {
if idx >= graphOffset {
return graphLines[idx-graphOffset]
} else {
return ""
}
}
}
} else {
Expand Down Expand Up @@ -205,6 +258,7 @@ func loadPipesets(commits []*models.Commit) [][]*graph.Pipe {
cacheKey := pipeSetCacheKey{
commitHash: commits[0].Hash,
commitCount: len(commits),
divergence: commits[0].Divergence,
}

pipeSets, ok := pipeSetCache[cacheKey]
Expand Down
179 changes: 179 additions & 0 deletions pkg/gui/presentation/commits_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,185 @@ func TestGetCommitListDisplayStrings(t *testing.T) {
hash3 ◯ commit3
`),
},
{
testName: "graph in divergence view - all commits visible",
commits: []*models.Commit{
{Name: "commit1", Hash: "hash1r", Parents: []string{"hash2r"}, Divergence: models.DivergenceRight},
{Name: "commit2", Hash: "hash2r", Parents: []string{"hash3r", "hash5r"}, Divergence: models.DivergenceRight},
{Name: "commit3", Hash: "hash3r", Parents: []string{"hash4r"}, Divergence: models.DivergenceRight},
{Name: "commit1", Hash: "hash1l", Parents: []string{"hash2l"}, Divergence: models.DivergenceLeft},
{Name: "commit2", Hash: "hash2l", Parents: []string{"hash3l", "hash4l"}, Divergence: models.DivergenceLeft},
{Name: "commit3", Hash: "hash3l", Parents: []string{"hash4l"}, Divergence: models.DivergenceLeft},
{Name: "commit4", Hash: "hash4l", Parents: []string{"hash5l"}, Divergence: models.DivergenceLeft},
{Name: "commit5", Hash: "hash5l", Parents: []string{"hash6l"}, Divergence: models.DivergenceLeft},
},
startIdx: 0,
endIdx: 8,
showGraph: true,
bisectInfo: git_commands.NewNullBisectInfo(),
cherryPickedCommitHashSet: set.New[string](),
showYouAreHereLabel: false,
now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
expected: formatExpected(`
↓ hash1r ◯ commit1
↓ hash2r ⏣─╮ commit2
↓ hash3r ◯ │ commit3
↑ hash1l ◯ commit1
↑ hash2l ⏣─╮ commit2
↑ hash3l ◯ │ commit3
↑ hash4l ◯─╯ commit4
↑ hash5l ◯ commit5
`),
},
{
testName: "graph in divergence view - not all remote commits visible",
commits: []*models.Commit{
{Name: "commit1", Hash: "hash1r", Parents: []string{"hash2r"}, Divergence: models.DivergenceRight},
{Name: "commit2", Hash: "hash2r", Parents: []string{"hash3r", "hash5r"}, Divergence: models.DivergenceRight},
{Name: "commit3", Hash: "hash3r", Parents: []string{"hash4r"}, Divergence: models.DivergenceRight},
{Name: "commit1", Hash: "hash1l", Parents: []string{"hash2l"}, Divergence: models.DivergenceLeft},
{Name: "commit2", Hash: "hash2l", Parents: []string{"hash3l", "hash4l"}, Divergence: models.DivergenceLeft},
{Name: "commit3", Hash: "hash3l", Parents: []string{"hash4l"}, Divergence: models.DivergenceLeft},
{Name: "commit4", Hash: "hash4l", Parents: []string{"hash5l"}, Divergence: models.DivergenceLeft},
{Name: "commit5", Hash: "hash5l", Parents: []string{"hash6l"}, Divergence: models.DivergenceLeft},
},
startIdx: 2,
endIdx: 8,
showGraph: true,
bisectInfo: git_commands.NewNullBisectInfo(),
cherryPickedCommitHashSet: set.New[string](),
showYouAreHereLabel: false,
now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
expected: formatExpected(`
↓ hash3r ◯ │ commit3
↑ hash1l ◯ commit1
↑ hash2l ⏣─╮ commit2
↑ hash3l ◯ │ commit3
↑ hash4l ◯─╯ commit4
↑ hash5l ◯ commit5
`),
},
{
testName: "graph in divergence view - not all local commits",
commits: []*models.Commit{
{Name: "commit1", Hash: "hash1r", Parents: []string{"hash2r"}, Divergence: models.DivergenceRight},
{Name: "commit2", Hash: "hash2r", Parents: []string{"hash3r", "hash5r"}, Divergence: models.DivergenceRight},
{Name: "commit3", Hash: "hash3r", Parents: []string{"hash4r"}, Divergence: models.DivergenceRight},
{Name: "commit1", Hash: "hash1l", Parents: []string{"hash2l"}, Divergence: models.DivergenceLeft},
{Name: "commit2", Hash: "hash2l", Parents: []string{"hash3l", "hash4l"}, Divergence: models.DivergenceLeft},
{Name: "commit3", Hash: "hash3l", Parents: []string{"hash4l"}, Divergence: models.DivergenceLeft},
{Name: "commit4", Hash: "hash4l", Parents: []string{"hash5l"}, Divergence: models.DivergenceLeft},
{Name: "commit5", Hash: "hash5l", Parents: []string{"hash6l"}, Divergence: models.DivergenceLeft},
},
startIdx: 0,
endIdx: 5,
showGraph: true,
bisectInfo: git_commands.NewNullBisectInfo(),
cherryPickedCommitHashSet: set.New[string](),
showYouAreHereLabel: false,
now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
expected: formatExpected(`
↓ hash1r ◯ commit1
↓ hash2r ⏣─╮ commit2
↓ hash3r ◯ │ commit3
↑ hash1l ◯ commit1
↑ hash2l ⏣─╮ commit2
`),
},
{
testName: "graph in divergence view - no remote commits visible",
commits: []*models.Commit{
{Name: "commit1", Hash: "hash1r", Parents: []string{"hash2r"}, Divergence: models.DivergenceRight},
{Name: "commit2", Hash: "hash2r", Parents: []string{"hash3r", "hash5r"}, Divergence: models.DivergenceRight},
{Name: "commit3", Hash: "hash3r", Parents: []string{"hash4r"}, Divergence: models.DivergenceRight},
{Name: "commit1", Hash: "hash1l", Parents: []string{"hash2l"}, Divergence: models.DivergenceLeft},
{Name: "commit2", Hash: "hash2l", Parents: []string{"hash3l", "hash4l"}, Divergence: models.DivergenceLeft},
{Name: "commit3", Hash: "hash3l", Parents: []string{"hash4l"}, Divergence: models.DivergenceLeft},
{Name: "commit4", Hash: "hash4l", Parents: []string{"hash5l"}, Divergence: models.DivergenceLeft},
{Name: "commit5", Hash: "hash5l", Parents: []string{"hash6l"}, Divergence: models.DivergenceLeft},
},
startIdx: 4,
endIdx: 8,
showGraph: true,
bisectInfo: git_commands.NewNullBisectInfo(),
cherryPickedCommitHashSet: set.New[string](),
showYouAreHereLabel: false,
now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
expected: formatExpected(`
↑ hash2l ⏣─╮ commit2
↑ hash3l ◯ │ commit3
↑ hash4l ◯─╯ commit4
↑ hash5l ◯ commit5
`),
},
{
testName: "graph in divergence view - no local commits visible",
commits: []*models.Commit{
{Name: "commit1", Hash: "hash1r", Parents: []string{"hash2r"}, Divergence: models.DivergenceRight},
{Name: "commit2", Hash: "hash2r", Parents: []string{"hash3r", "hash5r"}, Divergence: models.DivergenceRight},
{Name: "commit3", Hash: "hash3r", Parents: []string{"hash4r"}, Divergence: models.DivergenceRight},
{Name: "commit1", Hash: "hash1l", Parents: []string{"hash2l"}, Divergence: models.DivergenceLeft},
{Name: "commit2", Hash: "hash2l", Parents: []string{"hash3l", "hash4l"}, Divergence: models.DivergenceLeft},
{Name: "commit3", Hash: "hash3l", Parents: []string{"hash4l"}, Divergence: models.DivergenceLeft},
{Name: "commit4", Hash: "hash4l", Parents: []string{"hash5l"}, Divergence: models.DivergenceLeft},
{Name: "commit5", Hash: "hash5l", Parents: []string{"hash6l"}, Divergence: models.DivergenceLeft},
},
startIdx: 0,
endIdx: 2,
showGraph: true,
bisectInfo: git_commands.NewNullBisectInfo(),
cherryPickedCommitHashSet: set.New[string](),
showYouAreHereLabel: false,
now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
expected: formatExpected(`
↓ hash1r ◯ commit1
↓ hash2r ⏣─╮ commit2
`),
},
{
testName: "graph in divergence view - no remote commits present",
commits: []*models.Commit{
{Name: "commit1", Hash: "hash1l", Parents: []string{"hash2l"}, Divergence: models.DivergenceLeft},
{Name: "commit2", Hash: "hash2l", Parents: []string{"hash3l", "hash4l"}, Divergence: models.DivergenceLeft},
{Name: "commit3", Hash: "hash3l", Parents: []string{"hash4l"}, Divergence: models.DivergenceLeft},
{Name: "commit4", Hash: "hash4l", Parents: []string{"hash5l"}, Divergence: models.DivergenceLeft},
{Name: "commit5", Hash: "hash5l", Parents: []string{"hash6l"}, Divergence: models.DivergenceLeft},
},
startIdx: 0,
endIdx: 5,
showGraph: true,
bisectInfo: git_commands.NewNullBisectInfo(),
cherryPickedCommitHashSet: set.New[string](),
showYouAreHereLabel: false,
now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
expected: formatExpected(`
↑ hash1l ◯ commit1
↑ hash2l ⏣─╮ commit2
↑ hash3l ◯ │ commit3
↑ hash4l ◯─╯ commit4
↑ hash5l ◯ commit5
`),
},
{
testName: "graph in divergence view - no local commits present",
commits: []*models.Commit{
{Name: "commit1", Hash: "hash1r", Parents: []string{"hash2r"}, Divergence: models.DivergenceRight},
{Name: "commit2", Hash: "hash2r", Parents: []string{"hash3r", "hash5r"}, Divergence: models.DivergenceRight},
{Name: "commit3", Hash: "hash3r", Parents: []string{"hash4r"}, Divergence: models.DivergenceRight},
},
startIdx: 0,
endIdx: 3,
showGraph: true,
bisectInfo: git_commands.NewNullBisectInfo(),
cherryPickedCommitHashSet: set.New[string](),
showYouAreHereLabel: false,
now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
expected: formatExpected(`
↓ hash1r ◯ commit1
↓ hash2r ⏣─╮ commit2
↓ hash3r ◯ │ commit3
`),
},
{
testName: "custom time format",
commits: []*models.Commit{
Expand Down

0 comments on commit af0897f

Please sign in to comment.