Skip to content

Commit cecd185

Browse files
FractalBoyJohnstonCode
authored andcommitted
feat: Added Branches tree view (#729)
1 parent 4824881 commit cecd185

10 files changed

+352
-14
lines changed

package.json

+32
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@
115115
"id": "itemlog",
116116
"name": "File History",
117117
"when": "config.svn.enabled && svnOpenRepositoryCount != 0"
118+
},
119+
{
120+
"id": "branchchanges",
121+
"name": "Branch Changes",
122+
"when": "config.svn.enabled && svnOpenRepositoryCount != 0"
118123
}
119124
]
120125
},
@@ -191,6 +196,20 @@
191196
"category": "SVN",
192197
"title": "Copy message to clipboard"
193198
},
199+
{
200+
"command": "svn.branchchanges.openDiff",
201+
"category": "SVN",
202+
"title": "Open diff"
203+
},
204+
{
205+
"command": "svn.branchchanges.refresh",
206+
"category": "SVN",
207+
"title": "Refresh branch changes",
208+
"icon": {
209+
"dark": "icons/dark/refresh.svg",
210+
"light": "icons/light/refresh.svg"
211+
}
212+
},
194213
{
195214
"command": "svn.checkout",
196215
"title": "Checkout",
@@ -592,6 +611,14 @@
592611
"command": "svn.itemlog.copymsg",
593612
"when": "false"
594613
},
614+
{
615+
"command": "svn.branchchanges.openDiff",
616+
"when": "false"
617+
},
618+
{
619+
"command": "svn.branchchanges.refresh",
620+
"when": "false"
621+
},
595622
{
596623
"command": "svn.pickCommitMessage",
597624
"when": "config.svn.enabled && svnOpenRepositoryCount != 0"
@@ -617,6 +644,11 @@
617644
"command": "svn.itemlog.refresh",
618645
"when": "view == itemlog",
619646
"group": "navigation"
647+
},
648+
{
649+
"command": "svn.branchchanges.refresh",
650+
"when": "view == branchchanges",
651+
"group": "navigation"
620652
}
621653
],
622654
"view/item/context": [

src/common/types.ts

+21
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,24 @@ export interface ISvnInfo {
5353
};
5454
}
5555

56+
export interface ISvnPath {
57+
props: PropStatus;
58+
kind: SvnKindType;
59+
item: Status;
60+
_: string;
61+
}
62+
63+
export interface ISvnPathChange {
64+
oldPath: Uri;
65+
newPath: Uri;
66+
oldRevision: string;
67+
newRevision: string;
68+
props: PropStatus;
69+
kind: SvnKindType;
70+
item: Status;
71+
repo: Uri;
72+
}
73+
5674
export interface ISvnListItem {
5775
kind: SvnKindType;
5876
name: string;
@@ -91,6 +109,7 @@ export enum RepositoryState {
91109
export enum Operation {
92110
Add = "Add",
93111
AddChangelist = "AddChangelist",
112+
Changes = "Changes",
94113
CleanUp = "CleanUp",
95114
Commit = "Commit",
96115
CurrentBranch = "CurrentBranch",
@@ -272,6 +291,8 @@ export interface ISvnLogEntryPath {
272291
action: string;
273292
/** "file" | "dir" e.g. */
274293
kind: string;
294+
copyfromPath?: string;
295+
copyfromRev?: string;
275296
}
276297

277298
/** produced by svn log */

src/diffParser.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { ISvnPath } from "./common/types";
2+
import * as xml2js from "xml2js";
3+
import { camelcase } from "./util";
4+
5+
export async function parseDiffXml(content: string): Promise<ISvnPath[]> {
6+
return new Promise<ISvnPath[]>((resolve, reject) => {
7+
xml2js.parseString(
8+
content,
9+
{
10+
mergeAttrs: true,
11+
explicitRoot: false,
12+
explicitArray: false,
13+
attrNameProcessors: [camelcase],
14+
tagNameProcessors: [camelcase]
15+
},
16+
(err, result) => {
17+
if (
18+
err ||
19+
!result.paths ||
20+
!result.paths.path
21+
) {
22+
reject();
23+
}
24+
25+
if (!Array.isArray(result.paths.path)) {
26+
result.paths.path = [result.paths.path];
27+
}
28+
29+
resolve(result.paths.path);
30+
}
31+
);
32+
});
33+
}

src/extension.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,8 @@ import { Svn } from "./svn";
2020
import { SvnContentProvider } from "./svnContentProvider";
2121
import { SvnFinder } from "./svnFinder";
2222
import SvnProvider from "./treeView/dataProviders/svnProvider";
23-
import {
24-
toDisposable
25-
} from "./util";
23+
import { toDisposable } from "./util";
24+
import { BranchChangesProvider } from "./historyView/branchChangesProvider";
2625

2726
async function init(
2827
_context: ExtensionContext,
@@ -53,6 +52,10 @@ async function init(
5352
disposables.push(itemLogProvider);
5453
window.registerTreeDataProvider("itemlog", itemLogProvider);
5554

55+
const branchChangesProvider = new BranchChangesProvider(model);
56+
disposables.push(branchChangesProvider);
57+
window.registerTreeDataProvider("branchchanges", branchChangesProvider);
58+
5659
disposables.push(new CheckActiveEditor(model));
5760
disposables.push(new OpenRepositoryCount(model));
5861

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import {
2+
TreeDataProvider,
3+
Disposable,
4+
TreeItem,
5+
commands,
6+
EventEmitter
7+
} from "vscode";
8+
import { Model } from "../model";
9+
import { ISvnPathChange, Status } from "../common/types";
10+
import { openDiff, getIconObject, openFileRemote } from "./common";
11+
import { dispose } from "../util";
12+
13+
export class BranchChangesProvider
14+
implements TreeDataProvider<ISvnPathChange>, Disposable {
15+
private _dispose: Disposable[] = [];
16+
private _onDidChangeTreeData = new EventEmitter<ISvnPathChange | undefined>();
17+
public readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
18+
19+
constructor(private model: Model) {
20+
this._dispose.push(
21+
commands.registerCommand(
22+
"svn.branchchanges.openDiff",
23+
this.openDiffCmd,
24+
this
25+
)
26+
);
27+
28+
this._dispose.push(
29+
commands.registerCommand(
30+
"svn.branchchanges.refresh",
31+
() => this._onDidChangeTreeData.fire(),
32+
this
33+
)
34+
);
35+
36+
this.model.onDidChangeRepository(() => this._onDidChangeTreeData.fire());
37+
}
38+
39+
dispose() {
40+
dispose(this._dispose);
41+
}
42+
43+
getTreeItem(element: ISvnPathChange): TreeItem | Thenable<TreeItem> {
44+
let iconName: string = "";
45+
if (element.item === Status.ADDED) {
46+
iconName = "status-added";
47+
} else if (element.item === Status.DELETED) {
48+
iconName = "status-deleted";
49+
} else if (element.item === Status.MODIFIED) {
50+
iconName = "status-modified";
51+
}
52+
53+
const iconPath = getIconObject(iconName);
54+
55+
return {
56+
label: element.newPath.toString(),
57+
command: {
58+
command: "svn.branchchanges.openDiff",
59+
title: "Open diff",
60+
arguments: [element]
61+
},
62+
iconPath,
63+
tooltip: `${element.oldPath}@r${element.oldRevision}${element.newPath}@r${element.newRevision}`
64+
};
65+
}
66+
67+
getChildren(element?: ISvnPathChange): Promise<ISvnPathChange[]> {
68+
if (element !== undefined) {
69+
return Promise.resolve([]);
70+
}
71+
72+
const changes: Promise<ISvnPathChange[]>[] = [];
73+
74+
for (const repo of this.model.repositories) {
75+
changes.push(repo.getChanges());
76+
}
77+
78+
return Promise.all(changes).then(value =>
79+
value.reduce((prev, curr) => prev.concat(curr), [])
80+
);
81+
}
82+
83+
public async openDiffCmd(element: ISvnPathChange) {
84+
const repo = await this.model.getRemoteRepository(element.repo);
85+
86+
if (element.item === Status.MODIFIED) {
87+
return openDiff(
88+
repo,
89+
element.oldPath,
90+
element.oldRevision,
91+
element.newRevision,
92+
element.newPath
93+
);
94+
}
95+
if (element.item === Status.ADDED) {
96+
return openFileRemote(repo, element.newPath, element.newRevision);
97+
}
98+
}
99+
}

src/historyView/common.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -268,16 +268,17 @@ async function downloadFile(
268268

269269
export async function openDiff(
270270
repo: IRemoteRepository,
271-
arg: Uri,
271+
arg1: Uri,
272272
r1: string,
273-
r2: string
273+
r2: string,
274+
arg2?: Uri
274275
) {
275-
const uri1 = await downloadFile(repo, arg, r1);
276-
const uri2 = await downloadFile(repo, arg, r2);
276+
const uri1 = await downloadFile(repo, arg1, r1);
277+
const uri2 = await downloadFile(repo, arg2 || arg1, r2);
277278
const opts: TextDocumentShowOptions = {
278279
preview: true
279280
};
280-
const title = `${path.basename(arg.path)} (${r1} : ${r2})`;
281+
const title = `${path.basename(arg1.path)} (${r1} : ${r2})`;
281282
return commands.executeCommand<void>("vscode.diff", uri1, uri2, title, opts);
282283
}
283284

src/repository.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ import {
2424
RepositoryState,
2525
Status,
2626
SvnDepth,
27-
SvnUriAction
27+
SvnUriAction,
28+
ISvnPathChange
2829
} from "./common/types";
2930
import { debounce, globalSequentialize, memoize, throttle } from "./decorators";
3031
import { exists } from "./fs";
@@ -867,6 +868,10 @@ export class Repository implements IRemoteRepository {
867868
);
868869
}
869870

871+
public async getChanges(): Promise<ISvnPathChange[]> {
872+
return this.run(Operation.Changes, () => this.repository.getChanges());
873+
}
874+
870875
public async finishCheckout() {
871876
return this.run(Operation.SwitchBranch, () =>
872877
this.repository.finishCheckout()

0 commit comments

Comments
 (0)