github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/pkg/graveler/ref/merge_base_finder.go (about)

     1  package ref
     2  
     3  import (
     4  	"container/heap"
     5  	"context"
     6  
     7  	"github.com/treeverse/lakefs/pkg/graveler"
     8  )
     9  
    10  type CommitGetter interface {
    11  	GetCommit(ctx context.Context, repository *graveler.RepositoryRecord, commitID graveler.CommitID) (*graveler.Commit, error)
    12  }
    13  
    14  type reachedFlags uint8
    15  
    16  const (
    17  	fromLeft reachedFlags = 1 << iota
    18  	fromRight
    19  )
    20  
    21  // FindMergeBase finds the best common ancestor according to the definition in the git-merge-base documentation: https://git-scm.com/docs/git-merge-base
    22  // One common ancestor is better than another common ancestor if the latter is an ancestor of the former.
    23  func FindMergeBase(ctx context.Context, getter CommitGetter, repository *graveler.RepositoryRecord, leftID, rightID graveler.CommitID) (*graveler.Commit, error) {
    24  	var cr *graveler.CommitRecord
    25  	queue := NewCommitsGenerationPriorityQueue()
    26  	reached := make(map[graveler.CommitID]reachedFlags)
    27  	reached[rightID] |= fromRight
    28  	reached[leftID] |= fromLeft
    29  	commit, err := getCommitAndEnqueue(ctx, getter, &queue, repository, leftID)
    30  	if err != nil {
    31  		return nil, err
    32  	}
    33  	if leftID == rightID {
    34  		return commit, nil
    35  	}
    36  
    37  	_, err = getCommitAndEnqueue(ctx, getter, &queue, repository, rightID)
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  	for {
    42  		if queue.Len() == 0 {
    43  			return nil, nil
    44  		}
    45  		cr = heap.Pop(&queue).(*graveler.CommitRecord)
    46  		commitFlags := reached[cr.CommitID]
    47  		for _, parent := range cr.Parents {
    48  			if _, exist := reached[parent]; !exist {
    49  				// parent commit is queued only if it was not handled before. Otherwise, it and
    50  				// all its ancestors were already queued and so, will have entries in 'reached' map
    51  				_, err := getCommitAndEnqueue(ctx, getter, &queue, repository, parent)
    52  				if err != nil {
    53  					return nil, err
    54  				}
    55  			}
    56  			// mark the parent with the flag values from its descendents. This is done regardless
    57  			// of whether this parent commit is being queued in the current iteration or not. In
    58  			// both cases, if the 'reached' update signifies it was reached from both left and
    59  			// right nodes - it is the requested parent node
    60  			reached[parent] |= commitFlags
    61  			if reached[parent]&fromLeft != 0 && reached[parent]&fromRight != 0 {
    62  				// commit was reached from both left and right nodes
    63  				return getter.GetCommit(ctx, repository, parent)
    64  			}
    65  		}
    66  	}
    67  }
    68  
    69  func getCommitAndEnqueue(ctx context.Context, getter CommitGetter, queue *CommitsGenerationPriorityQueue, repository *graveler.RepositoryRecord, commitID graveler.CommitID) (*graveler.Commit, error) {
    70  	commit, err := getter.GetCommit(ctx, repository, commitID)
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  	heap.Push(queue, &graveler.CommitRecord{CommitID: commitID, Commit: commit})
    75  	return commit, nil
    76  }