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  }