2022-11-11 04:19:29 +02:00
|
|
|
package git_commands
|
2018-08-10 13:33:49 +02:00
|
|
|
|
|
|
|
import (
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
|
2022-03-19 03:26:30 +02:00
|
|
|
"github.com/jesseduffield/generics/set"
|
2022-03-19 06:36:46 +02:00
|
|
|
"github.com/jesseduffield/generics/slices"
|
2022-01-15 05:20:09 +02:00
|
|
|
"github.com/jesseduffield/go-git/v5/config"
|
2020-09-29 12:28:39 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
2021-12-29 02:37:15 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/common"
|
2018-08-13 12:26:02 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
2018-08-10 13:33:49 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// context:
|
|
|
|
// we want to only show 'safe' branches (ones that haven't e.g. been deleted)
|
|
|
|
// which `git branch -a` gives us, but we also want the recency data that
|
|
|
|
// git reflog gives us.
|
|
|
|
// So we get the HEAD, then append get the reflog branches that intersect with
|
|
|
|
// our safe branches, then add the remaining safe branches, ensuring uniqueness
|
|
|
|
// along the way
|
|
|
|
|
2019-02-19 00:18:30 +02:00
|
|
|
// if we find out we need to use one of these functions in the git.go file, we
|
|
|
|
// can just pull them out of here and put them there and then call them from in here
|
|
|
|
|
2022-01-15 05:20:09 +02:00
|
|
|
type BranchLoaderConfigCommands interface {
|
|
|
|
Branches() (map[string]*config.Branch, error)
|
|
|
|
}
|
|
|
|
|
2022-10-16 14:31:42 +02:00
|
|
|
type BranchInfo struct {
|
|
|
|
RefName string
|
|
|
|
DisplayName string // e.g. '(HEAD detached at 123asdf)'
|
|
|
|
DetachedHead bool
|
|
|
|
}
|
|
|
|
|
2021-12-30 04:44:41 +02:00
|
|
|
// BranchLoader returns a list of Branch objects for the current repo
|
|
|
|
type BranchLoader struct {
|
2021-12-29 02:37:15 +02:00
|
|
|
*common.Common
|
|
|
|
getRawBranches func() (string, error)
|
2022-10-16 14:31:42 +02:00
|
|
|
getCurrentBranchInfo func() (BranchInfo, error)
|
2022-01-15 05:20:09 +02:00
|
|
|
config BranchLoaderConfigCommands
|
2018-08-12 13:04:47 +02:00
|
|
|
}
|
2018-08-10 13:33:49 +02:00
|
|
|
|
2021-12-30 04:44:41 +02:00
|
|
|
func NewBranchLoader(
|
2021-12-29 02:37:15 +02:00
|
|
|
cmn *common.Common,
|
2022-01-02 01:34:33 +02:00
|
|
|
getRawBranches func() (string, error),
|
2022-10-16 14:31:42 +02:00
|
|
|
getCurrentBranchInfo func() (BranchInfo, error),
|
2022-01-15 05:20:09 +02:00
|
|
|
config BranchLoaderConfigCommands,
|
2021-12-30 04:44:41 +02:00
|
|
|
) *BranchLoader {
|
|
|
|
return &BranchLoader{
|
2021-12-29 02:37:15 +02:00
|
|
|
Common: cmn,
|
2022-01-02 01:34:33 +02:00
|
|
|
getRawBranches: getRawBranches,
|
2022-10-16 14:31:42 +02:00
|
|
|
getCurrentBranchInfo: getCurrentBranchInfo,
|
2022-01-15 05:20:09 +02:00
|
|
|
config: config,
|
2021-12-29 02:37:15 +02:00
|
|
|
}
|
2018-08-10 13:33:49 +02:00
|
|
|
}
|
|
|
|
|
2021-12-30 04:44:41 +02:00
|
|
|
// Load the list of branches for the current repo
|
2022-01-15 05:20:09 +02:00
|
|
|
func (self *BranchLoader) Load(reflogCommits []*models.Commit) ([]*models.Branch, error) {
|
2021-12-30 08:19:01 +02:00
|
|
|
branches := self.obtainBranches()
|
2021-12-29 05:33:38 +02:00
|
|
|
|
2022-01-02 01:21:32 +02:00
|
|
|
reflogBranches := self.obtainReflogBranches(reflogCommits)
|
2021-12-29 05:33:38 +02:00
|
|
|
|
|
|
|
// loop through reflog branches. If there is a match, merge them, then remove it from the branches and keep it in the reflog branches
|
|
|
|
branchesWithRecency := make([]*models.Branch, 0)
|
|
|
|
outer:
|
|
|
|
for _, reflogBranch := range reflogBranches {
|
|
|
|
for j, branch := range branches {
|
|
|
|
if branch.Head {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if strings.EqualFold(reflogBranch.Name, branch.Name) {
|
|
|
|
branch.Recency = reflogBranch.Recency
|
|
|
|
branchesWithRecency = append(branchesWithRecency, branch)
|
2022-03-19 07:34:46 +02:00
|
|
|
branches = slices.Remove(branches, j)
|
2021-12-29 05:33:38 +02:00
|
|
|
continue outer
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-19 07:34:46 +02:00
|
|
|
branches = slices.Prepend(branches, branchesWithRecency...)
|
2021-12-29 05:33:38 +02:00
|
|
|
|
|
|
|
foundHead := false
|
|
|
|
for i, branch := range branches {
|
|
|
|
if branch.Head {
|
|
|
|
foundHead = true
|
|
|
|
branch.Recency = " *"
|
2022-03-19 06:36:46 +02:00
|
|
|
branches = slices.Move(branches, i, 0)
|
2021-12-29 05:33:38 +02:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !foundHead {
|
2022-10-16 14:31:42 +02:00
|
|
|
info, err := self.getCurrentBranchInfo()
|
2021-12-29 05:33:38 +02:00
|
|
|
if err != nil {
|
2022-01-15 05:20:09 +02:00
|
|
|
return nil, err
|
2021-12-29 05:33:38 +02:00
|
|
|
}
|
2022-10-16 14:31:42 +02:00
|
|
|
branches = slices.Prepend(branches, &models.Branch{Name: info.RefName, DisplayName: info.DisplayName, Head: true, DetachedHead: info.DetachedHead, Recency: " *"})
|
2021-12-29 05:33:38 +02:00
|
|
|
}
|
2022-01-15 05:20:09 +02:00
|
|
|
|
|
|
|
configBranches, err := self.config.Branches()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, branch := range branches {
|
|
|
|
match := configBranches[branch.Name]
|
|
|
|
if match != nil {
|
|
|
|
branch.UpstreamRemote = match.Remote
|
|
|
|
branch.UpstreamBranch = match.Merge.Short()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return branches, nil
|
2021-12-29 05:33:38 +02:00
|
|
|
}
|
|
|
|
|
2022-03-24 09:14:49 +02:00
|
|
|
func (self *BranchLoader) obtainBranches() []*models.Branch {
|
|
|
|
output, err := self.getRawBranches()
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
trimmedOutput := strings.TrimSpace(output)
|
|
|
|
outputLines := strings.Split(trimmedOutput, "\n")
|
|
|
|
|
|
|
|
return slices.FilterMap(outputLines, func(line string) (*models.Branch, bool) {
|
|
|
|
if line == "" {
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
2022-03-26 14:55:44 +02:00
|
|
|
split := strings.Split(line, "\x00")
|
2022-03-24 09:14:49 +02:00
|
|
|
if len(split) != 4 {
|
|
|
|
// Ignore line if it isn't separated into 4 parts
|
|
|
|
// This is probably a warning message, for more info see:
|
|
|
|
// https://github.com/jesseduffield/lazygit/issues/1385#issuecomment-885580439
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
|
|
|
return obtainBranch(split), true
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-03-24 08:49:25 +02:00
|
|
|
// Obtain branch information from parsed line output of getRawBranches()
|
|
|
|
// split contains the '|' separated tokens in the line of output
|
|
|
|
func obtainBranch(split []string) *models.Branch {
|
|
|
|
name := strings.TrimPrefix(split[1], "heads/")
|
|
|
|
branch := &models.Branch{
|
|
|
|
Name: name,
|
|
|
|
Pullables: "?",
|
|
|
|
Pushables: "?",
|
|
|
|
Head: split[0] == "*",
|
|
|
|
}
|
|
|
|
|
|
|
|
upstreamName := split[2]
|
|
|
|
if upstreamName == "" {
|
|
|
|
// if we're here then it means we do not have a local version of the remote.
|
|
|
|
// The branch might still be tracking a remote though, we just don't know
|
|
|
|
// how many commits ahead/behind it is
|
|
|
|
return branch
|
|
|
|
}
|
|
|
|
|
|
|
|
track := split[3]
|
|
|
|
if track == "[gone]" {
|
|
|
|
branch.UpstreamGone = true
|
|
|
|
} else {
|
|
|
|
re := regexp.MustCompile(`ahead (\d+)`)
|
|
|
|
match := re.FindStringSubmatch(track)
|
|
|
|
if len(match) > 1 {
|
|
|
|
branch.Pushables = match[1]
|
|
|
|
} else {
|
|
|
|
branch.Pushables = "0"
|
|
|
|
}
|
|
|
|
|
|
|
|
re = regexp.MustCompile(`behind (\d+)`)
|
|
|
|
match = re.FindStringSubmatch(track)
|
|
|
|
if len(match) > 1 {
|
|
|
|
branch.Pullables = match[1]
|
|
|
|
} else {
|
|
|
|
branch.Pullables = "0"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return branch
|
|
|
|
}
|
|
|
|
|
2020-03-26 13:51:24 +02:00
|
|
|
// TODO: only look at the new reflog commits, and otherwise store the recencies in
|
|
|
|
// int form against the branch to recalculate the time ago
|
2022-01-02 01:21:32 +02:00
|
|
|
func (self *BranchLoader) obtainReflogBranches(reflogCommits []*models.Commit) []*models.Branch {
|
2022-03-19 03:26:30 +02:00
|
|
|
foundBranches := set.New[string]()
|
2020-03-26 13:51:24 +02:00
|
|
|
re := regexp.MustCompile(`checkout: moving from ([\S]+) to ([\S]+)`)
|
2022-01-02 01:21:32 +02:00
|
|
|
reflogBranches := make([]*models.Branch, 0, len(reflogCommits))
|
2022-03-19 07:34:46 +02:00
|
|
|
|
2022-01-02 01:21:32 +02:00
|
|
|
for _, commit := range reflogCommits {
|
2022-03-19 07:34:46 +02:00
|
|
|
match := re.FindStringSubmatch(commit.Name)
|
|
|
|
if len(match) != 3 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
recency := utils.UnixToTimeAgo(commit.UnixTimestamp)
|
|
|
|
for _, branchName := range match[1:] {
|
|
|
|
if !foundBranches.Includes(branchName) {
|
|
|
|
foundBranches.Add(branchName)
|
|
|
|
reflogBranches = append(reflogBranches, &models.Branch{
|
|
|
|
Recency: recency,
|
|
|
|
Name: branchName,
|
|
|
|
})
|
2020-03-26 13:51:24 +02:00
|
|
|
}
|
2020-03-17 12:22:07 +02:00
|
|
|
}
|
|
|
|
}
|
2020-03-26 13:51:24 +02:00
|
|
|
return reflogBranches
|
2020-03-17 12:22:07 +02:00
|
|
|
}
|