Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .gitattributes

Copy link
Copy Markdown
Member Author

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.

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
14 changes: 14 additions & 0 deletions .github/CODEOWNERS
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
45 changes: 45 additions & 0 deletions .github/workflows/lfs-verify.yml
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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The 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
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
{
"cSpell.words": [
"apng",
"Basenames",
"frontmatter",
"octocat",
"Slugified"
"Slugified",
"webp",
"Worktree"
]
}
127 changes: 127 additions & 0 deletions bin/lfs_verify.dart
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
''');
}
Loading
Loading