mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
* [Code] use native git to iterate git files * [Code] use native git to clone/update repository * [Code] git history using native git * [Code] use native git to read file tree and file content * [Code] fix the 'bad file' warning from status api * [Code] use native git to handle worktree * [Code] use native git to resolve references * [Code] use native git to handle blame / diff * [Code] patch git binaries in kibana build script * [Code] migrate unit tests to use native git
This commit is contained in:
parent
936e018172
commit
bb92b8b8b5
33 changed files with 732 additions and 1669 deletions
|
@ -16,62 +16,21 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import install from '@elastic/simple-git/scripts/install';
|
||||
import { deleteAll } from '../lib';
|
||||
import path from 'path';
|
||||
|
||||
import { scanCopy, untar, deleteAll } from '../lib';
|
||||
import { createWriteStream, mkdirSync } from 'fs';
|
||||
import { binaryInfo } from '../../../../x-pack/legacy/plugins/code/tasks/nodegit_info';
|
||||
import wreck from '@hapi/wreck';
|
||||
import { dirname, join, basename } from 'path';
|
||||
import { createPromiseFromStreams } from '../../../legacy/utils/streams';
|
||||
|
||||
async function download(url, destination, log) {
|
||||
const response = await wreck.request('GET', url);
|
||||
|
||||
if (response.statusCode !== 200) {
|
||||
throw new Error(`Unexpected status code ${response.statusCode} when downloading ${url}`);
|
||||
}
|
||||
mkdirSync(dirname(destination), { recursive: true });
|
||||
await createPromiseFromStreams([response, createWriteStream(destination)]);
|
||||
log.debug('Downloaded ', url);
|
||||
}
|
||||
|
||||
async function downloadAndExtractTarball(url, dest, log, retry) {
|
||||
try {
|
||||
await download(url, dest, log);
|
||||
const extractDir = join(dirname(dest), basename(dest, '.tar.gz'));
|
||||
await untar(dest, extractDir, {
|
||||
strip: 1,
|
||||
});
|
||||
return extractDir;
|
||||
} catch (e) {
|
||||
if (retry > 0) {
|
||||
await downloadAndExtractTarball(url, dest, log, retry - 1);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function patchNodeGit(config, log, build, platform) {
|
||||
const plat = platform.isWindows() ? 'win32' : platform.getName();
|
||||
const arch = platform.getNodeArch().split('-')[1];
|
||||
const { downloadUrl, packageName } = binaryInfo(plat, arch);
|
||||
|
||||
const downloadPath = build.resolvePathForPlatform(platform, '.nodegit_binaries', packageName);
|
||||
const extractDir = await downloadAndExtractTarball(downloadUrl, downloadPath, log, 3);
|
||||
|
||||
async function patchGit(config, log, build, platform) {
|
||||
const downloadPath = build.resolvePathForPlatform(platform, '.git_binaries', 'git.tar.gz');
|
||||
const destination = build.resolvePathForPlatform(
|
||||
platform,
|
||||
'node_modules/@elastic/nodegit/build/Release'
|
||||
'node_modules/@elastic/simple-git/native/git'
|
||||
);
|
||||
log.debug('Replacing nodegit binaries from ', extractDir);
|
||||
await deleteAll([destination], log);
|
||||
await scanCopy({
|
||||
source: extractDir,
|
||||
destination: destination,
|
||||
time: new Date(),
|
||||
});
|
||||
await deleteAll([extractDir, downloadPath], log);
|
||||
log.debug('Replacing git binaries from ' + downloadPath + ' to ' + destination);
|
||||
const p = platform.isWindows() ? 'win32' : platform.getName();
|
||||
await deleteAll([destination]);
|
||||
await install(p, downloadPath, destination);
|
||||
await deleteAll([path.dirname(downloadPath)], log);
|
||||
}
|
||||
|
||||
export const PatchNativeModulesTask = {
|
||||
|
@ -80,7 +39,7 @@ export const PatchNativeModulesTask = {
|
|||
await Promise.all(
|
||||
config.getTargetPlatforms().map(async platform => {
|
||||
if (!build.isOss()) {
|
||||
await patchNodeGit(config, log, build, platform);
|
||||
await patchGit(config, log, build, platform);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
|
|
@ -8,7 +8,9 @@ export interface CommitInfo {
|
|||
updated: Date;
|
||||
message: string;
|
||||
committer: string;
|
||||
committerEmail?: string;
|
||||
author: string;
|
||||
authorEmail?: string;
|
||||
id: string;
|
||||
parents: string[];
|
||||
treeId: string;
|
||||
|
@ -17,7 +19,7 @@ export interface CommitInfo {
|
|||
export interface ReferenceInfo {
|
||||
name: string;
|
||||
reference: string;
|
||||
commit: CommitInfo;
|
||||
commit?: CommitInfo;
|
||||
type: ReferenceType;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,14 +4,12 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import Git from '@elastic/nodegit';
|
||||
import assert from 'assert';
|
||||
import { delay } from 'bluebird';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import rimraf from 'rimraf';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { prepareProjectByCloning as prepareProject } from '../test_utils';
|
||||
import { CloneWorkerResult, Repository } from '../../model';
|
||||
import { DiskWatermarkService } from '../disk_watermark';
|
||||
import { GitOperations } from '../git_operations';
|
||||
|
@ -30,27 +28,6 @@ const esQueue = {};
|
|||
const serverOptions = createTestServerOption();
|
||||
const gitOps = new GitOperations(serverOptions.repoPath);
|
||||
|
||||
function prepareProject(url: string, p: string) {
|
||||
return new Promise(resolve => {
|
||||
if (!fs.existsSync(p)) {
|
||||
rimraf(p, error => {
|
||||
Git.Clone.clone(url, p, {
|
||||
fetchOpts: {
|
||||
callbacks: {
|
||||
certificateCheck: () => 0,
|
||||
},
|
||||
},
|
||||
bare: 1,
|
||||
}).then(repo => {
|
||||
resolve(repo);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function cleanWorkspace() {
|
||||
return new Promise(resolve => {
|
||||
rimraf(serverOptions.workspacePath, resolve);
|
||||
|
|
|
@ -4,13 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import Git, { CloneOptions } from '@elastic/nodegit';
|
||||
import assert from 'assert';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import rimraf from 'rimraf';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { prepareProjectByCloning as prepareProject } from '../test_utils';
|
||||
import { GitOperations } from '../git_operations';
|
||||
import { CommitIndexRequest, WorkerReservedProgress } from '../../model';
|
||||
import { CommitIndexer } from '../indexer/commit_indexer';
|
||||
|
@ -33,29 +31,6 @@ const esClient = {
|
|||
},
|
||||
};
|
||||
|
||||
function prepareProject(url: string, p: string) {
|
||||
const opts: CloneOptions = {
|
||||
bare: 1,
|
||||
fetchOpts: {
|
||||
callbacks: {
|
||||
certificateCheck: () => 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return new Promise(resolve => {
|
||||
if (!fs.existsSync(p)) {
|
||||
rimraf(p, error => {
|
||||
Git.Clone.clone(url, p, opts).then(repo => {
|
||||
resolve(repo);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const repoUri = 'github.com/elastic/TypeScript-Node-Starter';
|
||||
|
||||
const serverOptions = createTestServerOption();
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
*/
|
||||
|
||||
// @ts-ignore
|
||||
import Git from '@elastic/nodegit';
|
||||
import assert from 'assert';
|
||||
import { execSync } from 'child_process';
|
||||
import fs from 'fs';
|
||||
|
@ -13,6 +12,7 @@ import path from 'path';
|
|||
import rimraf from 'rimraf';
|
||||
import { GitOperations } from '../git_operations';
|
||||
import { createTestServerOption } from '../test_utils';
|
||||
import { prepareProjectByCloning as cloneProject, prepareProjectByInit } from '../test_utils';
|
||||
|
||||
describe('git_operations', () => {
|
||||
it('get default branch from a non master repo', async () => {
|
||||
|
@ -34,11 +34,10 @@ describe('git_operations', () => {
|
|||
|
||||
try {
|
||||
const g = new GitOperations(serverOptions.repoPath);
|
||||
const uri = path.join(repoUri, '.git');
|
||||
const defaultBranch = await g.getDefaultBranch(uri);
|
||||
const defaultBranch = await g.getDefaultBranch(repoUri);
|
||||
assert.strictEqual(defaultBranch, 'trunk');
|
||||
const headRevision = await g.getHeadRevision(uri);
|
||||
const headCommit = await g.getCommitInfo(uri, 'HEAD');
|
||||
const headRevision = await g.getHeadRevision(repoUri);
|
||||
const headCommit = await g.getCommitInfo(repoUri, 'HEAD');
|
||||
assert.strictEqual(headRevision, headCommit!.id);
|
||||
} finally {
|
||||
rimraf.sync(repoDir);
|
||||
|
@ -48,33 +47,16 @@ describe('git_operations', () => {
|
|||
async function prepareProject(repoPath: string) {
|
||||
fs.mkdirSync(repoPath, { recursive: true });
|
||||
const workDir = path.join(serverOptions.workspacePath, repoUri);
|
||||
const repo = await Git.Repository.init(workDir, 0);
|
||||
const content = '';
|
||||
fs.writeFileSync(path.join(workDir, '1'), content, 'utf8');
|
||||
const subFolder = 'src';
|
||||
fs.mkdirSync(path.join(workDir, subFolder));
|
||||
fs.writeFileSync(path.join(workDir, 'src/2'), content, 'utf8');
|
||||
fs.writeFileSync(path.join(workDir, 'src/3'), content, 'utf8');
|
||||
|
||||
const index = await repo.refreshIndex();
|
||||
await index.addByPath('1');
|
||||
await index.addByPath('src/2');
|
||||
await index.addByPath('src/3');
|
||||
index.write();
|
||||
const treeId = await index.writeTree();
|
||||
const committer = Git.Signature.create('tester', 'test@test.com', Date.now() / 1000, 60);
|
||||
const commit = await repo.createCommit(
|
||||
'HEAD',
|
||||
committer,
|
||||
committer,
|
||||
'commit for test',
|
||||
treeId,
|
||||
[]
|
||||
);
|
||||
const { git, commits } = await prepareProjectByInit(workDir, {
|
||||
'commit for test': {
|
||||
'1': '',
|
||||
'src/2': '',
|
||||
'src/3': '',
|
||||
},
|
||||
});
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`created commit ${commit.tostrS()}`);
|
||||
await Git.Clone.clone(workDir, repoPath, { bare: 1 });
|
||||
return Git.Repository.openBare(repoPath);
|
||||
console.log(`created commit ${commits[0]}`);
|
||||
await git.clone(workDir, repoPath, ['--bare']);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -97,13 +79,10 @@ describe('git_operations', () => {
|
|||
const iterator = await g.iterateRepo(repoUri, 'HEAD');
|
||||
for await (const value of iterator) {
|
||||
if (count === 0) {
|
||||
assert.strictEqual('1', value.name);
|
||||
assert.strictEqual('1', value.path);
|
||||
} else if (count === 1) {
|
||||
assert.strictEqual('2', value.name);
|
||||
assert.strictEqual('src/2', value.path);
|
||||
} else if (count === 2) {
|
||||
assert.strictEqual('3', value.name);
|
||||
assert.strictEqual('src/3', value.path);
|
||||
} else {
|
||||
assert.fail('this repo should contains exactly 3 files');
|
||||
|
@ -114,26 +93,6 @@ describe('git_operations', () => {
|
|||
assert.strictEqual(count, 3, 'this repo should contains exactly 3 files');
|
||||
assert.strictEqual(totalFiles, 3, 'this repo should contains exactly 3 files');
|
||||
});
|
||||
function cloneProject(url: string, p: string) {
|
||||
return new Promise(resolve => {
|
||||
if (!fs.existsSync(p)) {
|
||||
rimraf(p, error => {
|
||||
Git.Clone.clone(url, p, {
|
||||
bare: 1,
|
||||
fetchOpts: {
|
||||
callbacks: {
|
||||
certificateCheck: () => 0,
|
||||
},
|
||||
},
|
||||
}).then(repo => {
|
||||
resolve(repo);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
it('can resolve branches', async () => {
|
||||
const g = new GitOperations(serverOptions.repoPath);
|
||||
|
|
|
@ -4,9 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import Git, { CloneOptions } from '@elastic/nodegit';
|
||||
import assert from 'assert';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import rimraf from 'rimraf';
|
||||
import sinon from 'sinon';
|
||||
|
@ -23,6 +21,7 @@ import { LspService } from '../lsp/lsp_service';
|
|||
import { RepositoryConfigController } from '../repository_config_controller';
|
||||
import { createTestServerOption, emptyAsyncFunc, createTestHapiServer } from '../test_utils';
|
||||
import { ConsoleLoggerFactory } from '../utils/console_logger_factory';
|
||||
import { prepareProjectByCloning as prepareProject } from '../test_utils';
|
||||
|
||||
const log: Logger = new ConsoleLoggerFactory().getLogger(['test']);
|
||||
|
||||
|
@ -37,29 +36,6 @@ const esClient = {
|
|||
},
|
||||
};
|
||||
|
||||
function prepareProject(url: string, p: string) {
|
||||
const opts: CloneOptions = {
|
||||
bare: 1,
|
||||
fetchOpts: {
|
||||
callbacks: {
|
||||
certificateCheck: () => 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return new Promise(resolve => {
|
||||
if (!fs.existsSync(p)) {
|
||||
rimraf(p, error => {
|
||||
Git.Clone.clone(url, p, opts).then(repo => {
|
||||
resolve(repo);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const repoUri = 'github.com/elastic/TypeScript-Node-Starter';
|
||||
|
||||
const serverOptions = createTestServerOption();
|
||||
|
@ -196,7 +172,7 @@ describe('LSP incremental indexer unit tests', () => {
|
|||
|
||||
// DeletebyQuery is called 10 times (1 file + 1 symbol reuqests per diff item)
|
||||
// for 5 MODIFIED items
|
||||
assert.strictEqual(deleteByQuerySpy.callCount, 10);
|
||||
assert.strictEqual(deleteByQuerySpy.callCount, 5);
|
||||
|
||||
// There are 5 MODIFIED items and 1 ADDED item. Only 1 file is in supported
|
||||
// language. Each file with supported language has 1 file + 1 symbol + 1 reference.
|
||||
|
@ -207,7 +183,7 @@ describe('LSP incremental indexer unit tests', () => {
|
|||
for (let i = 0; i < bulkSpy.callCount; i++) {
|
||||
total += bulkSpy.getCall(i).args[0].body.length;
|
||||
}
|
||||
assert.strictEqual(total, 16);
|
||||
assert.strictEqual(total, 8);
|
||||
|
||||
// @ts-ignore
|
||||
}).timeout(20000);
|
||||
|
@ -313,8 +289,8 @@ describe('LSP incremental indexer unit tests', () => {
|
|||
for (let i = 0; i < bulkSpy.callCount; i++) {
|
||||
total += bulkSpy.getCall(i).args[0].body.length;
|
||||
}
|
||||
assert.strictEqual(total, 5 * 2);
|
||||
assert.strictEqual(deleteByQuerySpy.callCount, 4);
|
||||
assert.strictEqual(total, 4 * 2);
|
||||
assert.strictEqual(deleteByQuerySpy.callCount, 1);
|
||||
// @ts-ignore
|
||||
}).timeout(20000);
|
||||
// @ts-ignore
|
||||
|
|
|
@ -4,12 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import Git, { CloneOptions } from '@elastic/nodegit';
|
||||
import assert from 'assert';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import rimraf from 'rimraf';
|
||||
import sinon from 'sinon';
|
||||
import { prepareProjectByCloning as prepareProject } from '../test_utils';
|
||||
|
||||
import { GitOperations } from '../git_operations';
|
||||
import { WorkerReservedProgress } from '../../model';
|
||||
|
@ -36,29 +35,6 @@ const esClient = {
|
|||
},
|
||||
};
|
||||
|
||||
function prepareProject(url: string, p: string) {
|
||||
const opts: CloneOptions = {
|
||||
bare: 1,
|
||||
fetchOpts: {
|
||||
callbacks: {
|
||||
certificateCheck: () => 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return new Promise(resolve => {
|
||||
if (!fs.existsSync(p)) {
|
||||
rimraf(p, error => {
|
||||
Git.Clone.clone(url, p, opts).then(repo => {
|
||||
resolve(repo);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const repoUri = 'github.com/elastic/TypeScript-Node-Starter';
|
||||
|
||||
const serverOptions = createTestServerOption();
|
||||
|
|
|
@ -4,20 +4,24 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import Git from '@elastic/nodegit';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import rimraf from 'rimraf';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import assert from 'assert';
|
||||
|
||||
import { simplegit } from '@elastic/simple-git/dist';
|
||||
import { GitOperations } from '../git_operations';
|
||||
import { RepositoryConfigReservedField, RepositoryGitStatusReservedField } from '../indexer/schema';
|
||||
import { InstallManager } from '../lsp/install_manager';
|
||||
import { LspService } from '../lsp/lsp_service';
|
||||
import { RepositoryConfigController } from '../repository_config_controller';
|
||||
import { createTestHapiServer, createTestServerOption } from '../test_utils';
|
||||
import {
|
||||
createTestHapiServer,
|
||||
createTestServerOption,
|
||||
prepareProjectByCloning,
|
||||
prepareProjectByInit,
|
||||
} from '../test_utils';
|
||||
import { ConsoleLoggerFactory } from '../utils/console_logger_factory';
|
||||
|
||||
const filename = 'hello.ts';
|
||||
|
@ -42,41 +46,17 @@ describe('lsp_service tests', () => {
|
|||
let firstCommitSha = '';
|
||||
let secondCommitSha = '';
|
||||
async function prepareProject(repoPath: string) {
|
||||
fs.mkdirSync(repoPath, { recursive: true });
|
||||
const repo = await Git.Repository.init(repoPath, 0);
|
||||
const helloContent = "console.log('hello world');";
|
||||
fs.writeFileSync(path.join(repo.workdir(), filename), helloContent, 'utf8');
|
||||
const { commits } = await prepareProjectByInit(repoPath, {
|
||||
'commit for test': {
|
||||
[filename]: "console.log('hello world');",
|
||||
},
|
||||
'commit2 for test': {
|
||||
'package.json': packagejson,
|
||||
},
|
||||
});
|
||||
|
||||
let index = await repo.refreshIndex();
|
||||
await index.addByPath(filename);
|
||||
index.write();
|
||||
const treeId = await index.writeTree();
|
||||
const committer = Git.Signature.create('tester', 'test@test.com', Date.now() / 1000, 60);
|
||||
const commit = await repo.createCommit(
|
||||
'HEAD',
|
||||
committer,
|
||||
committer,
|
||||
'commit for test',
|
||||
treeId,
|
||||
[]
|
||||
);
|
||||
firstCommitSha = commit.tostrS();
|
||||
fs.writeFileSync(path.join(repo.workdir(), 'package.json'), packagejson, 'utf8');
|
||||
index = await repo.refreshIndex();
|
||||
await index.addByPath('package.json');
|
||||
index.write();
|
||||
const treeId2 = await index.writeTree();
|
||||
const commit2 = await repo.createCommit(
|
||||
'HEAD',
|
||||
committer,
|
||||
committer,
|
||||
'commit2 for test',
|
||||
treeId2,
|
||||
[commit]
|
||||
);
|
||||
secondCommitSha = commit2.tostrS();
|
||||
|
||||
return repo;
|
||||
firstCommitSha = commits[0];
|
||||
secondCommitSha = commits[1];
|
||||
}
|
||||
|
||||
const serverOptions = createTestServerOption();
|
||||
|
@ -110,14 +90,7 @@ describe('lsp_service tests', () => {
|
|||
before(async () => {
|
||||
const tmpRepo = path.join(serverOptions.repoPath, 'tmp');
|
||||
await prepareProject(tmpRepo);
|
||||
await Git.Clone.clone(`file://${tmpRepo}`, path.join(serverOptions.repoPath, repoUri), {
|
||||
bare: 1,
|
||||
fetchOpts: {
|
||||
callbacks: {
|
||||
certificateCheck: () => 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
await prepareProjectByCloning(`file://${tmpRepo}`, path.join(serverOptions.repoPath, repoUri));
|
||||
});
|
||||
|
||||
function comparePath(pathA: string, pathB: string) {
|
||||
|
@ -195,7 +168,7 @@ describe('lsp_service tests', () => {
|
|||
await lspservice.shutdown();
|
||||
}
|
||||
// @ts-ignore
|
||||
}).timeout(10000);
|
||||
}).timeout(30000);
|
||||
|
||||
it('unload a workspace', async () => {
|
||||
const lspservice = mockLspService();
|
||||
|
@ -234,40 +207,7 @@ describe('lsp_service tests', () => {
|
|||
await lspservice.shutdown();
|
||||
}
|
||||
// @ts-ignore
|
||||
}).timeout(10000);
|
||||
|
||||
it('should work if a worktree exists', async () => {
|
||||
const lspservice = mockLspService();
|
||||
try {
|
||||
const revision = 'master';
|
||||
// send a dummy request to open a workspace;
|
||||
const response = await sendHoverRequest(lspservice, revision);
|
||||
assert.ok(response);
|
||||
const workspacePath = path.resolve(serverOptions.workspacePath, repoUri, revision);
|
||||
const workspaceFolderExists = fs.existsSync(workspacePath);
|
||||
// workspace is opened
|
||||
assert.ok(workspaceFolderExists);
|
||||
const bareRepoWorktree = path.join(
|
||||
serverOptions.repoPath,
|
||||
repoUri,
|
||||
'worktrees',
|
||||
`workspace-${revision}`
|
||||
);
|
||||
// worktree is exists
|
||||
const bareRepoWorktreeExists = fs.existsSync(bareRepoWorktree);
|
||||
assert.ok(bareRepoWorktreeExists);
|
||||
// delete the workspace folder but leave worktree
|
||||
rimraf.sync(workspacePath);
|
||||
// send a dummy request to open it again
|
||||
await sendHoverRequest(lspservice, revision);
|
||||
assert.ok(fs.existsSync(workspacePath));
|
||||
|
||||
return;
|
||||
} finally {
|
||||
await lspservice.shutdown();
|
||||
}
|
||||
// @ts-ignore
|
||||
}).timeout(20000);
|
||||
}).timeout(30000);
|
||||
|
||||
it('should update if a worktree is not the newest', async () => {
|
||||
const lspservice = mockLspService();
|
||||
|
@ -277,23 +217,22 @@ describe('lsp_service tests', () => {
|
|||
const response = await sendHoverRequest(lspservice, revision);
|
||||
assert.ok(response);
|
||||
const workspacePath = path.resolve(serverOptions.workspacePath, repoUri, revision);
|
||||
const workspaceRepo = await Git.Repository.open(workspacePath);
|
||||
const git = simplegit(workspacePath);
|
||||
const workspaceCommit = await git.revparse(['HEAD']);
|
||||
// workspace is newest now
|
||||
assert.strictEqual((await workspaceRepo.getHeadCommit()).sha(), secondCommitSha);
|
||||
const firstCommit = await workspaceRepo.getCommit(firstCommitSha);
|
||||
assert.strictEqual(workspaceCommit, secondCommitSha);
|
||||
// reset workspace to an older one
|
||||
// @ts-ignore
|
||||
await Git.Reset.reset(workspaceRepo, firstCommit, Git.Reset.TYPE.HARD, {});
|
||||
assert.strictEqual((await workspaceRepo.getHeadCommit()).sha(), firstCommitSha);
|
||||
await git.reset([firstCommitSha, '--hard']);
|
||||
assert.strictEqual(await git.revparse(['HEAD']), firstCommitSha);
|
||||
|
||||
// send a request again;
|
||||
await sendHoverRequest(lspservice, revision);
|
||||
// workspace_handler should update workspace to the newest one
|
||||
assert.strictEqual((await workspaceRepo.getHeadCommit()).sha(), secondCommitSha);
|
||||
assert.strictEqual(await git.revparse(['HEAD']), secondCommitSha);
|
||||
return;
|
||||
} finally {
|
||||
await lspservice.shutdown();
|
||||
}
|
||||
// @ts-ignore
|
||||
}).timeout(10000);
|
||||
}).timeout(30000);
|
||||
});
|
||||
|
|
|
@ -28,7 +28,7 @@ describe('repository service test', () => {
|
|||
after(() => {
|
||||
return rimraf.sync(baseDir);
|
||||
});
|
||||
const service = new RepositoryService(repoDir, credsDir, log, true);
|
||||
const service = new RepositoryService(repoDir, credsDir, log);
|
||||
|
||||
it('can not clone a repo by ssh without a key', async () => {
|
||||
const repo = RepositoryUtils.buildRepository(
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import Git from '@elastic/nodegit';
|
||||
import assert from 'assert';
|
||||
import * as os from 'os';
|
||||
import rimraf from 'rimraf';
|
||||
|
@ -16,6 +15,7 @@ import { LspRequest } from '../../model';
|
|||
import { GitOperations } from '../git_operations';
|
||||
import { WorkspaceHandler } from '../lsp/workspace_handler';
|
||||
import { ConsoleLoggerFactory } from '../utils/console_logger_factory';
|
||||
import { prepareProjectByInit } from '../test_utils';
|
||||
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'code_test'));
|
||||
const workspaceDir = path.join(baseDir, 'workspace');
|
||||
|
@ -119,26 +119,11 @@ describe('workspace_handler tests', () => {
|
|||
|
||||
async function prepareProject(repoPath: string) {
|
||||
fs.mkdirSync(repoPath, { recursive: true });
|
||||
const repo = await Git.Repository.init(repoPath, 0);
|
||||
const content = 'console.log("test")';
|
||||
const subFolder = 'src';
|
||||
fs.mkdirSync(path.join(repo.workdir(), subFolder));
|
||||
fs.writeFileSync(path.join(repo.workdir(), 'src/app.ts'), content, 'utf8');
|
||||
|
||||
const index = await repo.refreshIndex();
|
||||
await index.addByPath('src/app.ts');
|
||||
index.write();
|
||||
const treeId = await index.writeTree();
|
||||
const committer = Git.Signature.create('tester', 'test@test.com', Date.now() / 1000, 60);
|
||||
const commit = await repo.createCommit(
|
||||
'HEAD',
|
||||
committer,
|
||||
committer,
|
||||
'commit for test',
|
||||
treeId,
|
||||
[]
|
||||
);
|
||||
return { repo, commit };
|
||||
await prepareProjectByInit(repoPath, {
|
||||
'commit for test': {
|
||||
'src/app.ts': 'console.log("test")',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
it('should throw a error if file path is external', async () => {
|
||||
|
|
|
@ -6,8 +6,7 @@
|
|||
|
||||
import fileType from 'file-type';
|
||||
import Boom from 'boom';
|
||||
import { Commit, Oid, Revwalk } from '@elastic/nodegit';
|
||||
import { commitInfo, GitOperations } from '../../git_operations';
|
||||
import { GitOperations } from '../../git_operations';
|
||||
import { FileTree } from '../../../model';
|
||||
import { RequestContext, ServiceHandlerFor } from '../service_definition';
|
||||
import { extractLines } from '../../utils/buffer';
|
||||
|
@ -144,29 +143,15 @@ export const getGitServiceHandler = (
|
|||
};
|
||||
},
|
||||
async history({ uri, path, revision, count, after }) {
|
||||
const repository = await gitOps.openRepo(uri);
|
||||
const commit = await gitOps.getCommitInfo(uri, revision);
|
||||
if (commit === null) {
|
||||
throw Boom.notFound(`commit ${revision} not found in repo ${uri}`);
|
||||
}
|
||||
const walk = repository.createRevWalk();
|
||||
walk.sorting(Revwalk.SORT.TIME);
|
||||
const commitId = Oid.fromString(commit!.id);
|
||||
walk.push(commitId);
|
||||
let commits: Commit[];
|
||||
if (path) {
|
||||
// magic number 10000: how many commits at the most to iterate in order to find the commits contains the path
|
||||
const results = await walk.fileHistoryWalk(path, count, 10000);
|
||||
commits = results.map(result => result.commit);
|
||||
} else {
|
||||
commits = await walk.getCommits(count);
|
||||
}
|
||||
let commits = await gitOps.log(uri, commit.id, after ? count + 1 : count, path);
|
||||
if (after && commits.length > 0) {
|
||||
if (commits[0].id().equal(commitId)) {
|
||||
commits = commits.slice(1);
|
||||
}
|
||||
commits = commits.slice(1);
|
||||
}
|
||||
return commits.map(commitInfo);
|
||||
return commits;
|
||||
},
|
||||
async branchesAndTags({ uri }) {
|
||||
return await gitOps.getBranchAndTags(uri);
|
||||
|
|
|
@ -6,188 +6,123 @@
|
|||
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
|
||||
import {
|
||||
Blame,
|
||||
Commit as NodeGitCommit,
|
||||
Diff as NodeGitDiff,
|
||||
Error as NodeGitError,
|
||||
Oid,
|
||||
Repository,
|
||||
Revwalk,
|
||||
} from '@elastic/nodegit';
|
||||
import { FileItem, LsTreeSummary, simplegit, SimpleGit } from '@elastic/simple-git/dist';
|
||||
import Boom from 'boom';
|
||||
import LruCache from 'lru-cache';
|
||||
import * as Path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { isBinaryFileSync } from 'isbinaryfile';
|
||||
import { BlameSummary, DiffResultTextFile } from '@elastic/simple-git/dist/response';
|
||||
import moment from 'moment';
|
||||
import { GitBlame } from '../common/git_blame';
|
||||
import { CommitDiff, Diff, DiffKind } from '../common/git_diff';
|
||||
import { Commit, FileTree, FileTreeItemType, RepositoryUri } from '../model';
|
||||
import { FileTree, FileTreeItemType, RepositoryUri } from '../model';
|
||||
import { CommitInfo, ReferenceInfo, ReferenceType } from '../model/commit';
|
||||
import { detectLanguage } from './utils/detect_language';
|
||||
import {
|
||||
GitPrime,
|
||||
TreeEntry,
|
||||
CommitDescription,
|
||||
TreeDescription,
|
||||
TagDescription,
|
||||
GitObjectDescription,
|
||||
BlobDescription,
|
||||
} from './utils/git_prime';
|
||||
import { FormatParser } from './utils/format_parser';
|
||||
|
||||
export const HEAD = 'HEAD';
|
||||
const REFS_HEADS = 'refs/heads/';
|
||||
export const DEFAULT_TREE_CHILDREN_LIMIT = 50;
|
||||
|
||||
/**
|
||||
* do a nodegit operation and check the results. If it throws a not found error or returns null,
|
||||
* rethrow a Boom.notFound error.
|
||||
* @param func the nodegit operation
|
||||
* @param message the message pass to Boom.notFound error
|
||||
*/
|
||||
async function checkExists<R>(func: () => Promise<R>, message: string): Promise<R> {
|
||||
let result: R;
|
||||
try {
|
||||
result = await func();
|
||||
} catch (e) {
|
||||
if (e.errno === NodeGitError.CODE.ENOTFOUND) {
|
||||
throw Boom.notFound(message);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
throw Boom.notFound(message);
|
||||
}
|
||||
return result;
|
||||
export interface Blob {
|
||||
isBinary(): boolean;
|
||||
content(): Buffer;
|
||||
rawsize(): number;
|
||||
}
|
||||
|
||||
function entry2Tree(entry: TreeEntry, prefixPath: string = ''): FileTree {
|
||||
function entry2Tree(entry: FileItem, prefixPath: string = ''): FileTree {
|
||||
const type: FileTreeItemType = GitOperations.mode2type(entry.mode);
|
||||
const { path, oid } = entry;
|
||||
const { path, id } = entry;
|
||||
return {
|
||||
name: path,
|
||||
path: prefixPath ? prefixPath + '/' + path : path,
|
||||
sha1: oid,
|
||||
sha1: id,
|
||||
type,
|
||||
};
|
||||
}
|
||||
|
||||
interface Tree {
|
||||
entries: TreeEntry[];
|
||||
gitdir: string;
|
||||
entries: FileItem[];
|
||||
oid: string;
|
||||
}
|
||||
export class GitOperations {
|
||||
private REPO_LRU_CACHE_SIZE = 16;
|
||||
private REPO_MAX_AGE_MS = 60 * 60 * 1000; // 1 hour;
|
||||
private repoRoot: string;
|
||||
private repoCache: LruCache<RepositoryUri, Repository>;
|
||||
|
||||
constructor(repoRoot: string) {
|
||||
this.repoRoot = repoRoot;
|
||||
|
||||
const options = {
|
||||
max: this.REPO_LRU_CACHE_SIZE,
|
||||
maxAge: this.REPO_MAX_AGE_MS,
|
||||
dispose: (repoUri: RepositoryUri, repo: Repository) => {
|
||||
// Clean up the repository before disposing this repo out of the cache.
|
||||
repo.cleanup();
|
||||
},
|
||||
};
|
||||
this.repoCache = new LruCache(options);
|
||||
}
|
||||
|
||||
public cleanRepo(uri: RepositoryUri) {
|
||||
if (this.repoCache.has(uri)) {
|
||||
this.repoCache.del(uri);
|
||||
}
|
||||
}
|
||||
|
||||
public async cleanAllRepo() {
|
||||
this.repoCache.reset();
|
||||
}
|
||||
|
||||
public async fileContent(uri: RepositoryUri, path: string, revision: string = 'master') {
|
||||
const gitdir = this.repoDir(uri);
|
||||
const git = await this.openGit(uri);
|
||||
const commit: CommitInfo = await this.getCommitOr404(uri, revision);
|
||||
const file = await GitPrime.readObject({
|
||||
gitdir,
|
||||
oid: commit.id,
|
||||
filepath: path,
|
||||
format: 'content',
|
||||
});
|
||||
if (file && file.type === 'blob') {
|
||||
return file.object as BlobDescription;
|
||||
}
|
||||
throw Boom.unsupportedMediaType(`${uri}/${path} is not a file.`);
|
||||
}
|
||||
const p = `${commit.id}:${path}`;
|
||||
|
||||
public async getCommit(uri: RepositoryUri, revision: string): Promise<NodeGitCommit> {
|
||||
const info = await this.getCommitOr404(uri, revision);
|
||||
const repo = await this.openRepo(uri);
|
||||
return (await checkExists(
|
||||
() => this.findCommit(repo, info.id),
|
||||
`revision or branch ${revision} not found in ${uri}`
|
||||
)) as NodeGitCommit;
|
||||
const type = await git.catFile(['-t', p]);
|
||||
if (type.trim() === 'blob') {
|
||||
const buffer: Buffer = await git.binaryCatFile(['blob', p]);
|
||||
return {
|
||||
content(): Buffer {
|
||||
return buffer;
|
||||
},
|
||||
rawsize(): number {
|
||||
return buffer.length;
|
||||
},
|
||||
isBinary(): boolean {
|
||||
return isBinaryFileSync(buffer);
|
||||
},
|
||||
} as Blob;
|
||||
} else {
|
||||
throw Boom.unsupportedMediaType(`${uri}/${path} is not a file.`);
|
||||
}
|
||||
}
|
||||
|
||||
public async getDefaultBranch(uri: RepositoryUri): Promise<string> {
|
||||
const gitdir = this.repoDir(uri);
|
||||
const ref = await GitPrime.resolveRef({ gitdir, ref: HEAD, depth: 2 });
|
||||
if (ref.startsWith(REFS_HEADS)) {
|
||||
return ref.substr(REFS_HEADS.length);
|
||||
}
|
||||
return ref;
|
||||
const git = await this.openGit(uri);
|
||||
return (await git.raw(['symbolic-ref', HEAD, '--short'])).trim();
|
||||
}
|
||||
|
||||
public async getHeadRevision(uri: RepositoryUri): Promise<string> {
|
||||
const gitdir = this.repoDir(uri);
|
||||
return await GitPrime.resolveRef({ gitdir, ref: HEAD, depth: 10 });
|
||||
return await this.getRevision(uri, HEAD);
|
||||
}
|
||||
|
||||
public async getRevision(uri: RepositoryUri, ref: string): Promise<string> {
|
||||
const git = await this.openGit(uri);
|
||||
return await git.revparse([ref]);
|
||||
}
|
||||
|
||||
public async blame(uri: RepositoryUri, revision: string, path: string): Promise<GitBlame[]> {
|
||||
const repo = await this.openRepo(uri);
|
||||
const newestCommit = (await this.getCommit(uri, revision)).id();
|
||||
const blame = await Blame.file(repo, path, { newestCommit });
|
||||
const git = await this.openGit(uri);
|
||||
const blameSummary: BlameSummary = await git.blame(revision, path);
|
||||
const results: GitBlame[] = [];
|
||||
for (let i = 0; i < blame.getHunkCount(); i++) {
|
||||
const hunk = blame.getHunkByIndex(i);
|
||||
// @ts-ignore wrong definition in nodegit
|
||||
const commit = await repo.getCommit(hunk.finalCommitId());
|
||||
for (const blame of blameSummary.blames) {
|
||||
results.push({
|
||||
committer: {
|
||||
// @ts-ignore wrong definition in nodegit
|
||||
name: hunk.finalSignature().name(),
|
||||
// @ts-ignore wrong definition in nodegit
|
||||
email: hunk.finalSignature().email(),
|
||||
name: blame.commit.author!.name,
|
||||
email: blame.commit.author!.email,
|
||||
},
|
||||
// @ts-ignore wrong definition in nodegit
|
||||
startLine: hunk.finalStartLineNumber(),
|
||||
// @ts-ignore wrong definition in nodegit
|
||||
lines: hunk.linesInHunk(),
|
||||
startLine: blame.resultLine,
|
||||
lines: blame.lines,
|
||||
commit: {
|
||||
id: commit.sha(),
|
||||
message: commit.message(),
|
||||
date: commit.date().toISOString(),
|
||||
id: blame.commit.id!,
|
||||
message: blame.commit.message!,
|
||||
date: moment
|
||||
.unix(blame.commit.author!.time)
|
||||
.utcOffset(blame.commit.author!.tz)
|
||||
.toISOString(true),
|
||||
},
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public async openRepo(uri: RepositoryUri): Promise<Repository> {
|
||||
if (this.repoCache.has(uri)) {
|
||||
const repo = this.repoCache.get(uri) as Repository;
|
||||
return Promise.resolve(repo);
|
||||
}
|
||||
|
||||
public async openGit(uri: RepositoryUri, check: boolean = true): Promise<SimpleGit> {
|
||||
const repoDir = this.repoDir(uri);
|
||||
const repo = await checkExists<Repository>(
|
||||
() => Repository.open(repoDir),
|
||||
`repo ${uri} not found`
|
||||
);
|
||||
this.repoCache.set(uri, repo);
|
||||
return Promise.resolve(repo);
|
||||
const git = simplegit(repoDir);
|
||||
if (!check) return git;
|
||||
if (await git.checkIsRepo()) {
|
||||
return git;
|
||||
} else {
|
||||
throw Boom.notFound(`repo ${uri} not found`);
|
||||
}
|
||||
}
|
||||
|
||||
private repoDir(uri: RepositoryUri) {
|
||||
|
@ -202,85 +137,33 @@ export class GitOperations {
|
|||
}
|
||||
}
|
||||
|
||||
private static async isTextFile(gitdir: string, entry: TreeEntry) {
|
||||
if (entry.type === 'blob') {
|
||||
const type = GitOperations.mode2type(entry.mode);
|
||||
if (type === FileTreeItemType.File) {
|
||||
return await GitPrime.isTextFile({ gitdir, oid: entry.oid });
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public async countRepoFiles(uri: RepositoryUri, revision: string): Promise<number> {
|
||||
let count = 0;
|
||||
const commit = await this.getCommitOr404(uri, revision);
|
||||
const gitdir = this.repoDir(uri);
|
||||
const commitObject = await GitPrime.readObject({ gitdir, oid: commit.id });
|
||||
const treeId = (commitObject.object as CommitDescription).tree;
|
||||
const git = await this.openGit(uri);
|
||||
const ls = new LsTreeSummary(git, revision, '.', { recursive: true });
|
||||
return (await ls.allFiles()).length;
|
||||
}
|
||||
|
||||
async function walk(oid: string) {
|
||||
const tree = await GitOperations.readTree(gitdir, oid);
|
||||
for (const entry of tree.entries) {
|
||||
if (entry.type === 'tree') {
|
||||
await walk(entry.oid);
|
||||
} else if (await GitOperations.isTextFile(gitdir, entry)) {
|
||||
count++;
|
||||
}
|
||||
public async *iterateRepo(uri: RepositoryUri, revision: string) {
|
||||
const git = await this.openGit(uri);
|
||||
const ls = new LsTreeSummary(git, revision, '.', { showSize: true, recursive: true });
|
||||
for await (const file of ls.iterator()) {
|
||||
const type = GitOperations.mode2type(file.mode);
|
||||
if (type === FileTreeItemType.File) {
|
||||
yield file;
|
||||
}
|
||||
}
|
||||
|
||||
await walk(treeId);
|
||||
return count;
|
||||
}
|
||||
|
||||
public async iterateRepo(
|
||||
uri: RepositoryUri,
|
||||
revision: string
|
||||
): Promise<AsyncIterableIterator<FileTree>> {
|
||||
const commit = await this.getCommitOr404(uri, revision);
|
||||
const gitdir = this.repoDir(uri);
|
||||
const commitObject = await GitPrime.readObject({ gitdir, oid: commit.id });
|
||||
const treeId = (commitObject.object as CommitDescription).tree;
|
||||
async function* walk(oid: string, prefix: string = ''): AsyncIterableIterator<FileTree> {
|
||||
const tree = await GitOperations.readTree(gitdir, oid);
|
||||
for (const entry of tree.entries) {
|
||||
const path = prefix ? `${prefix}/${entry.path}` : entry.path;
|
||||
if (entry.type === 'tree') {
|
||||
yield* walk(entry.oid, path);
|
||||
} else if (await GitOperations.isTextFile(gitdir, entry)) {
|
||||
const type = GitOperations.mode2type(entry.mode);
|
||||
yield {
|
||||
name: entry.path,
|
||||
type,
|
||||
path,
|
||||
repoUri: uri,
|
||||
sha1: entry.oid,
|
||||
} as FileTree;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return walk(treeId);
|
||||
public async readTree(git: SimpleGit, oid: string, path: string = '.'): Promise<Tree> {
|
||||
const lsTree = new LsTreeSummary(git, oid, path, {});
|
||||
const entries = await lsTree.allFiles();
|
||||
return {
|
||||
entries,
|
||||
oid,
|
||||
} as Tree;
|
||||
}
|
||||
|
||||
private static async readTree(gitdir: string, oid: string): Promise<Tree> {
|
||||
const { type, object } = await GitPrime.readObject({ gitdir, oid });
|
||||
if (type === 'commit') {
|
||||
return await this.readTree(gitdir, (object as CommitDescription).tree);
|
||||
} else if (type === 'tree') {
|
||||
const tree = object as TreeDescription;
|
||||
return {
|
||||
entries: tree.entries,
|
||||
gitdir,
|
||||
oid,
|
||||
} as Tree;
|
||||
} else {
|
||||
throw new Error(`${oid} is not a tree`);
|
||||
}
|
||||
}
|
||||
|
||||
static mode2type(mode: string): FileTreeItemType {
|
||||
public static mode2type(mode: string): FileTreeItemType {
|
||||
switch (mode) {
|
||||
case '100755':
|
||||
case '100644':
|
||||
|
@ -297,44 +180,6 @@ export class GitOperations {
|
|||
}
|
||||
}
|
||||
|
||||
public async iterateCommits(
|
||||
uri: RepositoryUri,
|
||||
startRevision: string,
|
||||
untilRevision?: string
|
||||
): Promise<Commit[]> {
|
||||
const repository = await this.openRepo(uri);
|
||||
const commit = await this.getCommit(uri, startRevision);
|
||||
|
||||
const revWalk = repository.createRevWalk();
|
||||
revWalk.sorting(Revwalk.SORT.TOPOLOGICAL);
|
||||
revWalk.push(commit.id());
|
||||
|
||||
const commits: NodeGitCommit[] = await revWalk.getCommitsUntil((c: NodeGitCommit) => {
|
||||
// Iterate commits all the way to the oldest one.
|
||||
return true;
|
||||
});
|
||||
|
||||
const res: Commit[] = commits.map(c => {
|
||||
return {
|
||||
repoUri: uri,
|
||||
id: c.sha(),
|
||||
message: c.message(),
|
||||
body: c.body(),
|
||||
date: c.date(),
|
||||
parents: c.parents().map(i => i.tostrS()),
|
||||
author: {
|
||||
name: c.author().name(),
|
||||
email: c.author().email(),
|
||||
},
|
||||
committer: {
|
||||
name: c.committer().name(),
|
||||
email: c.committer().email(),
|
||||
},
|
||||
} as Commit;
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a fileTree structure by walking the repo file tree.
|
||||
* @param uri the repo uri
|
||||
|
@ -355,7 +200,7 @@ export class GitOperations {
|
|||
flatten: boolean = false
|
||||
): Promise<FileTree> {
|
||||
const commit = await this.getCommitOr404(uri, revision);
|
||||
const gitdir = this.repoDir(uri);
|
||||
const git = await this.openGit(uri);
|
||||
if (path.startsWith('/')) {
|
||||
path = path.slice(1);
|
||||
}
|
||||
|
@ -382,38 +227,46 @@ export class GitOperations {
|
|||
path: '',
|
||||
type: FileTreeItemType.Directory,
|
||||
};
|
||||
const tree = await GitOperations.readTree(gitdir, commit.treeId);
|
||||
await this.fillChildren(root, tree, { skip, limit, flatten });
|
||||
const tree = await this.readTree(git, commit.treeId);
|
||||
await this.fillChildren(git, root, tree, { skip, limit, flatten });
|
||||
if (path) {
|
||||
await this.resolvePath(root, tree, path.split('/'), { skip, limit, flatten });
|
||||
await this.resolvePath(git, root, tree, path.split('/'), { skip, limit, flatten });
|
||||
}
|
||||
return root;
|
||||
} else {
|
||||
const obj = await GitPrime.readObject({ gitdir, oid: commit.id, filepath: path });
|
||||
const result: FileTree = {
|
||||
name: path.split('/').pop() || '',
|
||||
path,
|
||||
type: type2item(obj.type!),
|
||||
sha1: obj.oid,
|
||||
};
|
||||
if (result.type === FileTreeItemType.Directory) {
|
||||
await this.fillChildren(
|
||||
result,
|
||||
{
|
||||
gitdir,
|
||||
entries: (obj.object as TreeDescription).entries,
|
||||
oid: obj.oid,
|
||||
},
|
||||
{ skip, limit, flatten }
|
||||
);
|
||||
if (path) {
|
||||
const file = (await this.readTree(git, commit.id, path)).entries[0];
|
||||
const result: FileTree = {
|
||||
name: path.split('/').pop() || '',
|
||||
path,
|
||||
type: type2item(file.type!),
|
||||
sha1: file.id,
|
||||
};
|
||||
if (file.type === 'tree') {
|
||||
await this.fillChildren(git, result, await this.readTree(git, file.id), {
|
||||
skip,
|
||||
limit,
|
||||
flatten,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
const root: FileTree = {
|
||||
name: '',
|
||||
path: '',
|
||||
type: FileTreeItemType.Directory,
|
||||
};
|
||||
const tree = await this.readTree(git, commit.id, '.');
|
||||
await this.fillChildren(git, root, tree, { skip, limit, flatten });
|
||||
return root;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private async fillChildren(
|
||||
git: SimpleGit,
|
||||
result: FileTree,
|
||||
{ entries, gitdir }: Tree,
|
||||
{ entries }: Tree,
|
||||
{ skip, limit, flatten }: { skip: number; limit: number; flatten: boolean }
|
||||
) {
|
||||
result.childrenCount = entries.length;
|
||||
|
@ -422,15 +275,16 @@ export class GitOperations {
|
|||
const child = entry2Tree(e, result.path);
|
||||
result.children.push(child);
|
||||
if (flatten && child.type === FileTreeItemType.Directory) {
|
||||
const tree = await GitOperations.readTree(gitdir, e.oid);
|
||||
const tree = await this.readTree(git, e.id);
|
||||
if (tree.entries.length === 1) {
|
||||
await this.fillChildren(child, tree, { skip, limit, flatten });
|
||||
await this.fillChildren(git, child, tree, { skip, limit, flatten });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async resolvePath(
|
||||
git: SimpleGit,
|
||||
result: FileTree,
|
||||
tree: Tree,
|
||||
paths: string[],
|
||||
|
@ -443,17 +297,17 @@ export class GitOperations {
|
|||
result.children = [];
|
||||
}
|
||||
const child = entry2Tree(entry, result.path);
|
||||
const idx = result.children.findIndex(i => i.sha1 === entry.oid);
|
||||
const idx = result.children.findIndex(i => i.sha1 === entry.id);
|
||||
if (idx < 0) {
|
||||
result.children.push(child);
|
||||
} else {
|
||||
result.children[idx] = child;
|
||||
}
|
||||
if (entry.type === 'tree') {
|
||||
const subTree = await GitOperations.readTree(tree.gitdir, entry.oid);
|
||||
await this.fillChildren(child, subTree, opt);
|
||||
const subTree = await this.readTree(git, entry.id);
|
||||
await this.fillChildren(git, child, subTree, opt);
|
||||
if (rest.length > 0) {
|
||||
await this.resolvePath(child, subTree, rest, opt);
|
||||
await this.resolvePath(git, child, subTree, rest, opt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -461,71 +315,82 @@ export class GitOperations {
|
|||
}
|
||||
|
||||
public async getCommitDiff(uri: string, revision: string): Promise<CommitDiff> {
|
||||
const repo = await this.openRepo(uri);
|
||||
const commit = await this.getCommit(uri, revision);
|
||||
const diffs = await commit.getDiff();
|
||||
const git = await this.openGit(uri);
|
||||
const commit = await this.getCommitOr404(uri, revision);
|
||||
const diffs = await git.diffSummary([revision]);
|
||||
|
||||
const commitDiff: CommitDiff = {
|
||||
commit: commitInfo(commit),
|
||||
additions: 0,
|
||||
deletions: 0,
|
||||
commit,
|
||||
additions: diffs.insertions,
|
||||
deletions: diffs.deletions,
|
||||
files: [],
|
||||
};
|
||||
for (const diff of diffs) {
|
||||
const patches = await diff.patches();
|
||||
for (const patch of patches) {
|
||||
const { total_deletions, total_additions } = patch.lineStats();
|
||||
commitDiff.additions += total_additions;
|
||||
commitDiff.deletions += total_deletions;
|
||||
if (patch.isAdded()) {
|
||||
const path = patch.newFile().path();
|
||||
const modifiedCode = await this.getModifiedCode(commit, path);
|
||||
const language = await detectLanguage(path, modifiedCode);
|
||||
commitDiff.files.push({
|
||||
language,
|
||||
path,
|
||||
modifiedCode,
|
||||
additions: total_additions,
|
||||
deletions: total_deletions,
|
||||
kind: DiffKind.ADDED,
|
||||
});
|
||||
} else if (patch.isDeleted()) {
|
||||
const path = patch.oldFile().path();
|
||||
const originCode = await this.getOriginCode(commit, repo, path);
|
||||
const language = await detectLanguage(path, originCode);
|
||||
commitDiff.files.push({
|
||||
language,
|
||||
path,
|
||||
originCode,
|
||||
kind: DiffKind.DELETED,
|
||||
additions: total_additions,
|
||||
deletions: total_deletions,
|
||||
});
|
||||
} else if (patch.isModified()) {
|
||||
const path = patch.newFile().path();
|
||||
const modifiedCode = await this.getModifiedCode(commit, path);
|
||||
const originPath = patch.oldFile().path();
|
||||
const originCode = await this.getOriginCode(commit, repo, originPath);
|
||||
const language = await detectLanguage(patch.newFile().path(), modifiedCode);
|
||||
commitDiff.files.push({
|
||||
language,
|
||||
path,
|
||||
originPath,
|
||||
originCode,
|
||||
modifiedCode,
|
||||
kind: DiffKind.MODIFIED,
|
||||
additions: total_additions,
|
||||
deletions: total_deletions,
|
||||
});
|
||||
} else if (patch.isRenamed()) {
|
||||
const path = patch.newFile().path();
|
||||
commitDiff.files.push({
|
||||
path,
|
||||
originPath: patch.oldFile().path(),
|
||||
kind: DiffKind.RENAMED,
|
||||
additions: total_additions,
|
||||
deletions: total_deletions,
|
||||
});
|
||||
for (const d of diffs.files) {
|
||||
if (!d.binary) {
|
||||
const diff = d as DiffResultTextFile;
|
||||
const kind = this.diffKind(diff);
|
||||
switch (kind) {
|
||||
case DiffKind.ADDED:
|
||||
{
|
||||
const path = diff.file;
|
||||
const modifiedCode = await this.getModifiedCode(git, commit, path);
|
||||
const language = await detectLanguage(path, modifiedCode);
|
||||
commitDiff.files.push({
|
||||
language,
|
||||
path,
|
||||
modifiedCode,
|
||||
additions: diff.insertions,
|
||||
deletions: diff.deletions,
|
||||
kind,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case DiffKind.DELETED:
|
||||
{
|
||||
const path = diff.file;
|
||||
const originCode = await this.getOriginCode(git, commit, path);
|
||||
const language = await detectLanguage(path, originCode);
|
||||
commitDiff.files.push({
|
||||
language,
|
||||
path,
|
||||
originCode,
|
||||
kind,
|
||||
additions: diff.insertions,
|
||||
deletions: diff.deletions,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case DiffKind.MODIFIED:
|
||||
{
|
||||
const path = diff.rename || diff.file;
|
||||
const modifiedCode = await this.getModifiedCode(git, commit, path);
|
||||
const originPath = diff.file;
|
||||
const originCode = await this.getOriginCode(git, commit, originPath);
|
||||
const language = await detectLanguage(path, modifiedCode);
|
||||
commitDiff.files.push({
|
||||
language,
|
||||
path,
|
||||
originPath,
|
||||
originCode,
|
||||
modifiedCode,
|
||||
kind,
|
||||
additions: diff.insertions,
|
||||
deletions: diff.deletions,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case DiffKind.RENAMED:
|
||||
{
|
||||
const path = diff.rename || diff.file;
|
||||
commitDiff.files.push({
|
||||
path,
|
||||
originPath: diff.file,
|
||||
kind,
|
||||
additions: diff.insertions,
|
||||
deletions: diff.deletions,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -533,120 +398,91 @@ export class GitOperations {
|
|||
}
|
||||
|
||||
public async getDiff(uri: string, oldRevision: string, newRevision: string): Promise<Diff> {
|
||||
const repo = await this.openRepo(uri);
|
||||
const oldCommit = await this.getCommit(uri, oldRevision);
|
||||
const newCommit = await this.getCommit(uri, newRevision);
|
||||
const oldTree = await oldCommit.getTree();
|
||||
const newTree = await newCommit.getTree();
|
||||
|
||||
const diff = await NodeGitDiff.treeToTree(repo, oldTree, newTree);
|
||||
|
||||
const git = await this.openGit(uri);
|
||||
const diff = await git.diffSummary([oldRevision, newRevision]);
|
||||
const res: Diff = {
|
||||
additions: 0,
|
||||
deletions: 0,
|
||||
additions: diff.insertions,
|
||||
deletions: diff.deletions,
|
||||
files: [],
|
||||
};
|
||||
const patches = await diff.patches();
|
||||
for (const patch of patches) {
|
||||
const { total_deletions, total_additions } = patch.lineStats();
|
||||
res.additions += total_additions;
|
||||
res.deletions += total_deletions;
|
||||
if (patch.isAdded()) {
|
||||
const path = patch.newFile().path();
|
||||
diff.files.forEach(d => {
|
||||
if (!d.binary) {
|
||||
const td = d as DiffResultTextFile;
|
||||
const kind = this.diffKind(td);
|
||||
res.files.push({
|
||||
path,
|
||||
additions: total_additions,
|
||||
deletions: total_deletions,
|
||||
kind: DiffKind.ADDED,
|
||||
});
|
||||
} else if (patch.isDeleted()) {
|
||||
const path = patch.oldFile().path();
|
||||
res.files.push({
|
||||
path,
|
||||
kind: DiffKind.DELETED,
|
||||
additions: total_additions,
|
||||
deletions: total_deletions,
|
||||
});
|
||||
} else if (patch.isModified()) {
|
||||
const path = patch.newFile().path();
|
||||
const originPath = patch.oldFile().path();
|
||||
res.files.push({
|
||||
path,
|
||||
originPath,
|
||||
kind: DiffKind.MODIFIED,
|
||||
additions: total_additions,
|
||||
deletions: total_deletions,
|
||||
});
|
||||
} else if (patch.isRenamed()) {
|
||||
const path = patch.newFile().path();
|
||||
res.files.push({
|
||||
path,
|
||||
originPath: patch.oldFile().path(),
|
||||
kind: DiffKind.RENAMED,
|
||||
additions: total_additions,
|
||||
deletions: total_deletions,
|
||||
path: d.file,
|
||||
additions: td.insertions,
|
||||
deletions: td.deletions,
|
||||
kind,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private async getOriginCode(commit: NodeGitCommit, repo: Repository, path: string) {
|
||||
for (const oid of commit.parents()) {
|
||||
const parentCommit = await repo.getCommit(oid);
|
||||
if (parentCommit) {
|
||||
const entry = await parentCommit.getEntry(path);
|
||||
if (entry) {
|
||||
return (await entry.getBlob()).content().toString('utf8');
|
||||
}
|
||||
}
|
||||
private diffKind(diff: DiffResultTextFile) {
|
||||
let kind: DiffKind = DiffKind.MODIFIED;
|
||||
if (diff.changes === diff.insertions) {
|
||||
kind = DiffKind.ADDED;
|
||||
} else if (diff.changes === diff.deletions) {
|
||||
kind = DiffKind.DELETED;
|
||||
} else if (diff.rename) {
|
||||
kind = DiffKind.RENAMED;
|
||||
}
|
||||
return '';
|
||||
return kind;
|
||||
}
|
||||
|
||||
private async getModifiedCode(commit: NodeGitCommit, path: string) {
|
||||
const entry = await commit.getEntry(path);
|
||||
return (await entry.getBlob()).content().toString('utf8');
|
||||
private async getOriginCode(git: SimpleGit, commit: CommitInfo, path: string) {
|
||||
const buffer: Buffer = await git.binaryCatFile(['blob', `${commit.id}~1:${path}`]);
|
||||
return buffer.toString('utf8');
|
||||
}
|
||||
|
||||
private async findCommit(repo: Repository, oid: string): Promise<NodeGitCommit | null> {
|
||||
try {
|
||||
return repo.getCommit(Oid.fromString(oid));
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
private async getModifiedCode(git: SimpleGit, commit: CommitInfo, path: string) {
|
||||
const buffer: Buffer = await git.binaryCatFile(['blob', `${commit.id}:${path}`]);
|
||||
return buffer.toString('utf8');
|
||||
}
|
||||
|
||||
public async getBranchAndTags(repoUri: string): Promise<ReferenceInfo[]> {
|
||||
const gitdir = this.repoDir(repoUri);
|
||||
const remoteBranches = await GitPrime.listBranches({ gitdir, remote: 'origin' });
|
||||
const results: ReferenceInfo[] = [];
|
||||
for (const name of remoteBranches) {
|
||||
const reference = `refs/remotes/origin/${name}`;
|
||||
const commit = await this.getCommitInfo(repoUri, reference);
|
||||
if (commit) {
|
||||
results.push({
|
||||
name,
|
||||
reference,
|
||||
type: ReferenceType.REMOTE_BRANCH,
|
||||
commit,
|
||||
});
|
||||
const format = {
|
||||
name: '%(refname:short)',
|
||||
reference: '%(refname)',
|
||||
type: '%(objecttype)',
|
||||
commit: {
|
||||
updated: '%(*authordate)',
|
||||
message: '%(*contents)',
|
||||
committer: '%(*committername)',
|
||||
author: '%(*authorname)',
|
||||
id: '%(*objectname)',
|
||||
parents: '%(*parent)',
|
||||
treeId: '%(*tree)',
|
||||
},
|
||||
};
|
||||
const parser = new FormatParser(format);
|
||||
const git = await this.openGit(repoUri);
|
||||
const result = await git.raw([
|
||||
'for-each-ref',
|
||||
'--format=' + parser.toFormatStr(),
|
||||
'refs/tags/*',
|
||||
'refs/remotes/origin/*',
|
||||
]);
|
||||
const results = parser.parseResult(result);
|
||||
return results.map(r => {
|
||||
const ref: ReferenceInfo = {
|
||||
name: r.name.startsWith('origin/') ? r.name.slice(7) : r.name,
|
||||
reference: r.reference,
|
||||
type: r.type === 'tag' ? ReferenceType.TAG : ReferenceType.REMOTE_BRANCH,
|
||||
};
|
||||
if (r.commit && r.commit.id) {
|
||||
const commit = {
|
||||
...r.commit,
|
||||
};
|
||||
commit.parents = r.commit.parents ? r.commit.parents.split(' ') : [];
|
||||
commit.updated = new Date(r.commit.updated);
|
||||
ref.commit = commit;
|
||||
}
|
||||
}
|
||||
const tags = await GitPrime.listTags({ gitdir });
|
||||
for (const name of tags) {
|
||||
const reference = `refs/tags/${name}`;
|
||||
const commit = await this.getCommitInfo(repoUri, reference);
|
||||
if (commit) {
|
||||
results.push({
|
||||
name,
|
||||
reference,
|
||||
type: ReferenceType.TAG,
|
||||
commit,
|
||||
});
|
||||
}
|
||||
}
|
||||
return results;
|
||||
return ref;
|
||||
});
|
||||
}
|
||||
|
||||
public async getCommitOr404(repoUri: string, ref: string): Promise<CommitInfo> {
|
||||
|
@ -657,71 +493,63 @@ export class GitOperations {
|
|||
return commit;
|
||||
}
|
||||
|
||||
public async log(
|
||||
repoUri: string,
|
||||
revision: string,
|
||||
count: number,
|
||||
path?: string
|
||||
): Promise<CommitInfo[]> {
|
||||
const git = await this.openGit(repoUri);
|
||||
const options: any = {
|
||||
n: count,
|
||||
format: {
|
||||
updated: '%ai',
|
||||
message: '%B',
|
||||
author: '%an',
|
||||
authorEmail: '%ae',
|
||||
committer: '%cn',
|
||||
committerEmail: '%ce',
|
||||
id: '%H',
|
||||
parents: '%p',
|
||||
treeId: '%T',
|
||||
},
|
||||
from: revision,
|
||||
};
|
||||
if (path) {
|
||||
options.file = path;
|
||||
}
|
||||
const result = await git.log(options);
|
||||
return (result.all as unknown) as CommitInfo[];
|
||||
}
|
||||
|
||||
public async resolveRef(repoUri: string, ref: string): Promise<string | null> {
|
||||
const git = await this.openGit(repoUri);
|
||||
let oid = '';
|
||||
|
||||
try {
|
||||
// try local branches or tags
|
||||
oid = (await git.revparse(['-q', '--verify', ref])).trim();
|
||||
} catch (e) {
|
||||
// try remote branches
|
||||
}
|
||||
if (!oid) {
|
||||
try {
|
||||
oid = (await git.revparse(['-q', '--verify', `origin/${ref}`])).trim();
|
||||
} catch (e1) {
|
||||
// no match
|
||||
}
|
||||
}
|
||||
return oid || null;
|
||||
}
|
||||
|
||||
public async getCommitInfo(repoUri: string, ref: string): Promise<CommitInfo | null> {
|
||||
const gitdir = this.repoDir(repoUri);
|
||||
// depth: avoid infinite loop
|
||||
let obj: GitObjectDescription | null = null;
|
||||
let oid: string = '';
|
||||
if (/^[0-9a-f]{5,40}$/.test(ref)) {
|
||||
// it's possible ref is sha-1 object id
|
||||
try {
|
||||
oid = ref;
|
||||
if (oid.length < 40) {
|
||||
oid = await GitPrime.expandOid({ gitdir, oid });
|
||||
}
|
||||
obj = await GitPrime.readObject({ gitdir, oid, format: 'parsed' });
|
||||
} catch (e) {
|
||||
// expandOid or readObject failed
|
||||
}
|
||||
}
|
||||
// test if it is a reference
|
||||
if (!obj) {
|
||||
try {
|
||||
// try local branches or tags
|
||||
oid = await GitPrime.resolveRef({ gitdir, ref, depth: 10 });
|
||||
} catch (e) {
|
||||
// try remote branches
|
||||
try {
|
||||
oid = await GitPrime.resolveRef({ gitdir, ref: `origin/${ref}`, depth: 10 });
|
||||
} catch (e1) {
|
||||
// no match
|
||||
}
|
||||
}
|
||||
if (oid) {
|
||||
obj = await GitPrime.readObject({ gitdir, oid, format: 'parsed' });
|
||||
}
|
||||
}
|
||||
if (obj) {
|
||||
if (obj.type === 'commit') {
|
||||
const commit = obj.object as CommitDescription;
|
||||
return {
|
||||
id: obj.oid,
|
||||
author: commit.author.name,
|
||||
committer: commit.committer.name,
|
||||
message: commit.message,
|
||||
updated: new Date(commit.committer.timestamp * 1000),
|
||||
parents: commit.parent,
|
||||
treeId: commit.tree,
|
||||
} as CommitInfo;
|
||||
} else if (obj.type === 'tag') {
|
||||
const tag = obj.object as TagDescription;
|
||||
if (tag.type === 'commit') {
|
||||
return await this.getCommitInfo(repoUri, tag.object);
|
||||
}
|
||||
const oid = await this.resolveRef(repoUri, ref);
|
||||
if (oid) {
|
||||
const commits = await this.log(repoUri, oid, 1);
|
||||
if (commits.length > 0) {
|
||||
return commits[0];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function commitInfo(commit: NodeGitCommit): CommitInfo {
|
||||
return {
|
||||
updated: commit.date(),
|
||||
message: commit.message(),
|
||||
author: commit.author().name(),
|
||||
committer: commit.committer().name(),
|
||||
id: commit.sha().substr(0, 7),
|
||||
parents: commit.parents().map(oid => oid.toString().substring(0, 7)),
|
||||
treeId: commit.treeId().tostrS(),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -101,7 +101,24 @@ export class CommitIndexer extends AbstractIndexer {
|
|||
private commits: Commit[] | null = null;
|
||||
protected async getIndexRequestCount(): Promise<number> {
|
||||
try {
|
||||
this.commits = await this.gitOps.iterateCommits(this.repoUri, HEAD);
|
||||
this.commits = (await this.gitOps.log(this.repoUri, HEAD, Number.MAX_SAFE_INTEGER)).map(c => {
|
||||
const [message, ...body] = c.message.split('\n');
|
||||
return {
|
||||
author: {
|
||||
name: c.author,
|
||||
email: c.authorEmail,
|
||||
},
|
||||
committer: {
|
||||
name: c.committer,
|
||||
email: c.committer,
|
||||
},
|
||||
message,
|
||||
parents: c.parents,
|
||||
date: c.updated,
|
||||
id: c.id,
|
||||
body: body.join('\n'),
|
||||
} as Commit;
|
||||
});
|
||||
return this.commits.length;
|
||||
} catch (error) {
|
||||
if (this.isCancelled()) {
|
||||
|
|
|
@ -123,6 +123,7 @@ export class LspIncrementalIndexer extends LspIndexer {
|
|||
protected async *getIndexRequestIterator(): AsyncIterableIterator<LspIncIndexRequest> {
|
||||
try {
|
||||
if (this.diff) {
|
||||
await this.prepareWorkspace();
|
||||
for (const f of this.diff.files) {
|
||||
yield {
|
||||
repoUri: this.repoUri,
|
||||
|
|
|
@ -5,7 +5,10 @@
|
|||
*/
|
||||
|
||||
import { ResponseError } from 'vscode-jsonrpc';
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { promisify } from 'util';
|
||||
import { isBinaryFile } from 'isbinaryfile';
|
||||
import { ProgressReporter } from '.';
|
||||
import { TEXT_FILE_LIMIT } from '../../common/file';
|
||||
import {
|
||||
|
@ -36,6 +39,9 @@ import {
|
|||
} from './index_creation_request';
|
||||
import { ALL_RESERVED, DocumentIndexName, ReferenceIndexName, SymbolIndexName } from './schema';
|
||||
|
||||
const state = promisify(fs.stat);
|
||||
const readFile = promisify(fs.readFile);
|
||||
|
||||
export class LspIndexer extends AbstractIndexer {
|
||||
public type: IndexerType = IndexerType.LSP;
|
||||
// Batch index helper for symbols/references
|
||||
|
@ -45,6 +51,7 @@ export class LspIndexer extends AbstractIndexer {
|
|||
|
||||
private LSP_BATCH_INDEX_SIZE = 50;
|
||||
private DOC_BATCH_INDEX_SIZE = 50;
|
||||
private workspaceDir: string = '';
|
||||
|
||||
constructor(
|
||||
protected readonly repoUri: RepositoryUri,
|
||||
|
@ -105,6 +112,8 @@ export class LspIndexer extends AbstractIndexer {
|
|||
|
||||
protected async *getIndexRequestIterator(): AsyncIterableIterator<LspIndexRequest> {
|
||||
try {
|
||||
await this.prepareWorkspace();
|
||||
|
||||
const fileIterator = await this.gitOps.iterateRepo(this.repoUri, HEAD);
|
||||
for await (const file of fileIterator) {
|
||||
const filePath = file.path!;
|
||||
|
@ -126,6 +135,14 @@ export class LspIndexer extends AbstractIndexer {
|
|||
}
|
||||
}
|
||||
|
||||
protected async prepareWorkspace() {
|
||||
const { workspaceDir } = await this.lspService.workspaceHandler.openWorkspace(
|
||||
this.repoUri,
|
||||
HEAD
|
||||
);
|
||||
this.workspaceDir = workspaceDir;
|
||||
}
|
||||
|
||||
protected async getIndexRequestCount(): Promise<number> {
|
||||
try {
|
||||
return await this.gitOps.countRepoFiles(this.repoUri, HEAD);
|
||||
|
@ -198,14 +215,20 @@ export class LspIndexer extends AbstractIndexer {
|
|||
}
|
||||
|
||||
protected FILE_OVERSIZE_ERROR_MSG = 'File size exceeds limit. Skip index.';
|
||||
protected BINARY_FILE_ERROR_MSG = 'Binary file detected. Skip index.';
|
||||
protected async getFileSource(request: LspIndexRequest): Promise<string> {
|
||||
const { revision, filePath } = request;
|
||||
// Always read file content from the original bare repo
|
||||
const blob = await this.gitOps.fileContent(this.repoUri, filePath, revision);
|
||||
if (blob.rawsize() > TEXT_FILE_LIMIT) {
|
||||
const { filePath } = request;
|
||||
const fullPath = path.join(this.workspaceDir, filePath);
|
||||
const fileStat = await state(fullPath);
|
||||
const fileSize = fileStat.size;
|
||||
if (fileSize > TEXT_FILE_LIMIT) {
|
||||
throw new Error(this.FILE_OVERSIZE_ERROR_MSG);
|
||||
}
|
||||
return blob.content().toString();
|
||||
const bin = await isBinaryFile(fullPath);
|
||||
if (bin) {
|
||||
throw new Error(this.BINARY_FILE_ERROR_MSG);
|
||||
}
|
||||
return readFile(fullPath, { encoding: 'utf8' }) as Promise<string>;
|
||||
}
|
||||
|
||||
protected async execLspIndexing(
|
||||
|
@ -278,7 +301,10 @@ export class LspIndexer extends AbstractIndexer {
|
|||
try {
|
||||
content = await this.getFileSource(request);
|
||||
} catch (error) {
|
||||
if ((error as Error).message === this.FILE_OVERSIZE_ERROR_MSG) {
|
||||
if ((error as Error).message === this.BINARY_FILE_ERROR_MSG) {
|
||||
this.log.debug(this.BINARY_FILE_ERROR_MSG);
|
||||
return stats;
|
||||
} else if ((error as Error).message === this.FILE_OVERSIZE_ERROR_MSG) {
|
||||
// Skip this index request if the file is oversized
|
||||
this.log.debug(this.FILE_OVERSIZE_ERROR_MSG);
|
||||
return stats;
|
||||
|
|
|
@ -58,7 +58,6 @@ export function initLocalService(
|
|||
server.events.on('stop', async () => {
|
||||
loggerFactory.get().debug('shutdown lsp process');
|
||||
await lspService.shutdown();
|
||||
await gitOps.cleanAllRepo();
|
||||
});
|
||||
codeServices.registerHandler(
|
||||
LspServiceDefinition,
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
/* eslint-disable no-console */
|
||||
|
||||
import fs from 'fs';
|
||||
import Git from '@elastic/nodegit';
|
||||
import rimraf from 'rimraf';
|
||||
|
||||
import { TestConfig, Repo } from '../../model/test_config';
|
||||
import { prepareProjectByCloning } from '../test_utils';
|
||||
|
||||
export class TestRepoManager {
|
||||
private repos: Repo[];
|
||||
|
@ -20,32 +20,10 @@ export class TestRepoManager {
|
|||
|
||||
public async importAllRepos() {
|
||||
for (const repo of this.repos) {
|
||||
await this.importRepo(repo.url, repo.path);
|
||||
await prepareProjectByCloning(repo.url, repo.path);
|
||||
}
|
||||
}
|
||||
|
||||
public importRepo(url: string, path: string) {
|
||||
return new Promise(resolve => {
|
||||
if (!fs.existsSync(path)) {
|
||||
rimraf(path, error => {
|
||||
console.log(`begin to import ${url} to ${path}`);
|
||||
Git.Clone.clone(url, path, {
|
||||
fetchOpts: {
|
||||
callbacks: {
|
||||
certificateCheck: () => 0,
|
||||
},
|
||||
},
|
||||
}).then(repo => {
|
||||
console.log(`import ${url} done`);
|
||||
resolve(repo);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async cleanAllRepos() {
|
||||
this.repos.forEach(repo => {
|
||||
this.cleanRepo(repo.path);
|
||||
|
|
|
@ -4,38 +4,34 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
Commit,
|
||||
Error as GitError,
|
||||
Repository,
|
||||
Reset,
|
||||
TreeEntry,
|
||||
// @ts-ignore
|
||||
Worktree,
|
||||
} from '@elastic/nodegit';
|
||||
import Boom from 'boom';
|
||||
import del from 'del';
|
||||
import fs from 'fs';
|
||||
import { delay } from 'lodash';
|
||||
import path from 'path';
|
||||
import { promisify } from 'util';
|
||||
import { ResponseMessage } from 'vscode-jsonrpc/lib/messages';
|
||||
import { Hover, Location, TextDocumentPositionParams } from 'vscode-languageserver';
|
||||
|
||||
import { DetailSymbolInformation, Full } from '@elastic/lsp-extension';
|
||||
|
||||
import { SimpleGit } from '@elastic/simple-git/dist/promise';
|
||||
import { simplegit } from '@elastic/simple-git/dist';
|
||||
import { RepositoryUtils } from '../../common/repository_utils';
|
||||
import { parseLspUrl } from '../../common/uri_util';
|
||||
import { LspRequest, WorkerReservedProgress } from '../../model';
|
||||
import { GitOperations } from '../git_operations';
|
||||
import { FileTreeItemType, LspRequest, WorkerReservedProgress } from '../../model';
|
||||
import { GitOperations, HEAD } from '../git_operations';
|
||||
import { EsClient } from '../lib/esqueue';
|
||||
import { Logger } from '../log';
|
||||
import { RepositoryObjectClient } from '../search';
|
||||
import { LoggerFactory } from '../utils/log_factory';
|
||||
|
||||
export const MAX_RESULT_COUNT = 20;
|
||||
interface Worktree {
|
||||
path: string;
|
||||
revision: string;
|
||||
branch: string;
|
||||
}
|
||||
|
||||
const mkdirAsync = promisify(fs.mkdir);
|
||||
export const MAX_RESULT_COUNT = 20;
|
||||
|
||||
export class WorkspaceHandler {
|
||||
private revisionMap: { [uri: string]: string } = {};
|
||||
|
@ -58,9 +54,9 @@ export class WorkspaceHandler {
|
|||
/**
|
||||
* open workspace for repositoryUri, update it from bare repository if necessary.
|
||||
* @param repositoryUri the uri of bare repository.
|
||||
* @param revision
|
||||
* @param ref
|
||||
*/
|
||||
public async openWorkspace(repositoryUri: string, revision: string) {
|
||||
public async openWorkspace(repositoryUri: string, ref: string) {
|
||||
// Try get repository clone status with 3 retries at maximum.
|
||||
const tryGetGitStatus = async (retryCount: number) => {
|
||||
let gitStatus;
|
||||
|
@ -85,48 +81,85 @@ export class WorkspaceHandler {
|
|||
if (this.objectClient) {
|
||||
await tryGetGitStatus(0);
|
||||
}
|
||||
|
||||
const bareRepo = await this.gitOps.openRepo(repositoryUri);
|
||||
const targetCommit = await this.gitOps.getCommit(repositoryUri, revision);
|
||||
const git = await this.gitOps.openGit(repositoryUri);
|
||||
const defaultBranch = await this.gitOps.getDefaultBranch(repositoryUri);
|
||||
if (revision !== defaultBranch) {
|
||||
await this.checkCommit(bareRepo, targetCommit);
|
||||
revision = defaultBranch;
|
||||
const targetRevision = await this.gitOps.getRevision(repositoryUri, ref);
|
||||
if (ref !== defaultBranch) {
|
||||
await this.checkCommit(git, targetRevision);
|
||||
ref = defaultBranch;
|
||||
}
|
||||
let workspace;
|
||||
if (await this.workspaceExists(bareRepo, repositoryUri, revision)) {
|
||||
workspace = await this.updateWorkspace(repositoryUri, revision, targetCommit);
|
||||
const workspaceBranch = this.workspaceWorktreeBranchName(ref);
|
||||
const worktrees = await this.listWorktrees(git);
|
||||
let wt: Worktree;
|
||||
if (worktrees.has(workspaceBranch)) {
|
||||
wt = worktrees.get(workspaceBranch)!;
|
||||
} else {
|
||||
workspace = await this.cloneWorkspace(bareRepo, repositoryUri, revision, targetCommit);
|
||||
wt = await this.openWorktree(
|
||||
git,
|
||||
workspaceBranch,
|
||||
await this.revisionDir(repositoryUri, ref),
|
||||
targetRevision
|
||||
);
|
||||
}
|
||||
if (!targetRevision.startsWith(wt.revision)) {
|
||||
await this.setWorkspaceRevision(wt.path, targetRevision);
|
||||
}
|
||||
const { workspaceDir, workspaceHeadCommit } = workspace;
|
||||
|
||||
this.setWorkspaceRevision(workspaceDir, workspace.workspaceHeadCommit);
|
||||
return {
|
||||
workspaceDir,
|
||||
workspaceRevision: workspaceHeadCommit.sha().substring(0, 7),
|
||||
workspaceDir: wt.path,
|
||||
workspaceRevision: targetRevision,
|
||||
};
|
||||
}
|
||||
|
||||
public async openWorktree(
|
||||
git: SimpleGit,
|
||||
workspaceBranch: string,
|
||||
dir: string,
|
||||
revision: string
|
||||
) {
|
||||
await git.raw(['worktree', 'add', '-b', workspaceBranch, dir, revision]);
|
||||
return {
|
||||
revision,
|
||||
path: dir,
|
||||
branch: workspaceBranch,
|
||||
} as Worktree;
|
||||
}
|
||||
|
||||
public async listWorkspaceFolders(repoUri: string) {
|
||||
const workspaceDir = await this.workspaceDir(repoUri);
|
||||
const git = await this.gitOps.openGit(repoUri);
|
||||
const worktrees = await this.listWorktrees(git);
|
||||
const isDir = (source: string) => fs.lstatSync(source).isDirectory();
|
||||
try {
|
||||
return fs
|
||||
.readdirSync(workspaceDir)
|
||||
.map(name => path.join(workspaceDir, name))
|
||||
.filter(isDir);
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
this.log.debug('Cannot find workspace dirs');
|
||||
return [];
|
||||
} else {
|
||||
throw error;
|
||||
return [...worktrees.values()]
|
||||
.filter(wt => wt.branch.startsWith('workspace'))
|
||||
.map(wt => wt.path)
|
||||
.filter(isDir);
|
||||
}
|
||||
|
||||
public async listWorktrees(git: SimpleGit): Promise<Map<string, Worktree>> {
|
||||
const str = await git.raw(['worktree', 'list']);
|
||||
const regex = /(.*?)\s+([a-h0-9]+)\s+\[(.+)\]/gm;
|
||||
let m;
|
||||
const result: Map<string, Worktree> = new Map();
|
||||
while ((m = regex.exec(str)) !== null) {
|
||||
if (m.index === regex.lastIndex) {
|
||||
regex.lastIndex++;
|
||||
}
|
||||
const [, p, revision, branch] = m;
|
||||
result.set(branch, {
|
||||
path: p,
|
||||
revision,
|
||||
branch,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async clearWorkspace(repoUri: string) {
|
||||
const git = await this.gitOps.openGit(repoUri);
|
||||
const worktrees = await this.listWorktrees(git);
|
||||
for (const wt of worktrees.values()) {
|
||||
await git.raw(['worktree', 'remove', wt.path, '--force']);
|
||||
await git.deleteLocalBranch(wt.branch);
|
||||
}
|
||||
const workspaceDir = await this.workspaceDir(repoUri);
|
||||
await del([workspaceDir], { force: true });
|
||||
}
|
||||
|
@ -147,8 +180,8 @@ export class WorkspaceHandler {
|
|||
if (filePath) {
|
||||
request.documentUri = payload.textDocument.uri;
|
||||
payload.textDocument.uri = request.resolvedFilePath = filePath;
|
||||
request.workspacePath = workspacePath;
|
||||
request.workspaceRevision = workspaceRevision;
|
||||
request.workspacePath = workspacePath!;
|
||||
request.workspaceRevision = workspaceRevision!;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -337,26 +370,16 @@ export class WorkspaceHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private async checkCommit(repository: Repository, commit: Commit) {
|
||||
private async checkCommit(git: SimpleGit, targetRevision: string) {
|
||||
// we only support headCommit now.
|
||||
const headCommit = await repository.getHeadCommit();
|
||||
if (headCommit.sha() !== commit.sha()) {
|
||||
const headRevision = await git.revparse([HEAD]);
|
||||
if (headRevision !== targetRevision) {
|
||||
throw Boom.badRequest(`revision must be master.`);
|
||||
}
|
||||
}
|
||||
|
||||
private async workspaceExists(bareRepo: Repository, repositoryUri: string, revision: string) {
|
||||
const workTreeName = this.workspaceWorktreeBranchName(revision);
|
||||
const wt = this.getWorktree(bareRepo, workTreeName);
|
||||
if (wt) {
|
||||
const workspaceDir = await this.revisionDir(repositoryUri, revision);
|
||||
return fs.existsSync(workspaceDir);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public async revisionDir(repositoryUri: string, revision: string) {
|
||||
return path.join(await this.workspaceDir(repositoryUri), revision);
|
||||
public async revisionDir(repositoryUri: string, ref: string) {
|
||||
return path.join(await this.workspaceDir(repositoryUri), ref);
|
||||
}
|
||||
|
||||
private async workspaceDir(repoUri: string) {
|
||||
|
@ -374,81 +397,9 @@ export class WorkspaceHandler {
|
|||
return `workspace-${branch}`;
|
||||
}
|
||||
|
||||
private async updateWorkspace(repositoryUri: string, revision: string, targetCommit: Commit) {
|
||||
const workspaceDir = await this.revisionDir(repositoryUri, revision);
|
||||
const workspaceRepo = await Repository.open(workspaceDir);
|
||||
const workspaceHeadCommit = await workspaceRepo.getHeadCommit();
|
||||
if (workspaceHeadCommit.sha() !== targetCommit.sha()) {
|
||||
const commit = await workspaceRepo.getCommit(targetCommit.sha());
|
||||
this.log.info(`Checkout workspace ${workspaceDir} to ${targetCommit.sha()}`);
|
||||
// @ts-ignore
|
||||
const result = await Reset.reset(workspaceRepo, commit, Reset.TYPE.HARD, {});
|
||||
if (result !== undefined && result !== GitError.CODE.OK) {
|
||||
throw Boom.internal(`Reset workspace to commit ${targetCommit.sha()} failed.`);
|
||||
}
|
||||
}
|
||||
workspaceRepo.cleanup();
|
||||
return { workspaceHeadCommit, workspaceDir };
|
||||
}
|
||||
|
||||
private async cloneWorkspace(
|
||||
bareRepo: Repository,
|
||||
repositoryUri: string,
|
||||
revision: string,
|
||||
targetCommit: Commit
|
||||
) {
|
||||
const workspaceDir = await this.revisionDir(repositoryUri, revision);
|
||||
this.log.info(`Create workspace ${workspaceDir} from url ${bareRepo.path()}`);
|
||||
const parentDir = path.dirname(workspaceDir);
|
||||
// on windows, git clone will failed if parent folder is not exists;
|
||||
await mkdirAsync(parentDir, { recursive: true });
|
||||
const workTreeName = this.workspaceWorktreeBranchName(revision);
|
||||
await this.pruneWorktree(bareRepo, workTreeName);
|
||||
// Create the worktree and open it as Repository.
|
||||
const wt = await Worktree.add(bareRepo, workTreeName, workspaceDir, { lock: 0, version: 1 });
|
||||
// @ts-ignore
|
||||
const workspaceRepo = await Repository.openFromWorktree(wt);
|
||||
const workspaceHeadCommit = await workspaceRepo.getHeadCommit();
|
||||
// when we start supporting multi-revision, targetCommit may not be head
|
||||
if (workspaceHeadCommit.sha() !== targetCommit.sha()) {
|
||||
const commit = await workspaceRepo.getCommit(targetCommit.sha());
|
||||
this.log.info(`checkout ${workspaceRepo.workdir()} to commit ${targetCommit.sha()}`);
|
||||
// @ts-ignore
|
||||
const result = await Reset.reset(workspaceRepo, commit, Reset.TYPE.HARD, {});
|
||||
if (result !== undefined && result !== GitError.CODE.OK) {
|
||||
throw Boom.internal(`checkout workspace to commit ${targetCommit.sha()} failed.`);
|
||||
}
|
||||
}
|
||||
workspaceRepo.cleanup();
|
||||
return { workspaceHeadCommit, workspaceDir };
|
||||
}
|
||||
|
||||
private async getWorktree(bareRepo: Repository, workTreeName: string) {
|
||||
try {
|
||||
const wt: Worktree = await Worktree.lookup(bareRepo, workTreeName);
|
||||
return wt;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async pruneWorktree(bareRepo: Repository, workTreeName: string) {
|
||||
const wt = await this.getWorktree(bareRepo, workTreeName);
|
||||
if (wt) {
|
||||
wt.prune({ flags: 1 });
|
||||
try {
|
||||
// try delete the worktree branch
|
||||
const ref = await bareRepo.getReference(`refs/heads/${workTreeName}`);
|
||||
ref.delete();
|
||||
} catch (e) {
|
||||
// it doesn't matter if branch is not exists
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private setWorkspaceRevision(workspaceDir: string, headCommit: Commit) {
|
||||
const workspaceRelativePath = path.relative(this.workspacePath, workspaceDir);
|
||||
this.revisionMap[workspaceRelativePath] = headCommit.sha().substring(0, 7);
|
||||
private async setWorkspaceRevision(workspaceDir: string, revision: string) {
|
||||
const git = simplegit(workspaceDir);
|
||||
await git.reset(['--hard', revision]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -462,16 +413,15 @@ export class WorkspaceHandler {
|
|||
*/
|
||||
private async checkFile(repoUri: string, revision: string, filePath: string) {
|
||||
try {
|
||||
const commit = await this.gitOps.getCommit(repoUri, revision);
|
||||
const entry = await commit.getEntry(filePath);
|
||||
switch (entry.filemode()) {
|
||||
case TreeEntry.FILEMODE.TREE:
|
||||
case TreeEntry.FILEMODE.BLOB:
|
||||
case TreeEntry.FILEMODE.EXECUTABLE:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
const git = await this.gitOps.openGit(repoUri);
|
||||
const p = filePath.endsWith('/') ? filePath.slice(0, -1) : filePath;
|
||||
const tree = await this.gitOps.readTree(git, revision, p);
|
||||
if (tree.entries.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
const entry = tree.entries[0];
|
||||
const type = GitOperations.mode2type(entry.mode);
|
||||
return type === FileTreeItemType.File || type === FileTreeItemType.Directory;
|
||||
} catch (e) {
|
||||
// filePath may not exists
|
||||
return false;
|
||||
|
|
|
@ -8,7 +8,6 @@ import crypto from 'crypto';
|
|||
import * as _ from 'lodash';
|
||||
import { CoreSetup } from 'src/core/server';
|
||||
|
||||
import { GitOperations } from './git_operations';
|
||||
import { RepositoryIndexInitializerFactory, tryMigrateIndices } from './indexer';
|
||||
import { Esqueue } from './lib/esqueue';
|
||||
import { Logger } from './log';
|
||||
|
@ -59,7 +58,6 @@ import { PluginSetupContract } from '../../../../plugins/code/server/index';
|
|||
export class CodePlugin {
|
||||
private isCodeNode = false;
|
||||
|
||||
private gitOps: GitOperations | null = null;
|
||||
private queue: Esqueue | null = null;
|
||||
private log: Logger;
|
||||
private serverOptions: ServerOptions;
|
||||
|
@ -140,7 +138,6 @@ export class CodePlugin {
|
|||
repoConfigController
|
||||
);
|
||||
this.lspService = lspService;
|
||||
this.gitOps = gitOps;
|
||||
const { indexScheduler, updateScheduler, cloneWorker } = initWorkers(
|
||||
server,
|
||||
this.log,
|
||||
|
@ -188,7 +185,6 @@ export class CodePlugin {
|
|||
repoConfigController
|
||||
);
|
||||
this.lspService = lspService;
|
||||
this.gitOps = gitOps;
|
||||
const { indexScheduler, updateScheduler } = initWorkers(
|
||||
server,
|
||||
this.log,
|
||||
|
@ -215,7 +211,6 @@ export class CodePlugin {
|
|||
|
||||
public async stop() {
|
||||
if (this.isCodeNode) {
|
||||
if (this.gitOps) await this.gitOps.cleanAllRepo();
|
||||
if (this.indexScheduler) this.indexScheduler.stop();
|
||||
if (this.updateScheduler) this.updateScheduler.stop();
|
||||
if (this.queue) this.queue.destroy();
|
||||
|
|
|
@ -68,8 +68,7 @@ export class CloneWorker extends AbstractGitWorker {
|
|||
const repoService = this.repoServiceFactory.newInstance(
|
||||
this.serverOptions.repoPath,
|
||||
this.serverOptions.credsPath,
|
||||
this.log,
|
||||
this.serverOptions.security.enableGitCertCheck
|
||||
this.log
|
||||
);
|
||||
const repo = RepositoryUtils.buildRepository(url);
|
||||
|
||||
|
|
|
@ -79,11 +79,7 @@ test('Execute delete job.', async () => {
|
|||
esQueue as Esqueue,
|
||||
log,
|
||||
esClient as EsClient,
|
||||
{
|
||||
security: {
|
||||
enableGitCertCheck: true,
|
||||
},
|
||||
} as ServerOptions,
|
||||
{} as ServerOptions,
|
||||
(gitOps as any) as GitOperations,
|
||||
(cancellationService as any) as CancellationSerivce,
|
||||
(lspService as any) as LspService,
|
||||
|
|
|
@ -77,10 +77,8 @@ export class DeleteWorker extends AbstractWorker {
|
|||
const repoService = this.repoServiceFactory.newInstance(
|
||||
this.serverOptions.repoPath,
|
||||
this.serverOptions.credsPath,
|
||||
this.log,
|
||||
this.serverOptions.security.enableGitCertCheck
|
||||
this.log
|
||||
);
|
||||
this.gitOps.cleanRepo(uri);
|
||||
await this.deletePromiseWrapper(repoService.remove(uri), 'git data', uri);
|
||||
|
||||
// 4. Delete the document index and alias where the repository document and all status reside,
|
||||
|
|
|
@ -66,9 +66,6 @@ test('Execute update job', async () => {
|
|||
log,
|
||||
esClient as EsClient,
|
||||
{
|
||||
security: {
|
||||
enableGitCertCheck: true,
|
||||
},
|
||||
disk: {
|
||||
thresholdEnabled: true,
|
||||
watermarkLow: '80%',
|
||||
|
@ -112,9 +109,6 @@ test('On update job completed because of cancellation ', async () => {
|
|||
log,
|
||||
esClient as EsClient,
|
||||
{
|
||||
security: {
|
||||
enableGitCertCheck: true,
|
||||
},
|
||||
disk: {
|
||||
thresholdEnabled: true,
|
||||
watermarkLow: '80%',
|
||||
|
@ -187,9 +181,6 @@ test('Execute update job failed because of low available disk space', async () =
|
|||
log,
|
||||
esClient as EsClient,
|
||||
{
|
||||
security: {
|
||||
enableGitCertCheck: true,
|
||||
},
|
||||
disk: {
|
||||
thresholdEnabled: true,
|
||||
watermarkLow: '80%',
|
||||
|
@ -266,9 +257,6 @@ test('On update job error or timeout will not persist as error', async () => {
|
|||
log,
|
||||
esClient as EsClient,
|
||||
{
|
||||
security: {
|
||||
enableGitCertCheck: true,
|
||||
},
|
||||
disk: {
|
||||
thresholdEnabled: true,
|
||||
watermarkLow: '80%',
|
||||
|
|
|
@ -43,8 +43,7 @@ export class UpdateWorker extends AbstractGitWorker {
|
|||
const repoService = this.repoServiceFactory.newInstance(
|
||||
this.serverOptions.repoPath,
|
||||
this.serverOptions.credsPath,
|
||||
this.log,
|
||||
this.serverOptions.security.enableGitCertCheck
|
||||
this.log
|
||||
);
|
||||
|
||||
// Try to cancel any existing update job for this repository.
|
||||
|
|
|
@ -4,22 +4,18 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import Git, { RemoteCallbacks } from '@elastic/nodegit';
|
||||
import { Progress } from '@elastic/simple-git/dist';
|
||||
import del from 'del';
|
||||
import fs from 'fs';
|
||||
import { promisify } from 'util';
|
||||
import moment from 'moment';
|
||||
import path from 'path';
|
||||
|
||||
import { SimpleGit } from '@elastic/simple-git/dist/promise';
|
||||
import { RepositoryUtils } from '../common/repository_utils';
|
||||
import {
|
||||
CloneProgress,
|
||||
CloneWorkerResult,
|
||||
DeleteWorkerResult,
|
||||
Repository,
|
||||
UpdateWorkerResult,
|
||||
} from '../model';
|
||||
import { CloneProgress, CloneWorkerResult, DeleteWorkerResult, Repository } from '../model';
|
||||
import { Logger } from './log';
|
||||
import { GitOperations } from './git_operations';
|
||||
|
||||
// Return false to stop the clone progress. Return true to keep going;
|
||||
export type CloneProgressHandler = (
|
||||
|
@ -46,12 +42,14 @@ function isCancelled(error: any) {
|
|||
|
||||
// This is the service for any kind of repository handling, e.g. clone, update, delete, etc.
|
||||
export class RepositoryService {
|
||||
private readonly gitOps: GitOperations;
|
||||
constructor(
|
||||
private readonly repoVolPath: string,
|
||||
private readonly credsPath: string,
|
||||
private readonly log: Logger,
|
||||
private readonly enableGitCertCheck: boolean
|
||||
) {}
|
||||
private readonly log: Logger
|
||||
) {
|
||||
this.gitOps = new GitOperations(repoVolPath);
|
||||
}
|
||||
|
||||
public async clone(repo: Repository, handler?: CloneProgressHandler): Promise<CloneWorkerResult> {
|
||||
if (!repo) {
|
||||
|
@ -62,30 +60,23 @@ export class RepositoryService {
|
|||
this.log.info(`Repository exist in local path. Do update instead of clone.`);
|
||||
try {
|
||||
// Do update instead of clone if the local repo exists.
|
||||
const updateRes = await this.update(repo);
|
||||
return {
|
||||
uri: repo.uri,
|
||||
repo: {
|
||||
...repo,
|
||||
defaultBranch: updateRes.branch,
|
||||
revision: updateRes.revision,
|
||||
},
|
||||
};
|
||||
return await this.update(repo);
|
||||
} catch (error) {
|
||||
// If failed to update the current git repo living in the disk, clean up the local git repo and
|
||||
// move on with the clone.
|
||||
await this.remove(repo.uri);
|
||||
}
|
||||
} else {
|
||||
const parentDir = path.dirname(localPath);
|
||||
// on windows, git clone will failed if parent folder is not exists;
|
||||
await mkdirAsync(parentDir, { recursive: true });
|
||||
await mkdirAsync(localPath, { recursive: true });
|
||||
}
|
||||
// Go head with the actual clone.
|
||||
const git = await this.gitOps.openGit(repo.uri, false);
|
||||
await git.init(true);
|
||||
await git.addRemote('origin', repo.url);
|
||||
if (repo.protocol === 'ssh') {
|
||||
return this.tryWithKeys(key => this.doClone(repo, localPath, handler, key));
|
||||
return this.tryWithKeys(git, () => this.doFetch(git, repo, handler));
|
||||
} else {
|
||||
return await this.doClone(repo, localPath, handler);
|
||||
return await this.doFetch(git, repo, handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -105,103 +96,21 @@ export class RepositoryService {
|
|||
throw error;
|
||||
}
|
||||
}
|
||||
public async update(
|
||||
repo: Repository,
|
||||
handler?: UpdateProgressHandler
|
||||
): Promise<UpdateWorkerResult> {
|
||||
public async update(repo: Repository, handler?: UpdateProgressHandler) {
|
||||
const git = await this.gitOps.openGit(repo.uri);
|
||||
if (repo.protocol === 'ssh') {
|
||||
return await this.tryWithKeys(key => this.doUpdate(repo.uri, key, handler));
|
||||
return await this.tryWithKeys(git, () => this.doFetch(git, repo, handler));
|
||||
} else {
|
||||
return await this.doUpdate(repo.uri, /* key */ undefined, handler);
|
||||
}
|
||||
}
|
||||
public async doUpdate(
|
||||
uri: string,
|
||||
key?: string,
|
||||
handler?: UpdateProgressHandler
|
||||
): Promise<UpdateWorkerResult> {
|
||||
const localPath = RepositoryUtils.repositoryLocalPath(this.repoVolPath, uri);
|
||||
let repo: Git.Repository | undefined;
|
||||
try {
|
||||
repo = await Git.Repository.open(localPath);
|
||||
let lastProgressUpdate = moment();
|
||||
const cbs: RemoteCallbacks = {
|
||||
transferProgress: async (_: any) => {
|
||||
// Update progress update throttling.
|
||||
const now = moment();
|
||||
if (now.diff(lastProgressUpdate) < this.PROGRESS_UPDATE_THROTTLING_FREQ_MS) {
|
||||
return 0;
|
||||
}
|
||||
lastProgressUpdate = now;
|
||||
|
||||
if (handler) {
|
||||
const resumeUpdate = await handler();
|
||||
if (!resumeUpdate) {
|
||||
return GIT_FETCH_PROGRESS_CANCEL;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
credentials: this.credentialFunc(key),
|
||||
};
|
||||
// Ignore cert check on testing environment.
|
||||
if (!this.enableGitCertCheck) {
|
||||
cbs.certificateCheck = () => {
|
||||
// Ignore cert check failures.
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
await repo.fetchAll({
|
||||
callbacks: cbs,
|
||||
});
|
||||
// TODO(mengwei): deal with the case when the default branch has changed.
|
||||
const currentBranch = await repo.getCurrentBranch();
|
||||
const currentBranchName = currentBranch.shorthand();
|
||||
const originBranchName = `origin/${currentBranchName}`;
|
||||
const originRef = await repo.getReference(originBranchName);
|
||||
const headRef = await repo.getReference(currentBranchName);
|
||||
if (!originRef.target().equal(headRef.target())) {
|
||||
await headRef.setTarget(originRef.target(), 'update');
|
||||
}
|
||||
const headCommit = await repo.getHeadCommit();
|
||||
this.log.debug(`Update repository to revision ${headCommit.sha()}`);
|
||||
return {
|
||||
uri,
|
||||
branch: currentBranchName,
|
||||
revision: headCommit.sha(),
|
||||
};
|
||||
} catch (error) {
|
||||
if (isCancelled(error)) {
|
||||
// Update job was cancelled intentionally. Do not throw this error.
|
||||
this.log.info(`Update repository job for ${uri} was cancelled.`);
|
||||
this.log.debug(
|
||||
`Update repository job cancellation error: ${JSON.stringify(error, null, 2)}`
|
||||
);
|
||||
return {
|
||||
uri,
|
||||
branch: '',
|
||||
revision: '',
|
||||
cancelled: true,
|
||||
};
|
||||
} else if (error.message && error.message.startsWith(SSH_AUTH_ERROR.message)) {
|
||||
throw SSH_AUTH_ERROR;
|
||||
} else {
|
||||
const msg = `update repository ${uri} error: ${error}`;
|
||||
this.log.error(msg);
|
||||
throw new Error(msg);
|
||||
}
|
||||
} finally {
|
||||
if (repo) {
|
||||
repo.cleanup();
|
||||
}
|
||||
return await this.doFetch(git, repo, handler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* read credentials dir, try using each privateKey until action is successful
|
||||
* @param git
|
||||
* @param action
|
||||
*/
|
||||
private async tryWithKeys<R>(action: (key: string) => Promise<R>): Promise<R> {
|
||||
private async tryWithKeys<R>(git: SimpleGit, action: () => Promise<R>): Promise<R> {
|
||||
const files = fs.existsSync(this.credsPath)
|
||||
? new Set(fs.readdirSync(this.credsPath))
|
||||
: new Set([]);
|
||||
|
@ -211,7 +120,11 @@ export class RepositoryService {
|
|||
if (files.has(privateKey)) {
|
||||
try {
|
||||
this.log.debug(`try with key ${privateKey}`);
|
||||
return await action(privateKey);
|
||||
await git.addConfig(
|
||||
'core.sshCommand',
|
||||
`ssh -i ${path.join(this.credsPath, privateKey)}`
|
||||
);
|
||||
return await action();
|
||||
} catch (e) {
|
||||
if (e !== SSH_AUTH_ERROR) {
|
||||
throw e;
|
||||
|
@ -225,82 +138,42 @@ export class RepositoryService {
|
|||
}
|
||||
|
||||
private PROGRESS_UPDATE_THROTTLING_FREQ_MS = 1000;
|
||||
private async doClone(
|
||||
repo: Repository,
|
||||
localPath: string,
|
||||
handler?: CloneProgressHandler,
|
||||
keyFile?: string
|
||||
) {
|
||||
private async doFetch(git: SimpleGit, repo: Repository, handler?: CloneProgressHandler) {
|
||||
try {
|
||||
let lastProgressUpdate = moment();
|
||||
const cbs: RemoteCallbacks = {
|
||||
transferProgress: async (stats: any) => {
|
||||
// Clone progress update throttling.
|
||||
const now = moment();
|
||||
if (now.diff(lastProgressUpdate) < this.PROGRESS_UPDATE_THROTTLING_FREQ_MS) {
|
||||
return 0;
|
||||
}
|
||||
lastProgressUpdate = now;
|
||||
|
||||
if (handler) {
|
||||
const progress =
|
||||
(100 * (stats.receivedObjects() + stats.indexedObjects())) /
|
||||
(stats.totalObjects() * 2);
|
||||
const cloneProgress = {
|
||||
isCloned: false,
|
||||
receivedObjects: stats.receivedObjects(),
|
||||
indexedObjects: stats.indexedObjects(),
|
||||
totalObjects: stats.totalObjects(),
|
||||
localObjects: stats.localObjects(),
|
||||
totalDeltas: stats.totalDeltas(),
|
||||
indexedDeltas: stats.indexedDeltas(),
|
||||
receivedBytes: stats.receivedBytes(),
|
||||
};
|
||||
const resumeClone = await handler(progress, cloneProgress);
|
||||
if (!resumeClone) {
|
||||
return GIT_FETCH_PROGRESS_CANCEL;
|
||||
}
|
||||
}
|
||||
const progressCallback = async (progress: Progress) => {
|
||||
const now = moment();
|
||||
if (now.diff(lastProgressUpdate) < this.PROGRESS_UPDATE_THROTTLING_FREQ_MS) {
|
||||
return 0;
|
||||
},
|
||||
credentials: this.credentialFunc(keyFile),
|
||||
};
|
||||
// Ignore cert check on testing environment.
|
||||
if (!this.enableGitCertCheck) {
|
||||
cbs.certificateCheck = () => {
|
||||
// Ignore cert check failures.
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
let gitRepo: Git.Repository | undefined;
|
||||
|
||||
try {
|
||||
gitRepo = await Git.Clone.clone(repo.url, localPath, {
|
||||
bare: 1,
|
||||
fetchOpts: {
|
||||
callbacks: cbs,
|
||||
},
|
||||
});
|
||||
const headCommit = await gitRepo.getHeadCommit();
|
||||
const headRevision = headCommit.sha();
|
||||
const currentBranch = await gitRepo.getCurrentBranch();
|
||||
const currentBranchName = currentBranch.shorthand();
|
||||
this.log.info(
|
||||
`Clone repository from ${repo.url} done with head revision ${headRevision} and default branch ${currentBranchName}`
|
||||
);
|
||||
return {
|
||||
uri: repo.uri,
|
||||
repo: {
|
||||
...repo,
|
||||
defaultBranch: currentBranchName,
|
||||
revision: headRevision,
|
||||
},
|
||||
};
|
||||
} finally {
|
||||
if (gitRepo) {
|
||||
gitRepo.cleanup();
|
||||
}
|
||||
}
|
||||
lastProgressUpdate = now;
|
||||
|
||||
if (handler) {
|
||||
const resumeClone = await handler(progress.percentage);
|
||||
if (!resumeClone) {
|
||||
return GIT_FETCH_PROGRESS_CANCEL;
|
||||
}
|
||||
}
|
||||
};
|
||||
await git.fetch(['origin'], undefined, {
|
||||
progressCallback,
|
||||
});
|
||||
const currentBranchName = (await git.raw(['symbolic-ref', 'HEAD', '--short'])).trim();
|
||||
const headRevision = await git.revparse([`origin/${currentBranchName}`]);
|
||||
// Update master to match origin/master
|
||||
await git.raw(['update-ref', `refs/heads/${currentBranchName}`, headRevision]);
|
||||
|
||||
this.log.info(
|
||||
`Clone repository from ${repo.url} done with head revision ${headRevision} and default branch ${currentBranchName}`
|
||||
);
|
||||
return {
|
||||
uri: repo.uri,
|
||||
repo: {
|
||||
...repo,
|
||||
defaultBranch: currentBranchName,
|
||||
revision: headRevision,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isCancelled(error)) {
|
||||
// Clone job was cancelled intentionally. Do not throw this error.
|
||||
|
@ -323,20 +196,4 @@ export class RepositoryService {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private credentialFunc(keyFile: string | undefined) {
|
||||
return (url: string, userName: string) => {
|
||||
if (keyFile) {
|
||||
this.log.debug(`try with key ${path.join(this.credsPath, keyFile)}`);
|
||||
return Git.Cred.sshKeyNew(
|
||||
userName,
|
||||
path.join(this.credsPath, `${keyFile}.pub`),
|
||||
path.join(this.credsPath, keyFile),
|
||||
''
|
||||
);
|
||||
} else {
|
||||
return Git.Cred.defaultNew();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,12 +8,7 @@ import { Logger } from './log';
|
|||
import { RepositoryService } from './repository_service';
|
||||
|
||||
export class RepositoryServiceFactory {
|
||||
public newInstance(
|
||||
repoPath: string,
|
||||
credsPath: string,
|
||||
log: Logger,
|
||||
enableGitCertCheck: boolean
|
||||
): RepositoryService {
|
||||
return new RepositoryService(repoPath, credsPath, log, enableGitCertCheck);
|
||||
public newInstance(repoPath: string, credsPath: string, log: Logger): RepositoryService {
|
||||
return new RepositoryService(repoPath, credsPath, log);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ export interface SecurityOptions {
|
|||
installNodeDependency: boolean;
|
||||
gitHostWhitelist: string[];
|
||||
gitProtocolWhitelist: string[];
|
||||
enableGitCertCheck: boolean;
|
||||
}
|
||||
|
||||
export interface DiskOptions {
|
||||
|
|
|
@ -9,11 +9,54 @@ import { Server } from 'hapi';
|
|||
import * as os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import { simplegit } from '@elastic/simple-git/dist';
|
||||
import rimraf from 'rimraf';
|
||||
import { AnyObject } from './lib/esqueue';
|
||||
import { ServerOptions } from './server_options';
|
||||
import { ServerFacade } from '..';
|
||||
|
||||
// TODO migrate other duplicate classes, functions
|
||||
export function prepareProjectByCloning(url: string, p: string) {
|
||||
return new Promise(resolve => {
|
||||
if (!fs.existsSync(p)) {
|
||||
rimraf(p, error => {
|
||||
fs.mkdirSync(p, { recursive: true });
|
||||
const git = simplegit(p);
|
||||
git.clone(url, p, ['--bare']).then(resolve);
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function prepareProjectByInit(
|
||||
repoPath: string,
|
||||
commits: { [commitMessage: string]: { [path: string]: string } }
|
||||
) {
|
||||
if (!fs.existsSync(repoPath)) fs.mkdirSync(repoPath, { recursive: true });
|
||||
const git = simplegit(repoPath);
|
||||
await git.init();
|
||||
await git.addConfig('user.email', 'test@test.com');
|
||||
await git.addConfig('user.name', 'tester');
|
||||
const results: string[] = [];
|
||||
for (const [message, commit] of Object.entries(commits)) {
|
||||
const files = [];
|
||||
for (const [file, content] of Object.entries(commit)) {
|
||||
const filePath = path.join(repoPath, file);
|
||||
const dir = path.dirname(filePath);
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
fs.writeFileSync(filePath, content, 'utf8');
|
||||
files.push(file);
|
||||
await git.add(file);
|
||||
}
|
||||
await git.commit(message, files);
|
||||
const c = await git.revparse(['HEAD']);
|
||||
results.push(c);
|
||||
}
|
||||
return { git, commits: results };
|
||||
}
|
||||
|
||||
export const emptyAsyncFunc = async (_: AnyObject): Promise<any> => {
|
||||
Promise.resolve({});
|
||||
|
|
64
x-pack/legacy/plugins/code/server/utils/format_parser.ts
Normal file
64
x-pack/legacy/plugins/code/server/utils/format_parser.ts
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { set } from 'lodash';
|
||||
|
||||
export interface Format {
|
||||
[field: string]: string | Format;
|
||||
}
|
||||
|
||||
export interface Field {
|
||||
name: string;
|
||||
path: string;
|
||||
format: string;
|
||||
}
|
||||
|
||||
const BOUNDARY = 'ª––––––––º';
|
||||
|
||||
const SPLITTER = '≤∞≥';
|
||||
|
||||
export class FormatParser {
|
||||
private fields: Field[];
|
||||
|
||||
constructor(readonly format: Format) {
|
||||
this.fields = [];
|
||||
this.toFields(this.fields, format);
|
||||
}
|
||||
|
||||
private toFields(fields: Field[], format: Format, prefix: string = '') {
|
||||
Object.entries(format).forEach(entry => {
|
||||
const [key, value] = entry;
|
||||
if (typeof value === 'string') {
|
||||
fields.push({
|
||||
name: key,
|
||||
path: `${prefix}${key}`,
|
||||
format: value,
|
||||
});
|
||||
} else {
|
||||
this.toFields(fields, value, `${prefix}${key}.`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public toFormatStr(): string {
|
||||
return this.fields.map(f => f.format).join(SPLITTER) + BOUNDARY;
|
||||
}
|
||||
|
||||
public parseResult(result: string): any[] {
|
||||
return result
|
||||
.split(BOUNDARY)
|
||||
.map(item => item.trim().split(SPLITTER))
|
||||
.filter(items => items.length > 0)
|
||||
.map(items => this.toObject(items));
|
||||
}
|
||||
|
||||
private toObject(items: string[]) {
|
||||
const result = {};
|
||||
this.fields.forEach((f, i) => {
|
||||
set(result, f.path, items[i]);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -1,215 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line max-classes-per-file
|
||||
import { Object, OdbObject, Oid, Repository, Signature, TreeEntry as TE } from '@elastic/nodegit';
|
||||
import * as isogit from 'isomorphic-git';
|
||||
import { CommitDescription, TagDescription, TreeDescription, TreeEntry } from 'isomorphic-git';
|
||||
import * as fs from 'fs';
|
||||
|
||||
interface GitObjectParam {
|
||||
gitdir: string;
|
||||
oid: string;
|
||||
format?: string | undefined;
|
||||
filepath?: string | undefined;
|
||||
}
|
||||
|
||||
interface GitRefParam {
|
||||
gitdir: string;
|
||||
ref: string;
|
||||
depth: number;
|
||||
}
|
||||
|
||||
isogit.plugins.set('fs', fs);
|
||||
|
||||
export interface BlobDescription {
|
||||
isBinary(): boolean;
|
||||
content(): Buffer;
|
||||
rawsize(): number;
|
||||
}
|
||||
|
||||
export interface GitObjectDescription {
|
||||
oid: string;
|
||||
type: 'blob' | 'tree' | 'commit' | 'tag';
|
||||
format: 'content' | 'parsed';
|
||||
object: BlobDescription | CommitDescription | TreeDescription | TagDescription | null;
|
||||
}
|
||||
|
||||
export class GitPrime {
|
||||
public static async readObject({
|
||||
format = 'parsed',
|
||||
gitdir,
|
||||
oid,
|
||||
filepath,
|
||||
}: GitObjectParam): Promise<GitObjectDescription> {
|
||||
const repo = await Repository.openBare(gitdir);
|
||||
const odb = await repo.odb();
|
||||
const o = await odb.read(Oid.fromString(oid));
|
||||
const obj = new WrappedObj(o, repo);
|
||||
|
||||
if (obj.type === 'commit' && filepath) {
|
||||
const commit = await repo.getCommit(o.id());
|
||||
const entry = await commit.getEntry(filepath);
|
||||
return GitPrime.readObject({ oid: entry.oid(), gitdir, format });
|
||||
}
|
||||
|
||||
if (format === 'parsed' || format === 'content') {
|
||||
await obj.parse();
|
||||
}
|
||||
|
||||
return obj as GitObjectDescription;
|
||||
}
|
||||
|
||||
public static async resolveRef({ gitdir, ref, depth }: GitRefParam) {
|
||||
return await isogit.resolveRef({ gitdir, ref, depth });
|
||||
}
|
||||
|
||||
public static async expandOid({ gitdir, oid }: { gitdir: string; oid: string }) {
|
||||
const repo = await Repository.openBare(gitdir);
|
||||
const o = await Object.lookupPrefix(repo, Oid.fromString(oid), oid.length, Object.TYPE.COMMIT);
|
||||
return o.id().tostrS();
|
||||
}
|
||||
|
||||
static async listBranches(param: { gitdir: string; remote: string }) {
|
||||
return await isogit.listBranches(param);
|
||||
}
|
||||
|
||||
static async listTags(param: { gitdir: string }) {
|
||||
return await isogit.listTags(param);
|
||||
}
|
||||
|
||||
static async isTextFile({ gitdir, oid }: { gitdir: string; oid: string }) {
|
||||
const repo = await Repository.openBare(gitdir);
|
||||
const blob = await repo.getBlob(oid);
|
||||
return blob.isBinary() === 0;
|
||||
}
|
||||
}
|
||||
|
||||
class WrappedObj implements GitObjectDescription {
|
||||
_format: 'content' | 'parsed' = 'content';
|
||||
_object: CommitDescription | TreeDescription | TagDescription | BlobDescription | null;
|
||||
constructor(private readonly o: OdbObject, private readonly repo: Repository) {
|
||||
this._object = null;
|
||||
}
|
||||
|
||||
public get object() {
|
||||
return this._object;
|
||||
}
|
||||
|
||||
public get format(): 'content' | 'parsed' {
|
||||
return this._format;
|
||||
}
|
||||
|
||||
public get oid(): string {
|
||||
return this.o.id().tostrS();
|
||||
}
|
||||
public get type(): 'blob' | 'tree' | 'commit' | 'tag' {
|
||||
return type2str(this.o.type());
|
||||
}
|
||||
|
||||
async parse() {
|
||||
function fromSignature(sign: Signature) {
|
||||
return {
|
||||
name: sign.name(),
|
||||
email: sign.email(),
|
||||
timestamp: sign.when().time,
|
||||
timezoneOffset: sign.when().offset,
|
||||
};
|
||||
}
|
||||
switch (this.o.type()) {
|
||||
case 1:
|
||||
const commit = await this.repo.getCommit(this.o.id());
|
||||
this._object = {
|
||||
tree: commit.treeId().tostrS(),
|
||||
author: fromSignature(commit.author()),
|
||||
message: commit.message(),
|
||||
committer: fromSignature(commit.committer()),
|
||||
parent: commit.parents().map(o => o.tostrS()),
|
||||
} as CommitDescription;
|
||||
break;
|
||||
case 2:
|
||||
const tree = await this.repo.getTree(this.o.id());
|
||||
const entries = tree.entries().map(convertEntry);
|
||||
this._object = {
|
||||
entries,
|
||||
} as TreeDescription;
|
||||
break;
|
||||
case 3:
|
||||
const blob = await this.repo.getBlob(this.o.id());
|
||||
this._object = {
|
||||
content() {
|
||||
return blob.content();
|
||||
},
|
||||
isBinary() {
|
||||
return blob.isBinary() === 1;
|
||||
},
|
||||
rawsize() {
|
||||
return blob.rawsize();
|
||||
},
|
||||
} as BlobDescription;
|
||||
break;
|
||||
case 4:
|
||||
const tag = await this.repo.getTag(this.o.id());
|
||||
this._object = {
|
||||
message: tag.message(),
|
||||
object: tag.targetId().tostrS(),
|
||||
tagger: fromSignature(tag.tagger()),
|
||||
tag: tag.name(),
|
||||
type: type2str(tag.targetType()),
|
||||
} as TagDescription;
|
||||
break;
|
||||
default:
|
||||
throw new Error('invalid object type ' + this.o.type());
|
||||
}
|
||||
this._format = 'parsed';
|
||||
}
|
||||
}
|
||||
|
||||
function convertEntry(t: TE): TreeEntry {
|
||||
let mode: string;
|
||||
switch (t.filemode()) {
|
||||
case TE.FILEMODE.EXECUTABLE:
|
||||
mode = '100755';
|
||||
break;
|
||||
case TE.FILEMODE.BLOB:
|
||||
mode = '100644';
|
||||
break;
|
||||
case TE.FILEMODE.COMMIT:
|
||||
mode = '160000';
|
||||
break;
|
||||
case TE.FILEMODE.TREE:
|
||||
mode = '040000';
|
||||
break;
|
||||
case TE.FILEMODE.LINK:
|
||||
mode = '120000';
|
||||
break;
|
||||
default:
|
||||
throw new Error('invalid file mode.');
|
||||
}
|
||||
return {
|
||||
mode,
|
||||
path: t.path(),
|
||||
oid: t.sha(),
|
||||
type: type2str(t.type()),
|
||||
} as TreeEntry;
|
||||
}
|
||||
|
||||
function type2str(t: number) {
|
||||
switch (t) {
|
||||
case 1:
|
||||
return 'commit';
|
||||
case 2:
|
||||
return 'tree';
|
||||
case 3:
|
||||
return 'blob';
|
||||
case 4:
|
||||
return 'tag';
|
||||
default:
|
||||
throw new Error('invalid object type ' + t);
|
||||
}
|
||||
}
|
||||
|
||||
export { TreeEntry, CommitDescription, TreeDescription, TagDescription };
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
|
||||
// @ts-ignore
|
||||
import binary_info from '@elastic/nodegit/dist/utils/binary_info';
|
||||
|
||||
export function binaryInfo(platform: string, arch: string) {
|
||||
const info = binary_info(platform, arch);
|
||||
const downloadUrl = info.hosted_tarball;
|
||||
const packageName = info.package_name;
|
||||
return {
|
||||
downloadUrl,
|
||||
packageName,
|
||||
};
|
||||
}
|
|
@ -195,9 +195,9 @@
|
|||
"@elastic/lsp-extension": "^0.1.2",
|
||||
"@elastic/maki": "6.1.0",
|
||||
"@elastic/node-crypto": "^1.0.0",
|
||||
"@elastic/nodegit": "0.25.0-alpha.23",
|
||||
"@elastic/numeral": "2.3.3",
|
||||
"@elastic/request-crypto": "^1.0.2",
|
||||
"@elastic/simple-git": "1.124.0-elastic.15",
|
||||
"@kbn/babel-preset": "1.0.0",
|
||||
"@kbn/config-schema": "1.0.0",
|
||||
"@kbn/elastic-idx": "1.0.0",
|
||||
|
@ -272,7 +272,6 @@
|
|||
"intl": "^1.2.5",
|
||||
"io-ts": "^2.0.1",
|
||||
"isbinaryfile": "4.0.2",
|
||||
"isomorphic-git": "0.55.5",
|
||||
"joi": "^13.5.2",
|
||||
"jquery": "^3.4.1",
|
||||
"js-yaml": "3.13.1",
|
||||
|
|
214
yarn.lock
214
yarn.lock
|
@ -1268,22 +1268,6 @@
|
|||
resolved "https://registry.yarnpkg.com/@elastic/node-ctags/-/node-ctags-1.0.2.tgz#447d7694a5598f9413fe2b6f356d56f64f612dfd"
|
||||
integrity sha512-EHhJ0NPlCvYy+gbzBMU4/Z/55hftfdwlAG8JwOy7g0ITmH6rFPanEnzg1WL3/L+pp8OlYHyvDLwmyg0+06y8LQ==
|
||||
|
||||
"@elastic/nodegit@0.25.0-alpha.23":
|
||||
version "0.25.0-alpha.23"
|
||||
resolved "https://registry.yarnpkg.com/@elastic/nodegit/-/nodegit-0.25.0-alpha.23.tgz#34d06497030a45a3b8090c3fcb9504d722b9d0c9"
|
||||
integrity sha512-2zSLGigYW58g+DbcRtbCwwj6boTGNhyCG85IDK8yx/jlycLS6gnmxEbZ6FFfgdS2qKWi911lmKQ/3l0LpUaDRw==
|
||||
dependencies:
|
||||
fs-extra "^7.0.0"
|
||||
json5 "^2.1.0"
|
||||
lodash "^4.17.11"
|
||||
nan "^2.11.1"
|
||||
node-gyp "^3.8.0"
|
||||
node-pre-gyp "^0.11.0"
|
||||
promisify-node "~0.3.0"
|
||||
ramda "^0.25.0"
|
||||
request-promise-native "^1.0.5"
|
||||
tar-fs "^1.16.3"
|
||||
|
||||
"@elastic/numeral@2.3.3":
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@elastic/numeral/-/numeral-2.3.3.tgz#94d38a35bd315efa7a6918b22695128fc40a885e"
|
||||
|
@ -1298,6 +1282,15 @@
|
|||
"@types/node-jose" "1.1.0"
|
||||
node-jose "1.1.0"
|
||||
|
||||
"@elastic/simple-git@1.124.0-elastic.15":
|
||||
version "1.124.0-elastic.15"
|
||||
resolved "https://registry.yarnpkg.com/@elastic/simple-git/-/simple-git-1.124.0-elastic.15.tgz#b7975e9d1aeb424c87c817e1bd0549d25ec1d2c5"
|
||||
integrity sha512-Nb+WwJI9I2PN72Ue6stMUDVlqrNKuskpfKJwd+74YtyyfuV7dfHvt4FksqrjnOLbvFxJi214DZ1dHoROg3iQyQ==
|
||||
dependencies:
|
||||
debug "^4.0.1"
|
||||
request "^2.88.0"
|
||||
targz "^1.0.1"
|
||||
|
||||
"@elastic/ui-ace@0.2.3":
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@elastic/ui-ace/-/ui-ace-0.2.3.tgz#5281aed47a79b7216c55542b0675e435692f20cd"
|
||||
|
@ -5686,11 +5679,6 @@ async-limiter@~1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
|
||||
integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==
|
||||
|
||||
async-lock@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/async-lock/-/async-lock-1.2.0.tgz#cd6a53cb1ec3f86af25eafdeb6bc7c6e317258b8"
|
||||
integrity sha512-81HzTQm4+qMj6PwNlnR+y9g7pDdGGzd/YBUrQnHk+BhR28ja2qv497NkQQc1KcKEqh/RShm07di2b0cIWVFrNQ==
|
||||
|
||||
async-retry@^1.2.3:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.2.3.tgz#a6521f338358d322b1a0012b79030c6f411d1ce0"
|
||||
|
@ -6462,11 +6450,6 @@ base64-arraybuffer@0.1.5:
|
|||
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8"
|
||||
integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg=
|
||||
|
||||
base64-js@0.0.2, base64-js@^1.2.1, base64-js@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
|
||||
integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
|
||||
|
||||
base64-js@0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978"
|
||||
|
@ -6477,6 +6460,11 @@ base64-js@^1.0.2, base64-js@^1.1.2:
|
|||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886"
|
||||
integrity sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==
|
||||
|
||||
base64-js@^1.2.1, base64-js@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
|
||||
integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
|
||||
|
||||
base64-js@^1.2.3:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3"
|
||||
|
@ -6767,14 +6755,6 @@ boom@7.x.x, boom@^7.1.0, boom@^7.2.0:
|
|||
dependencies:
|
||||
hoek "6.x.x"
|
||||
|
||||
bops@~0.0.6:
|
||||
version "0.0.7"
|
||||
resolved "https://registry.yarnpkg.com/bops/-/bops-0.0.7.tgz#b4a0a5a839a406454af0fe05a8b91a7a766a54e2"
|
||||
integrity sha1-tKClqDmkBkVK8P4FqLkaenZqVOI=
|
||||
dependencies:
|
||||
base64-js "0.0.2"
|
||||
to-utf8 "0.0.1"
|
||||
|
||||
bottleneck@^2.15.3:
|
||||
version "2.18.0"
|
||||
resolved "https://registry.yarnpkg.com/bottleneck/-/bottleneck-2.18.0.tgz#41fa63ae185b65435d789d1700334bc48222dacf"
|
||||
|
@ -7877,11 +7857,6 @@ clean-css@4.2.x, clean-css@^4.1.11:
|
|||
dependencies:
|
||||
source-map "~0.6.0"
|
||||
|
||||
clean-git-ref@1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/clean-git-ref/-/clean-git-ref-1.0.3.tgz#5325dc839eab01c974ae0e97f5734782750f88ec"
|
||||
integrity sha1-UyXcg56rAcl0rg6X9XNHgnUPiOw=
|
||||
|
||||
clean-stack@^1.0.0, clean-stack@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-1.3.0.tgz#9e821501ae979986c46b1d66d2d432db2fd4ae31"
|
||||
|
@ -8817,14 +8792,6 @@ cpy@^7.3.0:
|
|||
globby "^9.2.0"
|
||||
nested-error-stacks "^2.1.0"
|
||||
|
||||
crc-32@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.0.tgz#cb2db6e29b88508e32d9dd0ec1693e7b41a18208"
|
||||
integrity sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==
|
||||
dependencies:
|
||||
exit-on-epipe "~1.0.1"
|
||||
printj "~1.1.0"
|
||||
|
||||
crc32-stream@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-3.0.1.tgz#cae6eeed003b0e44d739d279de5ae63b171b4e85"
|
||||
|
@ -10186,13 +10153,6 @@ diagnostics@^1.1.1:
|
|||
enabled "1.0.x"
|
||||
kuler "1.0.x"
|
||||
|
||||
diff-lines@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/diff-lines/-/diff-lines-1.1.1.tgz#4f10a709b6ce2af1d6b412ada5a90cf01d782571"
|
||||
integrity sha512-Oo5JzEEriF/+T0usOeRP5yOzr6SWvni2rrxvIgijMZSxPcEvf8JOvCO5GpnWwkte7fcOgnue/f5ECg1H9lMPCw==
|
||||
dependencies:
|
||||
diff "^3.5.0"
|
||||
|
||||
diff-sequences@^24.0.0:
|
||||
version "24.0.0"
|
||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.0.0.tgz#cdf8e27ed20d8b8d3caccb4e0c0d8fe31a173013"
|
||||
|
@ -11753,11 +11713,6 @@ exit-hook@^2.2.0:
|
|||
resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-2.2.0.tgz#f5502f92179018e867f2d8ee4428392da7f3894e"
|
||||
integrity sha512-YFH+2oGdldRH5GqGpnaiKbBxWHMmuXHmKTMtUC58kWSOrnTf95rKITVSFTTtas14DWvWpih429+ffAvFetPwNA==
|
||||
|
||||
exit-on-epipe@~1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz#0bdd92e87d5285d267daa8171d0eb06159689692"
|
||||
integrity sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==
|
||||
|
||||
exit@^0.1.2, exit@~0.1.1:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
|
||||
|
@ -13288,14 +13243,6 @@ gh-got@^5.0.0:
|
|||
got "^6.2.0"
|
||||
is-plain-obj "^1.1.0"
|
||||
|
||||
git-apply-delta@0.0.7:
|
||||
version "0.0.7"
|
||||
resolved "https://registry.yarnpkg.com/git-apply-delta/-/git-apply-delta-0.0.7.tgz#fb76ae144540d79440b52b31de03e63c993c7219"
|
||||
integrity sha1-+3auFEVA15RAtSsx3gPmPJk8chk=
|
||||
dependencies:
|
||||
bops "~0.0.6"
|
||||
varint "0.0.3"
|
||||
|
||||
git-clone@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/git-clone/-/git-clone-0.1.0.tgz#0d76163778093aef7f1c30238f2a9ef3f07a2eb9"
|
||||
|
@ -13544,11 +13491,6 @@ globals@^9.18.0, globals@^9.2.0:
|
|||
resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
|
||||
integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==
|
||||
|
||||
globalyzer@^0.1.0:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/globalyzer/-/globalyzer-0.1.4.tgz#bc8e273afe1ac7c24eea8def5b802340c5cc534f"
|
||||
integrity sha512-LeguVWaxgHN0MNbWC6YljNMzHkrCny9fzjmEUdnF1kQ7wATFD1RHFRqA1qxaX2tgxGENlcxjOflopBwj3YZiXA==
|
||||
|
||||
globby@8.0.2:
|
||||
version "8.0.2"
|
||||
resolved "https://registry.yarnpkg.com/globby/-/globby-8.0.2.tgz#5697619ccd95c5275dbb2d6faa42087c1a941d8d"
|
||||
|
@ -13650,11 +13592,6 @@ globby@^9.1.0, globby@^9.2.0:
|
|||
pify "^4.0.1"
|
||||
slash "^2.0.0"
|
||||
|
||||
globrex@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098"
|
||||
integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==
|
||||
|
||||
globule@^1.0.0:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.1.tgz#5dffb1b191f22d20797a9369b49eab4e9839696d"
|
||||
|
@ -15140,7 +15077,7 @@ ignore@^4.0.3, ignore@^4.0.6:
|
|||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
|
||||
integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
|
||||
|
||||
ignore@^5.0.4, ignore@^5.1.1:
|
||||
ignore@^5.1.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.2.tgz#e28e584d43ad7e92f96995019cc43b9e1ac49558"
|
||||
integrity sha512-vdqWBp7MyzdmHkkRWV5nY+PfGRbYbahfuvsBCh277tq+w9zyNi7h5CYJCK0kmzti9kU+O/cB7sE8HvKv6aXAKQ==
|
||||
|
@ -16349,27 +16286,6 @@ isomorphic-fetch@^2.1.1:
|
|||
node-fetch "^1.0.1"
|
||||
whatwg-fetch ">=0.10.0"
|
||||
|
||||
isomorphic-git@0.55.5:
|
||||
version "0.55.5"
|
||||
resolved "https://registry.yarnpkg.com/isomorphic-git/-/isomorphic-git-0.55.5.tgz#d232dddefa9d271470b9b496c14aa65aa98f2519"
|
||||
integrity sha512-qqFID4DYGjnMkxDowXbp5GUdId6SJCbOEZFRdodMgrk26hjeOiNiOpGlYOX4rBe2mJAsE7Bdj30EQAQJtE72DA==
|
||||
dependencies:
|
||||
async-lock "^1.1.0"
|
||||
clean-git-ref "1.0.3"
|
||||
crc-32 "^1.2.0"
|
||||
diff-lines "^1.1.1"
|
||||
git-apply-delta "0.0.7"
|
||||
globalyzer "^0.1.0"
|
||||
globrex "^0.1.2"
|
||||
ignore "^5.0.4"
|
||||
marky "^1.2.1"
|
||||
minimisted "^2.0.0"
|
||||
pako "^1.0.10"
|
||||
pify "^4.0.1"
|
||||
readable-stream "^3.1.1"
|
||||
sha.js "^2.4.9"
|
||||
simple-get "^3.0.2"
|
||||
|
||||
isstream@0.1.x, isstream@~0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
||||
|
@ -18819,11 +18735,6 @@ marksy@^7.0.0:
|
|||
he "^1.1.1"
|
||||
marked "^0.6.2"
|
||||
|
||||
marky@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/marky/-/marky-1.2.1.tgz#a3fcf82ffd357756b8b8affec9fdbf3a30dc1b02"
|
||||
integrity sha512-md9k+Gxa3qLH6sUKpeC2CNkJK/Ld+bEz5X96nYwloqphQE0CKCVEKco/6jxEZixinqNdz5RFi/KaCyfbMDMAXQ==
|
||||
|
||||
matchdep@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/matchdep/-/matchdep-2.0.0.tgz#c6f34834a0d8dbc3b37c27ee8bbcb27c7775582e"
|
||||
|
@ -19282,13 +19193,6 @@ minimist@~0.0.1:
|
|||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
|
||||
integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=
|
||||
|
||||
minimisted@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/minimisted/-/minimisted-2.0.0.tgz#5e3295e74ed701b1cbeaa863a888181d6efbe8ce"
|
||||
integrity sha512-oP88Dw3LK/pdrKyMdlbmg3W50969UNr4ctISzJfPl+YPYHTAOrS+dihXnsgRNKSRIzDsrnV3eE2CCVlZbpOKdQ==
|
||||
dependencies:
|
||||
minimist "^1.2.0"
|
||||
|
||||
minimost@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/minimost/-/minimost-1.0.0.tgz#1d07954aa0268873408b95552fbffc5977dfc78b"
|
||||
|
@ -19732,11 +19636,6 @@ nan@^2.10.0, nan@^2.9.2:
|
|||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"
|
||||
integrity sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==
|
||||
|
||||
nan@^2.11.1:
|
||||
version "2.12.1"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552"
|
||||
integrity sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==
|
||||
|
||||
nanomatch@^1.2.5:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.7.tgz#53cd4aa109ff68b7f869591fdc9d10daeeea3e79"
|
||||
|
@ -20102,22 +20001,6 @@ node-pre-gyp@^0.10.0:
|
|||
semver "^5.3.0"
|
||||
tar "^4"
|
||||
|
||||
node-pre-gyp@^0.11.0:
|
||||
version "0.11.0"
|
||||
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz#db1f33215272f692cd38f03238e3e9b47c5dd054"
|
||||
integrity sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==
|
||||
dependencies:
|
||||
detect-libc "^1.0.2"
|
||||
mkdirp "^0.5.1"
|
||||
needle "^2.2.1"
|
||||
nopt "^4.0.1"
|
||||
npm-packlist "^1.1.6"
|
||||
npmlog "^4.0.2"
|
||||
rc "^1.2.7"
|
||||
rimraf "^2.6.1"
|
||||
semver "^5.3.0"
|
||||
tar "^4"
|
||||
|
||||
node-releases@^1.1.25:
|
||||
version "1.1.25"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.25.tgz#0c2d7dbc7fed30fbe02a9ee3007b8c90bf0133d3"
|
||||
|
@ -20162,13 +20045,6 @@ node-status-codes@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/node-status-codes/-/node-status-codes-1.0.0.tgz#5ae5541d024645d32a58fcddc9ceecea7ae3ac2f"
|
||||
integrity sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8=
|
||||
|
||||
nodegit-promise@~4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/nodegit-promise/-/nodegit-promise-4.0.0.tgz#5722b184f2df7327161064a791d2e842c9167b34"
|
||||
integrity sha1-VyKxhPLfcycWEGSnkdLoQskWezQ=
|
||||
dependencies:
|
||||
asap "~2.0.3"
|
||||
|
||||
nodemailer@^4.7.0:
|
||||
version "4.7.0"
|
||||
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-4.7.0.tgz#4420e06abfffd77d0618f184ea49047db84f4ad8"
|
||||
|
@ -21115,7 +20991,7 @@ pako@^0.2.5:
|
|||
resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75"
|
||||
integrity sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=
|
||||
|
||||
pako@^1.0.10, pako@^1.0.5, pako@~1.0.2:
|
||||
pako@^1.0.5, pako@~1.0.2:
|
||||
version "1.0.10"
|
||||
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732"
|
||||
integrity sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==
|
||||
|
@ -22022,11 +21898,6 @@ pretty-ms@^4.0.0:
|
|||
dependencies:
|
||||
parse-ms "^2.0.0"
|
||||
|
||||
printj@~1.1.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222"
|
||||
integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==
|
||||
|
||||
prismjs@^1.8.4, prismjs@~1.16.0:
|
||||
version "1.16.0"
|
||||
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.16.0.tgz#406eb2c8aacb0f5f0f1167930cb83835d10a4308"
|
||||
|
@ -22095,13 +21966,6 @@ promise@^7.0.1, promise@^7.1.1:
|
|||
dependencies:
|
||||
asap "~2.0.3"
|
||||
|
||||
promisify-node@~0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/promisify-node/-/promisify-node-0.3.0.tgz#b4b55acf90faa7d2b8b90ca396899086c03060cf"
|
||||
integrity sha1-tLVaz5D6p9K4uQyjlomQhsAwYM8=
|
||||
dependencies:
|
||||
nodegit-promise "~4.0.0"
|
||||
|
||||
prompts@^2.0.1:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.0.3.tgz#c5ccb324010b2e8f74752aadceeb57134c1d2522"
|
||||
|
@ -22575,11 +22439,6 @@ ramda@^0.21.0:
|
|||
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.21.0.tgz#a001abedb3ff61077d4ff1d577d44de77e8d0a35"
|
||||
integrity sha1-oAGr7bP/YQd9T/HVd9RN536NCjU=
|
||||
|
||||
ramda@^0.25.0:
|
||||
version "0.25.0"
|
||||
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.25.0.tgz#8fdf68231cffa90bc2f9460390a0cb74a29b29a9"
|
||||
integrity sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ==
|
||||
|
||||
ramda@^0.26, ramda@^0.26.1:
|
||||
version "0.26.1"
|
||||
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06"
|
||||
|
@ -25313,14 +25172,6 @@ sha.js@^2.4.0, sha.js@^2.4.8:
|
|||
inherits "^2.0.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
sha.js@^2.4.9:
|
||||
version "2.4.11"
|
||||
resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7"
|
||||
integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==
|
||||
dependencies:
|
||||
inherits "^2.0.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
shallow-clone@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-0.1.2.tgz#5909e874ba77106d73ac414cfec1ffca87d97060"
|
||||
|
@ -25428,20 +25279,6 @@ signal-exit@^3.0.0, signal-exit@^3.0.2:
|
|||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
|
||||
integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
|
||||
|
||||
simple-concat@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6"
|
||||
integrity sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=
|
||||
|
||||
simple-get@^3.0.2:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.0.3.tgz#924528ac3f9d7718ce5e9ec1b1a69c0be4d62efa"
|
||||
integrity sha512-Wvre/Jq5vgoz31Z9stYWPLn0PqRqmBDpFSdypAnHu5AvRVCYPRYGnvryNLiXu8GOBNDH82J2FRHUGMjjHUpXFw==
|
||||
dependencies:
|
||||
decompress-response "^3.3.0"
|
||||
once "^1.3.1"
|
||||
simple-concat "^1.0.0"
|
||||
|
||||
simple-git@1.116.0:
|
||||
version "1.116.0"
|
||||
resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-1.116.0.tgz#ea6e533466f1e0152186e306e004d4eefa6e3e00"
|
||||
|
@ -26795,7 +26632,7 @@ tape@^4.5.1:
|
|||
string.prototype.trim "~1.1.2"
|
||||
through "~2.3.8"
|
||||
|
||||
tar-fs@^1.16.3:
|
||||
tar-fs@^1.16.3, tar-fs@^1.8.1:
|
||||
version "1.16.3"
|
||||
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-1.16.3.tgz#966a628841da2c4010406a82167cbd5e0c72d509"
|
||||
integrity sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==
|
||||
|
@ -26874,6 +26711,13 @@ tar@^4:
|
|||
safe-buffer "^5.1.2"
|
||||
yallist "^3.0.2"
|
||||
|
||||
targz@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/targz/-/targz-1.0.1.tgz#8f76a523694cdedfbb5d60a4076ff6eeecc5398f"
|
||||
integrity sha1-j3alI2lM3t+7XWCkB2/27uzFOY8=
|
||||
dependencies:
|
||||
tar-fs "^1.8.1"
|
||||
|
||||
tcomb-validation@^3.3.0:
|
||||
version "3.4.1"
|
||||
resolved "https://registry.yarnpkg.com/tcomb-validation/-/tcomb-validation-3.4.1.tgz#a7696ec176ce56a081d9e019f8b732a5a8894b65"
|
||||
|
@ -27330,11 +27174,6 @@ to-through@^2.0.0:
|
|||
dependencies:
|
||||
through2 "^2.0.3"
|
||||
|
||||
to-utf8@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/to-utf8/-/to-utf8-0.0.1.tgz#d17aea72ff2fba39b9e43601be7b3ff72e089852"
|
||||
integrity sha1-0Xrqcv8vujm55DYBvns/9y4ImFI=
|
||||
|
||||
toggle-selection@^1.0.3:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
|
||||
|
@ -28750,11 +28589,6 @@ value-or-function@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813"
|
||||
integrity sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=
|
||||
|
||||
varint@0.0.3:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/varint/-/varint-0.0.3.tgz#b821de9b04b38b3cd22f72c18d94a9fb72ab3518"
|
||||
integrity sha1-uCHemwSzizzSL3LBjZSp+3KrNRg=
|
||||
|
||||
vary@^1, vary@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue