fix: relative to the current directory rules (#3297)

* test for nested exact dir ignore rules

(cherry picked from commit 10aecef0c0678c84fbf7930294d6778d87568cbf)

* fix nested "only in current directory" statements

(starting with `/` symbol)

(cherry picked from commit ad3d5ac10ed5d1601f767e8db25e2fe5d39f88b2)

* add tests for delimeter in the middle

* do not append delimiter for single delimeter path

* dart fmt
diff --git a/lib/src/ignore.dart b/lib/src/ignore.dart
index ff22dff..34e51fb 100644
--- a/lib/src/ignore.dart
+++ b/lib/src/ignore.dart
@@ -25,7 +25,6 @@
 /// [Ignore.listFiles].
 ///
 /// [1]: https://git-scm.com/docs/gitignore
-
 import 'package:meta/meta.dart';
 
 /// A set of ignore rules representing a single ignore file.
@@ -148,7 +147,8 @@
         path.endsWith('/') ? path.substring(0, path.length - 1) : path;
     return listFiles(
       beneath: pathWithoutSlash,
-      includeDirs: true, // because we are listing below pathWithoutSlash
+      includeDirs: true,
+      // because we are listing below pathWithoutSlash
       listDir: (dir) {
         // List the next part of path:
         if (dir == pathWithoutSlash) return [];
@@ -285,8 +285,10 @@
       }
       if (currentIsDir) {
         final ignore = ignoreForDir(normalizedCurrent);
-        ignoreStack
-            .add(ignore == null ? null : _IgnorePrefixPair(ignore, current));
+        ignoreStack.add(ignore == null
+            ? null
+            : _IgnorePrefixPair(
+                ignore, current == '/' ? current : '$current/'));
         // Put all entities in current on the stack to be processed.
         toVisit.add(listDir(normalizedCurrent).map((x) => '/$x').toList());
         if (includeDirs) {
@@ -309,13 +311,16 @@
 
   // An invalid pattern is also considered empty.
   bool get empty => rule == null;
+
   bool get valid => exception == null;
 
   // For invalid patterns this contains a description of the problem.
   final FormatException? exception;
 
   _IgnoreParseResult(this.pattern, this.rule) : exception = null;
+
   _IgnoreParseResult.invalid(this.pattern, this.exception) : rule = null;
+
   _IgnoreParseResult.empty(this.pattern)
       : rule = null,
         exception = null;
@@ -540,7 +545,9 @@
 class _IgnorePrefixPair {
   final Ignore ignore;
   final String prefix;
+
   _IgnorePrefixPair(this.ignore, this.prefix);
+
   @override
   String toString() {
     return '{${ignore._rules.map((r) => r.original)} $prefix}';
diff --git a/test/package_list_files_test.dart b/test/package_list_files_test.dart
index 091001b..013f3a1 100644
--- a/test/package_list_files_test.dart
+++ b/test/package_list_files_test.dart
@@ -423,6 +423,170 @@
       p.join(root, 'pubignoredir', 'b.txt'),
     });
   });
+
+  group('relative to current directory rules', () {
+    setUp(ensureGit);
+    group('delimiter in the beginning', () {
+      test('ignore directory in exact directory', () async {
+        final repo = d.git(appPath, [
+          d.dir('packages', [
+            d.dir('nested', [
+              d.file('.gitignore', '/bin/'),
+              d.appPubspec(),
+              d.dir('bin', [
+                d.file('run.dart'),
+              ]),
+            ]),
+          ]),
+        ]);
+        await repo.create();
+        createEntrypoint(p.join(appPath, 'packages', 'nested'));
+
+        expect(entrypoint!.root.listFiles(), {
+          p.join(root, 'pubspec.yaml'),
+        });
+      });
+
+      test('ignore directory in exact directory', () async {
+        final repo = d.git(appPath, [
+          d.dir('packages', [
+            d.dir('nested', [
+              d.file('.gitignore', '/bin/'),
+              d.appPubspec(),
+              d.file('bin'),
+            ]),
+          ]),
+        ]);
+        await repo.create();
+        createEntrypoint(p.join(appPath, 'packages', 'nested'));
+
+        expect(entrypoint!.root.listFiles(), {
+          p.join(root, 'pubspec.yaml'),
+          p.join(root, 'bin'),
+        });
+      });
+
+      test('ignore file on exact directory', () async {
+        final repo = d.git(appPath, [
+          d.dir('packages', [
+            d.dir('nested', [
+              d.appPubspec(),
+              d.dir('bin', [
+                d.file('.gitignore', '/run.dart'),
+                d.file('run.dart'),
+              ]),
+            ]),
+          ]),
+        ]);
+        await repo.create();
+        createEntrypoint(p.join(appPath, 'packages', 'nested'));
+
+        expect(entrypoint!.root.listFiles(), {
+          p.join(root, 'pubspec.yaml'),
+        });
+      });
+
+      test('not ignore files beneath exact directory', () async {
+        final repo = d.git(appPath, [
+          d.dir('packages', [
+            d.dir('nested', [
+              d.appPubspec(),
+              d.dir('bin', [
+                d.file('.gitignore', '/run.dart'),
+                d.file('run.dart'),
+                d.dir('nested_again', [
+                  d.file('run.dart'),
+                ]),
+              ]),
+            ]),
+          ]),
+        ]);
+        await repo.create();
+        createEntrypoint(p.join(appPath, 'packages', 'nested'));
+
+        expect(entrypoint!.root.listFiles(), {
+          p.join(root, 'pubspec.yaml'),
+          p.join(root, 'bin', 'nested_again', 'run.dart'),
+        });
+      });
+
+      test('disable ignore in exact directory', () async {
+        final repo = d.git(appPath, [
+          d.dir('packages', [
+            d.file('.gitignore', 'run.dart'),
+            d.dir('nested', [
+              d.appPubspec(),
+              d.dir('bin', [
+                d.file('.gitignore', '!/run.dart'),
+                d.file('run.dart'),
+                d.dir('nested_again', [
+                  d.file('run.dart'),
+                ]),
+              ]),
+            ]),
+          ]),
+        ]);
+        await repo.create();
+        createEntrypoint(p.join(appPath, 'packages', 'nested'));
+
+        expect(entrypoint!.root.listFiles(), {
+          p.join(root, 'pubspec.yaml'),
+          p.join(root, 'bin', 'run.dart'),
+        });
+      });
+    });
+  });
+
+  group('delimiter in the middle', () {
+    test('should work with route relative to current directory ', () async {
+      final repo = d.git(appPath, [
+        d.dir('packages', [
+          d.file('.gitignore', 'nested/bin/run.dart'),
+          d.dir('nested', [
+            d.appPubspec(),
+            d.dir('bin', [
+              d.file('run.dart'),
+              d.dir('nested_again', [
+                d.file('run.dart'),
+              ]),
+            ]),
+          ]),
+        ]),
+      ]);
+      await repo.create();
+      createEntrypoint(p.join(appPath, 'packages', 'nested'));
+
+      expect(entrypoint!.root.listFiles(), {
+        p.join(root, 'pubspec.yaml'),
+        p.join(root, 'bin', 'nested_again', 'run.dart'),
+      });
+    });
+
+    test('should not have effect in nested folders', () async {
+      final repo = d.git(appPath, [
+        d.dir('packages', [
+          d.file('.gitignore', 'bin/run.dart'),
+          d.dir('nested', [
+            d.appPubspec(),
+            d.dir('bin', [
+              d.file('run.dart'),
+              d.dir('nested_again', [
+                d.file('run.dart'),
+              ]),
+            ]),
+          ]),
+        ]),
+      ]);
+      await repo.create();
+      createEntrypoint(p.join(appPath, 'packages', 'nested'));
+
+      expect(entrypoint!.root.listFiles(), {
+        p.join(root, 'pubspec.yaml'),
+        p.join(root, 'bin', 'run.dart'),
+        p.join(root, 'bin', 'nested_again', 'run.dart'),
+      });
+    });
+  });
 }
 
 void createEntrypoint([String? path]) {