| // 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 'package:path/path.dart' as p; |
| |
| import '../git.dart' as git; |
| import '../ignore.dart'; |
| import '../io.dart'; |
| import '../log.dart' as log; |
| 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 ? '...' : ''} |
| '''); |
| } |
| } |
| } |
| } |