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
//! Audit review-brief ownership output contracts.
use serde::Serialize;
/// Maximum number of owner groups listed in [`OwnershipFacts::groups`]. The
/// groups beyond the cap are the lightest ones and are counted in
/// [`OwnershipFacts::groups_omitted`].
pub const OWNER_GROUP_CAP: usize = 10;
/// How far a changeset reaches across CODEOWNERS owner groups.
///
/// Computed from the CODEOWNERS file alone: no git history is read, so the
/// section is present whenever a CODEOWNERS file is found, also when the churn
/// walk behind `routing` finds nothing. Each file maps to its primary owner
/// (the first owner of the last matching rule). A file that no rule matches,
/// or that a GitLab negation rule matches, belongs to the `(unowned)` group.
/// The owner strings use the same vocabulary as `routing.units[].expert`.
///
/// Absent from the brief when no CODEOWNERS file is found, or when the file
/// cannot be read or does not parse. A configured `codeowners` path that
/// fails also prints a warning on stderr.
///
/// `groups[].direct_count` counts all changed files, source or not, so the
/// sum over all groups is the number of changed files, not the size of
/// `impact_closure.in_diff`. Slice owners count only the files of the
/// partition units, which are source files.
#[derive(Debug, Clone, Default, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct OwnershipFacts {
/// Distinct owner groups across the changed files and the impact closure.
/// The `(unowned)` group counts as one group. Exact, never capped.
pub group_count: usize,
/// Owner groups that own no changed file and appear only through the
/// impact closure. Exact, never capped.
pub transitive_only_count: usize,
/// Changed files that belong to the `(unowned)` group.
pub unowned_direct_count: usize,
/// The owner groups, sorted by `direct_count` descending, then
/// `affected_count` descending, then `owner`. At most [`OWNER_GROUP_CAP`]
/// entries. The counts in each entry are exact.
pub groups: Vec<OwnerGroupFact>,
/// How many owner groups did not fit within [`OWNER_GROUP_CAP`] and are
/// absent from `groups`. Zero when nothing was omitted.
pub groups_omitted: usize,
/// The owner set of each independent slice, aligned by index with
/// `partition.independent_slices`. Present only when that list is present
/// (two or more slices). A fact for the reviewer, never a demand to split.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub slices: Vec<OwnershipSliceFact>,
}
/// One owner group and how many files of the changeset it owns.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct OwnerGroupFact {
/// The CODEOWNERS owner (`@user`, `@org/team`, or an email), or
/// `(unowned)`.
pub owner: String,
/// Changed files this group owns.
pub direct_count: usize,
/// Files of the impact closure (affected, not in the diff) this group owns.
pub affected_count: usize,
}
/// The owners of one independent slice of the partition.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct OwnershipSliceFact {
/// The module directories of the slice, as in
/// `partition.independent_slices`.
pub module_dirs: Vec<String>,
/// The distinct owners of the changed files in the slice, sorted. The
/// `(unowned)` group is a distinct owner. Never empty.
pub owners: Vec<String>,
/// True when the slice has exactly one owner, so one owner group can
/// review it on its own.
pub separable: bool,
}