Skip to content

Commit be1ddfb

Browse files
authored
Merge pull request #43 from edgardmessias/support_svn_location
Added support to configure the path to the svn executable
2 parents cdca7cc + 5beaa57 commit be1ddfb

File tree

3 files changed

+137
-9
lines changed

3 files changed

+137
-9
lines changed

package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,15 @@
123123
"type": "boolean",
124124
"description": "Whether svn is enabled",
125125
"default": true
126+
},
127+
"svn.path": {
128+
"type": [
129+
"string",
130+
"null"
131+
],
132+
"description": "Path to the svn executable",
133+
"default": null,
134+
"isExecutable": true
126135
}
127136
}
128137
}

src/extension.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
1-
import { ExtensionContext, Disposable, window } from "vscode";
2-
import { Svn } from "./svn";
1+
import { ExtensionContext, Disposable, workspace, window } from "vscode";
2+
import { Svn, findSvn } from "./svn";
33
import { SvnContentProvider } from "./svnContentProvider";
44
import { SvnCommands } from "./commands";
55
import { Model } from "./model";
66
import { toDisposable } from "./util";
77

8-
function activate(context: ExtensionContext) {
9-
const disposables: Disposable[] = [];
10-
8+
async function init(context: ExtensionContext, disposables: Disposable[]): Promise<void> {
119
const outputChannel = window.createOutputChannel("Svn");
1210
disposables.push(outputChannel);
1311

14-
const svn = new Svn();
12+
const config = workspace.getConfiguration('svn');
13+
const enabled = config.get<boolean>('enabled') === true;
14+
const pathHint = workspace.getConfiguration('svn').get<string>('path');
15+
const info = await findSvn(pathHint);
16+
17+
const svn = new Svn({svnPath: info.path, version: info.version});
1518
const model = new Model(svn);
1619
const contentProvider = new SvnContentProvider(model);
1720
const commands = new SvnCommands(model);
1821
disposables.push(model);
1922

20-
outputChannel.appendLine("svn-scm is now active!");
23+
outputChannel.appendLine("Using svn " + info.version + " from " + info.path);
2124

2225
context.subscriptions.push(
2326
new Disposable(() => Disposable.from(...disposables).dispose())
@@ -29,6 +32,16 @@ function activate(context: ExtensionContext) {
2932
toDisposable(() => svn.onOutput.removeListener("log", onOutput))
3033
);
3134
}
35+
36+
function activate(context: ExtensionContext): any {
37+
const disposables: Disposable[] = [];
38+
context.subscriptions.push(new Disposable(() => Disposable.from(...disposables).dispose()));
39+
40+
init(context, disposables)
41+
.catch(err => console.error(err));
42+
}
43+
44+
3245
exports.activate = activate;
3346

3447
// this method is called when your extension is deactivated

src/svn.ts

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,127 @@
1+
import { EventEmitter } from "events";
12
import { window } from "vscode";
23
import * as cp from "child_process";
34
import * as iconv from "iconv-lite";
45
import * as jschardet from "jschardet";
5-
import { EventEmitter } from "events";
6+
import * as path from 'path';
67

78
interface CpOptions {
89
cwd?: string;
910
encoding?: string;
1011
log?: boolean;
1112
}
1213

14+
export interface ISvn {
15+
path: string;
16+
version: string;
17+
}
18+
19+
function parseVersion(raw: string): string {
20+
const match = raw.match(/(\d+\.\d+\.\d+ \(r\d+\))/);
21+
22+
if(match && match[0]) {
23+
return match[0];
24+
}
25+
return raw.split(/[\r\n]+/)[0];
26+
}
27+
28+
function findSpecificSvn(path: string): Promise<ISvn> {
29+
return new Promise<ISvn>((c, e) => {
30+
const buffers: Buffer[] = [];
31+
const child = cp.spawn(path, ['--version']);
32+
child.stdout.on('data', (b: Buffer) => buffers.push(b));
33+
// child.on('error', cpErrorHandler(e));
34+
child.on('exit', code => code ? e(new Error('Not found')) : c({ path, version: parseVersion(Buffer.concat(buffers).toString('utf8').trim()) }));
35+
});
36+
}
37+
38+
function findSvnDarwin(): Promise<ISvn> {
39+
return new Promise<ISvn>((c, e) => {
40+
cp.exec('which svn', (err, svnPathBuffer) => {
41+
if (err) {
42+
return e('svn not found');
43+
}
44+
45+
const path = svnPathBuffer.toString().replace(/^\s+|\s+$/g, '');
46+
47+
function getVersion(path: string) {
48+
// make sure svn executes
49+
cp.exec('svn --version', (err, stdout) => {
50+
if (err) {
51+
return e('svn not found');
52+
}
53+
54+
return c({ path, version: parseVersion(stdout.trim()) });
55+
});
56+
}
57+
58+
if (path !== '/usr/bin/svn') {
59+
return getVersion(path);
60+
}
61+
62+
// must check if XCode is installed
63+
cp.exec('xcode-select -p', (err: any) => {
64+
if (err && err.code === 2) {
65+
// svn is not installed, and launching /usr/bin/svn
66+
// will prompt the user to install it
67+
68+
return e('svn not found');
69+
}
70+
71+
getVersion(path);
72+
});
73+
});
74+
});
75+
}
76+
77+
function findSystemSvnWin32(base: string): Promise<ISvn> {
78+
if (!base) {
79+
return Promise.reject<ISvn>('Not found');
80+
}
81+
82+
return findSpecificSvn(path.join(base, 'TortoiseSVN', 'bin', 'svn.exe'));
83+
}
84+
85+
function findSvnWin32(): Promise<ISvn> {
86+
return findSystemSvnWin32(process.env['ProgramW6432'])
87+
.then(void 0, () => findSystemSvnWin32(process.env['ProgramFiles(x86)']))
88+
.then(void 0, () => findSystemSvnWin32(process.env['ProgramFiles']))
89+
.then(void 0, () => findSpecificSvn('svn'));
90+
}
91+
92+
export function findSvn(hint: string | undefined): Promise<ISvn> {
93+
var first = hint ? findSpecificSvn(hint) : Promise.reject<ISvn>(null);
94+
95+
return first
96+
.then(void 0, () => {
97+
switch (process.platform) {
98+
case 'darwin': return findSvnDarwin();
99+
case 'win32': return findSvnWin32();
100+
default: return findSpecificSvn('svn');
101+
}
102+
})
103+
.then(null, () => Promise.reject(new Error('Svn installation not found.')));
104+
}
105+
106+
export interface ISvnOptions {
107+
svnPath: string;
108+
version: string;
109+
}
110+
13111
export class Svn {
112+
private svnPath: string;
113+
private version: string;
114+
14115
private _onOutput = new EventEmitter();
15116
get onOutput(): EventEmitter {
16117
return this._onOutput;
17118
}
18119

120+
constructor(options: ISvnOptions) {
121+
this.svnPath = options.svnPath;
122+
this.version = options.version;
123+
}
124+
19125
private log(output: string): void {
20126
this._onOutput.emit("log", output);
21127
}
@@ -29,7 +135,7 @@ export class Svn {
29135
this.log(`svn ${args.join(" ")}\n`);
30136
}
31137

32-
let process = cp.spawn("svn", args, options);
138+
let process = cp.spawn(this.svnPath, args, options);
33139

34140
let [exitCode, stdout, stderr] = await Promise.all<any>([
35141
new Promise<number>((resolve, reject) => {

0 commit comments

Comments
 (0)