aboutsummaryrefslogtreecommitdiff
path: root/internal/commitquery/merge_bases.go
blob: b0171f5ef308301e6b91e304de3584c148030b4a (about) (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
package commitquery

import "slices"

// MergeBases computes fully reduced merge bases using one query context.
func MergeBases(ctx *Context, left, right NodeIndex) ([]NodeIndex, error) {
	if left == right {
		return []NodeIndex{left}, nil
	}

	candidates, err := paintDownToCommon(ctx, left, []NodeIndex{right}, 0)
	if err != nil {
		return nil, err
	}

	if len(candidates) <= 1 {
		slices.SortFunc(candidates, ctx.Compare)

		return candidates, nil
	}

	ctx.ClearTouchedMarks(allMarks)

	reduced, err := removeRedundant(ctx, candidates)
	if err != nil {
		return nil, err
	}

	slices.SortFunc(reduced, ctx.Compare)

	return reduced, nil
}

func paintDownToCommon(ctx *Context, left NodeIndex, rights []NodeIndex, minGeneration uint64) ([]NodeIndex, error) {
	ctx.BeginMarkPhase()

	ctx.SetMarks(left, markLeft)

	if len(rights) == 0 {
		return []NodeIndex{left}, nil
	}

	queue := NewPriorityQueue(ctx)
	queue.PushNode(left)

	for _, right := range rights {
		ctx.SetMarks(right, markRight)
		queue.PushNode(right)
	}

	lastGeneration := generationInfinity
	results := make([]NodeIndex, 0, 4)

	for queueHasNonStale(ctx, queue) {
		idx := queue.PopNode()

		generation := ctx.EffectiveGeneration(idx)
		if generation > lastGeneration {
			return nil, errBadGenerationOrder
		}

		lastGeneration = generation
		if generation < minGeneration {
			break
		}

		flags := ctx.Marks(idx) & (markLeft | markRight | markStale)
		if flags == (markLeft | markRight) {
			if !ctx.HasAnyMarks(idx, markResult) {
				ctx.SetMarks(idx, markResult)
				results = append(results, idx)
			}

			flags |= markStale
		}

		for _, parent := range ctx.Parents(idx) {
			if ctx.HasAllMarks(parent, flags) {
				continue
			}

			ctx.SetMarks(parent, flags)
			queue.PushNode(parent)
		}
	}

	out := results[:0]
	for _, idx := range results {
		if !ctx.HasAnyMarks(idx, markStale) {
			out = append(out, idx)
		}
	}

	return out, nil
}

func queueHasNonStale(ctx *Context, queue *PriorityQueue) bool {
	for _, idx := range queue.items {
		if !ctx.HasAnyMarks(idx, markStale) {
			return true
		}
	}

	return false
}