Skip to content

Commit

Permalink
[git] use native modules to find git repositories instead of constrai…
Browse files Browse the repository at this point in the history
…nig by depth

Signed-off-by: Anton Kosiakov <anton.kosyakov@typefox.io>
  • Loading branch information
akosyakov committed Dec 1, 2017
1 parent de0021c commit b42dfaa
Show file tree
Hide file tree
Showing 10 changed files with 102 additions and 96 deletions.
2 changes: 1 addition & 1 deletion dev-packages/application-package/src/rebuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import cp = require('child_process');
export function rebuild(target: 'electron' | 'browser', modules: string[]) {
const nodeModulesPath = path.join(process.cwd(), 'node_modules');
const browserModulesPath = path.join(process.cwd(), '.browser_modules');
const modulesToProcess = modules || ['node-pty', 'nsfw'];
const modulesToProcess = modules || ['node-pty', 'nsfw', 'find-git-repositories'];

if (target === 'electron' && !fs.existsSync(browserModulesPath)) {
const dependencies: {
Expand Down
3 changes: 1 addition & 2 deletions packages/git/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
"@theia/preferences-api": "^0.2.3",
"@theia/workspace": "^0.2.3",
"dugite-extra": "0.0.1-alpha.15",
"findit2": "^2.2.3",
"ignore": "^3.3.7"
"find-git-repositories": "^0.1.0"
},
"publishConfig": {
"access": "public"
Expand Down
3 changes: 1 addition & 2 deletions packages/git/src/browser/git-repository-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@ export class GitRepositoryProvider {
return;
}
const repositories = await this.git.repositories(root.uri, {
...options,
maxDepth: this.preferences['git.locateMaxDepth']
...options
});
this._allRepositories = repositories;
const selectedRepository = this._selectedRepository;
Expand Down
10 changes: 1 addition & 9 deletions packages/git/src/common/git-preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,14 @@ import { createPreferenceProxy, PreferenceProxy, PreferenceService } from '@thei
import { PreferenceSchema } from '@theia/preferences-api/lib/common/';

export interface GitConfiguration {
'git.locateMaxDepth': number
}

const DefaultGitConfiguration: GitConfiguration = {
"git.locateMaxDepth": 4
};

export const GitPreferenceSchema: PreferenceSchema = {
'type': 'object',
'properties': {
"git.locateMaxDepth": {
"type": "number",
"default": 4,
"description": "Configure the max depth of the git repository look up"
}
}
'properties': {}
};

export const GitPreferences = Symbol('GitPreferences');
Expand Down
5 changes: 0 additions & 5 deletions packages/git/src/common/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,6 @@ export namespace Git {
*/
readonly maxCount?: number;

/**
* The max depth of repositories look up.
*/
readonly maxDepth: number;

}

/**
Expand Down
8 changes: 4 additions & 4 deletions packages/git/src/node/dugite-git.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe('git', async function () {
const git = await createGit();
const workspace = await createWorkspace(root);
const workspaceRootUri = await workspace.getRoot();
const repositories = await git.repositories(workspaceRootUri!, { maxDepth: 1, maxCount: 1 });
const repositories = await git.repositories(workspaceRootUri!, { maxCount: 1 });
expect(repositories.length).to.deep.equal(1);

});
Expand All @@ -64,7 +64,7 @@ describe('git', async function () {
const git = await createGit();
const workspace = await createWorkspace(root);
const workspaceRootUri = await workspace.getRoot();
const repositories = await git.repositories(workspaceRootUri!, { maxDepth: 1 });
const repositories = await git.repositories(workspaceRootUri!, {});
expect(repositories.map(r => path.basename(FileUri.fsPath(r.localUri))).sort()).to.deep.equal(['A', 'B', 'C']);

});
Expand All @@ -83,7 +83,7 @@ describe('git', async function () {
const git = await createGit();
const workspace = await createWorkspace(path.join(root, 'BASE'));
const workspaceRootUri = await workspace.getRoot();
const repositories = await git.repositories(workspaceRootUri!, { maxDepth: 2 });
const repositories = await git.repositories(workspaceRootUri!, {});
expect(repositories.map(r => path.basename(FileUri.fsPath(r.localUri))).sort()).to.deep.equal(['A', 'B', 'BASE', 'C']);

});
Expand All @@ -103,7 +103,7 @@ describe('git', async function () {
const git = await createGit();
const workspace = await createWorkspace(path.join(root, 'BASE', 'WS_ROOT'));
const workspaceRootUri = await workspace.getRoot();
const repositories = await git.repositories(workspaceRootUri!, { maxDepth: 2 });
const repositories = await git.repositories(workspaceRootUri!, {});
const repositoryNames = repositories.map(r => path.basename(FileUri.fsPath(r.localUri)));
expect(repositoryNames.shift()).to.equal('BASE'); // The first must be the container repository.
expect(repositoryNames.sort()).to.deep.equal(['A', 'B', 'C']);
Expand Down
1 change: 0 additions & 1 deletion packages/git/src/node/dugite-git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ export class DugiteGit implements Git {
return repositories;
}
for (const repositoryPath of await this.locator.locate(workspaceRootPath, {
...options,
maxCount
})) {
if (containingPath !== repositoryPath) {
Expand Down
151 changes: 88 additions & 63 deletions packages/git/src/node/git-locator/git-locator-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,93 +5,118 @@
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/

import * as fs from 'fs';
import * as paths from 'path';
import * as fs from 'fs-extra';
import * as path from 'path';
import { GitLocator, GitLocateOptions } from './git-locator-protocol';

// tslint:disable:no-console
export type FindGitRepositories = (path: string, progressCb: (repos: string[]) => void) => Promise<string[]>;
const findGitRepositories: FindGitRepositories = require('find-git-repositories');

const ignore: () => IgnoreFilter = require('ignore');
const finder: (path: string) => FindIt = require('findit2');

export interface IgnoreFilter {
add(patterns: string | IgnoreFilter): void
ignores(pathname: string): boolean;
depth: number;
}

export interface FindIt extends NodeJS.EventEmitter {
stop(): void;
export interface GitLocateContext {
maxCount: number
readonly visited: Map<string, boolean>
}

export class GitLocatorImpl implements GitLocator {

protected disposed = false;

dispose(): void {
this.disposed = true;
}

locate(path: string, options: GitLocateOptions): Promise<string[]> {
if (this.disposed) {
async locate(basePath: string, options: GitLocateOptions): Promise<string[]> {
return await this.doLocate(basePath, {
maxCount: typeof options.maxCount === 'number' ? options.maxCount : -1,
visited: new Map<string, boolean>()
});
}

protected doLocate(basePath: string, context: GitLocateContext): Promise<string[]> {
const realBasePath = fs.realpathSync(basePath);
if (context.visited.has(realBasePath)) {
return Promise.resolve([]);
}
return new Promise(resolve => {
const filters = new Map<string, IgnoreFilter>();
const repositoryPaths = new Set();
const emitter = finder(path);
emitter.on('directory', (dir: string, stat: fs.Stats, stop: () => void) => {
const base = paths.basename(dir);
if (base === '.git') {
const dirName = paths.dirname(dir);
try {
const resolvedPath = fs.realpathSync(dirName);
repositoryPaths.add(resolvedPath);
} catch (e) {
console.error(e);
context.visited.set(realBasePath, true);
return new Promise<string[]>(resolve => {
fs.stat(realBasePath).then(async stat => {
if (stat.isDirectory) {
const progress: string[] = [];
const paths = await findGitRepositories(realBasePath, repositories => {
progress.push(...repositories);
if (context.maxCount >= 0 && progress.length >= context.maxCount) {
resolve(progress.slice(0, context.maxCount).map(GitLocatorImpl.map));
}
});
if (context.maxCount >= 0 && paths.length >= context.maxCount) {
resolve(paths.slice(0, context.maxCount).map(GitLocatorImpl.map));
return;
}
if (!!options.maxCount && repositoryPaths.size >= options.maxCount) {
emitter.stop();
}
stop();
return;
const repositoryPaths = paths.map(GitLocatorImpl.map);
resolve(this.locateFrom(
newContext => this.generateNested(repositoryPaths, newContext),
context,
repositoryPaths
));
}
if (this.shouldStop(dir, filters, options)) {
stop();
return;
}, () => []);
});
}

protected * generateNested(repositoryPaths: string[], context: GitLocateContext) {
for (const repository of repositoryPaths) {
yield this.locateNested(repository, context);
}
}
protected locateNested(repositoryPath: string, context: GitLocateContext): Promise<string[]> {
return new Promise<string[]>(resolve => {
fs.readdir(repositoryPath, async (err, files) => {
if (err) {
console.error(err);
resolve([]);
} else {
resolve(this.locateFrom(
newContext => this.generateRepositories(repositoryPath, files, newContext),
context
));
}
filters.set(dir, this.createFilter(dir, filters));
});
const complete = () => resolve([...repositoryPaths]);
emitter.once('end', complete);
emitter.once('stop', complete);
emitter.on('error', error => console.error(error));
});
}

protected createFilter(path: string, filters: Map<string, IgnoreFilter>): IgnoreFilter {
const ig = ignore();
const parent = filters.get(paths.dirname(path));
ig.depth = parent ? parent.depth + 1 : 0;
if (parent && !fs.existsSync(paths.join(path, '.git'))) {
ig.add(parent);
protected * generateRepositories(repositoryPath: string, files: string[], context: GitLocateContext) {
for (const file of files) {
if (file !== '.git') {
yield this.doLocate(path.join(repositoryPath, file), {
...context
});
}
}
if (fs.existsSync(paths.join(path, '.gitignore'))) {
ig.add(fs.readFileSync(paths.join(path, '.gitignore')).toString());
}
return ig;
}

protected shouldStop(pathname: string, filters: Map<string, IgnoreFilter>, options: GitLocateOptions): boolean {
if (this.disposed) {
return true;
protected async locateFrom(
generator: (context: GitLocateContext) => IterableIterator<Promise<string[]>>, parentContext: GitLocateContext, initial?: string[]
): Promise<string[]> {
const result: string[] = [];
if (initial) {
result.push(...initial);
}
const parent = paths.dirname(pathname);
const ig = filters.get(parent);
if (!ig) {
return false;
const context = {
...parentContext,
maxCount: parentContext.maxCount - result.length
};
for (const locateRepositories of generator(context)) {
const repositories = await locateRepositories;
result.push(...repositories);
if (context.maxCount >= 0) {
if (result.length >= context.maxCount) {
return result.slice(0, context.maxCount);
}
context.maxCount -= repositories.length;
}
}
return ig.depth >= options.maxDepth || ig.ignores(pathname);
return result;
}

static map(repository: string): string {
return fs.realpathSync(path.dirname(repository));
}

}
1 change: 0 additions & 1 deletion packages/git/src/node/git-locator/git-locator-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { Disposable } from "vscode-jsonrpc";

export interface GitLocateOptions {
readonly maxCount?: number;
readonly maxDepth: number;
}

export const GitLocator = Symbol('GitLocator');
Expand Down
14 changes: 6 additions & 8 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2413,6 +2413,12 @@ find-git-exec@0.0.1-alpha.2:
dependencies:
"@types/node" "^8.0.26"

find-git-repositories@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/find-git-repositories/-/find-git-repositories-0.1.0.tgz#1ac886f0f54a11f5f073bca3bcdfddc03486305a"
dependencies:
nan "^2.0.0"

find-index@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/find-index/-/find-index-0.1.1.tgz#675d358b2ca3892d795a1ab47232f8b6e2e0dde4"
Expand All @@ -2430,10 +2436,6 @@ find-up@^2.0.0, find-up@^2.1.0:
dependencies:
locate-path "^2.0.0"

findit2@^2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/findit2/-/findit2-2.2.3.tgz#58a466697df8a6205cdfdbf395536b8bd777a5f6"

findup-sync@^0.4.2:
version "0.4.3"
resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.4.3.tgz#40043929e7bc60adf0b7f4827c4c6e75a0deca12"
Expand Down Expand Up @@ -3247,10 +3249,6 @@ ignore-loader@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/ignore-loader/-/ignore-loader-0.1.2.tgz#d81f240376d0ba4f0d778972c3ad25874117a463"

ignore@^3.3.7:
version "3.3.7"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021"

image-size@~0.5.0:
version "0.5.5"
resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c"
Expand Down

0 comments on commit b42dfaa

Please sign in to comment.