From 22e1f34480793e5e7784e898afc5fdd64ef6f8d3 Mon Sep 17 00:00:00 2001 From: Edgard Lorraine Messias Date: Mon, 22 Jan 2018 18:00:05 -0200 Subject: [PATCH] Added xml parser for svn info --- src/infoParser.ts | 52 ++++++++++++++++++++++++++++++++++++++++++++ src/svn.ts | 13 ++++++----- src/svnRepository.ts | 51 +++++++++++++++++++++++-------------------- 3 files changed, 87 insertions(+), 29 deletions(-) create mode 100644 src/infoParser.ts diff --git a/src/infoParser.ts b/src/infoParser.ts new file mode 100644 index 00000000..e1e94fd5 --- /dev/null +++ b/src/infoParser.ts @@ -0,0 +1,52 @@ +import * as xml2js from "xml2js"; + +export interface ISvnInfo { + kind: string; + path: string; + revision: string; + url: string; + relativeUrl: string; + repository: { + root: string; + uuid: string; + }; + wcInfo: { + wcrootAbspath: string; + uuid: string; + }; + commit: { + revision: string; + author: string; + date: string; + }; +} + +function camelcase(name: string) { + return name + .replace(/(?:^\w|[A-Z]|\b\w)/g, function(letter, index) { + return index == 0 ? letter.toLowerCase() : letter.toUpperCase(); + }) + .replace(/[\s\-]+/g, ""); +} + +export async function parseInfoXml(content: string): Promise { + return new Promise((resolve, reject) => { + xml2js.parseString( + content, + { + mergeAttrs: true, + explicitRoot: false, + explicitArray: false, + attrNameProcessors: [camelcase], + tagNameProcessors: [camelcase] + }, + (err, result) => { + if (err || typeof result.entry === "undefined") { + reject(); + } + + resolve(result.entry); + } + ); + }); +} diff --git a/src/svn.ts b/src/svn.ts index 56595c9b..d0a95e09 100644 --- a/src/svn.ts +++ b/src/svn.ts @@ -5,6 +5,7 @@ import * as iconv from "iconv-lite"; import * as jschardet from "jschardet"; import * as path from "path"; import { Repository } from "./svnRepository"; +import { parseInfoXml } from "./infoParser"; // List: https://github.com/apache/subversion/blob/1.6.x/subversion/svn/schema/status.rnc#L33 export enum Status { @@ -21,7 +22,7 @@ export enum Status { NORMAL = "normal", OBSTRUCTED = "obstructed", REPLACED = "replaced", - UNVERSIONED = "unversioned", + UNVERSIONED = "unversioned" } export interface CpOptions { @@ -180,12 +181,12 @@ export class Svn { async getRepositoryRoot(path: string) { try { - let result = await this.exec(path, ["info", "--xml"]); - let rootPath = result.stdout.match( - /(.*)<\/wcroot-abspath>/i - )[1]; - return rootPath; + const result = await this.exec(path, ["info", "--xml"]); + + const info = await parseInfoXml(result.stdout); + return info.wcInfo.wcrootAbspath; } catch (error) { + console.error(error); throw new Error("Unable to find repository root path"); } } diff --git a/src/svnRepository.ts b/src/svnRepository.ts index 93dcea95..a1196015 100644 --- a/src/svnRepository.ts +++ b/src/svnRepository.ts @@ -1,8 +1,11 @@ import { workspace } from "vscode"; import { Svn, CpOptions } from "./svn"; import { IFileStatus, parseStatusXml } from "./statusParser"; +import { parseInfoXml, ISvnInfo } from "./infoParser"; export class Repository { + private _info?: ISvnInfo; + constructor( private svn: Svn, public root: string, @@ -15,6 +18,22 @@ export class Repository { return await parseStatusXml(result.stdout); } + async getInfo(): Promise { + if (this._info) { + return this._info; + } + const result = await this.svn.info(this.workspaceRoot); + + this._info = await parseInfoXml(result.stdout); + + //Cache for 30 seconds + setTimeout(() => { + this._info = undefined; + }, 30000); + + return this._info; + } + async show( path: string, revision?: string, @@ -46,11 +65,7 @@ export class Repository { } async getCurrentBranch(): Promise { - const info = await this.svn.info(this.workspaceRoot); - - if (info.exitCode !== 0) { - throw new Error(info.stderr); - } + const info = await this.getInfo(); const config = workspace.getConfiguration("svn"); const trunkLayout = config.get("layout.trunk"); @@ -60,11 +75,9 @@ export class Repository { const trees = [trunkLayout, branchesLayout, tagsLayout].filter( x => x != null ); - const regex = new RegExp( - "(.*?)/(" + trees.join("|") + ")(/([^/]+))?.*?" - ); + const regex = new RegExp("(.*?)/(" + trees.join("|") + ")(/([^/]+))?.*?"); - const match = info.stdout.match(regex); + const match = info.url.match(regex); if (match) { if (match[4] && match[2] !== trunkLayout) { @@ -87,16 +100,12 @@ export class Repository { const trees = [trunkLayout, branchesLayout, tagsLayout].filter( x => x != null ); - const regex = new RegExp("(.*?)/(" + trees.join("|") + ").*?"); + const regex = new RegExp("(.*?)/(" + trees.join("|") + ").*?"); - const info = await this.svn.info(this.workspaceRoot); - - if (info.exitCode !== 0) { - throw new Error(info.stderr); - } + const info = await this.getInfo(); - let repoUrl = info.stdout.match(/(.*?)<\/root>/)[1]; - const match = info.stdout.match(regex); + let repoUrl = info.repository.root; + const match = info.url.match(regex); if (match && match[1]) { repoUrl = match[1]; @@ -188,14 +197,10 @@ export class Repository { const repoUrl = await this.getRepoUrl(); const newBranch = repoUrl + "/" + branchesLayout + "/" + name; - const resultBranch = await this.svn.info(this.workspaceRoot); - const currentBranch = resultBranch.stdout.match(/(.*?)<\/url>/)[1]; + const info = await this.getInfo(); + const currentBranch = info.url; const result = await this.svn.copy(currentBranch, newBranch, name); - if (result.exitCode !== 0) { - throw new Error(result.stderr); - } - const switchBranch = await this.svn.switchBranch( this.workspaceRoot, newBranch