mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-01-12 04:23:03 +02:00
6af8f278d0
Instead, derive it from context at display time (if we're rebasing, it's the first non-todo commit). This fixes the problem that unfolding the current commit's files in the local commits panel would show junk in the frame's title. Along the way we make sure to only display the "<--- YOU ARE HERE" string in the local commits panel; previously it would show for the top commit of a branch or tag if mid-rebase.
384 lines
9.3 KiB
Go
384 lines
9.3 KiB
Go
package presentation
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/jesseduffield/generics/set"
|
|
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
|
"github.com/jesseduffield/lazygit/pkg/common"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/presentation/authors"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/presentation/graph"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
|
"github.com/jesseduffield/lazygit/pkg/theme"
|
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
|
"github.com/kyokomi/emoji/v2"
|
|
"github.com/sasha-s/go-deadlock"
|
|
)
|
|
|
|
type pipeSetCacheKey struct {
|
|
commitSha string
|
|
commitCount int
|
|
}
|
|
|
|
var (
|
|
pipeSetCache = make(map[pipeSetCacheKey][][]*graph.Pipe)
|
|
mutex deadlock.Mutex
|
|
)
|
|
|
|
type bisectBounds struct {
|
|
newIndex int
|
|
oldIndex int
|
|
}
|
|
|
|
func GetCommitListDisplayStrings(
|
|
common *common.Common,
|
|
commits []*models.Commit,
|
|
fullDescription bool,
|
|
cherryPickedCommitShaSet *set.Set[string],
|
|
diffName string,
|
|
timeFormat string,
|
|
parseEmoji bool,
|
|
selectedCommitSha string,
|
|
startIdx int,
|
|
length int,
|
|
showGraph bool,
|
|
bisectInfo *git_commands.BisectInfo,
|
|
showYouAreHereLabel bool,
|
|
) [][]string {
|
|
mutex.Lock()
|
|
defer mutex.Unlock()
|
|
|
|
if len(commits) == 0 {
|
|
return nil
|
|
}
|
|
|
|
if startIdx > len(commits) {
|
|
return nil
|
|
}
|
|
|
|
end := utils.Min(startIdx+length, len(commits))
|
|
// this is where my non-TODO commits begin
|
|
rebaseOffset := utils.Min(indexOfFirstNonTODOCommit(commits), end)
|
|
|
|
filteredCommits := commits[startIdx:end]
|
|
|
|
bisectBounds := getbisectBounds(commits, bisectInfo)
|
|
|
|
// 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 := utils.Max(startIdx, rebaseOffset)
|
|
|
|
pipeSets := loadPipesets(commits[rebaseOffset:])
|
|
pipeSetOffset := utils.Max(startIdx-rebaseOffset, 0)
|
|
graphPipeSets := pipeSets[pipeSetOffset:utils.Max(end-rebaseOffset, 0)]
|
|
graphCommits := commits[graphOffset:end]
|
|
graphLines := graph.RenderAux(
|
|
graphPipeSets,
|
|
graphCommits,
|
|
selectedCommitSha,
|
|
)
|
|
getGraphLine = func(idx int) string {
|
|
if idx >= graphOffset {
|
|
return graphLines[idx-graphOffset]
|
|
} else {
|
|
return ""
|
|
}
|
|
}
|
|
} else {
|
|
getGraphLine = func(idx int) string { return "" }
|
|
}
|
|
|
|
lines := make([][]string, 0, len(filteredCommits))
|
|
var bisectStatus BisectStatus
|
|
for i, commit := range filteredCommits {
|
|
unfilteredIdx := i + startIdx
|
|
bisectStatus = getBisectStatus(unfilteredIdx, commit.Sha, bisectInfo, bisectBounds)
|
|
isYouAreHereCommit := showYouAreHereLabel && unfilteredIdx == rebaseOffset
|
|
lines = append(lines, displayCommit(
|
|
common,
|
|
commit,
|
|
cherryPickedCommitShaSet,
|
|
diffName,
|
|
timeFormat,
|
|
parseEmoji,
|
|
getGraphLine(unfilteredIdx),
|
|
fullDescription,
|
|
bisectStatus,
|
|
bisectInfo,
|
|
isYouAreHereCommit,
|
|
))
|
|
}
|
|
return lines
|
|
}
|
|
|
|
func getbisectBounds(commits []*models.Commit, bisectInfo *git_commands.BisectInfo) *bisectBounds {
|
|
if !bisectInfo.Bisecting() {
|
|
return nil
|
|
}
|
|
|
|
bisectBounds := &bisectBounds{}
|
|
|
|
for i, commit := range commits {
|
|
if commit.Sha == bisectInfo.GetNewSha() {
|
|
bisectBounds.newIndex = i
|
|
}
|
|
|
|
status, ok := bisectInfo.Status(commit.Sha)
|
|
if ok && status == git_commands.BisectStatusOld {
|
|
bisectBounds.oldIndex = i
|
|
return bisectBounds
|
|
}
|
|
}
|
|
|
|
// shouldn't land here
|
|
return nil
|
|
}
|
|
|
|
// precondition: slice is not empty
|
|
func indexOfFirstNonTODOCommit(commits []*models.Commit) int {
|
|
for i, commit := range commits {
|
|
if !commit.IsTODO() {
|
|
return i
|
|
}
|
|
}
|
|
|
|
// shouldn't land here
|
|
return 0
|
|
}
|
|
|
|
func loadPipesets(commits []*models.Commit) [][]*graph.Pipe {
|
|
// given that our cache key is a commit sha and a commit count, it's very important that we don't actually try to render pipes
|
|
// when dealing with things like filtered commits.
|
|
cacheKey := pipeSetCacheKey{
|
|
commitSha: commits[0].Sha,
|
|
commitCount: len(commits),
|
|
}
|
|
|
|
pipeSets, ok := pipeSetCache[cacheKey]
|
|
if !ok {
|
|
// pipe sets are unique to a commit head. and a commit count. Sometimes we haven't loaded everything for that.
|
|
// so let's just cache it based on that.
|
|
getStyle := func(commit *models.Commit) style.TextStyle {
|
|
return authors.AuthorStyle(commit.AuthorName)
|
|
}
|
|
pipeSets = graph.GetPipeSets(commits, getStyle)
|
|
pipeSetCache[cacheKey] = pipeSets
|
|
}
|
|
|
|
return pipeSets
|
|
}
|
|
|
|
// similar to the git_commands.BisectStatus but more gui-focused
|
|
type BisectStatus int
|
|
|
|
const (
|
|
BisectStatusNone BisectStatus = iota
|
|
BisectStatusOld
|
|
BisectStatusNew
|
|
BisectStatusSkipped
|
|
// adding candidate here which isn't present in the commands package because
|
|
// we need to actually go through the commits to get this info
|
|
BisectStatusCandidate
|
|
// also adding this
|
|
BisectStatusCurrent
|
|
)
|
|
|
|
func getBisectStatus(index int, commitSha string, bisectInfo *git_commands.BisectInfo, bisectBounds *bisectBounds) BisectStatus {
|
|
if !bisectInfo.Started() {
|
|
return BisectStatusNone
|
|
}
|
|
|
|
if bisectInfo.GetCurrentSha() == commitSha {
|
|
return BisectStatusCurrent
|
|
}
|
|
|
|
status, ok := bisectInfo.Status(commitSha)
|
|
if ok {
|
|
switch status {
|
|
case git_commands.BisectStatusNew:
|
|
return BisectStatusNew
|
|
case git_commands.BisectStatusOld:
|
|
return BisectStatusOld
|
|
case git_commands.BisectStatusSkipped:
|
|
return BisectStatusSkipped
|
|
}
|
|
} else {
|
|
if bisectBounds != nil && index >= bisectBounds.newIndex && index <= bisectBounds.oldIndex {
|
|
return BisectStatusCandidate
|
|
} else {
|
|
return BisectStatusNone
|
|
}
|
|
}
|
|
|
|
// should never land here
|
|
return BisectStatusNone
|
|
}
|
|
|
|
func getBisectStatusText(bisectStatus BisectStatus, bisectInfo *git_commands.BisectInfo) string {
|
|
if bisectStatus == BisectStatusNone {
|
|
return ""
|
|
}
|
|
|
|
style := getBisectStatusColor(bisectStatus)
|
|
|
|
switch bisectStatus {
|
|
case BisectStatusNew:
|
|
return style.Sprintf("<-- " + bisectInfo.NewTerm())
|
|
case BisectStatusOld:
|
|
return style.Sprintf("<-- " + bisectInfo.OldTerm())
|
|
case BisectStatusCurrent:
|
|
// TODO: i18n
|
|
return style.Sprintf("<-- current")
|
|
case BisectStatusSkipped:
|
|
return style.Sprintf("<-- skipped")
|
|
case BisectStatusCandidate:
|
|
return style.Sprintf("?")
|
|
case BisectStatusNone:
|
|
return ""
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func displayCommit(
|
|
common *common.Common,
|
|
commit *models.Commit,
|
|
cherryPickedCommitShaSet *set.Set[string],
|
|
diffName string,
|
|
timeFormat string,
|
|
parseEmoji bool,
|
|
graphLine string,
|
|
fullDescription bool,
|
|
bisectStatus BisectStatus,
|
|
bisectInfo *git_commands.BisectInfo,
|
|
isYouAreHereCommit bool,
|
|
) []string {
|
|
shaColor := getShaColor(commit, diffName, cherryPickedCommitShaSet, bisectStatus, bisectInfo)
|
|
bisectString := getBisectStatusText(bisectStatus, bisectInfo)
|
|
|
|
actionString := ""
|
|
if commit.Action != "" {
|
|
actionString = actionColorMap(commit.Action).Sprint(commit.Action) + " "
|
|
}
|
|
|
|
tagString := ""
|
|
if fullDescription {
|
|
if commit.ExtraInfo != "" {
|
|
tagString = style.FgMagenta.SetBold().Sprint(commit.ExtraInfo) + " "
|
|
}
|
|
} else {
|
|
if len(commit.Tags) > 0 {
|
|
tagString = theme.DiffTerminalColor.SetBold().Sprint(strings.Join(commit.Tags, " ")) + " "
|
|
}
|
|
}
|
|
|
|
name := commit.Name
|
|
if parseEmoji {
|
|
name = emoji.Sprint(name)
|
|
}
|
|
|
|
if isYouAreHereCommit {
|
|
youAreHere := style.FgYellow.Sprintf("<-- %s ---", common.Tr.YouAreHere)
|
|
name = fmt.Sprintf("%s %s", youAreHere, name)
|
|
}
|
|
|
|
authorFunc := authors.ShortAuthor
|
|
if fullDescription {
|
|
authorFunc = authors.LongAuthor
|
|
}
|
|
|
|
cols := make([]string, 0, 7)
|
|
if icons.IsIconEnabled() {
|
|
cols = append(cols, shaColor.Sprint(icons.IconForCommit(commit)))
|
|
}
|
|
cols = append(cols, shaColor.Sprint(commit.ShortSha()))
|
|
cols = append(cols, bisectString)
|
|
if fullDescription {
|
|
cols = append(cols, style.FgBlue.Sprint(utils.UnixToDate(commit.UnixTimestamp, timeFormat)))
|
|
}
|
|
cols = append(
|
|
cols,
|
|
actionString,
|
|
authorFunc(commit.AuthorName),
|
|
graphLine+tagString+theme.DefaultTextColor.Sprint(name),
|
|
)
|
|
|
|
return cols
|
|
}
|
|
|
|
func getBisectStatusColor(status BisectStatus) style.TextStyle {
|
|
switch status {
|
|
case BisectStatusNone:
|
|
return style.FgBlack
|
|
case BisectStatusNew:
|
|
return style.FgRed
|
|
case BisectStatusOld:
|
|
return style.FgGreen
|
|
case BisectStatusSkipped:
|
|
return style.FgYellow
|
|
case BisectStatusCurrent:
|
|
return style.FgMagenta
|
|
case BisectStatusCandidate:
|
|
return style.FgBlue
|
|
}
|
|
|
|
// shouldn't land here
|
|
return style.FgWhite
|
|
}
|
|
|
|
func getShaColor(
|
|
commit *models.Commit,
|
|
diffName string,
|
|
cherryPickedCommitShaSet *set.Set[string],
|
|
bisectStatus BisectStatus,
|
|
bisectInfo *git_commands.BisectInfo,
|
|
) style.TextStyle {
|
|
if bisectInfo.Started() {
|
|
return getBisectStatusColor(bisectStatus)
|
|
}
|
|
|
|
diffed := commit.Sha == diffName
|
|
shaColor := theme.DefaultTextColor
|
|
switch commit.Status {
|
|
case "unpushed":
|
|
shaColor = style.FgRed
|
|
case "pushed":
|
|
shaColor = style.FgYellow
|
|
case "merged":
|
|
shaColor = style.FgGreen
|
|
case "rebasing":
|
|
shaColor = style.FgBlue
|
|
case "reflog":
|
|
shaColor = style.FgBlue
|
|
}
|
|
|
|
if diffed {
|
|
shaColor = theme.DiffTerminalColor
|
|
} else if cherryPickedCommitShaSet.Includes(commit.Sha) {
|
|
shaColor = theme.CherryPickedCommitTextStyle
|
|
}
|
|
|
|
return shaColor
|
|
}
|
|
|
|
func actionColorMap(str string) style.TextStyle {
|
|
switch str {
|
|
case "pick":
|
|
return style.FgCyan
|
|
case "drop":
|
|
return style.FgRed
|
|
case "edit":
|
|
return style.FgGreen
|
|
case "fixup":
|
|
return style.FgMagenta
|
|
default:
|
|
return style.FgYellow
|
|
}
|
|
}
|