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
117
118
119
|
// Copyright 2024 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
package triage
import (
"time"
"github.com/google/syzkaller/pkg/debugtracer"
"github.com/google/syzkaller/pkg/vcs"
"github.com/google/syzkaller/syz-cluster/pkg/api"
)
// TODO: Some further improvements:
// 1. Add support for experimental sessions: these may be way behind the current HEAD.
type TreeOps interface {
HeadCommit(tree *api.Tree) (*vcs.Commit, error)
ApplySeries(commit string, patches [][]byte) error
}
type CommitSelector struct {
ops TreeOps
tracer debugtracer.DebugTracer
}
func NewCommitSelector(ops TreeOps, tracer debugtracer.DebugTracer) *CommitSelector {
return &CommitSelector{ops: ops, tracer: tracer}
}
type SelectResult struct {
Commit string
Reason string // Set if Commit is empty.
}
const (
reasonSeriesTooOld = "series lags behind the current HEAD too much"
reasonNotApplies = "series does not apply"
)
// Select returns the best matching commit hash.
func (cs *CommitSelector) Select(series *api.Series, tree *api.Tree, lastBuild *api.Build) (SelectResult, error) {
head, err := cs.ops.HeadCommit(tree)
if err != nil || head == nil {
return SelectResult{}, err
}
cs.tracer.Log("current HEAD: %q (commit date: %v)", head.Hash, head.CommitDate)
// If the series is already too old, it may be incompatible even if it applies cleanly.
const seriesLagsBehind = time.Hour * 24 * 7
if diff := head.CommitDate.Sub(series.PublishedAt); series.PublishedAt.Before(head.CommitDate) &&
diff > seriesLagsBehind {
cs.tracer.Log("the series is too old: %v before the HEAD", diff)
return SelectResult{Reason: reasonSeriesTooOld}, nil
}
// Algorithm:
// 1. If the last successful build is sufficiently new, prefer it over the last master.
// We should it be renewing it regularly, so the commit should be quite up to date.
// 2. If the last build is too old / the series does not apply, give a chance to the
// current HEAD.
var hashes []string
if lastBuild != nil {
// Check if the commit is still good enough.
if diff := head.CommitDate.Sub(lastBuild.CommitDate); diff > seriesLagsBehind {
cs.tracer.Log("the last successful build is already too old: %v, skipping", diff)
} else {
hashes = append(hashes, lastBuild.CommitHash)
}
}
for _, hash := range append(hashes, head.Hash) {
cs.tracer.Log("considering %q", hash)
err := cs.ops.ApplySeries(hash, series.PatchBodies())
if err == nil {
cs.tracer.Log("series can be applied to %q", hash)
return SelectResult{Commit: hash}, nil
} else {
cs.tracer.Log("failed to apply to %q: %v", hash, err)
}
}
return SelectResult{Reason: reasonNotApplies}, nil
}
func FromBaseCommits(series *api.Series, baseCommits []*vcs.BaseCommit, trees []*api.Tree) (*api.Tree, string) {
// Technically, any one of baseCommits could be a good match.
// However, the developers have their own expectations regarding
// what tree and what branch are actually preferred there.
// So, among baseCommits, we still give preference to those that
// align with the mailing lists Cc'd by the patch series.
tree, commit := bestCommit(baseCommits, SelectTrees(series, trees))
if tree != nil {
return tree, commit
}
return bestCommit(baseCommits, trees)
}
func bestCommit(baseCommits []*vcs.BaseCommit, trees []*api.Tree) (*api.Tree, string) {
retTreeIdx, retSameBranch, retCommit := -1, false, ""
for _, commit := range baseCommits {
for _, commitBranch := range commit.Branches {
treeIdx, branch := FindTree(trees, commitBranch)
if treeIdx < 0 {
continue
}
sameBranch := branch == trees[treeIdx].Branch
// If, for the same tree, we also have matched the branch, even better.
if retTreeIdx < 0 || treeIdx < retTreeIdx ||
treeIdx == retTreeIdx && !retSameBranch && sameBranch {
retTreeIdx = treeIdx
retSameBranch = sameBranch
retCommit = commit.Hash
}
}
}
if retTreeIdx < 0 {
return nil, ""
}
return trees[retTreeIdx], retCommit
}
|