2018-08-12 11:31:27 +02:00
|
|
|
package git
|
2018-08-10 13:33:49 +02:00
|
|
|
|
|
|
|
import (
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
|
2018-08-12 13:04:47 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
2018-08-13 12:26:02 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
2018-08-12 13:04:47 +02:00
|
|
|
|
2018-08-23 14:22:03 +02:00
|
|
|
"github.com/sirupsen/logrus"
|
2018-08-12 13:04:47 +02:00
|
|
|
|
2018-08-10 13:33:49 +02:00
|
|
|
"gopkg.in/src-d/go-git.v4/plumbing"
|
|
|
|
)
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
2018-08-13 12:26:02 +02:00
|
|
|
// BranchListBuilder returns a list of Branch objects for the current repo
|
2018-08-12 13:04:47 +02:00
|
|
|
type BranchListBuilder struct {
|
2018-08-13 12:26:02 +02:00
|
|
|
Log *logrus.Logger
|
2018-08-12 13:04:47 +02:00
|
|
|
GitCommand *commands.GitCommand
|
|
|
|
}
|
2018-08-10 13:33:49 +02:00
|
|
|
|
2018-08-13 12:26:02 +02:00
|
|
|
// NewBranchListBuilder builds a new branch list builder
|
|
|
|
func NewBranchListBuilder(log *logrus.Logger, gitCommand *commands.GitCommand) (*BranchListBuilder, error) {
|
|
|
|
return &BranchListBuilder{
|
|
|
|
Log: log,
|
|
|
|
GitCommand: gitCommand,
|
|
|
|
}, nil
|
2018-08-10 13:33:49 +02:00
|
|
|
}
|
|
|
|
|
2018-08-13 12:26:02 +02:00
|
|
|
func (b *BranchListBuilder) obtainCurrentBranch() commands.Branch {
|
2018-08-10 14:08:12 +02:00
|
|
|
// I used go-git for this, but that breaks if you've just done a git init,
|
|
|
|
// even though you're on 'master'
|
2018-08-14 09:47:33 +02:00
|
|
|
branchName, err := b.GitCommand.OSCommand.RunCommandWithOutput("git symbolic-ref --short HEAD")
|
|
|
|
if err != nil {
|
2018-08-15 01:42:02 +02:00
|
|
|
branchName, err = b.GitCommand.OSCommand.RunCommandWithOutput("git rev-parse --short HEAD")
|
|
|
|
if err != nil {
|
|
|
|
panic(err.Error())
|
|
|
|
}
|
2018-08-14 09:47:33 +02:00
|
|
|
}
|
2018-08-13 12:26:02 +02:00
|
|
|
return commands.Branch{Name: strings.TrimSpace(branchName), Recency: " *"}
|
2018-08-10 13:33:49 +02:00
|
|
|
}
|
|
|
|
|
2018-08-13 12:26:02 +02:00
|
|
|
func (b *BranchListBuilder) obtainReflogBranches() []commands.Branch {
|
|
|
|
branches := make([]commands.Branch, 0)
|
2018-08-14 10:02:14 +02:00
|
|
|
rawString, err := b.GitCommand.OSCommand.RunCommandWithOutput("git reflog -n100 --pretty='%cr|%gs' --grep-reflog='checkout: moving' HEAD")
|
2018-08-10 13:33:49 +02:00
|
|
|
if err != nil {
|
|
|
|
return branches
|
|
|
|
}
|
|
|
|
|
2018-08-13 12:26:02 +02:00
|
|
|
branchLines := utils.SplitLines(rawString)
|
2018-08-10 13:33:49 +02:00
|
|
|
for _, line := range branchLines {
|
|
|
|
timeNumber, timeUnit, branchName := branchInfoFromLine(line)
|
|
|
|
timeUnit = abbreviatedTimeUnit(timeUnit)
|
2018-08-13 12:26:02 +02:00
|
|
|
branch := commands.Branch{Name: branchName, Recency: timeNumber + timeUnit}
|
2018-08-10 13:33:49 +02:00
|
|
|
branches = append(branches, branch)
|
|
|
|
}
|
|
|
|
return branches
|
|
|
|
}
|
|
|
|
|
2018-08-13 12:26:02 +02:00
|
|
|
func (b *BranchListBuilder) obtainSafeBranches() []commands.Branch {
|
|
|
|
branches := make([]commands.Branch, 0)
|
2018-08-10 13:33:49 +02:00
|
|
|
|
2018-08-13 12:26:02 +02:00
|
|
|
bIter, err := b.GitCommand.Repo.Branches()
|
2018-08-10 13:33:49 +02:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
err = bIter.ForEach(func(b *plumbing.Reference) error {
|
|
|
|
name := b.Name().Short()
|
2018-08-13 12:26:02 +02:00
|
|
|
branches = append(branches, commands.Branch{Name: name})
|
2018-08-10 13:33:49 +02:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
return branches
|
|
|
|
}
|
|
|
|
|
2018-08-13 12:26:02 +02:00
|
|
|
func (b *BranchListBuilder) appendNewBranches(finalBranches, newBranches, existingBranches []commands.Branch, included bool) []commands.Branch {
|
2018-08-10 13:33:49 +02:00
|
|
|
for _, newBranch := range newBranches {
|
|
|
|
if included == branchIncluded(newBranch.Name, existingBranches) {
|
|
|
|
finalBranches = append(finalBranches, newBranch)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return finalBranches
|
|
|
|
}
|
|
|
|
|
2018-08-13 12:26:02 +02:00
|
|
|
func sanitisedReflogName(reflogBranch commands.Branch, safeBranches []commands.Branch) string {
|
2018-08-11 08:11:17 +02:00
|
|
|
for _, safeBranch := range safeBranches {
|
|
|
|
if strings.ToLower(safeBranch.Name) == strings.ToLower(reflogBranch.Name) {
|
|
|
|
return safeBranch.Name
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return reflogBranch.Name
|
|
|
|
}
|
|
|
|
|
2018-08-13 12:26:02 +02:00
|
|
|
// Build the list of branches for the current repo
|
|
|
|
func (b *BranchListBuilder) Build() []commands.Branch {
|
|
|
|
branches := make([]commands.Branch, 0)
|
2018-08-10 13:33:49 +02:00
|
|
|
head := b.obtainCurrentBranch()
|
2018-08-10 14:08:12 +02:00
|
|
|
safeBranches := b.obtainSafeBranches()
|
|
|
|
if len(safeBranches) == 0 {
|
|
|
|
return append(branches, head)
|
|
|
|
}
|
2018-08-10 13:38:51 +02:00
|
|
|
reflogBranches := b.obtainReflogBranches()
|
2018-08-13 12:26:02 +02:00
|
|
|
reflogBranches = uniqueByName(append([]commands.Branch{head}, reflogBranches...))
|
2018-08-11 08:11:17 +02:00
|
|
|
for i, reflogBranch := range reflogBranches {
|
|
|
|
reflogBranches[i].Name = sanitisedReflogName(reflogBranch, safeBranches)
|
|
|
|
}
|
2018-08-10 13:33:49 +02:00
|
|
|
|
2018-08-10 14:08:12 +02:00
|
|
|
branches = b.appendNewBranches(branches, reflogBranches, safeBranches, true)
|
|
|
|
branches = b.appendNewBranches(branches, safeBranches, branches, false)
|
2018-08-10 13:33:49 +02:00
|
|
|
|
|
|
|
return branches
|
|
|
|
}
|
|
|
|
|
2018-08-13 12:26:02 +02:00
|
|
|
func branchIncluded(branchName string, branches []commands.Branch) bool {
|
|
|
|
for _, existingBranch := range branches {
|
|
|
|
if strings.ToLower(existingBranch.Name) == strings.ToLower(branchName) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func uniqueByName(branches []commands.Branch) []commands.Branch {
|
|
|
|
finalBranches := make([]commands.Branch, 0)
|
2018-08-10 13:33:49 +02:00
|
|
|
for _, branch := range branches {
|
|
|
|
if branchIncluded(branch.Name, finalBranches) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
finalBranches = append(finalBranches, branch)
|
|
|
|
}
|
|
|
|
return finalBranches
|
|
|
|
}
|
|
|
|
|
|
|
|
// A line will have the form '10 days ago master' so we need to strip out the
|
|
|
|
// useful information from that into timeNumber, timeUnit, and branchName
|
|
|
|
func branchInfoFromLine(line string) (string, string, string) {
|
|
|
|
r := regexp.MustCompile("\\|.*\\s")
|
|
|
|
line = r.ReplaceAllString(line, " ")
|
|
|
|
words := strings.Split(line, " ")
|
2018-08-15 01:45:24 +02:00
|
|
|
return words[0], words[1], words[len(words)-1]
|
2018-08-10 13:33:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func abbreviatedTimeUnit(timeUnit string) string {
|
|
|
|
r := regexp.MustCompile("s$")
|
|
|
|
timeUnit = r.ReplaceAllString(timeUnit, "")
|
|
|
|
timeUnitMap := map[string]string{
|
|
|
|
"hour": "h",
|
|
|
|
"minute": "m",
|
|
|
|
"second": "s",
|
|
|
|
"week": "w",
|
|
|
|
"year": "y",
|
|
|
|
"day": "d",
|
|
|
|
"month": "m",
|
|
|
|
}
|
|
|
|
return timeUnitMap[timeUnit]
|
|
|
|
}
|