Skip to content

Commit 21ea5f9

Browse files
YanpasJohnstonCode
authored andcommitted
chore: remote repository, svn resource, path normalizer api (#424)
1 parent 30f7391 commit 21ea5f9

File tree

8 files changed

+273
-4
lines changed

8 files changed

+273
-4
lines changed

src/common/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export interface ISvnInfo {
4242
root: string;
4343
uuid: string;
4444
};
45-
wcInfo: {
45+
wcInfo?: {
4646
wcrootAbspath: string;
4747
uuid: string;
4848
};

src/model.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
} from "./common/types";
2222
import { debounce, sequentialize } from "./decorators";
2323
import { configuration } from "./helpers/configuration";
24+
import { RemoteRepository } from "./remoteRepository";
2425
import { Repository } from "./repository";
2526
import { Svn, svnErrorCodes } from "./svn";
2627
import SvnError from "./svnError";
@@ -334,6 +335,10 @@ export class Model implements IDisposable {
334335
}
335336
}
336337

338+
public async getRemoteRepository(uri: Uri): Promise<RemoteRepository> {
339+
return RemoteRepository.open(this.svn, uri);
340+
}
341+
337342
public getRepository(hint: any) {
338343
const liveRepository = this.getOpenRepository(hint);
339344
if (liveRepository && liveRepository.repository) {

src/pathNormalizer.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import * as path from "path";
2+
import { Uri } from "vscode";
3+
import { ISvnInfo } from "./common/types";
4+
import { memoize } from "./decorators";
5+
import { SvnRI } from "./svnRI";
6+
7+
enum ResourceKind {
8+
LocalRelative,
9+
LocalFull,
10+
RemoteFull
11+
}
12+
13+
/**
14+
* create from Repository class
15+
*/
16+
export class PathNormalizer {
17+
public readonly repoRoot: Uri;
18+
public readonly branchRoot: Uri;
19+
public readonly checkoutRoot?: Uri;
20+
21+
constructor(public readonly repoInfo: ISvnInfo) {
22+
this.repoRoot = Uri.parse(repoInfo.repository.root);
23+
this.branchRoot = Uri.parse(repoInfo.url);
24+
if (repoInfo.wcInfo) {
25+
this.checkoutRoot = Uri.file(repoInfo.wcInfo.wcrootAbspath);
26+
}
27+
}
28+
29+
/** svn://foo.org/domain/trunk/x -> trunk/x */
30+
private getFullRepoPathFromUrl(fpath: string): string {
31+
if (fpath.startsWith("/")) {
32+
return fpath.substr(1);
33+
} else if (fpath.startsWith("svn://") || fpath.startsWith("file://")) {
34+
const target = Uri.parse(fpath).path;
35+
return path.relative(this.repoRoot.path, target);
36+
} else {
37+
throw new Error("unknown path");
38+
}
39+
}
40+
41+
public parse(
42+
fpath: string,
43+
kind = ResourceKind.RemoteFull,
44+
rev?: string
45+
): SvnRI {
46+
let target: string;
47+
if (kind === ResourceKind.RemoteFull) {
48+
target = this.getFullRepoPathFromUrl(fpath);
49+
} else if (kind === ResourceKind.LocalFull) {
50+
if (!path.isAbsolute(fpath)) {
51+
throw new Error("Path isn't absolute");
52+
}
53+
if (this.checkoutRoot === undefined) {
54+
throw new Error("Local paths are not");
55+
}
56+
target = path.join(
57+
this.fromRootToBranch(),
58+
path.relative(this.checkoutRoot.path, fpath)
59+
);
60+
} else if (kind === ResourceKind.LocalRelative) {
61+
if (path.isAbsolute(fpath)) {
62+
throw new Error("Path is absolute");
63+
}
64+
if (this.checkoutRoot === undefined) {
65+
throw new Error("Local paths are not");
66+
}
67+
target = path.join(this.fromRootToBranch(), fpath);
68+
} else {
69+
throw new Error("unsupported kind");
70+
}
71+
72+
return new SvnRI(
73+
this.repoRoot,
74+
this.branchRoot,
75+
this.checkoutRoot,
76+
target,
77+
rev
78+
);
79+
}
80+
81+
@memoize
82+
public fromRootToBranch(): string {
83+
return path.relative(this.repoRoot.path, this.branchRoot.path);
84+
}
85+
86+
@memoize
87+
public fromBranchToRoot(): string {
88+
return path.relative(this.branchRoot.path, this.repoRoot.path);
89+
}
90+
}

src/remoteRepository.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { Uri } from "vscode";
2+
import { ISvnInfo, ISvnLogEntry } from "./common/types";
3+
import { PathNormalizer } from "./pathNormalizer";
4+
import { Svn } from "./svn";
5+
import { Repository as BaseRepository } from "./svnRepository";
6+
7+
export interface IRemoteRepository {
8+
branchRoot: Uri;
9+
10+
getPathNormalizer(): PathNormalizer;
11+
12+
log(
13+
rfrom: string,
14+
rto: string,
15+
limit: number,
16+
target?: string | Uri
17+
): Promise<ISvnLogEntry[]>;
18+
19+
show(filePath: string | Uri, revision?: string): Promise<string>;
20+
}
21+
22+
export class RemoteRepository implements IRemoteRepository {
23+
private info: ISvnInfo;
24+
private constructor(private repo: BaseRepository) {
25+
this.info = repo.info;
26+
}
27+
28+
public static async open(svn: Svn, uri: Uri): Promise<RemoteRepository> {
29+
const repo = await svn.open(uri.toString(true), "");
30+
return new RemoteRepository(repo);
31+
}
32+
33+
public getPathNormalizer(): PathNormalizer {
34+
return new PathNormalizer(this.info);
35+
}
36+
37+
public get branchRoot(): Uri {
38+
return Uri.parse(this.info.url);
39+
}
40+
41+
public async log(
42+
rfrom: string,
43+
rto: string,
44+
limit: number,
45+
target?: string | Uri
46+
): Promise<ISvnLogEntry[]> {
47+
return this.repo.log(rfrom, rto, limit, target);
48+
}
49+
50+
public async show(
51+
filePath: string | Uri,
52+
revision?: string
53+
): Promise<string> {
54+
return this.repo.show(filePath, revision);
55+
}
56+
}

src/repository.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import {
2929
import { debounce, memoize, throttle } from "./decorators";
3030
import { configuration } from "./helpers/configuration";
3131
import OperationsImpl from "./operationsImpl";
32+
import { PathNormalizer } from "./pathNormalizer";
33+
import { IRemoteRepository } from "./remoteRepository";
3234
import { Resource } from "./resource";
3335
import { SvnStatusBar } from "./statusBar";
3436
import { svnErrorCodes } from "./svn";
@@ -56,7 +58,7 @@ function shouldShowProgress(operation: Operation): boolean {
5658
}
5759
}
5860

59-
export class Repository {
61+
export class Repository implements IRemoteRepository {
6062
public sourceControl: SourceControl;
6163
public statusBar: SvnStatusBar;
6264
public changes: ISvnResourceGroup;
@@ -144,7 +146,7 @@ export class Repository {
144146
}
145147

146148
/** 'svn://repo.x/branches/b1' e.g. */
147-
get remoteRoot(): Uri {
149+
get branchRoot(): Uri {
148150
return Uri.parse(this.repository.info.url);
149151
}
150152

@@ -902,6 +904,10 @@ export class Repository {
902904
);
903905
}
904906

907+
public getPathNormalizer(): PathNormalizer {
908+
return new PathNormalizer(this.repository.info);
909+
}
910+
905911
public async promptAuth(): Promise<IAuth | undefined> {
906912
// Prevent multiple prompts for auth
907913
if (this.lastPromptAuth) {

src/svnRI.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import * as path from "path";
2+
import { Uri } from "vscode";
3+
import { memoize } from "./decorators";
4+
5+
export class SvnRI {
6+
constructor(
7+
private readonly remoteRoot: Uri,
8+
private readonly branchRoot: Uri,
9+
private readonly checkoutRoot: Uri | undefined,
10+
/** path relative from remoteRoot */
11+
private readonly _path: string,
12+
private readonly _revision?: string
13+
) {
14+
if (_path.length === 0 || _path.charAt(0) === "/") {
15+
throw new Error("Invalid _path " + _path);
16+
}
17+
}
18+
19+
@memoize
20+
get remoteFullPath(): Uri {
21+
return Uri.parse(this.remoteRoot.toString() + "/" + this._path);
22+
}
23+
24+
@memoize
25+
get localFullPath(): Uri | undefined {
26+
if (this.checkoutRoot === undefined) {
27+
return undefined;
28+
}
29+
return Uri.file(
30+
path.join(
31+
this.checkoutRoot.path,
32+
path.relative(this.fromRepoToBranch, this._path)
33+
)
34+
);
35+
}
36+
37+
@memoize
38+
get relativeFromBranch(): string {
39+
return path.relative(this.fromRepoToBranch, this._path);
40+
}
41+
42+
@memoize
43+
get fromRepoToBranch(): string {
44+
return path.relative(this.remoteRoot.path, this.branchRoot.path);
45+
}
46+
47+
@memoize
48+
get revision(): string | undefined {
49+
return this._revision;
50+
}
51+
52+
@memoize
53+
public toString(withRevision?: boolean): string {
54+
return this.remoteFullPath + (withRevision ? this._revision || "" : "");
55+
}
56+
}

src/svnRepository.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,7 @@ export class Repository {
522522
"-v"
523523
];
524524
if (target !== undefined) {
525-
args.push(target.toString());
525+
args.push(target instanceof Uri ? target.toString(true) : target);
526526
}
527527
const result = await this.exec(args);
528528

src/test/pathNormalizer.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/* tslint:disable */
2+
3+
//
4+
// Note: This example test is leveraging the Mocha test framework.
5+
// Please refer to their documentation on https://mochajs.org/ for help.
6+
//
7+
8+
// The module 'assert' provides assertion methods from node
9+
import * as assert from "assert";
10+
import * as path from "path";
11+
import { PathNormalizer } from "../pathNormalizer";
12+
import { Uri } from "vscode";
13+
import { ISvnInfo } from "../common/types";
14+
15+
// Defines a Mocha test suite to group tests of similar kind together
16+
suite("Url parsing", () => {
17+
const ri1 = {
18+
repository: {
19+
root: "svn://somedomain.x.org/public/devs"
20+
},
21+
url: "svn://somedomain.x.org/public/devs/branches/features/F1",
22+
wcInfo: {
23+
wcrootAbspath: "/home/user/dev/mypero"
24+
}
25+
};
26+
const nm1 = new PathNormalizer(ri1 as ISvnInfo);
27+
28+
suiteSetup(async () => {
29+
// do nothing
30+
});
31+
32+
suiteTeardown(async () => {
33+
// do nothing
34+
});
35+
36+
test("r1 ops", function() {
37+
assert.equal(nm1.branchRoot.toString(), Uri.parse(ri1.url).toString());
38+
assert.equal(
39+
nm1.repoRoot.toString(),
40+
Uri.parse(ri1.repository.root).toString()
41+
);
42+
if (!nm1.checkoutRoot) {
43+
throw new Error("impossible");
44+
}
45+
assert.equal(
46+
nm1.checkoutRoot.toString(),
47+
Uri.file(ri1.wcInfo.wcrootAbspath).toString()
48+
);
49+
const x1 = nm1.parse("/d1/f1");
50+
assert.equal(x1.remoteFullPath.toString(), ri1.repository.root + "/d1/f1");
51+
if (!x1.localFullPath) {
52+
throw new Error("impossible");
53+
}
54+
assert.equal(x1.localFullPath.toString(), "file:///home/d1/f1");
55+
});
56+
});

0 commit comments

Comments
 (0)