mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-11 08:06:25 -04:00
When restoring the commit selection after a refresh we match by hash and TODO status. The TODO status is part of the match so that a commit being reverted or cherry-picked is matched to the real commit rather than to the rebase TODO entry that shares its hash. But a selected commit can also change its TODO status across a refresh: when starting an interactive rebase that stops to edit it, the real commit becomes a TODO entry. Fall back to matching by hash alone when there is no exact match, so the selection is still restored in that case. The next commit relies on this to remove bespoke selection-restoration code in the local commits controller that matched by hash alone, which the generic mechanism otherwise wouldn't fully replace.
291 lines
7.6 KiB
Go
291 lines
7.6 KiB
Go
package helpers
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/commands/hosting_service"
|
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/context/traits"
|
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
|
"github.com/samber/lo"
|
|
"github.com/stefanhaller/git-todo-parser/todo"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestCaptureLocalCommitSelectionRange(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
commits []*models.Commit
|
|
selectedIdx int
|
|
rangeStartIdx int
|
|
expected *localCommitSelectionRange
|
|
}{
|
|
{
|
|
name: "captures selected commit and range start",
|
|
commits: makeCommits("a", "b"),
|
|
selectedIdx: 1,
|
|
rangeStartIdx: 0,
|
|
expected: &localCommitSelectionRange{
|
|
selectedHash: "b",
|
|
rangeStartHash: "a",
|
|
selectedIdx: 1,
|
|
rangeStartIdx: 0,
|
|
mode: traits.RangeSelectModeSticky,
|
|
},
|
|
},
|
|
{
|
|
name: "ignores invalid range start index",
|
|
commits: makeCommits("a"),
|
|
selectedIdx: 0,
|
|
rangeStartIdx: 1,
|
|
expected: nil,
|
|
},
|
|
{
|
|
name: "ignores empty selected hash",
|
|
commits: append(makeCommits("a"), makeTodoCommit(todo.UpdateRef)),
|
|
selectedIdx: 1,
|
|
rangeStartIdx: 0,
|
|
expected: nil,
|
|
},
|
|
{
|
|
name: "ignores empty range start hash",
|
|
commits: append(makeCommits("a"), makeTodoCommit(todo.Exec)),
|
|
selectedIdx: 0,
|
|
rangeStartIdx: 1,
|
|
expected: nil,
|
|
},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
selectionRange := captureLocalCommitSelectionRange(
|
|
testCase.commits,
|
|
testCase.selectedIdx,
|
|
testCase.rangeStartIdx,
|
|
traits.RangeSelectModeSticky,
|
|
)
|
|
|
|
assert.Equal(t, testCase.expected, selectionRange)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFindLocalCommitSelectionRange(t *testing.T) {
|
|
type expectation struct {
|
|
selectedIdx int
|
|
rangeStartIdx int
|
|
moved bool
|
|
found bool
|
|
}
|
|
|
|
selectionRange := localCommitSelectionRange{
|
|
selectedHash: "b",
|
|
rangeStartHash: "c",
|
|
selectedIdx: 1,
|
|
rangeStartIdx: 2,
|
|
mode: traits.RangeSelectModeSticky,
|
|
}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
commits []*models.Commit
|
|
expected expectation
|
|
}{
|
|
{
|
|
name: "finds selection after commits are inserted above it",
|
|
commits: makeCommits("new", "a", "b", "c"),
|
|
expected: expectation{
|
|
selectedIdx: 2,
|
|
rangeStartIdx: 3,
|
|
moved: true,
|
|
found: true,
|
|
},
|
|
},
|
|
{
|
|
name: "finds selection that did not move",
|
|
commits: makeCommits("a", "b", "c"),
|
|
expected: expectation{
|
|
selectedIdx: 1,
|
|
rangeStartIdx: 2,
|
|
found: true,
|
|
},
|
|
},
|
|
{
|
|
name: "reports not found when a hash is missing",
|
|
commits: makeCommits("a", "b"),
|
|
expected: expectation{},
|
|
},
|
|
{
|
|
name: "skips todo entries with the same hash as a selected commit",
|
|
commits: []*models.Commit{
|
|
makeTodoCommitWithHash("b", todo.Revert),
|
|
makeCommits("a")[0],
|
|
makeCommits("b")[0],
|
|
makeCommits("c")[0],
|
|
},
|
|
expected: expectation{
|
|
selectedIdx: 2,
|
|
rangeStartIdx: 3,
|
|
moved: true,
|
|
found: true,
|
|
},
|
|
},
|
|
{
|
|
name: "falls back to a todo entry when the selected commit became one",
|
|
commits: []*models.Commit{
|
|
makeTodoCommitWithHash("b", todo.Pick),
|
|
makeCommits("c")[0],
|
|
},
|
|
expected: expectation{
|
|
selectedIdx: 0,
|
|
rangeStartIdx: 1,
|
|
moved: true,
|
|
found: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
selectedIdx, rangeStartIdx, moved, found := findLocalCommitSelectionRange(testCase.commits, &selectionRange)
|
|
actual := expectation{
|
|
selectedIdx: selectedIdx,
|
|
rangeStartIdx: rangeStartIdx,
|
|
moved: moved,
|
|
found: found,
|
|
}
|
|
|
|
assert.Equal(t, testCase.expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetGithubBaseRemote(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
githubRemotes []githubRemoteInfo
|
|
configuredRemote string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "configured remote wins",
|
|
githubRemotes: makeGithubRemoteInfoList("origin", "upstream", "fork"),
|
|
configuredRemote: "fork",
|
|
expected: "fork",
|
|
},
|
|
{
|
|
name: "configured remote not in github remotes returns nil",
|
|
githubRemotes: makeGithubRemoteInfoList("origin"),
|
|
configuredRemote: "missing",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "single github remote is auto-picked",
|
|
githubRemotes: makeGithubRemoteInfoList("myremote"),
|
|
configuredRemote: "",
|
|
expected: "myremote",
|
|
},
|
|
{
|
|
name: "upstream is preferred when multiple github remotes exist",
|
|
githubRemotes: makeGithubRemoteInfoList("origin", "upstream", "fork"),
|
|
configuredRemote: "",
|
|
expected: "upstream",
|
|
},
|
|
{
|
|
name: "no upstream and multiple remotes returns nil",
|
|
githubRemotes: makeGithubRemoteInfoList("origin", "fork"),
|
|
configuredRemote: "",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "empty list returns nil",
|
|
githubRemotes: nil,
|
|
configuredRemote: "",
|
|
expected: "",
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
result := getGithubBaseRemote(c.githubRemotes, c.configuredRemote)
|
|
if c.expected == "" {
|
|
assert.Nil(t, result)
|
|
} else {
|
|
assert.NotNil(t, result)
|
|
assert.Equal(t, c.expected, result.remote.Name)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetAuthenticatedGithubRemotes(t *testing.T) {
|
|
githubRemotes := []githubRemoteInfo{
|
|
makeGithubRemoteInfo("origin", "github.com"),
|
|
makeGithubRemoteInfo("fork", "github.com"),
|
|
makeGithubRemoteInfo("enterprise", "ghe.example.com"),
|
|
makeGithubRemoteInfo("missing-auth", "no-token.example.com"),
|
|
}
|
|
|
|
callsByHost := map[string]int{}
|
|
result := getAuthenticatedGithubRemotes(githubRemotes, func(host string) string {
|
|
callsByHost[host]++
|
|
switch host {
|
|
case "github.com":
|
|
return "github-token"
|
|
case "ghe.example.com":
|
|
return "ghe-token"
|
|
default:
|
|
return ""
|
|
}
|
|
})
|
|
|
|
assert.Equal(t, []githubRemoteInfo{
|
|
makeAuthenticatedGithubRemoteInfo("origin", "github.com", "github-token"),
|
|
makeAuthenticatedGithubRemoteInfo("fork", "github.com", "github-token"),
|
|
makeAuthenticatedGithubRemoteInfo("enterprise", "ghe.example.com", "ghe-token"),
|
|
}, result)
|
|
// Two remotes share github.com; the lookup runs only once.
|
|
assert.Equal(t, map[string]int{
|
|
"github.com": 1,
|
|
"ghe.example.com": 1,
|
|
"no-token.example.com": 1,
|
|
}, callsByHost)
|
|
}
|
|
|
|
func makeGithubRemoteInfoList(names ...string) []githubRemoteInfo {
|
|
return lo.Map(names, func(name string, _ int) githubRemoteInfo {
|
|
return makeGithubRemoteInfo(name, name)
|
|
})
|
|
}
|
|
|
|
func makeGithubRemoteInfo(name string, webDomain string) githubRemoteInfo {
|
|
return githubRemoteInfo{
|
|
remote: &models.Remote{Name: name},
|
|
serviceInfo: hosting_service.ServiceInfo{
|
|
RepoName: name,
|
|
WebDomain: webDomain,
|
|
},
|
|
}
|
|
}
|
|
|
|
func makeAuthenticatedGithubRemoteInfo(name string, webDomain string, authToken string) githubRemoteInfo {
|
|
info := makeGithubRemoteInfo(name, webDomain)
|
|
info.authToken = authToken
|
|
return info
|
|
}
|
|
|
|
func makeCommits(hashes ...string) []*models.Commit {
|
|
hashPool := &utils.StringPool{}
|
|
return lo.Map(hashes, func(hash string, _ int) *models.Commit {
|
|
return models.NewCommit(hashPool, models.NewCommitOpts{Hash: hash})
|
|
})
|
|
}
|
|
|
|
func makeTodoCommit(action todo.TodoCommand) *models.Commit {
|
|
return models.NewCommit(&utils.StringPool{}, models.NewCommitOpts{Action: action})
|
|
}
|
|
|
|
func makeTodoCommitWithHash(hash string, action todo.TodoCommand) *models.Commit {
|
|
return models.NewCommit(&utils.StringPool{}, models.NewCommitOpts{Hash: hash, Action: action})
|
|
}
|