Skip to content
This repository has been archived by the owner on Apr 4, 2023. It is now read-only.

Commit

Permalink
Synchronize git projects in the workspace FS with projects list defin…
Browse files Browse the repository at this point in the history
…ed in the current workspace definition (#38)

Fix eclipse-che/che#12006

Signed-off-by: Sun Seng David TAN <sutan@redhat.com>
  • Loading branch information
sunix authored Jan 30, 2019
1 parent 348956e commit 1a5cafe
Show file tree
Hide file tree
Showing 11 changed files with 446 additions and 600 deletions.
9 changes: 6 additions & 3 deletions plugins/factory-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"src"
],
"devDependencies": {
"@theia/plugin": "^0.4.0-next.ac580dd6",
"@theia/plugin": "0.3.19",
"@theia/plugin-packager": "^0.0.1-1539891078",
"@eclipse-che/api": "^6.16.1",
"@eclipse-che/plugin": "^0.0.1-1546977605",
Expand All @@ -22,12 +22,15 @@
"typescript-formatter": "7.2.2"
},
"scripts": {
"prepare": "yarn run clean && yarn run build",
"prepare": "yarn run clean && yarn run build && yarn tslint-fix && yarn run test",
"clean": "rimraf lib",
"format-code": "tsfmt -r",
"watch": "tsc -watch",
"compile": "tsc",
"build": "yarn run format-code && yarn run compile && theia:plugin pack"
"build": "yarn run format-code && yarn run compile && theia:plugin pack",
"tslint-fix": "tslint --fix --project .",
"test": "jest",
"test-watch": "jest --watchAll"
},
"engines": {
"theiaPlugin": "next"
Expand Down
4 changes: 2 additions & 2 deletions plugins/factory-plugin/src/factory-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ import * as theia from '@theia/plugin';
import { FactoryInitializer } from "./factory-initializer";
import { WorkspaceProjectsManager } from "./workspace-projects-manager";

export async function start() {
export async function start(context: theia.PluginContext) {
let projectsRoot = '/projects';
const projectsRootEnvVar = await theia.env.getEnvVariable('CHE_PROJECTS_ROOT');
if (projectsRootEnvVar) {
projectsRoot = projectsRootEnvVar;
}

await new FactoryInitializer(projectsRoot).run();
await new WorkspaceProjectsManager(projectsRoot).run();
await new WorkspaceProjectsManager(context, projectsRoot).run();
}

export function stop() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
import * as path from 'path';

export default function convertToFileURI(file: string, rootFolder?: string): string {
export function convertToFileURI(file: string, rootFolder?: string): string {
if (file.startsWith('file://')) {
return file;
}
Expand All @@ -30,3 +31,11 @@ export default function convertToFileURI(file: string, rootFolder?: string): str
return `file://${rootFolder}${file}`;

}

export function convertToCheProjectPath(fileURI: string, rootFolder: string): string {
if (!rootFolder) {
// default value
rootFolder = '/projects';
}
return `/${path.relative(rootFolder, fileURI)}`;
}
71 changes: 71 additions & 0 deletions plugins/factory-plugin/src/git.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*********************************************************************
* Copyright (c) 2019 Red Hat, Inc.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
const { spawn } = require('child_process');

export interface GitUpstreamBranch {
remote: string;
branch: string;
remoteURL?: string;
}

export async function getUpstreamBranch(projectPath: string): Promise<GitUpstreamBranch | undefined> {
const remoteBranchRef = await execGit(projectPath, 'rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{upstream}');
if (!remoteBranchRef) {
return;
}
const gitUpstreamBranch = parseGitUpstreamBranch(remoteBranchRef);
if (gitUpstreamBranch) {
gitUpstreamBranch.remoteURL = await getRemoteURL(gitUpstreamBranch.remote, projectPath);
}
return gitUpstreamBranch;
}

export function getRemoteURL(remote: string, projectPath: string): Promise<string | undefined> {
return execGit(projectPath, 'config', '--get', `remote.${remote}.url`);
}

export function parseGitUpstreamBranch(gitBranchvvOutput: string): GitUpstreamBranch | undefined {

const branchOrRemote = '[^\\s^/]+';
const regexp = new RegExp(
`(${branchOrRemote})\\/(${branchOrRemote})`
);

const result: RegExpMatchArray | null = gitBranchvvOutput.match(regexp);

if (!result) {
return undefined;
}

return {
remote: result[1], branch: result[2]
};

}

export function getGitRootFolder(uri: string): string {
return uri.substring(0, uri.lastIndexOf('.git/'));
}

export async function execGit(directory: string, ...args: string[]): Promise<string | undefined> {
return new Promise<string | undefined>((resolve, reject) => {

const gitCommand = spawn('git', args, { cwd: directory });
let result = '';
gitCommand.stdout.on('data', (data: string) => {
result += data.toString();
});
gitCommand.on('close', () => {
result = result.trim();
resolve(result);
});
gitCommand.on('error', () => { resolve(undefined); });
});
}
67 changes: 67 additions & 0 deletions plugins/factory-plugin/src/projects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*********************************************************************
* Copyright (c) 2019 Red Hat, Inc.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
import { che as cheApi } from '@eclipse-che/api';

export function updateOrCreateGitProject(
projects: cheApi.workspace.ProjectConfig[], projectPath: string, projectGitLocation: string, projectGitRemoteBranch: string): cheApi.workspace.ProjectConfig[] {

const filteredProject = projects.filter(project => project.path === projectPath);

if (filteredProject.length === 0) {
const projectName = projectPath.split('/').pop();

// create a new one
projects.push({
"name": projectName ? projectName : "new-project",
"attributes": {},
"source": {
"location": projectGitLocation,
"type": "git",
"parameters": {
"branch": projectGitRemoteBranch
}
},
"path": projectPath,
"description": "",
"mixins": []
});
return projects;
}

filteredProject.forEach(project => {
if (!project.source) {
project.source = {
type: 'git',
location: '',
parameters: {}
};
}
project.source.location = projectGitLocation;
if (!project.source.parameters) {
project.source.parameters = {};
}
project.source.parameters['branch'] = projectGitRemoteBranch;
});

return projects;
}

export function deleteGitProject(
projects: cheApi.workspace.ProjectConfig[],
projectPath: string): cheApi.workspace.ProjectConfig[] {

projects
.filter(project => project.path === projectPath)
.forEach(project => {
projects.splice(projects.indexOf(project));
});

return projects;
}
4 changes: 2 additions & 2 deletions plugins/factory-plugin/src/theia-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
**********************************************************************/
import * as theia from '@theia/plugin';
import { che as cheApi } from '@eclipse-che/api';
import convertToFileURI from './openfile';
import * as fileuri from './file-uri';

const CHE_TASK_TYPE = 'che';

Expand Down Expand Up @@ -66,7 +66,7 @@ export class TheiaCommand {
execute(): PromiseLike<void> {
if (this.id === ActionId.OPEN_FILE) {
if (this.properties && this.properties.file) {
const fileLocation = convertToFileURI(this.properties.file);
const fileLocation = fileuri.convertToFileURI(this.properties.file);
return theia.commands.executeCommand('file-search.openFile', fileLocation)
.then(() => {

Expand Down
91 changes: 83 additions & 8 deletions plugins/factory-plugin/src/workspace-projects-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
**********************************************************************/

import { TheiaCloneCommand } from './theia-commands';
import * as git from './git';
import * as projectsHelper from "./projects";
import * as fileUri from "./file-uri";
import * as theia from '@theia/plugin';
import * as che from '@eclipse-che/plugin';
import { che as cheApi } from '@eclipse-che/api';
Expand All @@ -20,18 +23,19 @@ const fs = require('fs');
*/
export class WorkspaceProjectsManager {

constructor(protected projectsRoot: string) {
watchers: theia.FileSystemWatcher[] = [];

constructor(protected pluginContext: theia.PluginContext, protected projectsRoot: string) {
}

async run() {
const workspace = await che.workspace.getCurrentWorkspace();

const workspace = await che.workspace.getCurrentWorkspace();
const cloneCommandList = await this.selectProjectToCloneCommands(workspace);
if (cloneCommandList.length === 0) {
return;
}

await this.executeCloneCommands(cloneCommandList);

await this.startSyncWorkspaceProjects();

}

async selectProjectToCloneCommands(workspace: cheApi.workspace.Workspace): Promise<TheiaCloneCommand[]> {
Expand All @@ -48,13 +52,84 @@ export class WorkspaceProjectsManager {
}

private async executeCloneCommands(cloneCommandList: TheiaCloneCommand[]) {
theia.window.showInformationMessage("Che Workspace: Starting cloning projects.");
if (cloneCommandList.length === 0) {
return;
}

theia.window.showInformationMessage("Che Workspace: Starting cloning projects.");
await Promise.all(
cloneCommandList.map(cloneCommand => cloneCommand.execute())
);

theia.window.showInformationMessage("Che Workspace: Finished cloning projects.");
}

async startSyncWorkspaceProjects() {
const gitConfigPattern = '**/.git/{HEAD,config}';
const gitConfigWatcher = theia.workspace.createFileSystemWatcher(gitConfigPattern);
gitConfigWatcher.onDidCreate(uri => this.updateOrCreateGitProjectInWorkspace(git.getGitRootFolder(uri.path)));
gitConfigWatcher.onDidChange(uri => this.updateOrCreateGitProjectInWorkspace(git.getGitRootFolder(uri.path)));
gitConfigWatcher.onDidDelete(uri => this.deleteGitProjectInWorkspace(git.getGitRootFolder(uri.path)));
this.watchers.push(gitConfigWatcher);

this.pluginContext.subscriptions.push(theia.Disposable.create(() => {
this.watchers.forEach(watcher => watcher.dispose());
}));
}

async updateOrCreateGitProjectInWorkspace(projectFolderURI: string | undefined) {
if (!projectFolderURI) {
return;
}
const currentWorkspace = await che.workspace.getCurrentWorkspace();
if (!currentWorkspace || !currentWorkspace.id) {
console.error('Unexpected error: current workspace id is not defined');
return;
}

const projectUpstreamBranch: git.GitUpstreamBranch | undefined = await git.getUpstreamBranch(projectFolderURI);
if (!projectUpstreamBranch || !projectUpstreamBranch.remoteURL) {
console.error(`Could not detect git project branch for ${projectFolderURI}`);
return;
}

if (!currentWorkspace.config) {
currentWorkspace.config = {};
}

if (!currentWorkspace.config.projects) {
currentWorkspace.config.projects = [];
}

projectsHelper.updateOrCreateGitProject(currentWorkspace.config.projects,
fileUri.convertToCheProjectPath(projectFolderURI, this.projectsRoot),
projectUpstreamBranch.remoteURL,
projectUpstreamBranch.branch);

await che.workspace.update(currentWorkspace.id, currentWorkspace);
}

async deleteGitProjectInWorkspace(projectFolderURI: string | undefined) {
if (!projectFolderURI) {
return;
}
const currentWorkspace = await che.workspace.getCurrentWorkspace();
if (!currentWorkspace.id) {
console.error('Unexpected error: current workspace id is not defined');
return;
}

if (!currentWorkspace.config) {
currentWorkspace.config = {};
}

if (!currentWorkspace.config.projects) {
currentWorkspace.config.projects = [];
}

projectsHelper.deleteGitProject(currentWorkspace.config.projects,
fileUri.convertToCheProjectPath(projectFolderURI, this.projectsRoot)
);

await che.workspace.update(currentWorkspace.id, currentWorkspace);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018 Red Hat, Inc.
* Copyright (c) 2019 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
Expand All @@ -8,13 +8,11 @@
* Red Hat, Inc. - initial API and implementation
*/
import { TheiaCommand } from "../src/theia-commands";
import convertToFileURI from "../src/openfile"
import { convertToFileURI, convertToCheProjectPath } from "../src/file-uri"

describe("Test exec commands", () => {

test("test conversion of openfile file property to file URI", async () => {
//const theiaCommand = new TheiaCommand("openFile", {file: 'che/README.md'});
// openFileCommand.execute().then()
test("Convert a openfile file property to file URI", async () => {
expect(convertToFileURI('/che/README.md')).toBe('file:///projects/che/README.md');
expect(convertToFileURI('che/README.md')).toBe('file:///projects/che/README.md');
expect(convertToFileURI('file:///test-project/che/README.md')).toBe('file:///test-project/che/README.md');
Expand All @@ -23,5 +21,17 @@ describe("Test exec commands", () => {
expect(convertToFileURI('/che/README.md', '/test-projects')).toBe('file:///test-projects/che/README.md');
expect(convertToFileURI('/che/README.md', 'file:///test-projects')).toBe('file:///test-projects/che/README.md');
expect(convertToFileURI('/che/README.md', 'test-projects')).toBe('file:///test-projects/che/README.md');
expect(convertToFileURI('/che/', 'test-projects')).toBe('file:///test-projects/che/');

});
});

describe("Testing convertion of project paths to be stored in the workspace config", () => {
test("Converting fs project path to che project path", async () => {
expect(convertToCheProjectPath('/projects/che-factory-extension/', '/projects')).toBe('/che-factory-extension');
expect(convertToCheProjectPath('/projects/che-factory-extension', '/projects')).toBe('/che-factory-extension');
expect(convertToCheProjectPath('/projects/che/che-factory-extension/', '/projects')).toBe('/che/che-factory-extension');
expect(convertToCheProjectPath('/projects/theiadev_projects/blog.sunix.org/', '/projects/theiadev_projects')).toBe('/blog.sunix.org');
expect(convertToCheProjectPath('/projects/che/che-factory-extension/', undefined)).toBe('/che/che-factory-extension');
});
});
Loading

0 comments on commit 1a5cafe

Please sign in to comment.