From c6187b5944e5d84e9ebc6d86767f3557647c53c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Tue, 19 Apr 2022 12:16:32 +0000 Subject: [PATCH 01/13] introduce Glob --- index.js | 26 ++++++++++++++++++++++---- package.json | 4 +++- src/Glob.cc | 20 ++++++++++++++++++++ src/Glob.hh | 40 ++++++++++++++++++++++++++++++++++++++++ src/Watcher.cc | 17 ++++++++++++----- src/Watcher.hh | 10 ++++++---- src/binding.cc | 43 ++++++++++++++++++++++++++++++++++--------- yarn.lock | 22 +++++++++++++++++++++- 8 files changed, 158 insertions(+), 24 deletions(-) create mode 100644 src/Glob.cc create mode 100644 src/Glob.hh diff --git a/index.js b/index.js index 4c764f5e..d96c24eb 100644 --- a/index.js +++ b/index.js @@ -1,11 +1,29 @@ const binding = require('node-gyp-build')(__dirname); const path = require('path'); +const micromatch = require('micromatch'); +const isGlob = require('is-glob'); function normalizeOptions(dir, opts = {}) { - if (Array.isArray(opts.ignore)) { - opts = Object.assign({}, opts, { - ignore: opts.ignore.map(ignore => path.resolve(dir, ignore)), - }); + const { ignore, ...rest } = opts; + + if (Array.isArray(ignore)) { + opts = { ...rest }; + + for (const value of ignore) { + if (isGlob(value)) { + if (!opts.ignoreGlobs) { + opts.ignoreGlobs = []; + } + + opts.ignoreGlobs.push(micromatch.makeRe(value).toString()); + } else { + if (!opts.ignorePaths) { + opts.ignorePaths = []; + } + + opts.ignorePaths.push(path.resolve(dir, value)); + } + } } return opts; diff --git a/package.json b/package.json index 0af39f69..8dafc8fd 100644 --- a/package.json +++ b/package.json @@ -48,15 +48,17 @@ ] }, "dependencies": { + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", "node-addon-api": "^3.2.1", "node-gyp-build": "^4.3.0" }, "devDependencies": { "fs-extra": "^10.0.0", "husky": "^7.0.2", + "lint-staged": "^11.1.2", "mocha": "^9.1.1", "prebuildify": "^4.2.1", - "lint-staged": "^11.1.2", "prettier": "^2.3.2" }, "binary": { diff --git a/src/Glob.cc b/src/Glob.cc new file mode 100644 index 00000000..2dcd58cc --- /dev/null +++ b/src/Glob.cc @@ -0,0 +1,20 @@ +#include "Glob.hh" + +Glob::Glob(std::string raw) : Glob(raw, std::regex(raw)) + { } + +Glob::Glob(std::string raw, std::regex regex) + : mRaw(raw), + mRegex(regex) + { } + +bool Glob::isIgnored(std::string path) { + // for (auto it = mIgnore.begin(); it != mIgnore.end(); it++) { + // auto dir = *it + DIR_SEP; + // if (*it == path || path.compare(0, dir.size(), dir) == 0) { + // return true; + // } + // } + + return false; +} diff --git a/src/Glob.hh b/src/Glob.hh new file mode 100644 index 00000000..edfbebf7 --- /dev/null +++ b/src/Glob.hh @@ -0,0 +1,40 @@ +#ifndef GLOB_H +#define GLOB_H + +#include +#include + +struct Glob { + std::string mRaw; + std::regex mRegex; + + Glob(std::string raw); + Glob(std::string raw, std::regex regex); + + bool operator==(const Glob &other) const { + return mRaw == other.mRaw; + } + + bool isIgnored(std::string path) const; +}; + +namespace std +{ + template <> + struct hash + { + size_t operator()(const Glob& g) const { + return std::hash()(g.mRaw); + } + }; + + template <> + struct equal_to + { + size_t operator()(const Glob& a, const Glob& b) const { + return a.mRaw == b.mRaw; + } + }; +} + +#endif diff --git a/src/Watcher.cc b/src/Watcher.cc index a5b7ed6b..0e9f158a 100644 --- a/src/Watcher.cc +++ b/src/Watcher.cc @@ -17,8 +17,8 @@ struct WatcherCompare { static std::unordered_set, WatcherHash, WatcherCompare> sharedWatchers; -std::shared_ptr Watcher::getShared(std::string dir, std::unordered_set ignore) { - std::shared_ptr watcher = std::make_shared(dir, ignore); +std::shared_ptr Watcher::getShared(std::string dir, std::unordered_set ignorePaths, std::unordered_set ignoreGlobs) { + std::shared_ptr watcher = std::make_shared(dir, ignorePaths, ignoreGlobs); auto found = sharedWatchers.find(watcher); if (found != sharedWatchers.end()) { return *found; @@ -37,9 +37,10 @@ void removeShared(Watcher *watcher) { } } -Watcher::Watcher(std::string dir, std::unordered_set ignore) +Watcher::Watcher(std::string dir, std::unordered_set ignorePaths, std::unordered_set ignoreGlobs) : mDir(dir), - mIgnore(ignore), + mIgnorePaths(ignorePaths), + mIgnoreGlobs(ignoreGlobs), mWatched(false), mAsync(NULL), mCallingCallbacks(false) { @@ -199,12 +200,18 @@ void Watcher::onClose(uv_handle_t *handle) { } bool Watcher::isIgnored(std::string path) { - for (auto it = mIgnore.begin(); it != mIgnore.end(); it++) { + for (auto it = mIgnorePaths.begin(); it != mIgnorePaths.end(); it++) { auto dir = *it + DIR_SEP; if (*it == path || path.compare(0, dir.size(), dir) == 0) { return true; } } + for (auto it = mIgnoreGlobs.begin(); it != mIgnoreGlobs.end(); it++) { + if (it->isIgnored(path)) { + return true; + } + } + return false; } diff --git a/src/Watcher.hh b/src/Watcher.hh index d836911e..5aac4f59 100644 --- a/src/Watcher.hh +++ b/src/Watcher.hh @@ -6,6 +6,7 @@ #include #include #include +#include "Glob.hh" #include "Event.hh" #include "Debounce.hh" #include "DirTree.hh" @@ -15,16 +16,17 @@ using namespace Napi; struct Watcher { std::string mDir; - std::unordered_set mIgnore; + std::unordered_set mIgnorePaths; + std::unordered_set mIgnoreGlobs; EventList mEvents; void *state; bool mWatched; - Watcher(std::string dir, std::unordered_set ignore); + Watcher(std::string dir, std::unordered_set ignorePaths, std::unordered_set ignoreGlobs); ~Watcher(); bool operator==(const Watcher &other) const { - return mDir == other.mDir && mIgnore == other.mIgnore; + return mDir == other.mDir && mIgnorePaths == other.mIgnorePaths && mIgnoreGlobs == other.mIgnoreGlobs; } void wait(); @@ -35,7 +37,7 @@ struct Watcher { void unref(); bool isIgnored(std::string path); - static std::shared_ptr getShared(std::string dir, std::unordered_set ignore); + static std::shared_ptr getShared(std::string dir, std::unordered_set ignorePaths, std::unordered_set ignoreGlobs); private: std::mutex mMutex; diff --git a/src/binding.cc b/src/binding.cc index e2b54efe..747f2603 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -2,6 +2,7 @@ #include #include #include +#include "Glob.hh" #include "Event.hh" #include "Backend.hh" #include "Watcher.hh" @@ -9,23 +10,43 @@ using namespace Napi; -std::unordered_set getIgnore(Env env, Value opts) { - std::unordered_set ignore; +std::unordered_set getIgnorePaths(Env env, Value opts) { + std::unordered_set result; if (opts.IsObject()) { - Value v = opts.As().Get(String::New(env, "ignore")); + Value v = opts.As().Get(String::New(env, "ignorePaths")); if (v.IsArray()) { Array items = v.As(); for (size_t i = 0; i < items.Length(); i++) { Value item = items.Get(Number::New(env, i)); if (item.IsString()) { - ignore.insert(std::string(item.As().Utf8Value().c_str())); + result.insert(std::string(item.As().Utf8Value().c_str())); } } } } - return ignore; + return result; +} + +std::unordered_set getIgnoreGlobs(Env env, Value opts) { + std::unordered_set result; + + if (opts.IsObject()) { + Value v = opts.As().Get(String::New(env, "ignoreGlobs")); + if (v.IsArray()) { + Array items = v.As(); + for (size_t i = 0; i < items.Length(); i++) { + Value item = items.Get(Number::New(env, i)); + if (item.IsString()) { + auto key = item.As().Utf8Value(); + result.emplace(key, std::regex(key.c_str())); + } + } + } + } + + return result; } std::shared_ptr getBackend(Env env, Value opts) { @@ -45,7 +66,8 @@ class WriteSnapshotRunner : public PromiseRunner { snapshotPath(std::string(snap.As().Utf8Value().c_str())) { watcher = Watcher::getShared( std::string(dir.As().Utf8Value().c_str()), - getIgnore(env, opts) + getIgnorePaths(env, opts), + getIgnoreGlobs(env, opts) ); backend = getBackend(env, opts); @@ -72,7 +94,8 @@ class GetEventsSinceRunner : public PromiseRunner { snapshotPath(std::string(snap.As().Utf8Value().c_str())) { watcher = std::make_shared( std::string(dir.As().Utf8Value().c_str()), - getIgnore(env, opts) + getIgnorePaths(env, opts), + getIgnoreGlobs(env, opts) ); backend = getBackend(env, opts); @@ -137,7 +160,8 @@ class SubscribeRunner : public PromiseRunner { SubscribeRunner(Env env, Value dir, Value fn, Value opts) : PromiseRunner(env) { watcher = Watcher::getShared( std::string(dir.As().Utf8Value().c_str()), - getIgnore(env, opts) + getIgnorePaths(env, opts), + getIgnoreGlobs(env, opts) ); backend = getBackend(env, opts); @@ -160,7 +184,8 @@ class UnsubscribeRunner : public PromiseRunner { UnsubscribeRunner(Env env, Value dir, Value fn, Value opts) : PromiseRunner(env) { watcher = Watcher::getShared( std::string(dir.As().Utf8Value().c_str()), - getIgnore(env, opts) + getIgnorePaths(env, opts), + getIgnoreGlobs(env, opts) ); backend = getBackend(env, opts); diff --git a/yarn.lock b/yarn.lock index e680bbc6..8b0c7325 100644 --- a/yarn.lock +++ b/yarn.lock @@ -133,7 +133,7 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.1, braces@~3.0.2: +braces@^3.0.1, braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -561,6 +561,13 @@ is-glob@^4.0.1, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" +is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -698,6 +705,14 @@ micromatch@^4.0.4: braces "^3.0.1" picomatch "^2.2.3" +micromatch@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" @@ -889,6 +904,11 @@ picomatch@^2.2.3: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + please-upgrade-node@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" From 6fe2c02050a98910ea4da9faaeb1ad86a6f0a71f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Tue, 19 Apr 2022 18:37:00 +0000 Subject: [PATCH 02/13] wrap up implementation --- src/Glob.cc | 13 +++---------- src/Glob.hh | 16 ++++------------ src/Watcher.cc | 10 +++++++++- src/linux/InotifyBackend.cc | 2 +- src/macos/FSEventsBackend.cc | 4 ++-- src/unix/fts.cc | 2 +- src/unix/legacy.cc | 2 +- src/watchman/WatchmanBackend.cc | 4 ++-- src/windows/WindowsBackend.cc | 2 +- 9 files changed, 24 insertions(+), 31 deletions(-) diff --git a/src/Glob.cc b/src/Glob.cc index 2dcd58cc..2a37381d 100644 --- a/src/Glob.cc +++ b/src/Glob.cc @@ -4,17 +4,10 @@ Glob::Glob(std::string raw) : Glob(raw, std::regex(raw)) { } Glob::Glob(std::string raw, std::regex regex) - : mRaw(raw), + : mHash(std::hash()(raw)), mRegex(regex) { } -bool Glob::isIgnored(std::string path) { - // for (auto it = mIgnore.begin(); it != mIgnore.end(); it++) { - // auto dir = *it + DIR_SEP; - // if (*it == path || path.compare(0, dir.size(), dir) == 0) { - // return true; - // } - // } - - return false; +bool Glob::isIgnored(std::string relative_path) const { + return std::regex_match(relative_path, mRegex); } diff --git a/src/Glob.hh b/src/Glob.hh index edfbebf7..682c7d99 100644 --- a/src/Glob.hh +++ b/src/Glob.hh @@ -5,17 +5,17 @@ #include struct Glob { - std::string mRaw; + std::size_t mHash; std::regex mRegex; Glob(std::string raw); Glob(std::string raw, std::regex regex); bool operator==(const Glob &other) const { - return mRaw == other.mRaw; + return mHash == other.mHash; } - bool isIgnored(std::string path) const; + bool isIgnored(std::string relative_path) const; }; namespace std @@ -24,15 +24,7 @@ namespace std struct hash { size_t operator()(const Glob& g) const { - return std::hash()(g.mRaw); - } - }; - - template <> - struct equal_to - { - size_t operator()(const Glob& a, const Glob& b) const { - return a.mRaw == b.mRaw; + return g.mHash; } }; } diff --git a/src/Watcher.cc b/src/Watcher.cc index 0e9f158a..384c243d 100644 --- a/src/Watcher.cc +++ b/src/Watcher.cc @@ -207,8 +207,16 @@ bool Watcher::isIgnored(std::string path) { } } + auto basePath = mDir + DIR_SEP; + + if (path.rfind(basePath, 0) != 0) { + return false; + } + + auto relativePath = path.substr(basePath.size()); + for (auto it = mIgnoreGlobs.begin(); it != mIgnoreGlobs.end(); it++) { - if (it->isIgnored(path)) { + if (it->isIgnored(relativePath)) { return true; } } diff --git a/src/linux/InotifyBackend.cc b/src/linux/InotifyBackend.cc index 60ebc633..8de21e0e 100644 --- a/src/linux/InotifyBackend.cc +++ b/src/linux/InotifyBackend.cc @@ -154,7 +154,7 @@ bool InotifyBackend::handleSubscription(struct inotify_event *event, std::shared path += "/" + std::string(event->name); } - if (watcher->mIgnore.count(path) > 0) { + if (watcher->isIgnored(path)) { return false; } diff --git a/src/macos/FSEventsBackend.cc b/src/macos/FSEventsBackend.cc index 793b347f..0f74f163 100644 --- a/src/macos/FSEventsBackend.cc +++ b/src/macos/FSEventsBackend.cc @@ -187,8 +187,8 @@ void FSEventsBackend::startStream(Watcher &watcher, FSEventStreamEventId id) { kFSEventStreamCreateFlagFileEvents ); - CFMutableArrayRef exclusions = CFArrayCreateMutable(NULL, watcher.mIgnore.size(), NULL); - for (auto it = watcher.mIgnore.begin(); it != watcher.mIgnore.end(); it++) { + CFMutableArrayRef exclusions = CFArrayCreateMutable(NULL, watcher.mIgnorePaths.size(), NULL); + for (auto it = watcher.mIgnorePaths.begin(); it != watcher.mIgnorePaths.end(); it++) { CFStringRef path = CFStringCreateWithCString( NULL, it->c_str(), diff --git a/src/unix/fts.cc b/src/unix/fts.cc index 10e85958..5c4d81fc 100644 --- a/src/unix/fts.cc +++ b/src/unix/fts.cc @@ -36,7 +36,7 @@ void BruteForceBackend::readTree(Watcher &watcher, std::shared_ptr tree throw WatcherError(strerror(ENOTDIR), &watcher); } - if (watcher.mIgnore.count(std::string(node->fts_path)) > 0) { + if (watcher.mIgnorePaths.count(std::string(node->fts_path)) > 0) { fts_set(fts, node, FTS_SKIP); continue; } diff --git a/src/unix/legacy.cc b/src/unix/legacy.cc index e4108675..1e3bf8ed 100644 --- a/src/unix/legacy.cc +++ b/src/unix/legacy.cc @@ -44,7 +44,7 @@ void iterateDir(Watcher &watcher, const std::shared_ptr tree, const ch std::string fullPath = dirname + "/" + ent->d_name; - if (watcher.mIgnore.count(fullPath) == 0) { + if (watcher.mIgnorePaths.count(fullPath) == 0) { struct stat attrib; fstatat(new_fd, ent->d_name, &attrib, AT_SYMLINK_NOFOLLOW); bool isDir = ent->d_type == DT_DIR; diff --git a/src/watchman/WatchmanBackend.cc b/src/watchman/WatchmanBackend.cc index c4aa6099..cba4251b 100644 --- a/src/watchman/WatchmanBackend.cc +++ b/src/watchman/WatchmanBackend.cc @@ -292,12 +292,12 @@ void WatchmanBackend::subscribe(Watcher &watcher) { opts.emplace("fields", fields); opts.emplace("since", clock(watcher)); - if (watcher.mIgnore.size() > 0) { + if (watcher.mIgnorePaths.size() > 0) { BSER::Array ignore; BSER::Array anyOf; anyOf.push_back("anyof"); - for (auto it = watcher.mIgnore.begin(); it != watcher.mIgnore.end(); it++) { + for (auto it = watcher.mIgnorePaths.begin(); it != watcher.mIgnorePaths.end(); it++) { std::string pathStart = watcher.mDir + DIR_SEP; if (it->rfind(pathStart, 0) == 0) { auto relative = it->substr(pathStart.size()); diff --git a/src/windows/WindowsBackend.cc b/src/windows/WindowsBackend.cc index 2e54acbe..27de9680 100644 --- a/src/windows/WindowsBackend.cc +++ b/src/windows/WindowsBackend.cc @@ -37,7 +37,7 @@ void BruteForceBackend::readTree(Watcher &watcher, std::shared_ptr tree do { if (strcmp(ffd.cFileName, ".") != 0 && strcmp(ffd.cFileName, "..") != 0) { std::string fullPath = path + "\\" + ffd.cFileName; - if (watcher.mIgnore.count(fullPath) > 0) { + if (watcher.mIgnorePaths.count(fullPath) > 0) { continue; } From 5065a5db5d28c6b5eca23f188b2910463463eb8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Tue, 19 Apr 2022 19:05:50 +0000 Subject: [PATCH 03/13] add first test --- test/watcher.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/test/watcher.js b/test/watcher.js index 4646fdec..37c3161c 100644 --- a/test/watcher.js +++ b/test/watcher.js @@ -46,7 +46,7 @@ describe('watcher', () => { ...dir, `test${c++}${Math.random().toString(31).slice(2)}`, ); - let ignoreDir, ignoreFile, fileToRename, dirToRename, sub; + let ignoreDir, ignoreFile, ignoreGlobDir, fileToRename, dirToRename, sub; before(async () => { tmpDir = path.join( @@ -56,6 +56,7 @@ describe('watcher', () => { fs.mkdirpSync(tmpDir); ignoreDir = getFilename(); ignoreFile = getFilename(); + ignoreGlobDir = getFilename(); fileToRename = getFilename(); dirToRename = getFilename(); fs.writeFileSync(fileToRename, 'hi'); @@ -63,7 +64,7 @@ describe('watcher', () => { await new Promise((resolve) => setTimeout(resolve, 100)); sub = await watcher.subscribe(tmpDir, fn, { backend, - ignore: [ignoreDir, ignoreFile], + ignore: [ignoreDir, ignoreFile, `${ignoreGlobDir}/*.ignore`, `${ignoreGlobDir}/ignore/**`], }); }); @@ -516,6 +517,18 @@ describe('watcher', () => { let res = await nextEvent(); assert.deepEqual(res, [{type: 'create', path: f1}]); }); + + it('should ignore globs', async () => { + await fs.mkdir(path.join(ignoreGlobDir, 'ignore')); + + fs.writeFile(path.join(ignoreGlobDir, 'test.txt'), 'hello'); + fs.writeFile(path.join(ignoreGlobDir, 'test.ignore'), 'hello'); + fs.writeFile(path.join(ignoreGlobDir, 'ignore', 'test.txt'), 'hello'); + fs.writeFile(path.join(ignoreGlobDir, 'ignore', 'test.ignore'), 'hello'); + + let res = await nextEvent(); + assert.deepEqual(res, [{type: 'create', path: path.join(ignoreGlobDir, 'test.txt')}]); + }); }); describe('multiple', () => { From aa1630adbbdaaa2c9562ed73f2fd0d8bdb258124 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 12 Dec 2022 14:04:00 +0000 Subject: [PATCH 04/13] wip: make sure this compiles and fix the test --- binding.gyp | 2 +- index.js | 3 ++- test/watcher.js | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/binding.gyp b/binding.gyp index 5a33d887..c7dd5524 100644 --- a/binding.gyp +++ b/binding.gyp @@ -3,7 +3,7 @@ { "target_name": "watcher", "defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ], - "sources": [ "src/binding.cc", "src/Watcher.cc", "src/Backend.cc", "src/DirTree.cc" ], + "sources": [ "src/binding.cc", "src/Watcher.cc", "src/Backend.cc", "src/DirTree.cc", "src/Glob.cc" ], "include_dirs" : [" { ignoreDir = getFilename(); ignoreFile = getFilename(); ignoreGlobDir = getFilename(); + await fs.mkdir(ignoreGlobDir); + await fs.mkdir(path.join(ignoreGlobDir, 'ignore')); fileToRename = getFilename(); dirToRename = getFilename(); fs.writeFileSync(fileToRename, 'hi'); @@ -518,9 +520,7 @@ describe('watcher', () => { assert.deepEqual(res, [{type: 'create', path: f1}]); }); - it('should ignore globs', async () => { - await fs.mkdir(path.join(ignoreGlobDir, 'ignore')); - + it.only('should ignore globs', async () => { fs.writeFile(path.join(ignoreGlobDir, 'test.txt'), 'hello'); fs.writeFile(path.join(ignoreGlobDir, 'test.ignore'), 'hello'); fs.writeFile(path.join(ignoreGlobDir, 'ignore', 'test.txt'), 'hello'); From 489e5acd6da8bafb00b15979f121ee449642e5d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Tue, 13 Dec 2022 11:02:10 +0000 Subject: [PATCH 05/13] wip --- playground/.gitignore | 2 ++ playground/Makefile | 13 +++++++++++++ playground/main.cc | 21 +++++++++++++++++++++ src/Glob.cc | 10 ++++++++-- src/Glob.hh | 1 + src/binding.cc | 3 +++ test/watcher.js | 9 +++++++-- 7 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 playground/.gitignore create mode 100644 playground/Makefile create mode 100644 playground/main.cc diff --git a/playground/.gitignore b/playground/.gitignore new file mode 100644 index 00000000..cb1e3e4f --- /dev/null +++ b/playground/.gitignore @@ -0,0 +1,2 @@ +main +main.o \ No newline at end of file diff --git a/playground/Makefile b/playground/Makefile new file mode 100644 index 00000000..51467bd2 --- /dev/null +++ b/playground/Makefile @@ -0,0 +1,13 @@ +CC=g++ +CFLAGS=-c -Wall + +all: main + +main: main.o + $(CC) main.o -o main + +main.o: main.cc + $(CC) $(CFLAGS) main.cc + +clean: + rm -rf *o main diff --git a/playground/main.cc b/playground/main.cc new file mode 100644 index 00000000..700b95ac --- /dev/null +++ b/playground/main.cc @@ -0,0 +1,21 @@ +#include +#include + +int main() { + + // std::string raw("^((?!(\\/)\\.).)*$"); + + std::string raw("^(\\/tmp\\/k028ma9g29i\\/test2t71u8blfak5\\/ignore(\\/(?!\\.)(((?!(\\/)\\.).)*?)|$))$"); + // std::string raw("^(?:\\/tmp\\/k028ma9g29i\\/test2t71u8blfak5\\/(?!\\.)(?=.)[^/]*?\\.ignore)$"); + + + // micromatch + // std::string raw("^(?:(?!\\.)(?:(?:(?!(?:^|\\/)\\.).)*?)\\/?)$"); + + std::cout << raw << std::endl; + std::regex rx(raw); + std::cout << std::regex_match("/tmp/k028ma9g29i/test2t71u8blfak5/test.txt", rx) << std::endl; + std::cout << std::regex_match("/tmp/k028ma9g29i/test2t71u8blfak5/test.ignore", rx) << std::endl; + std::cout << std::regex_match("/tmp/k028ma9g29i/test2t71u8blfak5/ignore/test.ignore", rx) << std::endl; + std::cout << std::regex_match("/tmp/k028ma9g29i/test2t71u8blfak5/ignore/test.txt", rx) << std::endl; +} \ No newline at end of file diff --git a/src/Glob.cc b/src/Glob.cc index 2a37381d..927dd58a 100644 --- a/src/Glob.cc +++ b/src/Glob.cc @@ -1,13 +1,19 @@ #include "Glob.hh" +#include Glob::Glob(std::string raw) : Glob(raw, std::regex(raw)) { } Glob::Glob(std::string raw, std::regex regex) : mHash(std::hash()(raw)), - mRegex(regex) + mRegex(regex), + mRaw(raw) { } bool Glob::isIgnored(std::string relative_path) const { - return std::regex_match(relative_path, mRegex); + auto result = std::regex_match(relative_path, mRegex); + + std::cout << "isIgnored " << mRaw << ", " << relative_path << ": " << result << std::endl; + + return result; } diff --git a/src/Glob.hh b/src/Glob.hh index 682c7d99..1c491317 100644 --- a/src/Glob.hh +++ b/src/Glob.hh @@ -7,6 +7,7 @@ struct Glob { std::size_t mHash; std::regex mRegex; + std::string mRaw; Glob(std::string raw); Glob(std::string raw, std::regex regex); diff --git a/src/binding.cc b/src/binding.cc index 747f2603..75f8de5a 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -31,6 +31,8 @@ std::unordered_set getIgnorePaths(Env env, Value opts) { std::unordered_set getIgnoreGlobs(Env env, Value opts) { std::unordered_set result; + + std::cout << "getIgnoreGlobs" << std::endl; if (opts.IsObject()) { Value v = opts.As().Get(String::New(env, "ignoreGlobs")); @@ -40,6 +42,7 @@ std::unordered_set getIgnoreGlobs(Env env, Value opts) { Value item = items.Get(Number::New(env, i)); if (item.IsString()) { auto key = item.As().Utf8Value(); + std::cout << "glob " << key << std::endl; result.emplace(key, std::regex(key.c_str())); } } diff --git a/test/watcher.js b/test/watcher.js index d00b9745..cba3404d 100644 --- a/test/watcher.js +++ b/test/watcher.js @@ -57,6 +57,7 @@ describe('watcher', () => { ignoreDir = getFilename(); ignoreFile = getFilename(); ignoreGlobDir = getFilename(); + const ignoreGlobDirName = path.basename(ignoreGlobDir); await fs.mkdir(ignoreGlobDir); await fs.mkdir(path.join(ignoreGlobDir, 'ignore')); fileToRename = getFilename(); @@ -66,7 +67,8 @@ describe('watcher', () => { await new Promise((resolve) => setTimeout(resolve, 100)); sub = await watcher.subscribe(tmpDir, fn, { backend, - ignore: [ignoreDir, ignoreFile, `${ignoreGlobDir}/*.ignore`, `${ignoreGlobDir}/ignore/**`], + ignore: [ignoreDir, ignoreFile, /* `${ignoreGlobDirName}/*.ignore`, */ `${ignoreGlobDirName}/ignore/**` + ], }); }); @@ -527,7 +529,10 @@ describe('watcher', () => { fs.writeFile(path.join(ignoreGlobDir, 'ignore', 'test.ignore'), 'hello'); let res = await nextEvent(); - assert.deepEqual(res, [{type: 'create', path: path.join(ignoreGlobDir, 'test.txt')}]); + assert.deepEqual(res, [ + {type: 'create', path: path.join(ignoreGlobDir, 'test.ignore')}, + {type: 'create', path: path.join(ignoreGlobDir, 'test.txt')}, + ]); }); }); From 4537a9bcb334dd94d6fc0caa6d1c270a4af13b5a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 17 Dec 2022 14:00:05 +0100 Subject: [PATCH 06/13] use `dot: true` for `micromatch` --- index.js | 9 +++++++-- playground/.gitignore | 2 -- playground/Makefile | 13 ------------- playground/main.cc | 21 --------------------- src/Glob.cc | 6 +----- src/binding.cc | 3 --- test/watcher.js | 5 ++--- 7 files changed, 10 insertions(+), 49 deletions(-) delete mode 100644 playground/.gitignore delete mode 100644 playground/Makefile delete mode 100644 playground/main.cc diff --git a/index.js b/index.js index fdd5a694..486fb38c 100644 --- a/index.js +++ b/index.js @@ -15,8 +15,13 @@ function normalizeOptions(dir, opts = {}) { opts.ignoreGlobs = []; } - const regex = micromatch.makeRe(value).toString(); - opts.ignoreGlobs.push(regex.substring(1, regex.length - 1)); + // Ask micromatch to return a regular expression for + // the given glob pattern. We set `dot: true` to workaround + // an issue with the regular expression on Linux where + // the resulting negative lookahead `(?!(\\/|^)` was never + // matching in some cases. See also https://bit.ly/3UZlQDm + const regex = micromatch.makeRe(value, { dot: true }); + opts.ignoreGlobs.push(regex.source); } else { if (!opts.ignorePaths) { opts.ignorePaths = []; diff --git a/playground/.gitignore b/playground/.gitignore deleted file mode 100644 index cb1e3e4f..00000000 --- a/playground/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -main -main.o \ No newline at end of file diff --git a/playground/Makefile b/playground/Makefile deleted file mode 100644 index 51467bd2..00000000 --- a/playground/Makefile +++ /dev/null @@ -1,13 +0,0 @@ -CC=g++ -CFLAGS=-c -Wall - -all: main - -main: main.o - $(CC) main.o -o main - -main.o: main.cc - $(CC) $(CFLAGS) main.cc - -clean: - rm -rf *o main diff --git a/playground/main.cc b/playground/main.cc deleted file mode 100644 index 700b95ac..00000000 --- a/playground/main.cc +++ /dev/null @@ -1,21 +0,0 @@ -#include -#include - -int main() { - - // std::string raw("^((?!(\\/)\\.).)*$"); - - std::string raw("^(\\/tmp\\/k028ma9g29i\\/test2t71u8blfak5\\/ignore(\\/(?!\\.)(((?!(\\/)\\.).)*?)|$))$"); - // std::string raw("^(?:\\/tmp\\/k028ma9g29i\\/test2t71u8blfak5\\/(?!\\.)(?=.)[^/]*?\\.ignore)$"); - - - // micromatch - // std::string raw("^(?:(?!\\.)(?:(?:(?!(?:^|\\/)\\.).)*?)\\/?)$"); - - std::cout << raw << std::endl; - std::regex rx(raw); - std::cout << std::regex_match("/tmp/k028ma9g29i/test2t71u8blfak5/test.txt", rx) << std::endl; - std::cout << std::regex_match("/tmp/k028ma9g29i/test2t71u8blfak5/test.ignore", rx) << std::endl; - std::cout << std::regex_match("/tmp/k028ma9g29i/test2t71u8blfak5/ignore/test.ignore", rx) << std::endl; - std::cout << std::regex_match("/tmp/k028ma9g29i/test2t71u8blfak5/ignore/test.txt", rx) << std::endl; -} \ No newline at end of file diff --git a/src/Glob.cc b/src/Glob.cc index 927dd58a..c8747877 100644 --- a/src/Glob.cc +++ b/src/Glob.cc @@ -11,9 +11,5 @@ Glob::Glob(std::string raw, std::regex regex) { } bool Glob::isIgnored(std::string relative_path) const { - auto result = std::regex_match(relative_path, mRegex); - - std::cout << "isIgnored " << mRaw << ", " << relative_path << ": " << result << std::endl; - - return result; + return std::regex_match(relative_path, mRegex); } diff --git a/src/binding.cc b/src/binding.cc index 75f8de5a..70a01a1b 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -32,8 +32,6 @@ std::unordered_set getIgnorePaths(Env env, Value opts) { std::unordered_set getIgnoreGlobs(Env env, Value opts) { std::unordered_set result; - std::cout << "getIgnoreGlobs" << std::endl; - if (opts.IsObject()) { Value v = opts.As().Get(String::New(env, "ignoreGlobs")); if (v.IsArray()) { @@ -42,7 +40,6 @@ std::unordered_set getIgnoreGlobs(Env env, Value opts) { Value item = items.Get(Number::New(env, i)); if (item.IsString()) { auto key = item.As().Utf8Value(); - std::cout << "glob " << key << std::endl; result.emplace(key, std::regex(key.c_str())); } } diff --git a/test/watcher.js b/test/watcher.js index cba3404d..49cfe00e 100644 --- a/test/watcher.js +++ b/test/watcher.js @@ -67,8 +67,7 @@ describe('watcher', () => { await new Promise((resolve) => setTimeout(resolve, 100)); sub = await watcher.subscribe(tmpDir, fn, { backend, - ignore: [ignoreDir, ignoreFile, /* `${ignoreGlobDirName}/*.ignore`, */ `${ignoreGlobDirName}/ignore/**` - ], + ignore: [ignoreDir, ignoreFile, /*`${ignoreGlobDirName}/*.ignore`,*/ `${ignoreGlobDirName}/ignore/**`] }); }); @@ -522,7 +521,7 @@ describe('watcher', () => { assert.deepEqual(res, [{type: 'create', path: f1}]); }); - it.only('should ignore globs', async () => { + it('should ignore globs', async () => { fs.writeFile(path.join(ignoreGlobDir, 'test.txt'), 'hello'); fs.writeFile(path.join(ignoreGlobDir, 'test.ignore'), 'hello'); fs.writeFile(path.join(ignoreGlobDir, 'ignore', 'test.txt'), 'hello'); From 15ef7b80c3791a725455e073b2d02b3b4676660b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 17 Dec 2022 14:08:12 +0100 Subject: [PATCH 07/13] tweak tests more --- test/watcher.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/watcher.js b/test/watcher.js index 7089748b..d42fe343 100644 --- a/test/watcher.js +++ b/test/watcher.js @@ -67,7 +67,7 @@ describe('watcher', () => { await new Promise((resolve) => setTimeout(resolve, 100)); sub = await watcher.subscribe(tmpDir, fn, { backend, - ignore: [ignoreDir, ignoreFile, /*`${ignoreGlobDirName}/*.ignore`,*/ `${ignoreGlobDirName}/ignore/**`] + ignore: [ignoreDir, ignoreFile, `${ignoreGlobDirName}/*.ignore`, `${ignoreGlobDirName}/ignore/**`] }); }); @@ -560,7 +560,6 @@ describe('watcher', () => { let res = await nextEvent(); assert.deepEqual(res, [ - {type: 'create', path: path.join(ignoreGlobDir, 'test.ignore')}, {type: 'create', path: path.join(ignoreGlobDir, 'test.txt')}, ]); }); From 239d3bea03818a2b98e80ca2078bd95f7593b814 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 18 Dec 2022 08:07:23 +0100 Subject: [PATCH 08/13] test - add a more complex glob test --- test/watcher.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/watcher.js b/test/watcher.js index d42fe343..77371740 100644 --- a/test/watcher.js +++ b/test/watcher.js @@ -60,6 +60,8 @@ describe('watcher', () => { const ignoreGlobDirName = path.basename(ignoreGlobDir); await fs.mkdir(ignoreGlobDir); await fs.mkdir(path.join(ignoreGlobDir, 'ignore')); + await fs.mkdir(path.join(ignoreGlobDir, 'erongi')); + await fs.mkdir(path.join(ignoreGlobDir, 'erongi', 'deep')); fileToRename = getFilename(); dirToRename = getFilename(); fs.writeFileSync(fileToRename, 'hi'); @@ -67,7 +69,7 @@ describe('watcher', () => { await new Promise((resolve) => setTimeout(resolve, 100)); sub = await watcher.subscribe(tmpDir, fn, { backend, - ignore: [ignoreDir, ignoreFile, `${ignoreGlobDirName}/*.ignore`, `${ignoreGlobDirName}/ignore/**`] + ignore: [ignoreDir, ignoreFile, `${ignoreGlobDirName}/*.ignore`, `${ignoreGlobDirName}/ignore/**`, `${ignoreGlobDirName}/[a-e]?on(g|l)i/**`] }); }); @@ -557,6 +559,8 @@ describe('watcher', () => { fs.writeFile(path.join(ignoreGlobDir, 'test.ignore'), 'hello'); fs.writeFile(path.join(ignoreGlobDir, 'ignore', 'test.txt'), 'hello'); fs.writeFile(path.join(ignoreGlobDir, 'ignore', 'test.ignore'), 'hello'); + fs.writeFile(path.join(ignoreGlobDir, 'erongi', 'test.txt'), 'hello'); + fs.writeFile(path.join(ignoreGlobDir, 'erongi', 'deep', 'test.txt'), 'hello'); let res = await nextEvent(); assert.deepEqual(res, [ From 968d5c89405deaae890d6f870ca13bafa17861cd Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 18 Dec 2022 08:12:05 +0100 Subject: [PATCH 09/13] glob - disable lookbehinds --- index.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 486fb38c..66c8b252 100644 --- a/index.js +++ b/index.js @@ -15,12 +15,15 @@ function normalizeOptions(dir, opts = {}) { opts.ignoreGlobs = []; } - // Ask micromatch to return a regular expression for - // the given glob pattern. We set `dot: true` to workaround - // an issue with the regular expression on Linux where - // the resulting negative lookahead `(?!(\\/|^)` was never - // matching in some cases. See also https://bit.ly/3UZlQDm - const regex = micromatch.makeRe(value, { dot: true }); + const regex = micromatch.makeRe(value, { + // We set `dot: true` to workaround an issue with the + // regular expression on Linux where the resulting + // negative lookahead `(?!(\\/|^)` was never matching + // in some cases. See also https://bit.ly/3UZlQDm + dot: true, + // C++ does not support lookbehind regex patterns + lookbehinds: false + }); opts.ignoreGlobs.push(regex.source); } else { if (!opts.ignorePaths) { From 6ca15ed0e428bfebc910b8937d6103f1a0b44fc2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 18 Dec 2022 08:25:56 +0100 Subject: [PATCH 10/13] update comment --- index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 66c8b252..1a1bb40b 100644 --- a/index.js +++ b/index.js @@ -21,7 +21,9 @@ function normalizeOptions(dir, opts = {}) { // negative lookahead `(?!(\\/|^)` was never matching // in some cases. See also https://bit.ly/3UZlQDm dot: true, - // C++ does not support lookbehind regex patterns + // C++ does not support lookbehind regex patterns, they + // were only added later to JavaScript engines + // (https://bit.ly/3V7S6UL) lookbehinds: false }); opts.ignoreGlobs.push(regex.source); From ae0b6d51c3883902d4be3e80b51266a9b9fe2731 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 19 Dec 2022 09:14:41 +0100 Subject: [PATCH 11/13] more use of `isIgnored` over `mIgnorePaths` --- src/unix/fts.cc | 2 +- src/unix/legacy.cc | 2 +- src/windows/WindowsBackend.cc | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/unix/fts.cc b/src/unix/fts.cc index 5c4d81fc..6b411297 100644 --- a/src/unix/fts.cc +++ b/src/unix/fts.cc @@ -36,7 +36,7 @@ void BruteForceBackend::readTree(Watcher &watcher, std::shared_ptr tree throw WatcherError(strerror(ENOTDIR), &watcher); } - if (watcher.mIgnorePaths.count(std::string(node->fts_path)) > 0) { + if (watcher.isIgnored(std::string(node->fts_path))) { fts_set(fts, node, FTS_SKIP); continue; } diff --git a/src/unix/legacy.cc b/src/unix/legacy.cc index 1e3bf8ed..01911282 100644 --- a/src/unix/legacy.cc +++ b/src/unix/legacy.cc @@ -44,7 +44,7 @@ void iterateDir(Watcher &watcher, const std::shared_ptr tree, const ch std::string fullPath = dirname + "/" + ent->d_name; - if (watcher.mIgnorePaths.count(fullPath) == 0) { + if (!watcher.isIgnored(fullPath)) { struct stat attrib; fstatat(new_fd, ent->d_name, &attrib, AT_SYMLINK_NOFOLLOW); bool isDir = ent->d_type == DT_DIR; diff --git a/src/windows/WindowsBackend.cc b/src/windows/WindowsBackend.cc index 27de9680..b5045495 100644 --- a/src/windows/WindowsBackend.cc +++ b/src/windows/WindowsBackend.cc @@ -37,7 +37,7 @@ void BruteForceBackend::readTree(Watcher &watcher, std::shared_ptr tree do { if (strcmp(ffd.cFileName, ".") != 0 && strcmp(ffd.cFileName, "..") != 0) { std::string fullPath = path + "\\" + ffd.cFileName; - if (watcher.mIgnorePaths.count(fullPath) > 0) { + if (watcher.isIgnored(fullPath)) { continue; } From 3c513c032d9aff23bf293943016a49e64ecbed0e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 19 Dec 2022 09:22:31 +0100 Subject: [PATCH 12/13] update docs --- README.md | 4 +++- index.d.ts | 3 ++- index.js.flow | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d4a9b1df..a6934bdf 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,9 @@ You can specify the exact backend you wish to use by passing the `backend` optio All of the APIs in `@parcel/watcher` support the following options, which are passed as an object as the last function argument. -- `ignore` - an array of paths to ignore. They can be either files or directories. No events will be emitted about these files or directories or their children. +- `ignore` - an array of paths or glob patterns to ignore. uses [`is-glob`](https://github.com/micromatch/is-glob) to distinguish paths from globs. glob patterns are parsed with [`micromatch`](https://github.com/micromatch/micromatch) (see [features](https://github.com/micromatch/micromatch#matching-features)). + - paths have to be absolute and can either be files or directories. No events will be emitted about these files or directories or their children. + - glob patterns match on relative paths from the root that is watched. No events will be emitted for matching paths. - `backend` - the name of an explicitly chosen backend to use. Allowed options are `"fs-events"`, `"watchman"`, `"inotify"`, `"windows"`, or `"brute-force"` (only for querying). If the specified backend is not available on the current platform, the default backend will be used instead. ## Who is using this? diff --git a/index.d.ts b/index.d.ts index 6a828ea9..07ce9b58 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,4 +1,5 @@ declare type FilePath = string; +declare type GlobPattern = string; declare namespace ParcelWatcher { export type BackendType = @@ -9,7 +10,7 @@ declare namespace ParcelWatcher { | 'brute-force'; export type EventType = 'create' | 'update' | 'delete'; export interface Options { - ignore?: FilePath[]; + ignore?: (FilePath|GlobPattern)[]; backend?: BackendType; } export type SubscribeCallback = ( diff --git a/index.js.flow b/index.js.flow index 6f80d5d4..d75da93d 100644 --- a/index.js.flow +++ b/index.js.flow @@ -1,5 +1,6 @@ // @flow declare type FilePath = string; +declare type GlobPattern = string; export type BackendType = | 'fs-events' @@ -9,7 +10,7 @@ export type BackendType = | 'brute-force'; export type EventType = 'create' | 'update' | 'delete'; export interface Options { - ignore?: Array, + ignore?: Array, backend?: BackendType } export type SubscribeCallback = ( From 00fbf74738642f3eb9605343e9b65a806713b7be Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 21 Dec 2022 17:05:18 +0100 Subject: [PATCH 13/13] oops, paths can be relative or absolute --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a6934bdf..da51e695 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ You can specify the exact backend you wish to use by passing the `backend` optio All of the APIs in `@parcel/watcher` support the following options, which are passed as an object as the last function argument. - `ignore` - an array of paths or glob patterns to ignore. uses [`is-glob`](https://github.com/micromatch/is-glob) to distinguish paths from globs. glob patterns are parsed with [`micromatch`](https://github.com/micromatch/micromatch) (see [features](https://github.com/micromatch/micromatch#matching-features)). - - paths have to be absolute and can either be files or directories. No events will be emitted about these files or directories or their children. + - paths can be relative or absolute and can either be files or directories. No events will be emitted about these files or directories or their children. - glob patterns match on relative paths from the root that is watched. No events will be emitted for matching paths. - `backend` - the name of an explicitly chosen backend to use. Allowed options are `"fs-events"`, `"watchman"`, `"inotify"`, `"windows"`, or `"brute-force"` (only for querying). If the specified backend is not available on the current platform, the default backend will be used instead.