Skip to main content

fallow_output/
audit_ownership.rs

1//! Audit review-brief ownership output contracts.
2
3use serde::Serialize;
4
5/// Maximum number of owner groups listed in [`OwnershipFacts::groups`]. The
6/// groups beyond the cap are the lightest ones and are counted in
7/// [`OwnershipFacts::groups_omitted`].
8pub const OWNER_GROUP_CAP: usize = 10;
9
10/// How far a changeset reaches across CODEOWNERS owner groups.
11///
12/// Computed from the CODEOWNERS file alone: no git history is read, so the
13/// section is present whenever a CODEOWNERS file is found, also when the churn
14/// walk behind `routing` finds nothing. Each file maps to its primary owner
15/// (the first owner of the last matching rule). A file that no rule matches,
16/// or that a GitLab negation rule matches, belongs to the `(unowned)` group.
17/// The owner strings use the same vocabulary as `routing.units[].expert`.
18///
19/// Absent from the brief when no CODEOWNERS file is found, or when the file
20/// cannot be read or does not parse. A configured `codeowners` path that
21/// fails also prints a warning on stderr.
22///
23/// `groups[].direct_count` counts all changed files, source or not, so the
24/// sum over all groups is the number of changed files, not the size of
25/// `impact_closure.in_diff`. Slice owners count only the files of the
26/// partition units, which are source files.
27#[derive(Debug, Clone, Default, Serialize)]
28#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
29pub struct OwnershipFacts {
30    /// Distinct owner groups across the changed files and the impact closure.
31    /// The `(unowned)` group counts as one group. Exact, never capped.
32    pub group_count: usize,
33    /// Owner groups that own no changed file and appear only through the
34    /// impact closure. Exact, never capped.
35    pub transitive_only_count: usize,
36    /// Changed files that belong to the `(unowned)` group.
37    pub unowned_direct_count: usize,
38    /// The owner groups, sorted by `direct_count` descending, then
39    /// `affected_count` descending, then `owner`. At most [`OWNER_GROUP_CAP`]
40    /// entries. The counts in each entry are exact.
41    pub groups: Vec<OwnerGroupFact>,
42    /// How many owner groups did not fit within [`OWNER_GROUP_CAP`] and are
43    /// absent from `groups`. Zero when nothing was omitted.
44    pub groups_omitted: usize,
45    /// The owner set of each independent slice, aligned by index with
46    /// `partition.independent_slices`. Present only when that list is present
47    /// (two or more slices). A fact for the reviewer, never a demand to split.
48    #[serde(default, skip_serializing_if = "Vec::is_empty")]
49    pub slices: Vec<OwnershipSliceFact>,
50}
51
52/// One owner group and how many files of the changeset it owns.
53#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
54#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
55pub struct OwnerGroupFact {
56    /// The CODEOWNERS owner (`@user`, `@org/team`, or an email), or
57    /// `(unowned)`.
58    pub owner: String,
59    /// Changed files this group owns.
60    pub direct_count: usize,
61    /// Files of the impact closure (affected, not in the diff) this group owns.
62    pub affected_count: usize,
63}
64
65/// The owners of one independent slice of the partition.
66#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
67#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
68pub struct OwnershipSliceFact {
69    /// The module directories of the slice, as in
70    /// `partition.independent_slices`.
71    pub module_dirs: Vec<String>,
72    /// The distinct owners of the changed files in the slice, sorted. The
73    /// `(unowned)` group is a distinct owner. Never empty.
74    pub owners: Vec<String>,
75    /// True when the slice has exactly one owner, so one owner group can
76    /// review it on its own.
77    pub separable: bool,
78}