Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 1 | //! The dependency update file on the default branch: which of |
| 2 | //! [`DEPENDABOT_PATHS`] is read, and what it says, as the Security page | |
| 3 | //! shows it. A file under `.g1t/` is read in place of one under | |
| 4 | //! `.github/`, which is reported as ignored. Reading and checking the | |
| 5 | //! format is `config`'s; acting on it is `version_updates`'s. | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 6 | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 7 | use g1t_contracts::updates::{DEPENDABOT_PATHS, VersionUpdateEntry, VersionUpdatesState}; |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 8 | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 9 | use crate::config::{self, Config, Entry}; |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 10 | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 11 | /// A file found: its path and its text, if it is text. |
| 12 | pub type Found = (String, Option<String>); | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 13 | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 14 | /// The file to read among those found (each a path and its text, in any |
| 15 | /// order), and the others, which are ignored. | |
| 16 | pub fn choose(found: Vec<Found>) -> Option<(Found, Vec<String>)> { | |
| 17 | let mut found = found; | |
| 18 | found.sort_by_key(|(path, _)| DEPENDABOT_PATHS.iter().position(|known| known == path).unwrap_or(usize::MAX)); | |
| 19 | let mut found = found.into_iter(); | |
| 20 | let first = found.next()?; | |
| 21 | Some((first, found.map(|(path, _)| path).collect())) | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 22 | } |
| 23 | ||
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 24 | /// How one entry is shown. `seed` picks its time of day when the |
| 25 | /// schedule names none: the repository's id. | |
| 26 | pub fn entry_view(entry: &Entry, seed: &str, default_branch: Option<&str>) -> VersionUpdateEntry { | |
| 27 | let schedule_seed = format!("{seed}:{}", entry.id()); | |
| 28 | VersionUpdateEntry { | |
| 29 | id: entry.id(), | |
| 30 | ecosystem: entry.ecosystem.clone(), | |
| 31 | directories: entry.directories.clone(), | |
| 32 | supported: entry.supported(), | |
| 33 | interval: entry.schedule.as_ref().map(|schedule| schedule.interval.as_str().to_owned()).unwrap_or_default(), | |
| 34 | schedule: entry.schedule.as_ref().map(|schedule| schedule.describe(&schedule_seed)).unwrap_or_default(), | |
| 35 | open_pull_requests_limit: entry.open_pull_requests_limit, | |
| 36 | target_branch: entry.target_branch.clone(), | |
| 37 | multi_ecosystem_group: entry.multi_ecosystem_group.clone(), | |
| 38 | groups: entry.groups.clone(), | |
| 39 | ignore: entry.ignore.clone(), | |
| 40 | allow: entry.allow.clone(), | |
| 41 | labels: entry.labels.clone(), | |
| 42 | assignees: entry.assignees.clone(), | |
| 43 | reviewers: entry.reviewers.clone(), | |
| 44 | milestone: entry.milestone, | |
| 45 | versioning_strategy: entry.versioning_strategy.clone(), | |
| 46 | options: entry.options.clone(), | |
| 47 | notes: config::notes(entry, default_branch), | |
| 48 | ..VersionUpdateEntry::default() | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 49 | } |
| 50 | } | |
| 51 | ||
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 52 | /// The schedule seed for an entry of a repository: what keeps its picked |
| 53 | /// time of day the same from one read to the next. | |
| 54 | pub fn seed(repo_id: &str, entry: &Entry) -> String { | |
| 55 | format!("{repo_id}:{}", entry.id()) | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 56 | } |
| 57 | ||
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 58 | /// What the file at `path` says, for the Security page. `None` text is a |
| 59 | /// file too large or not text. | |
| 60 | pub fn state( | |
| 61 | path: &str, | |
| 62 | text: Option<&str>, | |
| 63 | ignored_paths: Vec<String>, | |
| 64 | repo_id: &str, | |
| 65 | default_branch: Option<&str>, | |
| 66 | ) -> (VersionUpdatesState, Option<Config>) { | |
| 67 | let mut state = VersionUpdatesState { found: true, path: Some(path.to_owned()), ignored_paths, ..VersionUpdatesState::default() }; | |
| 68 | let Some(text) = text else { | |
| 69 | state.error = Some(format!("{path} is too large to read or is not text.")); | |
| 70 | return (state, None); | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 71 | }; |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 72 | let read = config::read(text); |
| 73 | state.updates = read.config.updates.iter().map(|entry| entry_view(entry, repo_id, default_branch)).collect(); | |
| 74 | state.registries = read.config.registries.iter().map(|registry| registry.info()).collect(); | |
| 75 | state.error = read.problems.first().map(|problem| problem.sentence()); | |
| 76 | let valid = read.problems.is_empty(); | |
| 77 | state.problems = read.problems; | |
| 78 | (state, valid.then_some(read.config)) | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 79 | } |
| 80 | ||
| 81 | #[cfg(test)] | |
| 82 | mod tests { | |
| 83 | use super::*; | |
| 84 | ||
| 85 | #[test] | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 86 | fn g1t_comes_before_github() { |
| 87 | let found = vec![ | |
| 88 | (".github/dependabot.yml".to_owned(), Some("a".to_owned())), | |
| 89 | (".g1t/dependabot.yaml".to_owned(), Some("b".to_owned())), | |
| 90 | ]; | |
| 91 | let ((path, text), ignored) = choose(found).unwrap(); | |
| 92 | assert_eq!((path.as_str(), text.as_deref()), (".g1t/dependabot.yaml", Some("b"))); | |
| 93 | assert_eq!(ignored, [".github/dependabot.yml"]); | |
| 94 | assert!(choose(Vec::new()).is_none()); | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 95 | } |
| 96 | ||
| 97 | #[test] | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 98 | fn the_page_sees_entries_and_problems() { |
| 99 | let source = "version: 2\nupdates:\n - package-ecosystem: npm\n directory: /\n schedule:\n interval: weekly\n time: \"05:00\"\n - package-ecosystem: docker\n directory: /\n schedule: {interval: monthly}\n"; | |
| 100 | let (found, config) = state(".github/dependabot.yml", Some(source), Vec::new(), "rep_1", Some("main")); | |
| 101 | assert!(config.is_some() && found.error.is_none()); | |
| 102 | assert_eq!(found.updates.len(), 2); | |
| 103 | assert_eq!(found.updates[0].schedule, "Mondays at 05:00 (UTC)"); | |
| 104 | assert!(found.updates[0].supported && !found.updates[1].supported); | |
| 105 | assert_eq!(found.updates[0].id, "npm:/"); | |
| 106 | ||
| 107 | let (broken, config) = state(".github/dependabot.yml", Some("version: 2\nupdates:\n - package-ecosystem: npm\n"), Vec::new(), "rep_1", None); | |
| 108 | assert!(config.is_none()); | |
| 109 | assert_eq!(broken.error.as_deref(), Some("line 3, updates[0]: `directory` (or `directories`) is required: where the manifest is, such as \"/\".")); | |
| 110 | let (binary, _) = state(".g1t/dependabot.yml", None, Vec::new(), "rep_1", None); | |
| 111 | assert!(binary.error.unwrap().contains("too large")); | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 112 | } |
| 113 | } |