ci: scope format presubmit to changed files (#2232) - #2289
Conversation
There was a problem hiding this comment.
Code Review
This pull request updates the fix_format.sh script to support formatting only changed files via the --changed and --base flags, and adds a --plan option to preview which formatters would run. It also includes a new Python test suite (test_fix_format.py) to verify this scoping logic and updates the contribution guidelines. The review feedback suggests optimizing the changed-file discovery by removing redundant git diff commands and improving robustness by replacing echo with printf when printing file paths.
| git diff --name-only --diff-filter=ACMRT "$diff_point" -- | ||
| git diff --name-only --diff-filter=ACMRT -- | ||
| git diff --cached --name-only --diff-filter=ACMRT -- |
There was a problem hiding this comment.
The second and third git diff commands are redundant. git diff ... "$diff_point" compares the working tree against the merge base, which already includes all committed changes on the branch, staged changes in the index, and unstaged changes in the working tree. Removing the redundant git diff calls improves performance by avoiding unnecessary Git operations.
| git diff --name-only --diff-filter=ACMRT "$diff_point" -- | |
| git diff --name-only --diff-filter=ACMRT -- | |
| git diff --cached --name-only --diff-filter=ACMRT -- | |
| git diff --name-only --diff-filter=ACMRT "$diff_point" -- |
| git diff --cached --name-only --diff-filter=ACMRT -- | ||
| git ls-files --others --exclude-standard | ||
| } | sort -u | while IFS= read -r path; do | ||
| [[ -n "$path" && -f "$path" ]] && echo "$path" |
There was a problem hiding this comment.
Using echo to print arbitrary variables (like file paths) can lead to unexpected behavior if the path starts with a hyphen (e.g., -n or -e), as echo may interpret it as an option. It is safer and more robust to use printf '%s\n' instead.
| [[ -n "$path" && -f "$path" ]] && echo "$path" | |
| [[ -n "$path" && -f "$path" ]] && printf '%s\n' "$path" |
|
Both applied, thanks. Redundant
Also in this push, unrelated to the review: the |
|
I am rejecting your PR - our policy is to have all files formatted in a right way. If formatting rules change upstream, someone should reformat all the files. |
Fixes #2232
Problem
scripts/fix_format.shruns Prettier, Pyink,dart format,swift-format, and ktfmt across the entire workspace on every PR. A contributor touching only Swift can be blocked by unrelated Dart formatting drift, as happened in this run. Their choices are to pull unrelated formatting churn into their PR or to wait for someone else to fix it. As the monorepo grows this gets worse.Approach
The obvious fix — adding
paths:filters to thepresubmit-lintworkflow — is a trap.format-checkis a required check, and a required check that is filtered out never reports a conclusion, so PRs that skip it sit pending forever. So the job still always runs; it narrows its own work instead.fix_format.shgains a--changedmode that resolves the changed set from git and hands each formatter only its own files, skipping formatters whose languages the branch never touched. Default behaviour (whole repo) is unchanged, and post-submit onmainstill sweeps everything, so scoping can never let drift accumulate.Changes
scripts/fix_format.sh--changed,--base <ref>,--plan,--help. Existing invocations (fix_format.sh,fix_format.sh --check) behave exactly as before.mainafter the branch point are not dragged in.--changedcan never widen what a formatter touches:samples/client/flutterandrenderers/flutter.Package.swiftandswift/./generated/explicitly, because pyink'sextend-excludeinpyproject.tomlapplies to directory walks but not to explicitly listed files. Without this, scoped runs would have formatted generated code that whole-repo runs skip.--planprints which formatters would run and over how many files, then exits. It needs no language toolchains..github/workflows/presubmit-lint.ymlformat-checkcomputes its scope fromgithub.event.pull_request.base.shaon PRs, and stays in whole-repo mode for pushes tomain.--planoutput: Node, Python, and Dart setup are each skipped when that formatter has no work. A Swift-only PR now installs none of the three.fetch-depth: 0so the merge base is reachable.static-checksjob.scripts/test_fix_format.py— 21 tests covering the scoping logic.CONTRIBUTING.md— documents--changed,--base, and--plan.Verification
The issue's exact scenario, run against this repo with a single added Swift file:
Feeding that through the workflow's gating logic:
Dart formatting can no longer block a Swift-only PR, and the job stops installing three toolchains it will not use.
Scoped mode on this PR's own diff:
Correct failure behaviour — a deliberately malformed TS file is caught, and only that file is checked:
Backwards compatibility — the default whole-repo path is unchanged and still passes:
Unit tests:
They cover: Swift-only / TypeScript-only / Dart-only isolation,
Package.swiftat the root, generated-Python exclusion, Dart outside the two formatted roots, Kotlin→Gradle-module mapping (including modules without ktfmt), untracked files, deleted files, unioning committed and working-tree changes, merge-base isolation from unrelatedmaincommits, whole-repo mode, and the two error paths.Notes for reviewers
PRETTIER_EXTENSIONS). Handing Prettier the raw changed set with--ignore-unknownwas simpler, but then any changed file at all — a.sh, a.png— marks Prettier as needed and forces a Node install. The allowlist covers Prettier's built-in parsers; a Prettier plugin adding a new language would need a line here. The whole-repo post-submit run is the backstop if that is ever missed.mapfileis deliberately avoided so the script keeps running on the bash 3.2 that ships with macOS.workspace-lintstill runsyarn build:all+lint:allfor every PR, andstatic-checksstill checks license headers repo-wide. Both have the same "unrelated work blocks your PR" shape, but scoping them means reasoning about the Yarn workspace/wireit graph, which felt like a separate change rather than something to bundle here.