github.com/driusan/dgit@v0.0.0-20221118233547-f39f0c15edbb/git/mergebase.go (about)

     1  package git
     2  
     3  import (
     4  	"fmt"
     5  )
     6  
     7  type MergeBaseOptions struct {
     8  	IsAncestor bool
     9  	Octopus    bool
    10  }
    11  
    12  func MergeBase(c *Client, options MergeBaseOptions, commits []Commitish) (CommitID, error) {
    13  	if len(commits) == 2 {
    14  		// If there's only two commits specified, and one is
    15  		// an ancestor of the other, than that one is the merge-base
    16  		// (ie it's a fast-forward, so the earlier ancestor is always
    17  		// the merge-base)
    18  		cmt0, err := commits[0].CommitID(c)
    19  		if err != nil {
    20  			return CommitID{}, err
    21  		}
    22  		cmt1, err := commits[1].CommitID(c)
    23  		if err != nil {
    24  			return CommitID{}, err
    25  		}
    26  
    27  		if options.IsAncestor {
    28  			if cmt0.IsAncestor(c, cmt1) {
    29  				return cmt0, nil
    30  			} else {
    31  				return CommitID{}, fmt.Errorf("Not an ancestor")
    32  			}
    33  		}
    34  
    35  		if cmt0.IsAncestor(c, cmt1) {
    36  			return cmt0, nil
    37  		} else if cmt1.IsAncestor(c, cmt0) {
    38  			return cmt1, nil
    39  		}
    40  	}
    41  	if options.Octopus {
    42  		return MergeBaseOctopus(c, options, commits)
    43  	}
    44  	if len(commits) <= 1 {
    45  		return CommitID{}, fmt.Errorf("MergeBase requires at least 2 commits")
    46  	}
    47  
    48  	// Starting with commits[0]'s parents, perform a bread-first search
    49  	// looking for a commit who's an ancestor of commits[1:]
    50  	tip := commits[0]
    51  	cmt, err := tip.CommitID(c)
    52  	if err != nil {
    53  		return CommitID{}, err
    54  	}
    55  
    56  	// the first level is commit[0]'s parents.
    57  	nextlevel, err := cmt.Parents(c)
    58  	if err != nil {
    59  		return CommitID{}, err
    60  	}
    61  
    62  	// Keep looking until there's no commits left.
    63  	for len(nextlevel) > 0 {
    64  		// Check if we've found a commit who's an ancestor
    65  		// of another commit that was passed. If so, this is
    66  		// the common ancestor of the "hypothetical merge commit"
    67  		// that git-merge-base(1) talks about.
    68  		for _, level := range nextlevel {
    69  			check, err := level.CommitID(c)
    70  			if err != nil {
    71  				return CommitID{}, err
    72  			}
    73  			for _, otherhead := range commits[1:] {
    74  				othercmt, err := otherhead.CommitID(c)
    75  				if err != nil {
    76  					return CommitID{}, err
    77  				}
    78  				if check.IsAncestor(c, othercmt) {
    79  					return check, nil
    80  				}
    81  			}
    82  		}
    83  
    84  		// Found nothing, so create a new queue of the
    85  		// next level of parents to check.
    86  		newnextlevel := make([]CommitID, 0)
    87  		for _, parent := range nextlevel {
    88  			parents, err := parent.Parents(c)
    89  			if err != nil {
    90  				return CommitID{}, err
    91  			}
    92  			newnextlevel = append(newnextlevel, parents...)
    93  		}
    94  		nextlevel = newnextlevel
    95  	}
    96  
    97  	// If nothing was found it's not an error, it just means the
    98  	// merge-base is 00000000000000000000
    99  	return CommitID{}, nil
   100  }
   101  
   102  func MergeBaseOctopus(c *Client, options MergeBaseOptions, commits []Commitish) (CommitID, error) {
   103  	var bestSoFar Commitish = commits[0]
   104  	for _, commit := range commits[1:] {
   105  		closest, err := NearestCommonParent(c, bestSoFar, commit)
   106  		if err != nil {
   107  			return CommitID{}, err
   108  		}
   109  		bestSoFar = closest
   110  	}
   111  	return bestSoFar.CommitID(c)
   112  }