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 }