| // Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| import 'dart:async'; | |
| import 'dart:convert'; | |
| import 'dart:io'; | |
| import 'dart:typed_data'; | |
| import '../git.dart' as git; | |
| import '../ignore.dart'; | |
| import '../io.dart'; | |
| import '../log.dart' as log; | |
| import '../path.dart'; | |
| import '../platform_info.dart'; | |
| import '../utils.dart'; | |
| import '../validator.dart'; | |
| /// A validator that validates that no checked in files are ignored by a | |
| /// .gitignore. These would be considered part of the package by previous | |
| /// versions of pub. | |
| class GitignoreValidator extends Validator { | |
| @override | |
| Future<void> validate() async { | |
| if (package.inGitRepo) { | |
| final Uint8List output; | |
| try { | |
| output = git.runSyncBytes([ | |
| '-c', | |
| 'core.quotePath=false', | |
| 'ls-files', | |
| '-z', | |
| '--cached', | |
| '--exclude-standard', | |
| '--recurse-submodules', | |
| ], workingDir: package.dir); | |
| } on git.GitException catch (e) { | |
| log.fine('Could not run `git ls-files` files in repo (${e.message}).'); | |
| // This validation is only a warning. | |
| // If git is not supported on the platform, or too old to support | |
| // --recurse-submodules we just continue silently. | |
| return; | |
| } | |
| final List<String> checkedIntoGit; | |
| try { | |
| checkedIntoGit = | |
| git.splitZeroTerminated(output).map((b) { | |
| return utf8.decode(b); | |
| }).toList(); | |
| } on FormatException catch (e) { | |
| log.fine('Failed decoding git output. Skipping validation. $e.'); | |
| return; | |
| } | |
| final root = git.repoRoot(package.dir) ?? package.dir; | |
| var beneath = p.posix.joinAll( | |
| p.split(p.normalize(p.relative(package.dir, from: root))), | |
| ); | |
| if (beneath == './') { | |
| beneath = ''; | |
| } | |
| String resolve(String path) { | |
| if (platform.isWindows) { | |
| return p.joinAll([root, ...p.posix.split(path)]); | |
| } | |
| return p.join(root, path); | |
| } | |
| final unignoredByGitignore = | |
| Ignore.listFiles( | |
| beneath: beneath, | |
| listDir: (dir) { | |
| final contents = Directory( | |
| resolve(dir), | |
| ).listSync(followLinks: false); | |
| return contents.map( | |
| (entity) => p.posix.joinAll( | |
| p.split(p.relative(entity.path, from: root)), | |
| ), | |
| ); | |
| }, | |
| ignoreForDir: (dir) { | |
| final gitIgnore = resolve('$dir/.gitignore'); | |
| final rules = [ | |
| if (fileExists(gitIgnore)) readTextFile(gitIgnore), | |
| ]; | |
| return rules.isEmpty ? null : Ignore(rules); | |
| }, | |
| isDir: (dir) { | |
| final resolved = resolve(dir); | |
| return dirExists(resolved) && !linkExists(resolved); | |
| }, | |
| ).map((file) { | |
| final relative = p.relative(resolve(file), from: package.dir); | |
| return platform.isWindows | |
| ? p.posix.joinAll(p.split(relative)) | |
| : relative; | |
| }).toSet(); | |
| final ignoredFilesCheckedIn = | |
| checkedIntoGit | |
| .where((file) => !unignoredByGitignore.contains(file)) | |
| .toList(); | |
| if (ignoredFilesCheckedIn.isNotEmpty) { | |
| warnings.add(''' | |
| ${ignoredFilesCheckedIn.length} checked-in ${pluralize('file', ignoredFilesCheckedIn.length)} ${ignoredFilesCheckedIn.length == 1 ? 'is' : 'are'} ignored by a `.gitignore`. | |
| Previous versions of Pub would include those in the published package. | |
| Consider adjusting your `.gitignore` files to not ignore those files, and if you do not wish to | |
| publish these files use `.pubignore`. See also dart.dev/go/pubignore | |
| Files that are checked in while gitignored: | |
| ${ignoredFilesCheckedIn.take(10).join('\n')} | |
| ${ignoredFilesCheckedIn.length > 10 ? '...' : ''} | |
| '''); | |
| } | |
| } | |
| } | |
| } |