diff --git a/.github/workflows/flutter_packages.yaml b/.github/workflows/flutter_packages.yaml index 583d152e4..c06eb288a 100644 --- a/.github/workflows/flutter_packages.yaml +++ b/.github/workflows/flutter_packages.yaml @@ -14,6 +14,7 @@ on: - ".github/workflows/flutter_packages.yaml" - "packages/**" - "examples/**" + - "tool/**" pull_request: branches: - main @@ -21,6 +22,7 @@ on: - ".github/workflows/flutter_packages.yaml" - "packages/**" - "examples/**" + - "tool/**" concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} diff --git a/packages/spikes/gulf_genkit_eval/src/index.ts b/packages/spikes/gulf_genkit_eval/src/index.ts index 7999cd1d1..e1da8a15c 100644 --- a/packages/spikes/gulf_genkit_eval/src/index.ts +++ b/packages/spikes/gulf_genkit_eval/src/index.ts @@ -1,4 +1,4 @@ -// Copyright 2025 The Flutter Authors. All rights reserved. +// Copyright 2025 The Flutter Authors. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. diff --git a/packages/spikes/gulf_genkit_eval/src/models.ts b/packages/spikes/gulf_genkit_eval/src/models.ts index 7df0d870b..e7218bfaa 100644 --- a/packages/spikes/gulf_genkit_eval/src/models.ts +++ b/packages/spikes/gulf_genkit_eval/src/models.ts @@ -1,4 +1,4 @@ -// Copyright 2025 The Flutter Authors. All rights reserved. +// Copyright 2025 The Flutter Authors. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. diff --git a/packages/spikes/gulf_genkit_eval/src/prompts.ts b/packages/spikes/gulf_genkit_eval/src/prompts.ts index 91d421b77..affe62178 100644 --- a/packages/spikes/gulf_genkit_eval/src/prompts.ts +++ b/packages/spikes/gulf_genkit_eval/src/prompts.ts @@ -1,4 +1,4 @@ -// Copyright 2025 The Flutter Authors. All rights reserved. +// Copyright 2025 The Flutter Authors. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. diff --git a/packages/spikes/gulf_genkit_eval/src/validator.ts b/packages/spikes/gulf_genkit_eval/src/validator.ts index 8693e31ac..d8a5808bf 100644 --- a/packages/spikes/gulf_genkit_eval/src/validator.ts +++ b/packages/spikes/gulf_genkit_eval/src/validator.ts @@ -1,4 +1,4 @@ -// Copyright 2025 The Flutter Authors. All rights reserved. +// Copyright 2025 The Flutter Authors. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. diff --git a/tool/fix_copyright/lib/src/fix_copyright.dart b/tool/fix_copyright/lib/src/fix_copyright.dart index 1d439d580..7bd786642 100644 --- a/tool/fix_copyright/lib/src/fix_copyright.dart +++ b/tool/fix_copyright/lib/src/fix_copyright.dart @@ -30,96 +30,43 @@ Future fixCopyrights( void stdErr(String message) => (error ?? stderr.writeln as LogFunction).call(message); - final Set submodulePaths; + String getExtension(File file) { + final pathExtension = path.extension(file.path); + return pathExtension.isNotEmpty ? pathExtension.substring(1) : ''; + } + final gitRootResult = await processManager.run([ 'git', 'rev-parse', '--show-toplevel', ]); if (gitRootResult.exitCode != 0) { - stdErr('Warning: not a git repository. Cannot check for submodules.'); - submodulePaths = {}; - } else { - final repoRoot = gitRootResult.stdout.toString().trim(); - final result = await processManager.run([ - 'git', - 'submodule', - 'status', - '--recursive', - ], workingDirectory: repoRoot); - if (result.exitCode == 0) { - submodulePaths = result.stdout - .toString() - .split('\n') - .where((line) => line.trim().isNotEmpty) - .map((line) { - final parts = line.trim().split(RegExp(r'\s+')); - if (parts.length > 1) { - return path.canonicalize(path.join(repoRoot, parts[1])); - } - return null; - }) - .whereType() - .toSet(); - } else { - submodulePaths = {}; - stdErr( - 'Warning: could not get submodule status. ' - 'Not skipping any submodules.', - ); - } + stdErr( + 'Error: not a git repository. ' + 'This tool only works within a git repository.', + ); + return 1; } + final repoRoot = gitRootResult.stdout.toString().trim(); - String getExtension(File file) { - final pathExtension = path.extension(file.path); - return pathExtension.isNotEmpty ? pathExtension.substring(1) : ''; - } + final gitFilesResult = await processManager.run([ + 'git', + 'ls-files', + ...paths, + ], workingDirectory: repoRoot); - Iterable matchingFiles(Directory dir) { - final files = []; - final directories = [dir]; - while (directories.isNotEmpty) { - final currentDir = directories.removeAt(0); - if (submodulePaths.contains(path.canonicalize(currentDir.path))) { - stdLog('Skipping submodule: ${currentDir.path}'); - continue; - } - try { - for (final entity in currentDir.listSync()) { - if (entity is File) { - if (extensionMap.containsKey(getExtension(entity))) { - files.add(entity.absolute); - } - } else if (entity is Directory) { - directories.add(entity); - } - } - } on FileSystemException catch (e) { - stdErr('Could not list directory ${currentDir.path}: $e'); - } - } - return files; + if (gitFilesResult.exitCode != 0) { + stdErr('Error running "git ls-files":\n${gitFilesResult.stderr}'); + return 1; } - final rest = paths.isEmpty ? ['.'] : paths; - - final files = []; - for (final fileOrDir in rest) { - switch (fileSystem.typeSync(fileOrDir)) { - case FileSystemEntityType.directory: - files.addAll(matchingFiles(fileSystem.directory(fileOrDir))); - break; - case FileSystemEntityType.file: - files.add(fileSystem.file(fileOrDir)); - break; - case FileSystemEntityType.link: - case FileSystemEntityType.notFound: - case FileSystemEntityType.pipe: - case FileSystemEntityType.unixDomainSock: - // We don't care about these, just ignore them. - break; - } - } + final files = gitFilesResult.stdout + .toString() + .split('\n') + .where((line) => line.trim().isNotEmpty) + .map((filePath) => fileSystem.file(path.join(repoRoot, filePath))) + .where((file) => extensionMap.containsKey(getExtension(file))) + .toList(); final nonCompliantFiles = []; for (final file in files) { diff --git a/tool/fix_copyright/test/fix_copyright_test.dart b/tool/fix_copyright/test/fix_copyright_test.dart index 54ffb71bf..7335faff0 100644 --- a/tool/fix_copyright/test/fix_copyright_test.dart +++ b/tool/fix_copyright/test/fix_copyright_test.dart @@ -43,6 +43,13 @@ $prefix found in the LICENSE file.'''; late List error; late FakeProcessManager processManager; + void mockGitLsFiles({List paths = const [], required String stdout}) { + processManager.mockCommands.add(MockCommand( + command: ['git', 'ls-files', ...paths], + stdout: stdout, + )); + } + setUp(() { fileSystem = MemoryFileSystem(); log = []; @@ -51,7 +58,7 @@ $prefix found in the LICENSE file.'''; processManager.mockCommands = [ MockCommand( command: ['git', 'rev-parse', '--show-toplevel'], - exitCode: 128, + stdout: '/', ), ]; }); @@ -59,7 +66,6 @@ $prefix found in the LICENSE file.'''; Future runFixCopyrights({ List paths = const [], bool force = false, - bool skipSubmodules = true, String year = year, }) async { return fixCopyrights( @@ -67,7 +73,6 @@ $prefix found in the LICENSE file.'''; force: force, year: year, paths: paths, - skipSubmodules: skipSubmodules, processManager: processManager, log: log.add, error: error.add, @@ -75,6 +80,7 @@ $prefix found in the LICENSE file.'''; } test('updates a file with an incorrect date', () async { + mockGitLsFiles(paths: ['test.dart'], stdout: 'test.dart'); final testFile = fileSystem.file('test.dart') ..writeAsStringSync(getBadCopyright()); final result = await runFixCopyrights(paths: ['test.dart']); @@ -88,6 +94,7 @@ $prefix found in the LICENSE file.'''; }); test('updates a file with an incorrect date when forced', () async { + mockGitLsFiles(paths: ['test.dart'], stdout: 'test.dart'); final testFile = fileSystem.file('test.dart') ..writeAsStringSync(getBadCopyright()); final result = await runFixCopyrights(paths: ['test.dart'], force: true); @@ -101,6 +108,7 @@ $prefix found in the LICENSE file.'''; }); test('updates a file with a non-matching copyright', () async { + mockGitLsFiles(paths: ['test.dart'], stdout: 'test.dart'); final testFile = fileSystem.file('test.dart') ..writeAsStringSync(wrongCopyright); final result = await runFixCopyrights(paths: ['test.dart']); @@ -114,6 +122,7 @@ $prefix found in the LICENSE file.'''; }); test('updates a file with a non-matching copyright when forced', () async { + mockGitLsFiles(paths: ['test.dart'], stdout: 'test.dart'); final testFile = fileSystem.file('test.dart') ..writeAsStringSync(wrongCopyright); final result = await runFixCopyrights(paths: ['test.dart'], force: true); @@ -127,6 +136,7 @@ $prefix found in the LICENSE file.'''; }); test('updates a file with no copyright', () async { + mockGitLsFiles(paths: ['test.dart'], stdout: 'test.dart'); final testFile = fileSystem.file('test.dart') ..writeAsStringSync(randomPreamble); final result = await runFixCopyrights(paths: ['test.dart']); @@ -140,6 +150,7 @@ $prefix found in the LICENSE file.'''; }); test('updates a file with no copyright when forced', () async { + mockGitLsFiles(paths: ['test.dart'], stdout: 'test.dart'); final testFile = fileSystem.file('test.dart') ..writeAsStringSync(randomPreamble); final result = await runFixCopyrights(paths: ['test.dart'], force: true); @@ -156,6 +167,7 @@ $prefix found in the LICENSE file.'''; }); test('updates a shell script with a shebang and bad copyright', () async { + mockGitLsFiles(paths: ['test.sh'], stdout: 'test.sh'); final testFile = fileSystem.file('test.sh') ..writeAsStringSync( '$bashShebang\n${getBadCopyright(prefix: '#')}\n$randomShellPreamble', @@ -175,6 +187,7 @@ $prefix found in the LICENSE file.'''; }); test('updates a file with an unrecognized shebang', () async { + mockGitLsFiles(paths: ['test.sh'], stdout: 'test.sh'); final testFile = fileSystem.file('test.sh') ..writeAsStringSync( '$badShebang\n${getBadCopyright(prefix: "#")}\n$randomPreamble', @@ -194,15 +207,13 @@ $prefix found in the LICENSE file.'''; }); test('does not update a file that is OK', () async { + mockGitLsFiles(paths: ['test.dart'], stdout: 'test.dart'); final testFile = fileSystem.file('test.dart') ..writeAsStringSync('$copyright\n\n$randomPreamble'); final result = await runFixCopyrights(paths: ['test.dart']); expect(result, equals(0)); expect(log, isEmpty); - expect( - error, - contains('Warning: not a git repository. Cannot check for submodules.'), - ); + expect(error, isEmpty); expect( testFile.readAsStringSync(), equals('$copyright\n\n$randomPreamble'), @@ -210,6 +221,7 @@ $prefix found in the LICENSE file.'''; }); test('updates a directory of files', () async { + mockGitLsFiles(stdout: 'test1.dart\ntest2.dart\ntest3.dart\ntest4.dart'); final testFile1 = fileSystem.file('test1.dart') ..writeAsStringSync(getBadCopyright()); final testFile2 = fileSystem.file('test2.dart') @@ -238,18 +250,17 @@ $prefix found in the LICENSE file.'''; }); test('does not update an empty file', () async { + mockGitLsFiles(paths: ['test.dart'], stdout: 'test.dart'); final testFile = fileSystem.file('test.dart')..writeAsStringSync(''); final result = await runFixCopyrights(paths: ['test.dart'], force: true); expect(result, equals(0)); expect(log, isEmpty); - expect( - error, - contains('Warning: not a git repository. Cannot check for submodules.'), - ); + expect(error, isEmpty); expect(testFile.readAsStringSync(), isEmpty); }); test('updates a file with case-insensitive copyright', () async { + mockGitLsFiles(paths: ['test.dart'], stdout: 'test.dart'); final testFile = fileSystem.file('test.dart') ..writeAsStringSync(getBadCopyright().toLowerCase()); final result = await runFixCopyrights(paths: ['test.dart'], force: true); @@ -263,6 +274,7 @@ $prefix found in the LICENSE file.'''; }); test('updates a file with windows line endings', () async { + mockGitLsFiles(paths: ['test.dart'], stdout: 'test.dart'); final testFile = fileSystem.file('test.dart') ..writeAsStringSync(getBadCopyright().replaceAll('\n', '\r\n')); final result = await runFixCopyrights(paths: ['test.dart'], force: true); @@ -276,6 +288,7 @@ $prefix found in the LICENSE file.'''; }); test('updates an xml file', () async { + mockGitLsFiles(paths: ['test.xml'], stdout: 'test.xml'); const xmlPreamble = '\n'; const xmlCopyright = ''' '''; ); }); - group('submodule handling', () { - setUp(() { - fileSystem.directory('/submodule').createSync(); - fileSystem - .file('/submodule/test.dart') - .writeAsStringSync(getBadCopyright()); - fileSystem.file('/test.dart').writeAsStringSync(getBadCopyright()); - }); - - test('skips submodules by default', () async { - processManager.mockCommands = [ - MockCommand( - command: ['git', 'rev-parse', '--show-toplevel'], - stdout: '/', - ), - MockCommand( - command: ['git', 'submodule', 'status', '--recursive'], - stdout: - ' 1234567890abcdef1234567890abcdef12345678 submodule (v1.2.3)', - ), - ]; - - final result = await runFixCopyrights(force: true); - - expect(result, equals(1)); - expect(log, contains('/test.dart')); - expect(log, contains('Skipping submodule: ./submodule')); - expect(log, isNot(contains('/submodule/test.dart'))); - expect( - error, - contains('Found 1 files which have out-of-compliance copyrights.'), - ); - expect( - fileSystem.file('/test.dart').readAsStringSync(), - startsWith(copyright), - ); - expect( - fileSystem.file('/submodule/test.dart').readAsStringSync(), - equals(getBadCopyright()), - ); - expect(processManager.commands, hasLength(2)); - }); - - test('processes submodules when --no-skip-submodules is passed', () async { - processManager.mockCommands = []; // No git commands should be run. - - final result = await runFixCopyrights(force: true, skipSubmodules: false); - - expect(result, equals(1)); - expect(log, unorderedEquals(['/test.dart', '/submodule/test.dart'])); - expect( - error, - contains('Found 2 files which have out-of-compliance copyrights.'), - ); - expect( - fileSystem.file('/test.dart').readAsStringSync(), - startsWith(copyright), - ); - expect( - fileSystem.file('/submodule/test.dart').readAsStringSync(), - startsWith(copyright), - ); - expect(processManager.commands, isEmpty); - }); - - test('handles non-git repository gracefully', () async { - processManager.mockCommands = [ - MockCommand( - command: ['git', 'rev-parse', '--show-toplevel'], - exitCode: 128, - ), - ]; - - final result = await runFixCopyrights(force: true); - - expect(result, equals(1)); - expect(log, unorderedEquals(['/test.dart', '/submodule/test.dart'])); - expect( - error, - contains('Warning: not a git repository. Cannot check for submodules.'), - ); - expect( - error, - contains('Found 2 files which have out-of-compliance copyrights.'), - ); - expect( - fileSystem.file('/test.dart').readAsStringSync(), - startsWith(copyright), - ); - expect( - fileSystem.file('/submodule/test.dart').readAsStringSync(), - startsWith(copyright), - ); - expect(processManager.commands, hasLength(1)); - }); - - test('handles git submodule status failure gracefully', () async { - processManager.mockCommands = [ - MockCommand( - command: ['git', 'rev-parse', '--show-toplevel'], - stdout: '/', - ), - MockCommand( - command: ['git', 'submodule', 'status', '--recursive'], - exitCode: 1, - ), - ]; - - final result = await runFixCopyrights(force: true); - - expect(result, equals(1)); - expect(log, unorderedEquals(['/test.dart', '/submodule/test.dart'])); - expect( - error, - contains( - 'Warning: could not get submodule status. Not skipping any ' - 'submodules.', - ), - ); - expect( - error, - contains('Found 2 files which have out-of-compliance copyrights.'), - ); - expect( - fileSystem.file('/test.dart').readAsStringSync(), - startsWith(copyright), - ); - expect( - fileSystem.file('/submodule/test.dart').readAsStringSync(), - startsWith(copyright), - ); - expect(processManager.commands, hasLength(2)); - }); - }); } class MockCommand {