diff --git a/lib/load-paths-handler.coffee b/lib/load-paths-handler.coffee index 9d29a2a3..eae536a7 100644 --- a/lib/load-paths-handler.coffee +++ b/lib/load-paths-handler.coffee @@ -1,107 +1,57 @@ async = require 'async' -fs = require 'fs' path = require 'path' _ = require 'underscore-plus' -{GitRepository} = require 'atom' -{Minimatch} = require 'minimatch' +GitUtils = require 'git-utils' +{PathSearcher, PathScanner, search} = require 'scandal' -PathsChunkSize = 100 - -emittedPaths = new Set - -class PathLoader - constructor: (@rootPath, ignoreVcsIgnores, @traverseSymlinkDirectories, @ignoredNames) -> - @paths = [] - @realPathCache = {} - @repo = null - if ignoreVcsIgnores - repo = GitRepository.open(@rootPath, refreshOnWindowFocus: false) - @repo = repo if repo?.relativize(path.join(@rootPath, 'test')) is 'test' - - load: (done) -> - @loadPath @rootPath, => - @flushPaths() - @repo?.destroy() - done() - - isIgnored: (loadedPath) -> - relativePath = path.relative(@rootPath, loadedPath) - if @repo?.isPathIgnored(relativePath) - true - else - for ignoredName in @ignoredNames - return true if ignoredName.match(relativePath) - - pathLoaded: (loadedPath, done) -> - unless @isIgnored(loadedPath) or emittedPaths.has(loadedPath) - @paths.push(loadedPath) - emittedPaths.add(loadedPath) - - if @paths.length is PathsChunkSize - @flushPaths() - done() - - flushPaths: -> - emit('load-paths:paths-found', @paths) - @paths = [] - - loadPath: (pathToLoad, done) -> - return done() if @isIgnored(pathToLoad) - fs.lstat pathToLoad, (error, stats) => - return done() if error? - if stats.isSymbolicLink() - @isInternalSymlink pathToLoad, (isInternal) => - return done() if isInternal - fs.stat pathToLoad, (error, stats) => - return done() if error? - if stats.isFile() - @pathLoaded(pathToLoad, done) - else if stats.isDirectory() - if @traverseSymlinkDirectories - @loadFolder(pathToLoad, done) - else - done() - else - done() - else if stats.isDirectory() - @loadFolder(pathToLoad, done) - else if stats.isFile() - @pathLoaded(pathToLoad, done) - else - done() - - loadFolder: (folderPath, done) -> - fs.readdir folderPath, (error, children=[]) => - async.each( - children, - (childName, next) => - @loadPath(path.join(folderPath, childName), next) - done - ) - - isInternalSymlink: (pathToLoad, done) -> - fs.realpath pathToLoad, @realPathCache, (err, realPath) => - if err - done(false) - else - done(realPath.search(@rootPath) is 0) - -module.exports = (rootPaths, followSymlinks, ignoreVcsIgnores, ignores=[]) -> - ignoredNames = [] - for ignore in ignores when ignore - try - ignoredNames.push(new Minimatch(ignore, matchBase: true, dot: true)) - catch error - console.warn "Error parsing ignore pattern (#{ignore}): #{error.message}" +module.exports = (rootPaths, options={}) -> + pathsSearched = 0 + PATHS_COUNTER_SEARCHED_CHUNK = 100 + emittedPaths = new Set async.each( rootPaths, (rootPath, next) -> - new PathLoader( - rootPath, - ignoreVcsIgnores, - followSymlinks, - ignoredNames - ).load(next) + options2 = _.extend {}, options, + inclusions: processPaths(rootPath, options.inclusions) + globalExclusions: processPaths(rootPath, options.globalExclusions) + + if options.ignoreProjectParentVcsIgnores + repo = GitUtils.open(rootPath) + if repo and '' isnt repo.relativize(rootPath) + options2.excludeVcsIgnores = false + + scanner = new PathScanner(rootPath, options2) + + paths = [] + + scanner.on 'path-found', (path) -> + unless emittedPaths.has(path) + paths.push path + emittedPaths.add(path) + pathsSearched++ + if pathsSearched % PATHS_COUNTER_SEARCHED_CHUNK is 0 + emit('load-paths:paths-found', paths) + paths = [] + scanner.on 'finished-scanning', -> + emit('load-paths:paths-found', paths) + next() + scanner.scan() + @async() ) + +processPaths = (rootPath, paths) -> + return paths unless paths?.length > 0 + rootPathBase = path.basename(rootPath) + results = [] + for givenPath in paths + segments = givenPath.split(path.sep) + firstSegment = segments.shift() + results.push(givenPath) + if firstSegment is rootPathBase + if segments.length is 0 + results.push(path.join("**", "*")) + else + results.push(path.join(segments...)) + results diff --git a/lib/path-loader.coffee b/lib/path-loader.coffee index 972c11bd..5d665424 100644 --- a/lib/path-loader.coffee +++ b/lib/path-loader.coffee @@ -4,18 +4,22 @@ module.exports = startTask: (callback) -> projectPaths = [] taskPath = require.resolve('./load-paths-handler') - followSymlinks = atom.config.get 'core.followSymlinks' + ignoredNames = atom.config.get('fuzzy-finder.ignoredNames') ? [] ignoredNames = ignoredNames.concat(atom.config.get('core.ignoredNames') ? []) - ignoreVcsIgnores = atom.config.get('core.excludeVcsIgnoredPaths') + + options = + excludeVcsIgnores: atom.config.get 'core.excludeVcsIgnoredPaths' + exclusions: ignoredNames + follow: atom.config.get 'core.followSymlinks' + includeHidden: true + ignoreProjectParentVcsIgnores: true task = Task.once( taskPath, atom.project.getPaths(), - followSymlinks, - ignoreVcsIgnores, - ignoredNames, -> - callback(projectPaths) + options, + -> callback(projectPaths) ) task.on 'load-paths:paths-found', (paths) -> diff --git a/package.json b/package.json index 9225e149..5ca5ae74 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,10 @@ "fs-plus": "^2.0.0", "fuzzaldrin": "^2.0", "fuzzaldrin-plus": "^0.1", + "git-utils": "^4.0", "humanize-plus": "~1.4.2", "minimatch": "~0.3.0", + "scandal": "^2.2", "temp": "~0.8.1", "underscore-plus": "^1.0.0", "wrench": "^1.5" diff --git a/spec/fixtures/root-dir1/git/working-dir/another-repo/c.txt b/spec/fixtures/root-dir1/git/working-dir/another-repo/c.txt new file mode 100644 index 00000000..e69de29b diff --git a/spec/fixtures/root-dir1/git/working-dir/another-repo/d.txt b/spec/fixtures/root-dir1/git/working-dir/another-repo/d.txt new file mode 100644 index 00000000..e69de29b diff --git a/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/HEAD b/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/HEAD new file mode 100644 index 00000000..cb089cd8 --- /dev/null +++ b/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/config b/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/config new file mode 100644 index 00000000..af107929 --- /dev/null +++ b/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/config @@ -0,0 +1,6 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true diff --git a/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/index b/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/index new file mode 100644 index 00000000..67a98bd9 Binary files /dev/null and b/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/index differ diff --git a/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/objects/28/569d0a51e27dd112c0d4994c1e2914dd0db754 b/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/objects/28/569d0a51e27dd112c0d4994c1e2914dd0db754 new file mode 100644 index 00000000..169ad87f Binary files /dev/null and b/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/objects/28/569d0a51e27dd112c0d4994c1e2914dd0db754 differ diff --git a/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/objects/65/a457425a679cbe9adf0d2741785d3ceabb44a7 b/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/objects/65/a457425a679cbe9adf0d2741785d3ceabb44a7 new file mode 100644 index 00000000..ba1f06fc Binary files /dev/null and b/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/objects/65/a457425a679cbe9adf0d2741785d3ceabb44a7 differ diff --git a/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/objects/97/ff2919c02606bcad55588f3fa723b5a357470f b/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/objects/97/ff2919c02606bcad55588f3fa723b5a357470f new file mode 100644 index 00000000..b6cb9859 Binary files /dev/null and b/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/objects/97/ff2919c02606bcad55588f3fa723b5a357470f differ diff --git a/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 new file mode 100644 index 00000000..71122389 Binary files /dev/null and b/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 differ diff --git a/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/objects/ec/5e386905ff2d36e291086a1207f2585aaa8920 b/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/objects/ec/5e386905ff2d36e291086a1207f2585aaa8920 new file mode 100644 index 00000000..4010ee8c Binary files /dev/null and b/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/objects/ec/5e386905ff2d36e291086a1207f2585aaa8920 differ diff --git a/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/objects/ef/046e9eecaa5255ea5e9817132d4001724d6ae1 b/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/objects/ef/046e9eecaa5255ea5e9817132d4001724d6ae1 new file mode 100644 index 00000000..eaf6eeff Binary files /dev/null and b/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/objects/ef/046e9eecaa5255ea5e9817132d4001724d6ae1 differ diff --git a/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/refs/heads/master b/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/refs/heads/master new file mode 100644 index 00000000..99d72600 --- /dev/null +++ b/spec/fixtures/root-dir1/git/working-dir/another-repo/git.git/refs/heads/master @@ -0,0 +1 @@ +97ff2919c02606bcad55588f3fa723b5a357470f diff --git a/spec/fuzzy-finder-spec.coffee b/spec/fuzzy-finder-spec.coffee index 4e0464d2..feacdd96 100644 --- a/spec/fuzzy-finder-spec.coffee +++ b/spec/fuzzy-finder-spec.coffee @@ -595,7 +595,7 @@ describe 'FuzzyFinder', -> it "passes the indexed paths into the project view when it is created", -> {projectPaths} = fuzzyFinder - expect(projectPaths.length).toBe 18 + expect(projectPaths.length).toBe 30 projectView = fuzzyFinder.createProjectView() expect(projectView.paths).toBe projectPaths expect(projectView.reloadPaths).toBe false @@ -876,6 +876,7 @@ describe 'FuzzyFinder', -> beforeEach -> projectPath = atom.project.getDirectories()[0].resolve('git/working-dir') fs.moveSync(path.join(projectPath, 'git.git'), path.join(projectPath, '.git')) + fs.moveSync(path.join(projectPath, 'another-repo', 'git.git'), path.join(projectPath, 'another-repo', '.git')) atom.project.setPaths([rootDir2, projectPath]) gitDirectory = atom.project.getDirectories()[1] @@ -996,3 +997,30 @@ describe 'FuzzyFinder', -> runs -> expect(projectView.list.find("li:contains(file.txt)")).toExist() + + describe "when the project's path is a repository inside another repository", -> + beforeEach -> + ignoreFile = path.join(projectPath, '.gitignore') + fs.writeFileSync(ignoreFile, '*') + atom.project.setPaths([gitDirectory.resolve('another-repo')]) + + ignoreFile = path.join(gitDirectory.resolve('another-repo'), '.gitignore') + fs.writeFileSync(ignoreFile, 'd.txt') + + it "excludes paths that are git ignored in the child repository", -> + dispatchCommand('toggle-file-finder') + projectView.setMaxItems(Infinity) + + waitForPathsToDisplay(projectView) + + runs -> + expect(projectView.list.find("li:contains(d.txt)")).not.toExist() + + it "does not exclude the entire child repository if the parent repository is ignoring it", -> + dispatchCommand('toggle-file-finder') + projectView.setMaxItems(Infinity) + + waitForPathsToDisplay(projectView) + + runs -> + expect(projectView.list.find("li:contains(c.txt)")).toExist()