aboutsummaryrefslogtreecommitdiff
path: root/internal/commitquery/merge_bases.go
blob: 62ab9bd6d86a37ff60a51f5bdaf21c6801fe4c2f (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
106
107
108
109
110
111
112
113
114
115
116
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
	}

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

	candidates := collectMarkedResults(ctx)

	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) error {
	ctx.BeginMarkPhase()

	ctx.SetMarks(left, markLeft)

	if len(rights) == 0 {
		ctx.SetMarks(left, markResult)

		return nil
	}

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

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

	lastGeneration := generationInfinity

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

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

		lastGeneration = generation
		if generation < minGeneration {
			break
		}

		flags := ctx.Marks(idx) & (markLeft | markRight | markStale)
		if flags == (markLeft | markRight) {
			ctx.SetMarks(idx, markResult)

			flags |= markStale
		}

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

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

	return nil
}

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

	return false
}

func collectMarkedResults(ctx *Context) []NodeIndex {
	out := make([]NodeIndex, 0, 4)

	for _, idx := range ctx.touched {
		if !ctx.HasAnyMarks(idx, markResult) {
			continue
		}

		if ctx.HasAnyMarks(idx, markStale) {
			continue
		}

		out = append(out, idx)
	}

	return out
}