Skip to content

Commit 7572e0f

Browse files
authored
Merge pull request #8 from nickel-lang/ci-changes
Detect CI changes and don't fail the job
2 parents 654530f + 8a6df80 commit 7572e0f

File tree

2 files changed

+142
-16
lines changed

2 files changed

+142
-16
lines changed

src/main.rs

Lines changed: 137 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use clap::Parser;
2+
use gitpatch::Patch;
23
use miette::{IntoDiagnostic, bail};
34
use nickel_lang_package::{
45
config::Config,
@@ -67,7 +68,7 @@ impl Permission {
6768

6869
enum Report {
6970
InvalidDiff(package::Error),
70-
PackageReports(Vec<PackageReport>),
71+
PackageReports(Vec<Box<dyn ReportItem>>),
7172
}
7273

7374
impl Report {
@@ -85,13 +86,19 @@ impl std::fmt::Display for Report {
8586
Report::InvalidDiff(e) => writeln!(f, "❌ invalid index changes: {e}"),
8687
Report::PackageReports(package_reports) => {
8788
for r in package_reports {
88-
r.format(f, " - ")?;
89+
r.format_with_indent(f, " - ")?;
8990
}
9091
Ok(())
9192
}
9293
}
9394
}
9495
}
96+
97+
trait ReportItem {
98+
fn is_good(&self) -> bool;
99+
fn format_with_indent(&self, f: &mut std::fmt::Formatter, indent: &str) -> std::fmt::Result;
100+
}
101+
95102
struct PackageReport {
96103
pkg: Package,
97104
permission: Permission,
@@ -128,7 +135,9 @@ impl PackageReport {
128135
status,
129136
})
130137
}
138+
}
131139

140+
impl ReportItem for PackageReport {
132141
fn is_good(&self) -> bool {
133142
self.permission.is_allowed
134143
&& match &self.status {
@@ -137,7 +146,7 @@ impl PackageReport {
137146
}
138147
}
139148

140-
fn format(&self, f: &mut std::fmt::Formatter, indent: &str) -> std::fmt::Result {
149+
fn format_with_indent(&self, f: &mut std::fmt::Formatter, indent: &str) -> std::fmt::Result {
141150
let PreciseId::Github {
142151
org, name, path, ..
143152
} = &self.pkg.id;
@@ -151,26 +160,26 @@ impl PackageReport {
151160
if perm.is_allowed {
152161
writeln!(
153162
f,
154-
"{indent_spaces}* ✅ this PR is by {}, a collaborator on {}/{}",
163+
"{indent_spaces}*✅ this PR is by {}, a collaborator on {}/{}",
155164
perm.user, perm.org, perm.repo
156165
)?;
157166
} else {
158167
writeln!(
159168
f,
160-
"{indent_spaces}* ❌ this PR is by {}, who is not a public member of {}",
169+
"{indent_spaces}*❌ this PR is by {}, who is not a public member of {}",
161170
perm.user, perm.org
162171
)?;
163172
};
164173

165174
if let PackageStatus::FetchFailed(e) = &self.status {
166-
writeln!(f, "{indent_spaces}* ❌ failed to fetch package: {e}",)?;
175+
writeln!(f, "{indent_spaces}*❌ failed to fetch package: {e}",)?;
167176
} else {
168-
writeln!(f, "{indent_spaces}* ✅ fetched package",)?;
177+
writeln!(f, "{indent_spaces}*✅ fetched package",)?;
169178

170179
if let PackageStatus::EvalFailed(e) = &self.status {
171-
writeln!(f, "{indent_spaces}* ❌ failed to evaluate manifest: {e}",)?;
180+
writeln!(f, "{indent_spaces}*❌ failed to evaluate manifest: {e}",)?;
172181
} else {
173-
writeln!(f, "{indent_spaces}* ✅ evaluated manifest",)?;
182+
writeln!(f, "{indent_spaces}*✅ evaluated manifest",)?;
174183

175184
let PackageStatus::Manifest(checks) = &self.status else {
176185
unreachable!()
@@ -183,22 +192,78 @@ impl PackageReport {
183192
}
184193
}
185194

195+
/// A diagnostic for showing that an unexpected path was modified.
196+
struct PathReport {
197+
is_good: bool,
198+
path: String,
199+
}
200+
201+
impl ReportItem for PathReport {
202+
fn is_good(&self) -> bool {
203+
self.is_good
204+
}
205+
206+
fn format_with_indent(&self, f: &mut std::fmt::Formatter, indent: &str) -> std::fmt::Result {
207+
let sym = if self.is_good { "⚠️" } else { "❌" };
208+
let path = &self.path;
209+
writeln!(f, "{indent}{sym} this PR modifies {path}")
210+
}
211+
}
212+
186213
enum PackageStatus {
187214
FetchFailed(String),
188215
EvalFailed(String),
189216
Manifest(Box<ManifestChecks>),
190217
}
191218

219+
/// Checks the paths of modified files. Removes the ones that aren't modifying
220+
/// packages and adds diagnostic messages for them.
221+
fn check_diff_paths(patches: &mut Vec<Patch>, reports: &mut Vec<Box<dyn ReportItem>>) {
222+
patches.retain(|patch| {
223+
let path = &patch.new.path;
224+
let mut parts = path.split('/');
225+
if parts.next() != Some("b") {
226+
reports.push(Box::new(PathReport {
227+
is_good: false,
228+
path: patch.new.path.clone().into_owned(),
229+
}));
230+
return false;
231+
}
232+
233+
// Trim off the "b/" for a better error message.
234+
let path_without_prefix = &patch.new.path[2..];
235+
let dir = parts.next();
236+
if dir != Some("github") {
237+
// Modifications to our CI are not necessarily bad. Any other path
238+
// is definitely a mistake.
239+
let is_good = dir == Some(".github");
240+
reports.push(Box::new(PathReport {
241+
is_good,
242+
path: path_without_prefix.to_owned(),
243+
}));
244+
return false;
245+
}
246+
true
247+
});
248+
}
249+
192250
async fn make_report(diff: &str, client: &Octocrab, user: &str) -> miette::Result<Report> {
193-
let pkgs = match package::changed_packages(diff) {
251+
let mut reports = Vec::new();
252+
let mut patches = match Patch::from_multiple(diff) {
253+
Ok(p) => p,
254+
Err(e) => return Ok(Report::InvalidDiff(e.into())),
255+
};
256+
check_diff_paths(&mut patches, &mut reports);
257+
let pkgs = match package::changed_packages(patches) {
194258
Ok(p) => p,
195259
Err(e) => return Ok(Report::InvalidDiff(e)),
196260
};
197261

198262
let index = PackageIndex::refreshed(Config::new().into_diag()?).into_diag()?;
199-
let mut reports = Vec::new();
200263
for pkg in pkgs {
201-
reports.push(PackageReport::new(client, user, &index, pkg).await?);
264+
reports.push(Box::new(
265+
PackageReport::new(client, user, &index, pkg).await?,
266+
));
202267
}
203268

204269
Ok(Report::PackageReports(reports))
@@ -230,3 +295,63 @@ async fn main() -> miette::Result<()> {
230295
bail!("Failing report")
231296
}
232297
}
298+
299+
#[cfg(test)]
300+
mod tests {
301+
use gitpatch::Patch;
302+
303+
use crate::{Report, check_diff_paths};
304+
305+
const SAMPLE_CI_DIFF: &str = r#"
306+
diff --git a/.github/workflows/foo.yaml b/.github/workflows/foo.yaml
307+
index df1cd2a..2229806 100644
308+
--- a/.github/workflows/foo.yaml
309+
+++ b/.github/workflows/foo.yaml
310+
@@ -1 +1,2 @@
311+
foo
312+
+bar
313+
"#;
314+
315+
const BAD_PATH_DIFF: &str = r#"
316+
diff --git a/weird_path/foo.yaml b/weird_path/foo.yaml
317+
index df1cd2a..2229806 100644
318+
--- a/weird_path/foo.yaml
319+
+++ b/weird_path/foo.yaml
320+
@@ -1 +1,2 @@
321+
foo
322+
+bar
323+
"#;
324+
325+
#[test]
326+
fn test_ci_changes() {
327+
let mut reports = Vec::new();
328+
let mut patches = Patch::from_multiple(SAMPLE_CI_DIFF).unwrap();
329+
check_diff_paths(&mut patches, &mut reports);
330+
331+
// The CI patch should have been removed from the list.
332+
assert!(patches.is_empty());
333+
let report = Report::PackageReports(reports);
334+
assert!(
335+
report
336+
.to_string()
337+
.contains("this PR modifies .github/workflows/foo.yaml")
338+
);
339+
assert!(report.is_good());
340+
}
341+
342+
#[test]
343+
fn test_bad_path_changes() {
344+
let mut reports = Vec::new();
345+
let mut patches = Patch::from_multiple(BAD_PATH_DIFF).unwrap();
346+
check_diff_paths(&mut patches, &mut reports);
347+
348+
assert!(patches.is_empty());
349+
let report = Report::PackageReports(reports);
350+
assert!(
351+
report
352+
.to_string()
353+
.contains("this PR modifies weird_path/foo.yaml")
354+
);
355+
assert!(!report.is_good());
356+
}
357+
}

src/package.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@ impl<'a> From<gitpatch::ParseError<'a>> for Error {
3737
}
3838
}
3939

40-
pub fn changed_packages(diff: &str) -> Result<Vec<Package>, Error> {
41-
let patches = Patch::from_multiple(diff)?;
40+
pub fn changed_packages(patches: Vec<Patch>) -> Result<Vec<Package>, Error> {
4241
let mut ret = Vec::new();
4342
for patch in patches {
4443
let path = patch.new.path;
@@ -262,14 +261,16 @@ index 0000000..17e1150
262261

263262
#[test]
264263
fn test_changed_packages() {
265-
let packages = changed_packages(SAMPLE_DIFF).unwrap();
264+
let patches = Patch::from_multiple(SAMPLE_DIFF).unwrap();
265+
let packages = changed_packages(patches).unwrap();
266266
assert_eq!(packages.len(), 1);
267267
assert_eq!(packages[0].version, SemVer::new(0, 2, 0));
268268
}
269269

270270
#[test]
271271
fn test_changed_packages_with_subdir() {
272-
let packages = changed_packages(SAMPLE_DIFF_WITH_SUBDIR).unwrap();
272+
let patches = Patch::from_multiple(SAMPLE_DIFF_WITH_SUBDIR).unwrap();
273+
let packages = changed_packages(patches).unwrap();
273274
assert_eq!(packages.len(), 1);
274275
assert_eq!(
275276
packages[0].id,

0 commit comments

Comments
 (0)