github.com/andrewrech/lazygit@v0.8.1/pkg/git/branch_list_builder.go (about) 1 package git 2 3 import ( 4 "regexp" 5 "strings" 6 7 "github.com/jesseduffield/lazygit/pkg/commands" 8 "github.com/jesseduffield/lazygit/pkg/utils" 9 10 "github.com/sirupsen/logrus" 11 12 "gopkg.in/src-d/go-git.v4/plumbing" 13 ) 14 15 // context: 16 // we want to only show 'safe' branches (ones that haven't e.g. been deleted) 17 // which `git branch -a` gives us, but we also want the recency data that 18 // git reflog gives us. 19 // So we get the HEAD, then append get the reflog branches that intersect with 20 // our safe branches, then add the remaining safe branches, ensuring uniqueness 21 // along the way 22 23 // if we find out we need to use one of these functions in the git.go file, we 24 // can just pull them out of here and put them there and then call them from in here 25 26 // BranchListBuilder returns a list of Branch objects for the current repo 27 type BranchListBuilder struct { 28 Log *logrus.Entry 29 GitCommand *commands.GitCommand 30 } 31 32 // NewBranchListBuilder builds a new branch list builder 33 func NewBranchListBuilder(log *logrus.Entry, gitCommand *commands.GitCommand) (*BranchListBuilder, error) { 34 return &BranchListBuilder{ 35 Log: log, 36 GitCommand: gitCommand, 37 }, nil 38 } 39 40 func (b *BranchListBuilder) obtainCurrentBranch() *commands.Branch { 41 branchName, err := b.GitCommand.CurrentBranchName() 42 if err != nil { 43 panic(err.Error()) 44 } 45 46 return &commands.Branch{Name: strings.TrimSpace(branchName)} 47 } 48 49 func (b *BranchListBuilder) obtainReflogBranches() []*commands.Branch { 50 branches := make([]*commands.Branch, 0) 51 rawString, err := b.GitCommand.OSCommand.RunCommandWithOutput("git reflog -n100 --pretty='%cr|%gs' --grep-reflog='checkout: moving' HEAD") 52 if err != nil { 53 return branches 54 } 55 56 branchLines := utils.SplitLines(rawString) 57 for _, line := range branchLines { 58 timeNumber, timeUnit, branchName := branchInfoFromLine(line) 59 timeUnit = abbreviatedTimeUnit(timeUnit) 60 branch := &commands.Branch{Name: branchName, Recency: timeNumber + timeUnit} 61 branches = append(branches, branch) 62 } 63 return uniqueByName(branches) 64 } 65 66 func (b *BranchListBuilder) obtainSafeBranches() []*commands.Branch { 67 branches := make([]*commands.Branch, 0) 68 69 bIter, err := b.GitCommand.Repo.Branches() 70 if err != nil { 71 panic(err) 72 } 73 err = bIter.ForEach(func(b *plumbing.Reference) error { 74 name := b.Name().Short() 75 branches = append(branches, &commands.Branch{Name: name}) 76 return nil 77 }) 78 79 return branches 80 } 81 82 func (b *BranchListBuilder) appendNewBranches(finalBranches, newBranches, existingBranches []*commands.Branch, included bool) []*commands.Branch { 83 for _, newBranch := range newBranches { 84 if included == branchIncluded(newBranch.Name, existingBranches) { 85 finalBranches = append(finalBranches, newBranch) 86 } 87 } 88 return finalBranches 89 } 90 91 func sanitisedReflogName(reflogBranch *commands.Branch, safeBranches []*commands.Branch) string { 92 for _, safeBranch := range safeBranches { 93 if strings.ToLower(safeBranch.Name) == strings.ToLower(reflogBranch.Name) { 94 return safeBranch.Name 95 } 96 } 97 return reflogBranch.Name 98 } 99 100 // Build the list of branches for the current repo 101 func (b *BranchListBuilder) Build() []*commands.Branch { 102 branches := make([]*commands.Branch, 0) 103 head := b.obtainCurrentBranch() 104 safeBranches := b.obtainSafeBranches() 105 106 reflogBranches := b.obtainReflogBranches() 107 for i, reflogBranch := range reflogBranches { 108 reflogBranches[i].Name = sanitisedReflogName(reflogBranch, safeBranches) 109 } 110 111 branches = b.appendNewBranches(branches, reflogBranches, safeBranches, true) 112 branches = b.appendNewBranches(branches, safeBranches, branches, false) 113 114 if len(branches) == 0 || branches[0].Name != head.Name { 115 branches = append([]*commands.Branch{head}, branches...) 116 } 117 118 branches[0].Recency = " *" 119 120 return branches 121 } 122 123 func branchIncluded(branchName string, branches []*commands.Branch) bool { 124 for _, existingBranch := range branches { 125 if strings.ToLower(existingBranch.Name) == strings.ToLower(branchName) { 126 return true 127 } 128 } 129 return false 130 } 131 132 func uniqueByName(branches []*commands.Branch) []*commands.Branch { 133 finalBranches := make([]*commands.Branch, 0) 134 for _, branch := range branches { 135 if branchIncluded(branch.Name, finalBranches) { 136 continue 137 } 138 finalBranches = append(finalBranches, branch) 139 } 140 return finalBranches 141 } 142 143 // A line will have the form '10 days ago master' so we need to strip out the 144 // useful information from that into timeNumber, timeUnit, and branchName 145 func branchInfoFromLine(line string) (string, string, string) { 146 r := regexp.MustCompile("\\|.*\\s") 147 line = r.ReplaceAllString(line, " ") 148 words := strings.Split(line, " ") 149 return words[0], words[1], words[len(words)-1] 150 } 151 152 func abbreviatedTimeUnit(timeUnit string) string { 153 r := regexp.MustCompile("s$") 154 timeUnit = r.ReplaceAllString(timeUnit, "") 155 timeUnitMap := map[string]string{ 156 "hour": "h", 157 "minute": "m", 158 "second": "s", 159 "week": "w", 160 "year": "y", 161 "day": "d", 162 "month": "m", 163 } 164 return timeUnitMap[timeUnit] 165 }