From 5af9b50f2b358ac758fc797701362eaf00d66628 Mon Sep 17 00:00:00 2001 From: Eric Follana Date: Fri, 13 Sep 2024 18:01:27 +0200 Subject: [PATCH 1/8] Added "maxSubfolderDeep" parameter --- cpp/src/FindGitRepos.cpp | 42 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/cpp/src/FindGitRepos.cpp b/cpp/src/FindGitRepos.cpp index 2ce5b10..eef4520 100644 --- a/cpp/src/FindGitRepos.cpp +++ b/cpp/src/FindGitRepos.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "../includes/Queue.h" #if defined(_WIN32) #include "../includes/WindowsHelpers.h" @@ -16,7 +17,8 @@ class FindGitReposWorker: public Napi::AsyncWorker { std::string _path, std::shared_ptr _progressQueue, Napi::ThreadSafeFunction _progressCallback, - uint32_t _throttleTimeoutMS + uint32_t _throttleTimeoutMS, + uint32_t _maxSubfolderDeep ): Napi::AsyncWorker(env), deferred(Napi::Promise::Deferred::New(env)), @@ -24,6 +26,7 @@ class FindGitReposWorker: public Napi::AsyncWorker { progressQueue(_progressQueue), progressCallback(_progressCallback), throttleTimeoutMS(_throttleTimeoutMS), + maxSubfolderDeep(_maxSubfolderDeep), lastProgressCallbackTimePoint(std::chrono::steady_clock::now()) { lastProgressCallbackTimePoint = lastProgressCallbackTimePoint - throttleTimeoutMS; @@ -52,6 +55,8 @@ class FindGitReposWorker: public Napi::AsyncWorker { rootPath = prefixWithNtPath(rootPath); } + std::uint32_t basePathSubfolderDeep = count(rootPath.begin(), rootPath.end(), L'\\'); + foundPaths.push_back(rootPath); while (foundPaths.size()) { @@ -66,6 +71,11 @@ class FindGitReposWorker: public Napi::AsyncWorker { continue; } + std::uint32_t currentSubfolderDeep = count(currentPath.begin(), currentPath.end(), L'\\'); + if (maxSubfolderDeep > 0 && (currentSubfolderDeep - basePathSubfolderDeep) > maxSubfolderDeep) { + continue; + } + std::list tempPaths; if ( (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY @@ -134,6 +144,7 @@ class FindGitReposWorker: public Napi::AsyncWorker { uv_fs_t scandirRequest; std::list foundPaths; foundPaths.push_back(path); + std::uint32_t basePathSubfolderDeep = count(path.begin(), path.end(), '/'); while (foundPaths.size()) { std::list temp; @@ -145,6 +156,11 @@ class FindGitReposWorker: public Napi::AsyncWorker { continue; } + std::uint32_t currentSubfolderDeep = count(currentPath.begin(), currentPath.end(), '/'); + if (maxSubfolderDeep > 0 && (currentSubfolderDeep - basePathSubfolderDeep) > maxSubfolderDeep) { + continue; + } + while (uv_fs_scandir_next(&scandirRequest, &directoryEntry) != UV_EOF) { std::string nextPath = currentPath + '/' + directoryEntry.name; @@ -230,6 +246,7 @@ class FindGitReposWorker: public Napi::AsyncWorker { std::shared_ptr progressQueue; Napi::ThreadSafeFunction progressCallback; std::chrono::milliseconds throttleTimeoutMS; + std::uint32_t maxSubfolderDeep; std::chrono::steady_clock::time_point lastProgressCallbackTimePoint; std::vector repositories; }; @@ -249,6 +266,8 @@ Napi::Promise FindGitRepos(const Napi::CallbackInfo& info) { } uint32_t throttleTimeoutMS = 0; + uint32_t maxSubfolderDeep = 0; + if (info.Length() >= 3) { if (!info[2].IsObject()) { Napi::Promise::Deferred deferred(env); @@ -276,6 +295,25 @@ Napi::Promise FindGitRepos(const Napi::CallbackInfo& info) { throttleTimeoutMS = temp; } + + Napi::Value maybeMaxSubfolderDeep = options["maxSubfolderDeep"]; + if (options.Has("maxSubfolderDeep") && !maybeMaxSubfolderDeep.IsNumber()) { + Napi::Promise::Deferred deferred(env); + deferred.Reject(Napi::TypeError::New(env, "options.maxSubfolderDeep must be a number, if passed.").Value()); + return deferred.Promise(); + } + + if (maybeMaxSubfolderDeep.IsNumber()) { + Napi::Number temp = maybeMaxSubfolderDeep.ToNumber(); + double bounds = temp.DoubleValue(); + if (bounds < 1) { + Napi::Promise::Deferred deferred(env); + deferred.Reject(Napi::TypeError::New(env, "options.maxSubfolderDeep must be > 0, if passed.").Value()); + return deferred.Promise(); + } + + maxSubfolderDeep = temp; + } } std::shared_ptr progressQueue(new RepositoryQueue); @@ -288,7 +326,7 @@ Napi::Promise FindGitRepos(const Napi::CallbackInfo& info) { [progressQueue](Napi::Env env) {} ); - FindGitReposWorker *worker = new FindGitReposWorker(info.Env(), info[0].ToString(), progressQueue, progressCallback, throttleTimeoutMS); + FindGitReposWorker *worker = new FindGitReposWorker(info.Env(), info[0].ToString(), progressQueue, progressCallback, throttleTimeoutMS, maxSubfolderDeep); worker->Queue(); return worker->Promise(); From eeba9912ccbea9b6b8f6f837cbc3a011732692f3 Mon Sep 17 00:00:00 2001 From: Eric Follana Date: Mon, 16 Sep 2024 18:43:30 +0200 Subject: [PATCH 2/8] Added cancel mechanism --- cpp/src/FindGitRepos.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/cpp/src/FindGitRepos.cpp b/cpp/src/FindGitRepos.cpp index eef4520..0dc5ead 100644 --- a/cpp/src/FindGitRepos.cpp +++ b/cpp/src/FindGitRepos.cpp @@ -30,6 +30,7 @@ class FindGitReposWorker: public Napi::AsyncWorker { lastProgressCallbackTimePoint(std::chrono::steady_clock::now()) { lastProgressCallbackTimePoint = lastProgressCallbackTimePoint - throttleTimeoutMS; + cancel = false; } ~FindGitReposWorker() { @@ -44,6 +45,7 @@ class FindGitReposWorker: public Napi::AsyncWorker { std::list foundPaths; auto rootPath = convertMultiByteToWideChar(path); const bool wasNtPath = isNtPath(rootPath); + cancel = false; if (!wasNtPath) { while (!rootPath.empty() && rootPath.back() == L'\\') { @@ -59,7 +61,7 @@ class FindGitReposWorker: public Napi::AsyncWorker { foundPaths.push_back(rootPath); - while (foundPaths.size()) { + while (!cancel && foundPaths.size()) { WIN32_FIND_DATAW FindFileData; HANDLE hFind = INVALID_HANDLE_VALUE; std::wstring currentPath = foundPaths.front(); @@ -103,7 +105,7 @@ class FindGitReposWorker: public Napi::AsyncWorker { } bool isGitRepo = false; - while (FindNextFileW(hFind, &FindFileData)) { + while (!cancel && FindNextFileW(hFind, &FindFileData)) { if (dot == FindFileData.cFileName || dotdot == FindFileData.cFileName) { continue; } @@ -129,6 +131,7 @@ class FindGitReposWorker: public Napi::AsyncWorker { } tempPaths.push_back(currentPath + L"\\" + std::wstring(FindFileData.cFileName)); + ThrottledProgressCallback(); } if (!isGitRepo) { @@ -145,8 +148,9 @@ class FindGitReposWorker: public Napi::AsyncWorker { std::list foundPaths; foundPaths.push_back(path); std::uint32_t basePathSubfolderDeep = count(path.begin(), path.end(), '/'); + cancel = false; - while (foundPaths.size()) { + while (!cancel && foundPaths.size()) { std::list temp; bool isGitRepo = false; std::string currentPath = foundPaths.front(); @@ -161,7 +165,7 @@ class FindGitReposWorker: public Napi::AsyncWorker { continue; } - while (uv_fs_scandir_next(&scandirRequest, &directoryEntry) != UV_EOF) { + while (!cancel && uv_fs_scandir_next(&scandirRequest, &directoryEntry) != UV_EOF) { std::string nextPath = currentPath + '/' + directoryEntry.name; if (directoryEntry.type == UV_DIRENT_UNKNOWN) { @@ -179,6 +183,7 @@ class FindGitReposWorker: public Napi::AsyncWorker { if (strcmp(directoryEntry.name, ".git")) { temp.push_back(nextPath); + ThrottledProgressCallback(); continue; } @@ -211,7 +216,7 @@ class FindGitReposWorker: public Napi::AsyncWorker { } void ThrottledProgressCallback() { - auto callback = [](Napi::Env env, Napi::Function jsCallback, RepositoryQueue *progressQueue) { + auto callback = [&cancel = cancel](Napi::Env env, Napi::Function jsCallback, RepositoryQueue *progressQueue) { int numRepos = progressQueue->count(); if (numRepos == 0) { return; @@ -223,7 +228,10 @@ class FindGitReposWorker: public Napi::AsyncWorker { repositoryArray[(uint32_t)i] = Napi::String::New(env, progressQueue->dequeue()); } - jsCallback.Call({ repositoryArray }); + Napi::Value val = jsCallback.Call({ repositoryArray }); + if (val.IsBoolean() && val.As()) { + cancel = true; + } }; if (throttleTimeoutMS.count() == 0) { @@ -249,6 +257,7 @@ class FindGitReposWorker: public Napi::AsyncWorker { std::uint32_t maxSubfolderDeep; std::chrono::steady_clock::time_point lastProgressCallbackTimePoint; std::vector repositories; + bool cancel; }; Napi::Promise FindGitRepos(const Napi::CallbackInfo& info) { From 06dfa446d9d5051470e699f0ec8708820dc2f02d Mon Sep 17 00:00:00 2001 From: Eric Follana Date: Tue, 17 Sep 2024 11:19:27 +0200 Subject: [PATCH 3/8] Optimize when callback is triggered --- cpp/src/FindGitRepos.cpp | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/cpp/src/FindGitRepos.cpp b/cpp/src/FindGitRepos.cpp index 0dc5ead..6ea1f24 100644 --- a/cpp/src/FindGitRepos.cpp +++ b/cpp/src/FindGitRepos.cpp @@ -31,6 +31,7 @@ class FindGitReposWorker: public Napi::AsyncWorker { { lastProgressCallbackTimePoint = lastProgressCallbackTimePoint - throttleTimeoutMS; cancel = false; + prevNumRepos = 0; } ~FindGitReposWorker() { @@ -62,22 +63,24 @@ class FindGitReposWorker: public Napi::AsyncWorker { foundPaths.push_back(rootPath); while (!cancel && foundPaths.size()) { + ThrottledProgressCallback(); + WIN32_FIND_DATAW FindFileData; HANDLE hFind = INVALID_HANDLE_VALUE; std::wstring currentPath = foundPaths.front(); foundPaths.pop_front(); + std::uint32_t currentSubfolderDeep = count(currentPath.begin(), currentPath.end(), L'\\'); + if (maxSubfolderDeep > 0 && (currentSubfolderDeep - basePathSubfolderDeep) > maxSubfolderDeep) { + break; + } + std::wstring wildcardPath = currentPath + L"\\*"; hFind = FindFirstFileW(wildcardPath.c_str(), &FindFileData); if (hFind == INVALID_HANDLE_VALUE) { continue; } - std::uint32_t currentSubfolderDeep = count(currentPath.begin(), currentPath.end(), L'\\'); - if (maxSubfolderDeep > 0 && (currentSubfolderDeep - basePathSubfolderDeep) > maxSubfolderDeep) { - continue; - } - std::list tempPaths; if ( (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY @@ -106,6 +109,8 @@ class FindGitReposWorker: public Napi::AsyncWorker { bool isGitRepo = false; while (!cancel && FindNextFileW(hFind, &FindFileData)) { + ThrottledProgressCallback(); + if (dot == FindFileData.cFileName || dotdot == FindFileData.cFileName) { continue; } @@ -151,22 +156,25 @@ class FindGitReposWorker: public Napi::AsyncWorker { cancel = false; while (!cancel && foundPaths.size()) { + ThrottledProgressCallback(); + std::list temp; bool isGitRepo = false; std::string currentPath = foundPaths.front(); foundPaths.pop_front(); - if (uv_fs_scandir(NULL, &scandirRequest, (currentPath + '/').c_str(), 0, NULL) < 0) { - continue; - } - std::uint32_t currentSubfolderDeep = count(currentPath.begin(), currentPath.end(), '/'); if (maxSubfolderDeep > 0 && (currentSubfolderDeep - basePathSubfolderDeep) > maxSubfolderDeep) { + break; + } + + if (uv_fs_scandir(NULL, &scandirRequest, (currentPath + '/').c_str(), 0, NULL) < 0) { continue; } while (!cancel && uv_fs_scandir_next(&scandirRequest, &directoryEntry) != UV_EOF) { std::string nextPath = currentPath + '/' + directoryEntry.name; + ThrottledProgressCallback(); if (directoryEntry.type == UV_DIRENT_UNKNOWN) { uv_fs_t lstatRequest; @@ -183,7 +191,6 @@ class FindGitReposWorker: public Napi::AsyncWorker { if (strcmp(directoryEntry.name, ".git")) { temp.push_back(nextPath); - ThrottledProgressCallback(); continue; } @@ -218,9 +225,6 @@ class FindGitReposWorker: public Napi::AsyncWorker { void ThrottledProgressCallback() { auto callback = [&cancel = cancel](Napi::Env env, Napi::Function jsCallback, RepositoryQueue *progressQueue) { int numRepos = progressQueue->count(); - if (numRepos == 0) { - return; - } Napi::Array repositoryArray = Napi::Array::New(env, numRepos); for (int i = 0; i < numRepos; ++i) { @@ -234,8 +238,9 @@ class FindGitReposWorker: public Napi::AsyncWorker { } }; - if (throttleTimeoutMS.count() == 0) { + if (throttleTimeoutMS.count() == 0 && prevNumRepos != progressQueue->count()) { progressCallback.NonBlockingCall(progressQueue.get(), callback); + prevNumRepos = progressQueue->count(); return; } @@ -258,6 +263,7 @@ class FindGitReposWorker: public Napi::AsyncWorker { std::chrono::steady_clock::time_point lastProgressCallbackTimePoint; std::vector repositories; bool cancel; + int prevNumRepos; // Used to determine if we should throttle if throttleTimeoutMS is 0 }; Napi::Promise FindGitRepos(const Napi::CallbackInfo& info) { From d95bca1bbd2420ee79edd307d49dc547aa78e767 Mon Sep 17 00:00:00 2001 From: Eric Follana Date: Mon, 16 Sep 2024 10:49:06 +0200 Subject: [PATCH 4/8] Fix compiling node module with spaces in path --- binding.gyp | 2 +- yarn.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/binding.gyp b/binding.gyp index 10e142f..27b045b 100644 --- a/binding.gyp +++ b/binding.gyp @@ -11,7 +11,7 @@ "cpp/src/Queue.cpp" ], "include_dirs": [ - " Date: Tue, 17 Sep 2024 12:39:39 +0200 Subject: [PATCH 5/8] Updated readme file --- README.md | 163 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 162 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e92d1ad..b99bf56 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,169 @@ # find-git-repositories -[![Actions Status](https://github.com/implausible/find-git-repositories/workflows/Tests/badge.svg)](https://github.com/implausible/find-git-repositories/actions) +[![Actions Status](https://github.com/Axosoft/find-git-repositories/workflows/Tests/badge.svg)](https://github.com/implausible/find-git-repositories/actions) +Find Git repositories in a directory and it's subfolders and return an array of paths to the found repositories. + +## Usage + +`findGitRepos(pathToSearch, progressCallback, options): Promise` + +#### Arguments + +- `pathToSearch`: path to search for repositories in. +- `progressCallback`: function to be called with an array of found repositories. + - Definition: `progressCallback(repositories: string[]): boolean`. + - As optional, we could return `true` from the progress callback to cancel the search. +- `options`: optional object with the following properties: + - `throttleTimeoutMS`: optional number of milliseconds to wait before calling the progress callback. + - `maxSubfolderDeep`: optional maximum number of subfolders to search in. + +### Basic example ```javascript const findGitRepos = require('find-git-repositories'); findGitRepos('some/path', repos => console.log('progress:', repos)) .then(allFoundRepositories => console.log('all the repositories found in this search:', allFoundRepositories)); ``` + +### Example with options +```javascript +const findGitRepos = require('find-git-repositories'); +findGitRepos( + 'some/path', + repos => { + console.log('progress:', repos); + return shouldCancelSearch(); // Return true to cancel the search + ), + { + throttleTimeoutMS: 100, // Only call the progress callback every 100ms + maxSubfolderDeep: 2 // Only search in the first 2 subfolders + } +).then( + allFoundRepositories => console.log('all the repositories found in this search:', allFoundRepositories) +); +``` + +## How to build + +Run `yarn` or `yarn install` to install dependencies and build the native addon in release mode. + +## How to run tests + +Run `yarn test`. + +## How to debug (in VS Code and MacOS) + +1. Install `CodeLLBD` addon for VS Code. +2. Modify the `main` property in the `package.json` file to point to `debug` instead of `release`: `"main": "build/Debug/findGitRepos.node",`. +3. Create a `.vscode/launch.json` file with the following content: +```json +{ + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "attach", + "name": "Attach", + "pid": "${command:pickMyProcess}" // use ${command:pickProcess} to pick other users' processes + } + ] +} +``` +4. Create a `.vscode/c_cpp_properties.json` file with a similar content: +```json +{ + "configurations": [ + { + "name": "Mac", + "includePath": [ + "${workspaceFolder}/**", + "/Users/MY_USER/.nvm/versions/node/v20.14.0/include/node/**" // enter your node path here + ], + "defines": [], + "macFrameworkPath": [ + "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks" + ], + "compilerPath": "/usr/bin/clang", + "cStandard": "c17", + "cppStandard": "c++17", + "intelliSenseMode": "macos-clang-x64" + } + ], + "version": 4 +} +``` +5. Create a `.vscode/settings.json` file with the following content: +```json +{ + "files.associations": { + "__bit_reference": "cpp", + "__bits": "cpp", + "__config": "cpp", + "__debug": "cpp", + "__errc": "cpp", + "__functional_base": "cpp", + "__hash_table": "cpp", + "__locale": "cpp", + "__mutex_base": "cpp", + "__node_handle": "cpp", + "__nullptr": "cpp", + "__split_buffer": "cpp", + "__string": "cpp", + "__threading_support": "cpp", + "__tuple": "cpp", + "algorithm": "cpp", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "complex": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "exception": "cpp", + "functional": "cpp", + "initializer_list": "cpp", + "ios": "cpp", + "iosfwd": "cpp", + "istream": "cpp", + "iterator": "cpp", + "limits": "cpp", + "list": "cpp", + "locale": "cpp", + "memory": "cpp", + "mutex": "cpp", + "new": "cpp", + "optional": "cpp", + "ostream": "cpp", + "ratio": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "string": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "typeinfo": "cpp", + "unordered_map": "cpp", + "utility": "cpp", + "vector": "cpp" + } +} +``` +6. Run `npx node-gyp rebuild --debug` to install dependencies and build the native addon in debug mode. +7. Run your app with the debugger attached in VSCode and add a breakpoint. +8. When it stops at the breakpoint, run in the console: `process.pid` and copy the process number to use in the next step. +9. In the `find-git-repositories` VSCode project, click on the `Attach` button in the debug toolbar and enter the process number from the previous step. +10. Add breakpoints where ever you want to start debugging. + + From 1562f329e9c0c0a40db3b26139b4de6fbebf30e2 Mon Sep 17 00:00:00 2001 From: Eric Follana Date: Tue, 17 Sep 2024 16:06:33 +0200 Subject: [PATCH 6/8] Added new tests --- test/index-spec.js | 136 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/test/index-spec.js b/test/index-spec.js index 297de96..93f16f0 100644 --- a/test/index-spec.js +++ b/test/index-spec.js @@ -19,6 +19,36 @@ describe('findGitRepos', function() { .then(() => done('Should not have succeeded')) .catch(() => done()); }); + + it('will fail if throttleTimeoutMS value is not a number', function(done) { + findGitRepos('test', () => {}, { throttleTimeoutMS: 'WrongValue' }) + .then(() => done('Should not have succeeded')) + .catch(() => done()); + }); + + it('will fail if throttleTimeoutMS number is less than 0', function(done) { + findGitRepos('test', () => {}, { throttleTimeoutMS: -1 }) + .then(() => done('Should not have succeeded')) + .catch(() => done()); + }); + + it('will fail if throttleTimeoutMS number is larger than 60000', function(done) { + findGitRepos('test', () => {}, { throttleTimeoutMS: 60001 }) + .then(() => done('Should not have succeeded')) + .catch(() => done()); + }); + + it('will fail if maxSubfolderDeep is not a number', function(done) { + findGitRepos('test', () => {}, { maxSubfolderDeep: 'WrongValue' }) + .then(() => done('Should not have succeeded')) + .catch(() => done()); + }); + + it('will fail if maxSubfolderDeep number is less than 0', function(done) { + findGitRepos('test', () => {}, { maxSubfolderDeep: -1 }) + .then(() => done('Should not have succeeded')) + .catch(() => done()); + }); }); describe('Features', function() { @@ -194,5 +224,111 @@ describe('findGitRepos', function() { .catch(error => done(error)); }); } + + it('can find repositories at a specified subfolders depth in a file system', function(done) { + const maxSubfolderDeep = 2; + const basePathSubfolderDeep = basePath.split(path.sep).length; + const { repositoryPaths } = this; + + let callbackPromisesChain = Promise.resolve(); + let triggeredProgressCallbackOnce = false; + + const progressCallback = paths => { + triggeredProgressCallbackOnce = true; + + callbackPromisesChain = paths.reduce((chain, repositoryPath) => + chain.then(() => { + assert.equal( + repositoryPaths[repositoryPath], + Boolean(repositoryPaths[repositoryPath]), + 'Found a repo that should not exist' + ); + + const currentSubfolderDeep = repositoryPath.split(path.sep).length; + assert.equal( + (currentSubfolderDeep - basePathSubfolderDeep) <= maxSubfolderDeep, + true, + 'Found a repo that is too deep' + ); + + assert.equal(repositoryPaths[repositoryPath], false, 'Duplicate repositoryPath received'); + repositoryPaths[repositoryPath] = true; + }), callbackPromisesChain); + }; + + findGitRepos(basePath, progressCallback, { maxSubfolderDeep: maxSubfolderDeep }) + .then(paths => Promise.all([paths, callbackPromisesChain])) + .then(([paths]) => { + assert.equal(triggeredProgressCallbackOnce, true, 'Never called progress callback'); + paths.forEach(repositoryPath => { + assert.equal( + repositoryPaths[repositoryPath], + Boolean(repositoryPaths[repositoryPath]), + 'Found a repo that should not exist' + ); + + const currentSubfolderDeep = repositoryPath.split(path.sep).length; + assert.equal( + (currentSubfolderDeep - basePathSubfolderDeep) <= maxSubfolderDeep, + true, + 'Found a repo that is too deep' + ); + + repositoryPaths[repositoryPath] = true; + }); + + Object.keys(repositoryPaths) + .filter(repositoryPath => { + const currentSubfolderDeep = repositoryPath.split(path.sep).length; + return (currentSubfolderDeep - basePathSubfolderDeep) <= maxSubfolderDeep; + }) + .forEach(repositoryPath => { + assert.equal(repositoryPaths[repositoryPath], true, 'Did not find a path in the file system'); + }); + }) + .then(() => done()) + .catch(error => done(error)); + }); + + it('can cancel the repository search', function(done) { + const { repositoryPaths } = this; + + let callbackPromisesChain = Promise.resolve(); + let triggeredProgressCallbackOnce = false; + + const maxCallbackCalls = 3; + let numCallbackCalls = 0; + + const progressCallback = paths => { + triggeredProgressCallbackOnce = true + + callbackPromisesChain = paths.reduce((chain, repositoryPath) => + chain.then(() => { + repositoryPaths[repositoryPath] = true; + }), callbackPromisesChain); + + if (numCallbackCalls >= maxCallbackCalls) { + return true; + } + + numCallbackCalls++; + }; + + findGitRepos(basePath, progressCallback) + .then(paths => Promise.all([paths, callbackPromisesChain])) + .then(([paths]) => { + assert.equal(triggeredProgressCallbackOnce, true, 'Never called progress callback'); + paths.forEach(repositoryPath => { + repositoryPaths[repositoryPath] = true; + }); + + const totalProcessedPaths = Object.keys(repositoryPaths).filter(repositoryPath => repositoryPaths[repositoryPath]).length; + const totalPaths = Object.keys(repositoryPaths).length; + + assert.equal(totalProcessedPaths < totalPaths, true, 'Did not cancel the search'); + }) + .then(() => done()) + .catch(error => done(error)); + }); }); }); From c9ebe0b2774b3f0666710aabf29f8b82203e3737 Mon Sep 17 00:00:00 2001 From: Eric Follana Date: Tue, 17 Sep 2024 13:31:58 +0200 Subject: [PATCH 7/8] Fixed GitHub action for the tests --- .github/workflows/tests.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 7ef841c..4aa5964 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -12,7 +12,7 @@ jobs: name: Tests strategy: matrix: - node: [10, 12] + node: [18, 20, 22] os: [windows-2016, ubuntu-16.04, ubuntu-18.04, macOS-latest] arch: [x86, x64] exclude: @@ -24,13 +24,13 @@ jobs: arch: x86 runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@master + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node }} - uses: implausible/setup-node@feature/expose-architecture-override + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} - node-arch: ${{ matrix.arch }} + architecture: ${{ matrix.arch }} - run: yarn From 0aa394e5b12bc6e29e224010ba37becc84d5ce45 Mon Sep 17 00:00:00 2001 From: Eric Follana Date: Wed, 18 Sep 2024 10:24:13 +0200 Subject: [PATCH 8/8] Updated OS versions in the GitHub actions --- .github/workflows/tests.yaml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 4aa5964..8826226 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -13,15 +13,7 @@ jobs: strategy: matrix: node: [18, 20, 22] - os: [windows-2016, ubuntu-16.04, ubuntu-18.04, macOS-latest] - arch: [x86, x64] - exclude: - - os: ubuntu-16.04 - arch: x86 - - os: ubuntu-18.04 - arch: x86 - - os: macOS-latest - arch: x86 + os: [windows-latest, ubuntu-20.04, ubuntu-22.04, ubuntu-24.04, macOS-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -30,7 +22,6 @@ jobs: uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} - architecture: ${{ matrix.arch }} - run: yarn