Skip to content

Commit

Permalink
Obtain branches in a more robust way. Begin refactor work on gitcommands
Browse files Browse the repository at this point in the history
  • Loading branch information
jesseduffield committed Aug 10, 2018
1 parent 3f89b5b commit d08241b
Show file tree
Hide file tree
Showing 12 changed files with 221 additions and 178 deletions.
42 changes: 42 additions & 0 deletions branch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package main

import (
"strings"

"github.com/fatih/color"
)

// Branch : A git branch
type Branch struct {
Name string
Recency string
}

func (b *Branch) getDisplayString() string {
return withPadding(b.Recency, 4) + coloredString(b.Name, b.getColor())
}

func (b *Branch) getColor() color.Attribute {
switch b.getType() {
case "feature":
return color.FgGreen
case "bugfix":
return color.FgYellow
case "hotfix":
return color.FgRed
default:
return color.FgWhite
}
}

// expected to return feature/bugfix/hotfix or blank string
func (b *Branch) getType() string {
return strings.Split(b.Name, "/")[0]
}

func withPadding(str string, padding int) string {
if padding-len(str) < 0 {
return str
}
return str + strings.Repeat(" ", padding-len(str))
}
122 changes: 122 additions & 0 deletions branch_list_builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package main

import (
"regexp"
"strings"

"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

type branchListBuilder struct{}

func newBranchListBuilder() *branchListBuilder {
return &branchListBuilder{}
}

func (b *branchListBuilder) obtainCurrentBranch() Branch {
// Using git-go whenever possible
head, err := r.Head()
if err != nil {
panic(err)
}
name := head.Name().Short()
return Branch{Name: name, Recency: " *"}
}

func (*branchListBuilder) obtainReflogBranches() []Branch {
branches := make([]Branch, 0)
rawString, err := runDirectCommand("git reflog -n100 --pretty='%cr|%gs' --grep-reflog='checkout: moving' HEAD")
if err != nil {
return branches
}

branchLines := splitLines(rawString)
for _, line := range branchLines {
timeNumber, timeUnit, branchName := branchInfoFromLine(line)
timeUnit = abbreviatedTimeUnit(timeUnit)
branch := Branch{Name: branchName, Recency: timeNumber + timeUnit}
branches = append(branches, branch)
}
return branches
}

func (b *branchListBuilder) obtainSafeBranches() []Branch {
branches := make([]Branch, 0)

bIter, err := r.Branches()
if err != nil {
panic(err)
}
err = bIter.ForEach(func(b *plumbing.Reference) error {
name := b.Name().Short()
branches = append(branches, Branch{Name: name})
return nil
})

return branches
}

func (b *branchListBuilder) appendNewBranches(finalBranches, newBranches, existingBranches []Branch, included bool) []Branch {
for _, newBranch := range newBranches {
if included == branchIncluded(newBranch.Name, existingBranches) {
finalBranches = append(finalBranches, newBranch)
}

}
return finalBranches
}

func (b *branchListBuilder) build() []Branch {
branches := make([]Branch, 0)
head := b.obtainCurrentBranch()
validBranches := b.obtainSafeBranches()
reflogBranches := uniqueByName(append(b.obtainReflogBranches(), head))

branches = b.appendNewBranches(branches, reflogBranches, validBranches, true)
branches = b.appendNewBranches(branches, validBranches, branches, false)

return branches
}

func uniqueByName(branches []Branch) []Branch {
finalBranches := make([]Branch, 0)
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, " ")
return words[0], words[1], words[3]
}

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]
}
4 changes: 2 additions & 2 deletions branches_panel.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func handleBranchSelect(g *gocui.Gui, v *gocui.View) error {
}
go func() {
branch := getSelectedBranch(v)
diff, err := getBranchGraph(branch.Name, branch.BaseBranch)
diff, err := getBranchGraph(branch.Name)
if err != nil && strings.HasPrefix(diff, "fatal: ambiguous argument") {
diff = "There is no tracking for this branch"
}
Expand All @@ -111,7 +111,7 @@ func refreshBranches(g *gocui.Gui) error {
state.Branches = getGitBranches()
v.Clear()
for _, branch := range state.Branches {
fmt.Fprintln(v, branch.DisplayString)
fmt.Fprintln(v, branch.getDisplayString())
}
resetOrigin(v)
return refreshStatus(g)
Expand Down
17 changes: 17 additions & 0 deletions colorer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package main

import (
"fmt"

"github.com/fatih/color"
)

func coloredString(str string, colorAttribute color.Attribute) string {
colour := color.New(colorAttribute)
return coloredStringDirect(str, colour)
}

// used for aggregating a few color attributes rather than just sending a single one
func coloredStringDirect(str string, colour *color.Color) string {
return colour.SprintFunc()(fmt.Sprint(str))
}
Loading

0 comments on commit d08241b

Please sign in to comment.