-
Notifications
You must be signed in to change notification settings - Fork 3
feat(rfc_tools): enforce Git LFS tracking for media assets #15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
Draft
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| # Source and documentation files | ||
| *.dart text eol=lf diff=dart | ||
| *.md text eol=lf | ||
| *.yaml text eol=lf | ||
| *.yml text eol=lf | ||
| *.json text eol=lf | ||
| *.svg text eol=lf | ||
|
|
||
| # ----------------------------------------------------------------------------- | ||
| # Git LFS Tracking for Media Files | ||
| # Bracketed case patterns (e.g. *.[pP][nN][gG]) ensure case-insensitive matching | ||
| # across Linux, macOS, and Windows environments. | ||
| # ----------------------------------------------------------------------------- | ||
|
|
||
| # Image formats | ||
| *.[pP][nN][gG] filter=lfs diff=lfs merge=lfs -text | ||
| *.[jJ][pP][gG] filter=lfs diff=lfs merge=lfs -text | ||
| *.[jJ][pP][eE][gG] filter=lfs diff=lfs merge=lfs -text | ||
| *.[gG][iI][fF] filter=lfs diff=lfs merge=lfs -text | ||
| *.[wW][eE][bB][pP] filter=lfs diff=lfs merge=lfs -text | ||
| *.[aA][vV][iI][fF] filter=lfs diff=lfs merge=lfs -text | ||
| *.[iI][cC][oO] filter=lfs diff=lfs merge=lfs -text | ||
| *.[bB][mM][pP] filter=lfs diff=lfs merge=lfs -text | ||
| *.[tT][iI][fF][fF] filter=lfs diff=lfs merge=lfs -text | ||
| *.[tT][iI][fF] filter=lfs diff=lfs merge=lfs -text | ||
| *.[hH][eE][iI][cC] filter=lfs diff=lfs merge=lfs -text | ||
| *.[hH][eE][iI][fF] filter=lfs diff=lfs merge=lfs -text | ||
| *.[aA][pP][nN][gG] filter=lfs diff=lfs merge=lfs -text | ||
|
|
||
| # Video formats | ||
| *.[mM][pP]4 filter=lfs diff=lfs merge=lfs -text | ||
| *.[wW][eE][bB][mM] filter=lfs diff=lfs merge=lfs -text | ||
| *.[mM][oO][vV] filter=lfs diff=lfs merge=lfs -text | ||
| *.[aA][vV][iI] filter=lfs diff=lfs merge=lfs -text | ||
| *.[mM][kK][vV] filter=lfs diff=lfs merge=lfs -text | ||
| *.[mM]4[vV] filter=lfs diff=lfs merge=lfs -text | ||
|
|
||
| # Audio formats | ||
| *.[mM][pP]3 filter=lfs diff=lfs merge=lfs -text | ||
| *.[wW][aA][vV] filter=lfs diff=lfs merge=lfs -text | ||
| *.[oO][gG][gG] filter=lfs diff=lfs merge=lfs -text | ||
| *.[mM]4[aA] filter=lfs diff=lfs merge=lfs -text | ||
| *.[aA][aA][cC] filter=lfs diff=lfs merge=lfs -text | ||
| *.[fF][lL][aA][cC] filter=lfs diff=lfs merge=lfs -text | ||
|
|
||
| # Document formats | ||
| *.[pP][dD][fF] filter=lfs diff=lfs merge=lfs -text | ||
|
|
||
| # Font formats | ||
| *.[tT][tT][fF] filter=lfs diff=lfs merge=lfs -text | ||
| *.[oO][tT][fF] filter=lfs diff=lfs merge=lfs -text | ||
| *.[wW][oO][fF][fF] filter=lfs diff=lfs merge=lfs -text | ||
| *.[wW][oO][fF][fF]2 filter=lfs diff=lfs merge=lfs -text | ||
|
|
||
| # Archive formats | ||
| *.[zZ][iI][pP] filter=lfs diff=lfs merge=lfs -text | ||
| *.[tT][aA][rR] filter=lfs diff=lfs merge=lfs -text | ||
| *.[tT][gG][zZ] filter=lfs diff=lfs merge=lfs -text | ||
| *.[tT][aA][rR].[gG][zZ] filter=lfs diff=lfs merge=lfs -text | ||
| *.[gG][zZ] filter=lfs diff=lfs merge=lfs -text | ||
|
|
||
| # Binary artifacts | ||
| *.[jJ][aA][rR] filter=lfs diff=lfs merge=lfs -text | ||
| *.[wW][aA][sS][mM] filter=lfs diff=lfs merge=lfs -text | ||
| *.[aA][pP][kK] filter=lfs diff=lfs merge=lfs -text | ||
|
|
||
| # Design source files | ||
| *.[pP][sS][dD] filter=lfs diff=lfs merge=lfs -text | ||
| *.[sS][kK][eE][tT][cC][hH] filter=lfs diff=lfs merge=lfs -text |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| # Code owners for the flutter/rfc repository. | ||
| # | ||
| # Git LFS enforcement is defined by `.gitattributes` and executed by the | ||
| # workflows below. Because `git lfs fsck` evaluates `.gitattributes` at the | ||
| # revision being checked, a pull request that edits `.gitattributes` can | ||
| # disable the very rule that would have blocked it. These paths therefore | ||
| # require an explicit owner review. | ||
| # | ||
| # NOTE: CODEOWNERS is only enforced when branch protection on `main` has | ||
| # "Require review from Code Owners" enabled. | ||
|
|
||
| /.gitattributes @flutter/owners @flutter/flutter-engprod | ||
| /.github/CODEOWNERS @flutter/owners @flutter/flutter-engprod | ||
| /.github/workflows/ @flutter/owners @flutter/flutter-engprod |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| name: Git LFS Verification | ||
|
|
||
| on: | ||
| pull_request: | ||
| branches: [main] | ||
| types: [opened, synchronize, reopened] | ||
| merge_group: | ||
| types: [checks_requested] | ||
| push: | ||
| branches: [main] | ||
|
|
||
| jobs: | ||
| verify-lfs: | ||
| name: verify-lfs | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
|
|
||
| steps: | ||
| - name: Checkout Code | ||
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| fetch-depth: 0 | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dropped any magic around fetch-depth because it was breaking assumptions. this isn't going to be a big tree, so silly optimization gone. |
||
| persist-credentials: false | ||
|
|
||
| - name: Setup Dart | ||
| uses: dart-lang/setup-dart@6afc89df92d6eb3834022f73cd65adc8cdfcb92d # v1.8.1 | ||
|
|
||
| - name: Install Dependencies | ||
| run: dart pub get | ||
|
|
||
| - name: Verify Git LFS Pointers (PR Mode) | ||
| if: github.event_name == 'pull_request' | ||
| env: | ||
| BASE_REF: ${{ github.base_ref }} | ||
| run: | | ||
| dart run bin/lfs_verify.dart \ | ||
| --base-branch "origin/${BASE_REF:-main}" \ | ||
| --github-actions | ||
|
|
||
| - name: Verify Git LFS Pointers (Merge Queue & Main Mode) | ||
| if: github.event_name != 'pull_request' | ||
| run: | | ||
| dart run bin/lfs_verify.dart \ | ||
| --github-actions | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,11 @@ | ||
| { | ||
| "cSpell.words": [ | ||
| "apng", | ||
| "Basenames", | ||
| "frontmatter", | ||
| "octocat", | ||
| "Slugified" | ||
| "Slugified", | ||
| "webp", | ||
| "Worktree" | ||
| ] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| // Copyright 2026 The Flutter Authors. | ||
| // Use of this source code is governed by a BSD-style license that can be | ||
| // found in the LICENSE file. | ||
|
|
||
| import 'dart:io'; | ||
|
|
||
| import 'package:args/args.dart'; | ||
| import 'package:rfc_tools/src/lfs_verifier.dart'; | ||
|
|
||
| final parser = ArgParser() | ||
| ..addOption( | ||
| 'base-branch', | ||
| abbr: 'b', | ||
| help: 'Git base branch or ref to compare against in pull request mode.', | ||
| ) | ||
| ..addFlag( | ||
| 'audit-intermediate-commits', | ||
| negatable: false, | ||
| help: | ||
| 'Audit every commit in the PR branch for raw binaries.\n' | ||
| 'Only needed for repositories that do not enforce squash merges\n' | ||
| '(e.g. merge-commit or rebase-merge).', | ||
| ) | ||
| ..addFlag( | ||
| 'github-actions', | ||
| negatable: false, | ||
| help: | ||
| 'Output errors in GitHub Actions annotation format (::error file=...::).', | ||
| ) | ||
| ..addFlag( | ||
| 'help', | ||
| abbr: 'h', | ||
| negatable: false, | ||
| help: 'Show usage instructions.', | ||
| ); | ||
|
|
||
| void main(List<String> arguments) async { | ||
| ArgResults results; | ||
| try { | ||
| results = parser.parse(arguments); | ||
| } catch (e) { | ||
| stderr.writeln('Error parsing arguments: $e\n'); | ||
| stderr.writeln(parser.usage); | ||
| exitCode = 1; | ||
| return; | ||
| } | ||
|
|
||
| if (results.flag('help')) { | ||
| printUsage(stdout); | ||
| return; | ||
| } | ||
|
|
||
| if (results.rest.isNotEmpty) { | ||
| stderr.writeln( | ||
| 'Error: Positional file arguments are not supported. Git LFS verification audits trees.\n', | ||
| ); | ||
| printUsage(stdout); | ||
| exitCode = 1; | ||
| return; | ||
| } | ||
|
|
||
| final baseBranch = switch (results.option('base-branch')?.trim()) { | ||
| null || '' => null, | ||
| final branch => branch, | ||
| }; | ||
| final githubActions = results.flag('github-actions'); | ||
| final auditIntermediateCommits = results.flag('audit-intermediate-commits'); | ||
| final effectiveBaseBranch = baseBranch ?? resolveEnvironmentBaseBranch(); | ||
| final verifier = LfsVerifier(onLog: stdout.writeln, onError: stderr.writeln); | ||
|
|
||
| final (:isSuccess, :issues) = await verifier.verify( | ||
| baseBranch: effectiveBaseBranch, | ||
| auditIntermediateCommits: auditIntermediateCommits, | ||
| ); | ||
|
|
||
| if (!isSuccess) { | ||
| _reportVerificationErrors(issues: issues, githubActions: githubActions); | ||
| exitCode = 1; | ||
| return; | ||
| } | ||
|
|
||
| stdout.writeln('✅ Git LFS verification passed.'); | ||
| } | ||
|
|
||
| void printUsage(Stdout stdout) { | ||
| stdout.write(''' | ||
| Git LFS Verifier - Flutter RFC Repository Tooling | ||
|
|
||
| Usage: dart bin/lfs_verify.dart [OPTIONS] | ||
|
|
||
| Options: | ||
| ${parser.usage.replaceAll('\n', '\n ')} | ||
| '''); | ||
| } | ||
|
|
||
| void _reportVerificationErrors({ | ||
| required List<LfsIssue> issues, | ||
| required bool githubActions, | ||
| }) { | ||
| stderr.writeln( | ||
| 'Git LFS verification failed with ${issues.length} issue(s):\n', | ||
| ); | ||
| for (final issue in issues) { | ||
| stderr.writeln(githubActions ? issue.toGithubAnnotation() : '$issue'); | ||
| } | ||
|
|
||
| // Only suppress the remediation block when *every* issue is repository-level | ||
| // (e.g. a bad base ref); a single unresolvable file still warrants the steps. | ||
| if (issues.every((issue) => issue.filePath.isEmpty)) { | ||
| return; | ||
| } | ||
|
|
||
| stderr.writeln(''' | ||
|
|
||
| ❌ ERROR: One or more files bypassed Git LFS! | ||
| To resolve this: | ||
| 1. Ensure Git LFS is installed and initialized: | ||
| git lfs install | ||
| 2. Re-normalize files so Git LFS clean filters are applied: | ||
| git add --renormalize . | ||
| git commit -m "Fix: properly serialize LFS files" | ||
|
|
||
| This repository squash-merges, so fixing the tip commit is sufficient. | ||
| If you are not squash-merging, you must edit history instead: | ||
| git lfs migrate import --include="*.png" --everything | ||
| '''); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is the source of truth for what should be placed into gitlfs.