github.com/yourbase/yb@v0.7.1/cmd/yb/best_ancestor.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"regexp"
     7  	"strings"
     8  
     9  	"gopkg.in/src-d/go-git.v4"
    10  	"gopkg.in/src-d/go-git.v4/plumbing"
    11  	"gopkg.in/src-d/go-git.v4/plumbing/object"
    12  	"gopkg.in/src-d/go-git.v4/plumbing/storer"
    13  	"zombiezen.com/go/log"
    14  )
    15  
    16  func fastFindAncestor(ctx context.Context, r *git.Repository) (h plumbing.Hash, c int, branchName string, err error) {
    17  	/*
    18  		go doc gopkg.in/src-d/go-git.v4/plumbing/object Commit.MergeBase
    19  
    20  		func (c *Commit) MergeBase(other *Commit) ([]*Commit, error)
    21  		MergeBase mimics the behavior of `git merge-base actual other`, returning
    22  		the best common ancestor between the actual and the passed one. The best
    23  		common ancestors can not be reached from other common ancestors.
    24  	*/
    25  	c = -1
    26  
    27  	ref, err := r.Head()
    28  	if err != nil {
    29  		err = fmt.Errorf("find ancestor: %w", err)
    30  		return
    31  	}
    32  
    33  	remoteBranch := findRemoteBranch(ctx, ref, r)
    34  	branchName = ref.Name().Short()
    35  	if remoteBranch == nil {
    36  		if branchName == "master" {
    37  			// Well, we don't need to try it again
    38  			err = fmt.Errorf("find ancestor: unable to find remote master branch")
    39  			return
    40  		}
    41  		//Search again, on master
    42  		ref, err = r.Reference(plumbing.NewBranchReferenceName("master"), false)
    43  		if err != nil {
    44  			err = fmt.Errorf("find ancestor: %w", err)
    45  			return
    46  		}
    47  		remoteBranch = findRemoteBranch(ctx, ref, r)
    48  		branchName = ref.Name().Short() // "master"
    49  	}
    50  
    51  	headCommit, err := r.CommitObject(ref.Hash())
    52  	if err != nil {
    53  		err = fmt.Errorf("find ancestor: %w", err)
    54  		return
    55  	}
    56  	remoteCommit, err := r.CommitObject(remoteBranch.Hash())
    57  	if err != nil {
    58  		err = fmt.Errorf("find ancestor: %w", err)
    59  		return
    60  	}
    61  	commonAncestors, err := remoteCommit.MergeBase(headCommit)
    62  	if err != nil {
    63  		err = fmt.Errorf("find ancestor: %w", err)
    64  		return
    65  	}
    66  
    67  	for i, ancestor := range commonAncestors {
    68  		log.Infof(ctx, "Merge-base commit #%d '%v': %v", i, ancestor.Hash.String(), strings.ReplaceAll(fmt.Sprintf("%12s (...)", ancestor.Message), "\n", " "))
    69  	}
    70  	// For now we'll return the first one
    71  	if len(commonAncestors) > 0 {
    72  		h = commonAncestors[0].Hash
    73  	}
    74  
    75  	// Show remote name from branch complete name
    76  	remoteName := remoteBranch.Name().String()
    77  	remoteRefRegexp := regexp.MustCompile(`refs/remotes/(\w+)/(\w+)`)
    78  
    79  	if remoteRefRegexp.MatchString(remoteName) {
    80  		submatches := remoteRefRegexp.FindStringSubmatch(remoteName)
    81  
    82  		if len(submatches) > 1 {
    83  			remoteInfo := submatches[1]
    84  			if config, err := r.Config(); err == nil {
    85  				remoteInfo = fmt.Sprintf("\"%s [ %s ]\"", remoteInfo, config.Remotes[submatches[1]].URLs[0])
    86  			}
    87  			log.Infof(ctx, "Remote found for branch '%v': %v", branchName, remoteInfo)
    88  		} else {
    89  			log.Errorf(ctx, "Unable to parse remote branch '%v'", branchName)
    90  		}
    91  	}
    92  
    93  	// Count commits between head and 'h'
    94  	commitIter, err := r.Log(&git.LogOptions{})
    95  	if err != nil {
    96  		err = fmt.Errorf("find ancestor: %w", err)
    97  		return
    98  	}
    99  	x := 0
   100  	err = commitIter.ForEach(func(cmt *object.Commit) error {
   101  		x++
   102  		if cmt.Hash.String() == h.String() {
   103  			// Stop here
   104  			c = x
   105  			return storer.ErrStop
   106  		}
   107  		return nil
   108  	})
   109  
   110  	return
   111  }
   112  
   113  func findRemoteBranch(ctx context.Context, reference *plumbing.Reference, r *git.Repository) (remoteBranch *plumbing.Reference) {
   114  	branchIter, _ := r.References()
   115  	_ = branchIter.ForEach(func(rem *plumbing.Reference) error {
   116  		if rem.Name().IsRemote() {
   117  			log.Debugf(ctx, "Branch found: %v, %s ?== %s", rem, rem.Name().Short(), reference.Name().Short())
   118  			if strings.HasSuffix(rem.Name().Short(), reference.Name().Short()) {
   119  				remoteBranch = rem
   120  				return storer.ErrStop
   121  			}
   122  		}
   123  		return nil
   124  	})
   125  	return
   126  }