Skip to content

Commit

Permalink
[Code] try prune a worktree if it already exists (elastic#36338) (ela…
Browse files Browse the repository at this point in the history
  • Loading branch information
Yulong authored May 10, 2019
1 parent 15dc75c commit 92e70ca
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 68 deletions.
2 changes: 1 addition & 1 deletion x-pack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@
"@elastic/javascript-typescript-langserver": "^0.1.23",
"@elastic/lsp-extension": "^0.1.1",
"@elastic/node-crypto": "^1.0.0",
"@elastic/nodegit": "0.25.0-alpha.19",
"@elastic/nodegit": "0.25.0-alpha.20",
"@elastic/numeral": "2.3.3",
"@kbn/babel-preset": "1.0.0",
"@kbn/elastic-idx": "1.0.0",
Expand Down
151 changes: 108 additions & 43 deletions x-pack/plugins/code/server/__tests__/lsp_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Git from '@elastic/nodegit';
import fs from 'fs';
import mkdirp from 'mkdirp';
import path from 'path';
// import rimraf from 'rimraf';
import rimraf from 'rimraf';
import sinon from 'sinon';

import assert from 'assert';
Expand Down Expand Up @@ -39,15 +39,16 @@ const packagejson = `
}
`;
describe('lsp_service tests', () => {
let firstCommitSha = '';
let secondCommitSha = '';
async function prepareProject(repoPath: string) {
mkdirp.sync(repoPath);
const repo = await Git.Repository.init(repoPath, 0);
const helloContent = "console.log('hello world');";
fs.writeFileSync(path.join(repo.workdir(), filename), helloContent, 'utf8');
fs.writeFileSync(path.join(repo.workdir(), 'package.json'), packagejson, 'utf8');
const index = await repo.refreshIndex();

let index = await repo.refreshIndex();
await index.addByPath(filename);
await index.addByPath('package.json');
index.write();
const treeId = await index.writeTree();
const committer = Git.Signature.create('tester', '[email protected]', Date.now() / 1000, 60);
Expand All @@ -59,8 +60,22 @@ describe('lsp_service tests', () => {
treeId,
[]
);
// eslint-disable-next-line no-console
console.log(`created commit ${commit.tostrS()}`);
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;
}

Expand Down Expand Up @@ -109,36 +124,44 @@ describe('lsp_service tests', () => {
return path.resolve(pa) === path.resolve(pb);
}

it('process a hover request', async () => {
function mockLspService() {
const esClient = mockEsClient();
const revision = 'master';

const lspservice = new LspService(
return new LspService(
'127.0.0.1',
serverOptions,
esClient,
installManager,
new ConsoleLoggerFactory(),
new RepositoryConfigController(esClient)
);
}

async function sendHoverRequest(lspservice: LspService, revision: string) {
const method = 'textDocument/hover';

const params = {
textDocument: {
uri: `git://${repoUri}/blob/${revision}/${filename}`,
},
position: {
line: 0,
character: 1,
},
};
return await lspservice.sendRequest(method, params);
}

it('process a hover request', async () => {
const lspservice = mockLspService();
try {
const params = {
textDocument: {
uri: `git://${repoUri}/blob/${revision}/${filename}`,
},
position: {
line: 0,
character: 1,
},
};
const workspaceHandler = lspservice.workspaceHandler;
const wsSpy = sinon.spy(workspaceHandler, 'handleRequest');
const controller = lspservice.controller;
const ctrlSpy = sinon.spy(controller, 'handleRequest');

const method = 'textDocument/hover';
const revision = 'master';

const response = await lspservice.sendRequest(method, params);
const response = await sendHoverRequest(lspservice, revision);
assert.ok(response);
assert.ok(response.result.contents);

Expand Down Expand Up @@ -172,30 +195,11 @@ describe('lsp_service tests', () => {
}).timeout(10000);

it('unload a workspace', async () => {
const esClient = mockEsClient();
const revision = 'master';
const lspservice = new LspService(
'127.0.0.1',
serverOptions,
esClient,
installManager,
new ConsoleLoggerFactory(),
new RepositoryConfigController(esClient)
);
const lspservice = mockLspService();
try {
const params = {
textDocument: {
uri: `git://${repoUri}/blob/${revision}/${filename}`,
},
position: {
line: 0,
character: 1,
},
};

const method = 'textDocument/hover';
const revision = 'master';
// send a dummy request to open a workspace;
const response = await lspservice.sendRequest(method, params);
const response = await sendHoverRequest(lspservice, revision);
assert.ok(response);
const workspacePath = path.resolve(serverOptions.workspacePath, repoUri, revision);
const workspaceFolderExists = fs.existsSync(workspacePath);
Expand Down Expand Up @@ -228,4 +232,65 @@ describe('lsp_service tests', () => {
}
// @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(10000);

it('should update if a worktree is not the newest', 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 workspaceRepo = await Git.Repository.open(workspacePath);
// workspace is newest now
assert.strictEqual((await workspaceRepo.getHeadCommit()).sha(), secondCommitSha);
const firstCommit = await workspaceRepo.getCommit(firstCommitSha);
// 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);

// send a request again;
await sendHoverRequest(lspservice, revision);
// workspace_handler should update workspace to the newest one
assert.strictEqual((await workspaceRepo.getHeadCommit()).sha(), secondCommitSha);
return;
} finally {
await lspservice.shutdown();
}
// @ts-ignore
}).timeout(10000);
});
73 changes: 53 additions & 20 deletions x-pack/plugins/code/server/lsp/workspace_handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,10 @@ export class WorkspaceHandler {
revision = defaultBranch;
}
let workspaceRepo: Repository;
if (await this.workspaceExists(repositoryUri, revision)) {
if (await this.workspaceExists(bareRepo, repositoryUri, revision)) {
workspaceRepo = await this.updateWorkspace(repositoryUri, revision, targetCommit);
} else {
workspaceRepo = await this.cloneWorkspace(bareRepo, repositoryUri, revision);
workspaceRepo = await this.cloneWorkspace(bareRepo, repositoryUri, revision, targetCommit);
}

const workspaceHeadCommit = await workspaceRepo.getHeadCommit();
Expand All @@ -122,13 +122,9 @@ export class WorkspaceHandler {
.filter(isDir);
}

public async clearWorkspace(repoUri: string, revision?: string) {
public async clearWorkspace(repoUri: string) {
const workspaceDir = await this.workspaceDir(repoUri);
if (revision) {
await del([await this.revisionDir(repoUri, revision)], { force: true });
} else {
await del([workspaceDir], { force: true });
}
await del([workspaceDir], { force: true });
}

public async handleRequest(request: LspRequest): Promise<void> {
Expand Down Expand Up @@ -341,9 +337,14 @@ export class WorkspaceHandler {
}
}

private async workspaceExists(repositoryUri: string, revision: string) {
const workspaceDir = await this.revisionDir(repositoryUri, revision);
return fs.existsSync(workspaceDir);
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;
}

private async revisionDir(repositoryUri: string, revision: string) {
Expand Down Expand Up @@ -388,12 +389,12 @@ export class WorkspaceHandler {
private async cloneWorkspace(
bareRepo: Repository,
repositoryUri: string,
revision: string
revision: string,
targetCommit: Commit
): Promise<Repository> {
const workspaceDir = await this.revisionDir(repositoryUri, revision);
this.log.info(`Create workspace ${workspaceDir} from url ${bareRepo.path()}`);
const parentDir = path.dirname(workspaceDir);
const mainBranchName = path.basename(workspaceDir);
// on windows, git clone will failed if parent folder is not exists;
await new Promise((resolve, reject) =>
mkdirp(parentDir, err => {
Expand All @@ -404,15 +405,47 @@ export class WorkspaceHandler {
}
})
);
const workTreeName = this.workspaceWorktreeBranchName(revision);
await this.pruneWorktree(bareRepo, workTreeName);
// Create the worktree and open it as Repository.
const wt = await Worktree.add(
bareRepo,
this.workspaceWorktreeBranchName(mainBranchName),
workspaceDir,
{}
);
const wt = await Worktree.add(bareRepo, workTreeName, workspaceDir, { lock: 0, version: 1 });
// @ts-ignore
return await Repository.openFromWorktree(wt);
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.`);
}
}
return workspaceRepo;
}

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(workspaceRepo: Repository, headCommit: Commit) {
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1478,10 +1478,10 @@
resolved "https://registry.yarnpkg.com/@elastic/node-crypto/-/node-crypto-1.0.0.tgz#4d325df333fe1319556bb4d54214098ada1171d4"
integrity sha512-bbjbEyILPRTRt0xnda18OttLtlkJBPuXx3CjISUSn9jhWqHoFMzfOaZ73D5jxZE2SaFZUrJYfPpqXP6qqPufAQ==

"@elastic/[email protected].19":
version "0.25.0-alpha.19"
resolved "https://registry.yarnpkg.com/@elastic/nodegit/-/nodegit-0.25.0-alpha.19.tgz#a05a712dedbdbd7fe649cb970eb5677c5ec4ada8"
integrity sha512-fOG7tXkf8wmZEVMld+tkZtS3BRsUlrh95sRNUglHDd0G3g+ow9YDjV3dFHlLST0pTWirKyJuxrow2KgpLoWplA==
"@elastic/[email protected].20":
version "0.25.0-alpha.20"
resolved "https://registry.yarnpkg.com/@elastic/nodegit/-/nodegit-0.25.0-alpha.20.tgz#74f36cb8c137386aeebacded574d1d6a4e6d9a01"
integrity sha512-wvBTfKVAhFkTMh/N6oO/5NOa+m+cRiZLrIPHpC3ITPBQiDAUP+g2/NzKVGh/5vx5D/Y9ucy7VsbP13zU9rSi0g==
dependencies:
fs-extra "^7.0.0"
json5 "^2.1.0"
Expand Down

0 comments on commit 92e70ca

Please sign in to comment.