diff --git a/pubspec.yaml b/pubspec.yaml index 5ad4f638825..40b23f7ec9f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,3 +15,7 @@ workspace: dev_dependencies: build_runner: ^2.3.3 flutter_lints: ^5.0.0 + +dependency_overrides: + analyzer: 6.11.0 + dart_style: 2.3.3 diff --git a/tool/lib/commands/update_licenses.dart b/tool/lib/commands/update_licenses.dart new file mode 100644 index 00000000000..a2a39886a8d --- /dev/null +++ b/tool/lib/commands/update_licenses.dart @@ -0,0 +1,78 @@ +// Copyright 2024 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. +import 'dart:io'; + +import 'package:args/command_runner.dart'; +import 'package:cli_util/cli_logging.dart'; +import 'package:path/path.dart' as p; + +import '../license_utils.dart'; + +const _argConfig = 'config'; +const _argDirectory = 'directory'; +const _dryRun = 'dry-run'; + +/// This command updates license headers for the configured files. +/// +/// The config file is a YAML file as defined in [LicenseConfig]. +/// +/// If directory is not set, it will default to the current directory. +/// +/// When the '--dry-run' flag is passed in, a list of files to update will +/// be logged, but no files will be modified. +/// +/// To run this script +/// `dt update-licenses [--f <config-file>] [--d <directory>] [--dry-run]` +class UpdateLicensesCommand extends Command { + UpdateLicensesCommand() { + argParser + ..addOption( + _argConfig, + abbr: 'c', + defaultsTo: p.join(Directory.current.path, 'update_licenses.yaml'), + help: + 'The path to the YAML license config file. Defaults to ' + 'update_licenses.yaml', + ) + ..addOption( + _argDirectory, + defaultsTo: Directory.current.path, + abbr: 'd', + help: 'Update license headers for files in the directory.', + ) + ..addFlag( + _dryRun, + negatable: false, + defaultsTo: false, + help: + 'If set, log a list of files that require an update, but do not ' + 'modify any files.', + ); + } + + @override + String get description => 'Update license headers as configured.'; + + @override + String get name => 'update-licenses'; + + @override + Future run() async { + final config = LicenseConfig.fromYamlFile( + File(argResults![_argConfig] as String), + ); + final directory = Directory(argResults![_argDirectory] as String); + final dryRun = argResults![_dryRun] as bool; + final log = Logger.standard(); + final header = LicenseHeader(); + final results = await header.bulkUpdate( + directory: directory, + config: config, + dryRun: dryRun, + ); + final updatedPaths = results.updatedPaths; + final prefix = dryRun ? 'Requires update: ' : 'Updated: '; + log.stdout('$prefix ${updatedPaths.join(", ")}'); + } +} diff --git a/tool/lib/devtools_command_runner.dart b/tool/lib/devtools_command_runner.dart index 35ab59b8495..be532b04b9a 100644 --- a/tool/lib/devtools_command_runner.dart +++ b/tool/lib/devtools_command_runner.dart @@ -13,6 +13,7 @@ import 'package:devtools_tool/commands/serve.dart'; import 'package:devtools_tool/commands/sync.dart'; import 'package:devtools_tool/commands/tag_version.dart'; import 'package:devtools_tool/commands/update_flutter_sdk.dart'; +import 'package:devtools_tool/commands/update_licenses.dart'; import 'package:devtools_tool/commands/update_perfetto.dart'; import 'package:devtools_tool/model.dart'; @@ -47,6 +48,7 @@ class DevToolsCommandRunner extends CommandRunner { addCommand(UpdateDartSdkDepsCommand()); addCommand(UpdateDevToolsVersionCommand()); addCommand(UpdateFlutterSdkCommand()); + addCommand(UpdateLicensesCommand()); addCommand(UpdatePerfettoCommand()); argParser.addFlag( diff --git a/tool/lib/license_utils.dart b/tool/lib/license_utils.dart index c171d13bfa9..e8b8f5e8c5e 100644 --- a/tool/lib/license_utils.dart +++ b/tool/lib/license_utils.dart @@ -25,27 +25,27 @@ import 'package:yaml/yaml.dart'; /// ```yaml /// # sequence of license text strings that should be matched against at the top of a file and removed. <value>, which normally represents a date, will be stored. /// remove_licenses: -/// - | -/// // This is some <value1> multiline license +/// - |- +/// // This is some <value> multiline license /// // text that should be removed from the file. -/// - | -/// /* This is other <value2> multiline license +/// - |- +/// /* This is other <value> multiline license /// text that should be removed from the file. */ -/// - | -/// # This is more <value3> multiline license +/// - |- +/// # This is more <value> multiline license /// # text that should be removed from the file. -/// - | +/// - |- /// // This is some multiline license text to /// // remove that does not contain a stored value. -/// # sequence of license text strings that should be added to the top of a file. {value} will be replaced. +/// # sequence of license text strings that should be added to the top of a file. <value> will be replaced. /// add_licenses: -/// - | -/// // This is some <value1> multiline license +/// - |- +/// // This is some <value> multiline license /// // text that should be added to the file. -/// - | -/// # This is other <value3> multiline license +/// - |- +/// # This is other <value> multiline license /// # text that should be added to the file. -/// - | +/// - |- /// // This is some multiline license text to /// // add that does not contain a stored value. /// # defines which files should have license text added or updated. @@ -121,17 +121,23 @@ class LicenseConfig { final YamlMap fileTypes; /// Returns the list of indices for the given [ext] of [removeLicenses] - /// containing the license text to remove. + /// containing the license text to remove if they exist or an empty YamlList. YamlList getRemoveIndicesForExtension(String ext) { final fileType = fileTypes[_removeDotFromExtension(ext)]; - return fileType['remove'] as YamlList; + if (fileType != null) { + return fileType['remove'] as YamlList; + } + return YamlList(); } /// Returns the index for the given [ext] of [addLicenses] containing the - /// license text to add. + /// license text to add if it exists or -1. int getAddIndexForExtension(String ext) { final fileType = fileTypes[_removeDotFromExtension(ext)]; - return fileType['add']; + if (fileType != null) { + return fileType['add']; + } + return -1; } /// Returns whether the file should be excluded according to the config. @@ -202,14 +208,14 @@ class LicenseHeader { .handleError( (e) => throw StateError( - 'License header expected, but error reading file - $e', + 'License header expected, but error reading $file - $e', ), ); await for (final content in stream) { - // Return just the license headers for the simple case with no stored - // value requested (i.e. content matches licenseText verbatim) + final storedName = _parseStoredName(replacementLicenseText); if (content.contains(existingLicenseText)) { - final storedName = _parseStoredName(replacementLicenseText); + // Return just the license headers for the simple case with no stored + // value requested (i.e. content matches licenseText verbatim) replacementLicenseText = replacementLicenseText.replaceAll( '<$storedName>', defaultStoredValue ?? DateTime.now().year.toString(), @@ -221,17 +227,14 @@ class LicenseHeader { } // Return a non-empty map for the case where there is a stored value // requested (i.e. when there is a '<value>' defined in the license text) - final storedName = _parseStoredName(existingLicenseText); - if (storedName.isNotEmpty) { - return _processHeaders( - storedName: storedName, - existingLicenseText: existingLicenseText, - replacementLicenseText: replacementLicenseText, - content: content, - ); - } + return _processHeaders( + storedName: storedName, + existingLicenseText: existingLicenseText, + replacementLicenseText: replacementLicenseText, + content: content, + ); } - throw StateError('License header expected in ${file.path}, but not found!'); + throw StateError('License header could not be added to ${file.path}'); } /// Returns a copy of the given [file] with the [existingHeader] replaced by @@ -254,6 +257,24 @@ class LicenseHeader { return rewrittenFile; } + /// Returns a copy of the given [file] that is missing a license header + /// with the [replacementHeader] added to the top. + /// + /// Reads and writes the entire file contents all at once, so performance may + /// degrade for large files. + File addLicenseHeader({ + required File file, + required String replacementHeader, + }) { + final rewrittenFile = File('${file.path}.tmp'); + final contents = file.readAsStringSync(); + rewrittenFile.writeAsStringSync( + '$replacementHeader${Platform.lineTerminator}$contents', + flush: true, + ); + return rewrittenFile; + } + /// Bulk update license headers for files in the [directory] as configured /// in the [config] and return a processed paths Record containing: /// - list of included paths @@ -274,20 +295,42 @@ class LicenseHeader { if (!config.shouldExclude(file)) { includedPathsList.add(file.path); final extension = p.extension(file.path); + final addIndex = config.getAddIndexForExtension(extension); + if (addIndex == -1) { + // skip if add index doesn't exist for extension + continue; + } + final fileLength = file.lengthSync(); + const bufferSize = 20; + final replacementLicenseText = config.addLicenses[addIndex]; + final byteCount = min( + bufferSize + replacementLicenseText.length, + fileLength, + ); + var replacementInfo = await getReplacementInfo( + file: file, + existingLicenseText: replacementLicenseText, + replacementLicenseText: replacementLicenseText, + byteCount: byteCount as int, + ); + if (replacementInfo.existingHeader.isNotEmpty && + replacementInfo.replacementHeader.isNotEmpty && + replacementInfo.existingHeader == + replacementInfo.replacementHeader) { + // Do nothing if the replacement header is the same as the + // existing header + continue; + } final removeIndices = config.getRemoveIndicesForExtension(extension); for (final removeIndex in removeIndices) { final existingLicenseText = config.removeLicenses[removeIndex]; - final addIndex = config.getAddIndexForExtension(extension); - final replacementLicenseText = config.addLicenses[addIndex]; - final fileLength = file.lengthSync(); - const bufferSize = 20; // Assume that the license text will be near the start of the file, // but add in some buffer. final byteCount = min( bufferSize + existingLicenseText.length, fileLength, ); - final replacementInfo = await getReplacementInfo( + replacementInfo = await getReplacementInfo( file: file, existingLicenseText: existingLicenseText, replacementLicenseText: replacementLicenseText, @@ -295,6 +338,7 @@ class LicenseHeader { ); if (replacementInfo.existingHeader.isNotEmpty && replacementInfo.replacementHeader.isNotEmpty) { + // Case 1: Existing header needs to be replaced if (dryRun) { updatedPathsList.add(file.path); } else { @@ -303,15 +347,27 @@ class LicenseHeader { existingHeader: replacementInfo.existingHeader, replacementHeader: replacementInfo.replacementHeader, ); - if (rewrittenFile.lengthSync() > 0) { - file.writeAsStringSync( - rewrittenFile.readAsStringSync(), - mode: FileMode.writeOnly, - flush: true, - ); - updatedPathsList.add(file.path); - } - rewrittenFile.deleteSync(); + _updateLicense(rewrittenFile, file, updatedPathsList); + } + } + } + if (!updatedPathsList.contains(file.path)) { + final licenseHeaders = _processHeaders( + storedName: _parseStoredName(replacementLicenseText), + existingLicenseText: '', + replacementLicenseText: replacementLicenseText, + content: '', + ); + if (licenseHeaders.replacementHeader.isNotEmpty) { + // Case 2: Missing header needs to be added + if (dryRun) { + updatedPathsList.add(file.path); + } else { + final rewrittenFile = addLicenseHeader( + file: file, + replacementHeader: licenseHeaders.replacementHeader, + ); + _updateLicense(rewrittenFile, file, updatedPathsList); } } } @@ -320,6 +376,22 @@ class LicenseHeader { return (includedPaths: includedPathsList, updatedPaths: updatedPathsList); } + void _updateLicense( + File rewrittenFile, + File file, + List<String> updatedPathsList, + ) { + if (rewrittenFile.lengthSync() > 0) { + file.writeAsStringSync( + rewrittenFile.readAsStringSync(), + mode: FileMode.writeOnly, + flush: true, + ); + updatedPathsList.add(file.path); + } + rewrittenFile.deleteSync(); + } + ({String existingHeader, String replacementHeader}) _processHeaders({ required String storedName, required String existingLicenseText, @@ -356,7 +428,11 @@ class LicenseHeader { ); } } - return const (existingHeader: '', replacementHeader: ''); + final defaultReplacementHeader = replacementLicenseText.replaceAll( + '<$storedName>', + DateTime.now().year.toString(), + ); + return (existingHeader: '', replacementHeader: defaultReplacementHeader); } // TODO(mossmana) Add support for multiple stored names diff --git a/tool/test/license_utils_test.dart b/tool/test/license_utils_test.dart index 5cbd21725c9..98b8fea0ee9 100644 --- a/tool/test/license_utils_test.dart +++ b/tool/test/license_utils_test.dart @@ -52,6 +52,8 @@ late File testFile9; late File testFile10; late File excludeFile1; late File excludeFile2; +late File skipFile; +late File doNothingFile; void main() { group('config file tests', () { @@ -73,24 +75,20 @@ void main() { expect(config.removeLicenses.length, equals(4)); - var expectedVal = '''// This is some <value1> multiline license -// text that should be removed from the file. -'''; + var expectedVal = '''// This is some <value> multiline license +// text that should be removed from the file.'''; expect(config.removeLicenses[0], equals(expectedVal)); - expectedVal = '''/* This is other <value2> multiline license -text that should be removed from the file. */ -'''; + expectedVal = '''/* This is other <value> multiline license +text that should be removed from the file. */'''; expect(config.removeLicenses[1], equals(expectedVal)); - expectedVal = '''# This is more <value3> multiline license -# text that should be removed from the file. -'''; + expectedVal = '''# This is more <value> multiline license +# text that should be removed from the file.'''; expect(config.removeLicenses[2], equals(expectedVal)); expectedVal = '''// This is some multiline license text to -// remove that does not contain a stored value. -'''; +// remove that does not contain a stored value.'''; expect(config.removeLicenses[3], equals(expectedVal)); }); @@ -99,19 +97,16 @@ text that should be removed from the file. */ expect(config.addLicenses.length, equals(3)); - var expectedVal = '''// This is some <value1> multiline license -// text that should be added to the file. -'''; + var expectedVal = '''// This is some <value> multiline license +// text that should be added to the file.'''; expect(config.addLicenses[0], equals(expectedVal)); - expectedVal = '''# This is other <value3> multiline license -# text that should be added to the file. -'''; + expectedVal = '''# This is other <value> multiline license +# text that should be added to the file.'''; expect(config.addLicenses[1], equals(expectedVal)); expectedVal = '''// This is some multiline license text to -// add that does not contain a stored value. -'''; +// add that does not contain a stored value.'''; expect(config.addLicenses[2], equals(expectedVal)); }); @@ -179,10 +174,11 @@ text that should be removed from the file. */ const existingLicenseText = '''// This is some multiline license text to // remove that does not contain a stored value.'''; const replacementLicenseText = - '''// This is some <value4> multiline license + '''// This is some <value> multiline license // text that should be added to the file.'''; - final replacementInfo = await _getTestReplacementInfo( + // Contains existing license + var replacementInfo = await _getTestReplacementInfo( testFile: testFile10, existingLicenseText: existingLicenseText, replacementLicenseText: replacementLicenseText, @@ -205,6 +201,20 @@ text that should be removed from the file. */ replacementInfo.replacementHeader, equals(expectedReplacementHeader), ); + + // Missing existing license + replacementInfo = await _getTestReplacementInfo( + testFile: testFile10, + existingLicenseText: existingLicenseText, + replacementLicenseText: replacementLicenseText, + ); + + expect(replacementInfo.existingHeader, equals(expectedExistingHeader)); + + expect( + replacementInfo.replacementHeader, + equals(expectedReplacementHeader), + ); }); test('stored value preserved in replacement header', () async { @@ -263,27 +273,6 @@ text that should be added to the file. */''', } }); - test('update skipped if license text not found', () async { - var errorMessage = ''; - final header = LicenseHeader(); - try { - await header.getReplacementInfo( - file: testFile9, - existingLicenseText: 'test', - replacementLicenseText: 'test', - byteCount: 50, - ); - } on StateError catch (e) { - errorMessage = e.toString(); - } - expect( - errorMessage, - equals( - 'Bad state: License header expected in ${testFile9.path}, but not found!', - ), - ); - }); - test("update skipped if file can't be read", () async { var errorMessage = ''; final header = LicenseHeader(); @@ -300,7 +289,7 @@ text that should be added to the file. */''', expect( errorMessage, contains( - 'Bad state: License header expected, but error reading file - PathNotFoundException', + 'Bad state: License header expected, but error reading File: \'bad.txt\' - PathNotFoundException', ), ); }); @@ -337,42 +326,69 @@ text that should be added to the file. */''', ); }); + test('license header can be added on disk', () async { + final header = LicenseHeader(); + const replacementHeader = '''// This is some 2015 multiline license +// text that should be added to the file.'''; + final rewrittenFile = header.addLicenseHeader( + file: testFile9, + replacementHeader: replacementHeader, + ); + + expect(rewrittenFile.lengthSync(), greaterThan(0)); + + final rewrittenContents = rewrittenFile.readAsStringSync(); + expect( + rewrittenContents.substring(0, replacementHeader.length), + equals(replacementHeader), + ); + }); + test('license headers can be updated in bulk', () async { await _setupTestConfigFile(); final config = LicenseConfig.fromYamlFile(configFile); final header = LicenseHeader(); - final contentsBeforeUpdate = testFile1.readAsStringSync(); + // missing license + final contentsBeforeUpdate = testFile9.readAsStringSync(); final results = await header.bulkUpdate( directory: testDirectory, config: config, ); - final contentsAfterUpdate = testFile1.readAsStringSync(); + final contentsAfterUpdate = testFile9.readAsStringSync(); final includedPaths = results.includedPaths; expect(includedPaths, isNotNull); - expect(includedPaths.length, equals(7)); + expect(includedPaths.length, equals(9)); // Order is not guaranteed expect(includedPaths.contains(testFile1.path), true); - expect(contentsBeforeUpdate, isNot(equals(contentsAfterUpdate))); expect(includedPaths.contains(testFile2.path), true); expect(includedPaths.contains(testFile3.path), true); expect(includedPaths.contains(testFile7.path), true); expect(includedPaths.contains(testFile8.path), true); expect(includedPaths.contains(testFile9.path), true); expect(includedPaths.contains(testFile10.path), true); + expect(includedPaths.contains(skipFile.path), true); + expect(includedPaths.contains(doNothingFile.path), true); final updatedPaths = results.updatedPaths; expect(updatedPaths, isNotNull); - // testFile9 and testFile10 are intentionally misconfigured and so they - // won't be updated even though they are on the include list. - expect(updatedPaths.length, equals(5)); + expect(updatedPaths.length, equals(7)); // Order is not guaranteed expect(updatedPaths.contains(testFile1.path), true); expect(updatedPaths.contains(testFile2.path), true); expect(updatedPaths.contains(testFile3.path), true); expect(updatedPaths.contains(testFile7.path), true); expect(updatedPaths.contains(testFile8.path), true); + expect(updatedPaths.contains(testFile9.path), true); + expect(updatedPaths.contains(testFile10.path), true); + expect(updatedPaths.contains(skipFile.path), false); + expect(updatedPaths.contains(doNothingFile.path), false); + + expect(contentsBeforeUpdate, isNot(equals(contentsAfterUpdate))); + // There is an extremely rare failure case on the year boundary. + // TODO(mossmana): Handle running test on Dec 31 - Jan 1. + expect(contentsAfterUpdate, contains(DateTime.now().year.toString())); }); test('license headers bulk update can be dry run', () async { @@ -390,7 +406,7 @@ text that should be added to the file. */''', final updatedPaths = results.updatedPaths; expect(updatedPaths, isNotNull); - expect(updatedPaths.length, equals(5)); + expect(updatedPaths.length, equals(7)); expect(updatedPaths.contains(testFile1.path), true); expect(contentsBeforeUpdate, equals(contentsAfterUpdate)); }); @@ -483,27 +499,34 @@ Future<void> _setupTestConfigFile() async { final contents = '''--- # sequence of license text strings that should be matched against at the top of a file and removed. <value>, which normally represents a date, will be stored. remove_licenses: - - | - // This is some <value1> multiline license + #0 + - |- + // This is some <value> multiline license // text that should be removed from the file. - - | - /* This is other <value2> multiline license + #1 + - |- + /* This is other <value> multiline license text that should be removed from the file. */ - - | - # This is more <value3> multiline license + #2 + - |- + # This is more <value> multiline license # text that should be removed from the file. - - | + #3 + - |- // This is some multiline license text to // remove that does not contain a stored value. -# sequence of license text strings that should be added to the top of a file. {value} will be replaced. -add_licenses: - - | - // This is some <value1> multiline license +# sequence of license text strings that should be added to the top of a file. <value> will be replaced. +add_licenses: + #0 + - |- + // This is some <value> multiline license // text that should be added to the file. - - | - # This is other <value3> multiline license + #1 + - |- + # This is other <value> multiline license # text that should be added to the file. - - | + #2 + - |- // This is some multiline license text to // add that does not contain a stored value. # defines which files should have license text added or updated. @@ -530,6 +553,10 @@ update_paths: ext2: remove: - 2 + add: 1 + ext3: + remove: + - 3 add: 1'''; configFile.writeAsStringSync(contents, flush: true); @@ -539,6 +566,7 @@ update_paths: /// repo_root/ /// test1.ext1 /// test2.ext2 +/// test.skip /// .hidden/ /// test3.ext1 /// sub_dir1/ @@ -573,6 +601,18 @@ Future<void> _setupTestDirectoryStructure() async { ..createSync(recursive: true); testFile2.writeAsStringSync(licenseText3 + extraText, flush: true); + final licenseText = ''' +# This is other 2001 multiline license +# text that should be added to the file. +'''; + doNothingFile = File(p.join(repoRoot.path, 'doNothingFile.ext3')) + ..createSync(recursive: true); + doNothingFile.writeAsStringSync(licenseText + extraText, flush: true); + + skipFile = File(p.join(repoRoot.path, 'test.skip')) + ..createSync(recursive: true); + skipFile.writeAsStringSync(extraText, flush: true); + // Setup /repo_root/.hidden directory structure Directory(p.join(repoRoot.path, '.hidden')).createSync(recursive: true); @@ -624,6 +664,7 @@ Future<void> _setupTestDirectoryStructure() async { p.join(repoRoot.path, 'sub_dir2', 'sub_dir4'), ).createSync(recursive: true); + // Missing license header testFile9 = File(p.join(repoRoot.path, 'sub_dir2', 'sub_dir4', 'test9.ext1')) ..createSync(recursive: true); testFile9.writeAsStringSync(extraText, flush: true); @@ -633,6 +674,7 @@ Future<void> _setupTestDirectoryStructure() async { p.join(repoRoot.path, 'sub_dir2', 'sub_dir4', 'sub_dir5'), ).createSync(recursive: true); + // Will be treated like a missing license header since not configured testFile10 = File( p.join(repoRoot.path, 'sub_dir2', 'sub_dir4', 'sub_dir5', 'test10.ext2'), )..createSync(recursive: true); diff --git a/update_licenses.yaml b/update_licenses.yaml new file mode 100644 index 00000000000..e151e6d8be9 --- /dev/null +++ b/update_licenses.yaml @@ -0,0 +1,159 @@ +# 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 or at https://developers.google.com/open-source/licenses/bsd. +--- +remove_licenses: + #0 + - |- + // Copyright <value> The Chromium Authors. All rights reserved. + // Use of this source code is governed by a BSD-style license that can be + // found in the LICENSE file. + #1 + - |- + /** + Copyright <value> The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + **/ + #2 + - |- + #!/bin/bash + # + # Copyright <value> The Chromium Authors. All rights reserved. + # Use of this source code is governed by a BSD-style license that can be + # found in the LICENSE file. + #3 + - |- + <!-- + Copyright <value> The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> + #4 + - |- + REM Copyright <value> The Chromium Authors. All rights reserved. + REM Use of this source code is governed by a BSD-style license that can be + REM found in the LICENSE file. + #5 + - |- + // Copyright <value> The Chromium Authors. All rights reserved. + // Use of this source code is governed by a BSD-style license that can be + // found in the LICENSE file + #6 + - |- + // Copyright <value> The Chromium Authors. All rights reserved. + // Use of this source code is governed by a BSD-style license that can be found + // in the LICENSE file. +add_licenses: + #0 + - |- + // Copyright <value> The Flutter Authors + // Use of this source code is governed by a BSD-style license that can be + // found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. + #1 + - |- + /** + Copyright <value> The Flutter Authors + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. + **/ + #2 + - |- + #!/bin/bash + # + # Copyright <value> The Flutter Authors + # Use of this source code is governed by a BSD-style license that can be + # found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. + #3 + - |- + <!-- + Copyright <value> The Flutter Authors + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. + --> + #4 + - |- + REM Copyright <value> The Flutter Authors + REM Use of this source code is governed by a BSD-style license that can be + REM found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. +update_paths: + include: + - /Users/mossman/Development/devtools + exclude: + - /Users/mossman/Development/devtools/third_party + - /Users/mossman/Development/devtools/packages/devtools_app/build + - /Users/mossman/Development/devtools/tool/flutter-sdk + - /Users/mossman/Development/devtools/.github + - /Users/mossman/Development/devtools/.vscode + - /Users/mossman/Development/devtools/packages/devtools_app/lib/src/shared/http/_http_date.dart + - /Users/mossman/Development/devtools/packages/devtools_app/lib/src/shared/http/_http_exception.dart + file_types: + bat: + remove: + - 4 + add: 4 + cc: + remove: + - 0 + - 1 + add: 0 + cpp: + remove: + - 0 + - 1 + add: 0 + css: + remove: + - 1 + add: 1 + dart: + remove: + - 0 + - 1 + - 5 + - 6 + add: 0 + gradle: + remove: + - 0 + - 1 + add: 0 + h: + remove: + - 0 + - 1 + add: 0 + html: + remove: + - 3 + add: 3 + java: + remove: + - 0 + - 1 + add: 0 + md: + remove: + - 3 + add: 3 + sh: + remove: + - 2 + add: 2 + swift: + remove: + - 0 + - 1 + add: 0 + xml: + remove: + - 3 + add: 3 + yaml: + remove: + - 2 + add: 2 + yml: + remove: + - 2 + add: 2