Make gitignore validator use gitignores from repo-root and down. (#3169)
diff --git a/lib/src/git.dart b/lib/src/git.dart
index 0633169..5fa7536 100644
--- a/lib/src/git.dart
+++ b/lib/src/git.dart
@@ -5,6 +5,8 @@
/// Helper functionality for invoking Git.
import 'dart:async';
+import 'package:path/path.dart' as p;
+
import 'exceptions.dart';
import 'io.dart';
import 'log.dart' as log;
@@ -103,6 +105,22 @@
String? _commandCache;
+/// Returns the root of the git repo [dir] belongs to. Returns `null` if not
+/// in a git repo or git is not installed.
+String? repoRoot(String dir) {
+ if (isInstalled) {
+ try {
+ return p.normalize(
+ runSync(['rev-parse', '--show-toplevel'], workingDir: dir).first,
+ );
+ } on GitException {
+ // Not in a git folder.
+ return null;
+ }
+ }
+ return null;
+}
+
/// Checks whether [command] is the Git command for this computer.
bool _tryGitCommand(String command) {
// If "git --version" prints something familiar, git is working.
diff --git a/lib/src/package.dart b/lib/src/package.dart
index 5c46709..1aeb9dc 100644
--- a/lib/src/package.dart
+++ b/lib/src/package.dart
@@ -224,16 +224,7 @@
// An in-memory package has no files.
if (dir == null) return [];
- var root = dir;
- if (git.isInstalled) {
- try {
- root = p.normalize(
- git.runSync(['rev-parse', '--show-toplevel'], workingDir: dir).first,
- );
- } on git.GitException {
- // Not in a git folder.
- }
- }
+ var root = git.repoRoot(dir) ?? dir;
beneath = p
.toUri(p.normalize(p.relative(p.join(dir, beneath ?? '.'), from: root)))
.path;
diff --git a/lib/src/validator/gitignore.dart b/lib/src/validator/gitignore.dart
index df01214..0986fed 100644
--- a/lib/src/validator/gitignore.dart
+++ b/lib/src/validator/gitignore.dart
@@ -31,18 +31,25 @@
'--exclude-standard',
'--recurse-submodules'
], workingDir: entrypoint.root.dir);
+ final root = git.repoRoot(entrypoint.root.dir) ?? entrypoint.root.dir;
+ var beneath = p.posix.joinAll(
+ p.split(p.normalize(p.relative(entrypoint.root.dir, from: root))));
+ if (beneath == './') {
+ beneath = '';
+ }
String resolve(String path) {
if (Platform.isWindows) {
- return p.joinAll([entrypoint.root.dir, ...p.posix.split(path)]);
+ return p.joinAll([root, ...p.posix.split(path)]);
}
- return p.join(entrypoint.root.dir, path);
+ return p.join(root, path);
}
final unignoredByGitignore = Ignore.listFiles(
+ beneath: beneath,
listDir: (dir) {
var contents = Directory(resolve(dir)).listSync();
- return contents.map((entity) => p.posix.joinAll(
- p.split(p.relative(entity.path, from: entrypoint.root.dir))));
+ return contents.map((entity) =>
+ p.posix.joinAll(p.split(p.relative(entity.path, from: root))));
},
ignoreForDir: (dir) {
final gitIgnore = resolve('$dir/.gitignore');
@@ -52,8 +59,12 @@
return rules.isEmpty ? null : Ignore(rules);
},
isDir: (dir) => dirExists(resolve(dir)),
- ).toSet();
-
+ ).map((file) {
+ final relative = p.relative(resolve(file), from: entrypoint.root.dir);
+ return Platform.isWindows
+ ? p.posix.joinAll(p.split(relative))
+ : relative;
+ }).toSet();
final ignoredFilesCheckedIn = checkedIntoGit
.where((file) => !unignoredByGitignore.contains(file))
.toList();
diff --git a/test/validator/gitignore_test.dart b/test/validator/gitignore_test.dart
index d6140f5..06d3be4 100644
--- a/test/validator/gitignore_test.dart
+++ b/test/validator/gitignore_test.dart
@@ -4,18 +4,23 @@
// @dart=2.10
+import 'package:path/path.dart' as p;
import 'package:pub/src/exit_codes.dart' as exit_codes;
import 'package:test/test.dart';
import '../descriptor.dart' as d;
import '../test_pub.dart';
-Future<void> expectValidation(error, int exitCode) async {
+Future<void> expectValidation(
+ error,
+ int exitCode, {
+ String workingDirectory,
+}) async {
await runPub(
error: error,
args: ['publish', '--dry-run'],
environment: {'_PUB_TEST_SDK_VERSION': '2.12.0'},
- workingDirectory: d.path(appPath),
+ workingDirectory: workingDirectory ?? d.path(appPath),
exitCode: exitCode,
);
}
@@ -46,4 +51,37 @@
]),
exit_codes.DATA);
});
+
+ test('Should also consider gitignores from above the package root', () async {
+ await d.git('reporoot', [
+ d.dir(
+ 'myapp',
+ [
+ d.file('foo.txt'),
+ ...d.validPackage.contents,
+ ],
+ ),
+ ]).create();
+ final packageRoot = p.join(d.sandbox, 'reporoot', 'myapp');
+ await pubGet(
+ environment: {'_PUB_TEST_SDK_VERSION': '1.12.0'},
+ workingDirectory: packageRoot);
+
+ await expectValidation(contains('Package has 0 warnings.'), 0,
+ workingDirectory: packageRoot);
+
+ await d.dir('reporoot', [
+ d.file('.gitignore', '*.txt'),
+ ]).create();
+
+ await expectValidation(
+ allOf([
+ contains('Package has 1 warning.'),
+ contains('foo.txt'),
+ contains(
+ 'Consider adjusting your `.gitignore` files to not ignore those files'),
+ ]),
+ exit_codes.DATA,
+ workingDirectory: packageRoot);
+ });
}