From af84e04f2a33a8b94bc65e61a429ccae6b9627d1 Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Tue, 12 May 2026 22:28:56 +0200 Subject: [PATCH] Add a lint-chmod task to catch stray executable bits --- external/iccs/CGATS001Compat-v2-micro.icc | Bin gulpfile.mjs | 84 +++++++++++++++++++++- test/images/samplesignature.png | Bin 3 files changed, 83 insertions(+), 1 deletion(-) mode change 100755 => 100644 external/iccs/CGATS001Compat-v2-micro.icc mode change 100755 => 100644 test/images/samplesignature.png diff --git a/external/iccs/CGATS001Compat-v2-micro.icc b/external/iccs/CGATS001Compat-v2-micro.icc old mode 100755 new mode 100644 diff --git a/gulpfile.mjs b/gulpfile.mjs index 1b948115258fd..3cad6a0cecf32 100644 --- a/gulpfile.mjs +++ b/gulpfile.mjs @@ -2311,6 +2311,88 @@ gulp.task("lint-licenses", function (done) { }); }); +gulp.task("lint-chmod", function (done) { + console.log("\n### Checking executable bit on tracked files"); + + // Tracked files allowed to keep the executable bit (shebang scripts). + const EXECUTABLE_FILES = new Set(["test/chromium/test-telemetry.js"]); + + let lsFiles; + try { + lsFiles = execSync("git ls-files -s", { encoding: "utf8" }); + } catch (e) { + done(e); + return; + } + + const offenders = []; + for (const line of lsFiles.split("\n")) { + if (!line) { + continue; + } + // " \t" + const tab = line.indexOf("\t"); + const mode = line.slice(0, 6); + const file = line.slice(tab + 1); + if (mode === "100755" && !EXECUTABLE_FILES.has(file)) { + offenders.push(file); + } + } + + if (offenders.length === 0) { + console.log("files checked, no errors found"); + done(); + return; + } + + // --fix here also stages the chmod (via `git update-index`), which is + // unlike every other --fix in `gulp lint` (those only touch the working + // tree). Restrict the auto-fix to a direct `gulp lint-chmod --fix` + // invocation so the umbrella `gulp lint --fix` never silently stages. + const fix = + process.argv.includes("lint-chmod") && process.argv.includes("--fix"); + if (!fix) { + for (const file of offenders.sort()) { + console.log(` Unexpected executable bit: ${file}`); + } + done( + new Error( + "Executable-bit check failed (run `gulp lint-chmod --fix` to clear, then commit)." + ) + ); + return; + } + + // Drop the bit on disk too: `git update-index --chmod=-x` only edits the + // index, so a later `git add` would re-read the still-executable working + // tree and undo the staged chmod. + for (const file of offenders) { + try { + const { mode } = fs.statSync(file); + fs.chmodSync(file, mode & ~0o111); + } catch (e) { + done(e); + return; + } + } + + // Chunk the path list so we stay well under ARG_MAX. + const CHUNK = 256; + for (let i = 0; i < offenders.length; i += CHUNK) { + const result = spawnSync( + "git", + ["update-index", "--chmod=-x", "--", ...offenders.slice(i, i + CHUNK)], + { stdio: "inherit" } + ); + if (result.status !== 0) { + done(new Error("git update-index failed.")); + return; + } + } + console.log(`cleared executable bit on ${offenders.length} file(s)`); + done(); +}); + gulp.task("lint", function (done) { console.log("\n### Linting JS/CSS/JSON/SVG/HTML files"); @@ -2375,7 +2457,7 @@ gulp.task("lint", function (done) { return; } - gulp.task("lint-licenses")(done); + gulp.series("lint-licenses", "lint-chmod")(done); }); }); diff --git a/test/images/samplesignature.png b/test/images/samplesignature.png old mode 100755 new mode 100644