Skip to content

Commit 64cc5d0

Browse files
authored
Merge pull request #352 from edgardmessias/added_checkout
feat: Added support to checkout repository (Close #332)
2 parents 81ee460 + 01e1e58 commit 64cc5d0

File tree

5 files changed

+183
-14
lines changed

5 files changed

+183
-14
lines changed

package.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@
8282
]
8383
},
8484
"commands": [
85+
{
86+
"command": "svn.checkout",
87+
"title": "Checkout",
88+
"category": "SVN"
89+
},
8590
{
8691
"command": "svn.add",
8792
"title": "Add",
@@ -268,6 +273,10 @@
268273
],
269274
"menus": {
270275
"commandPalette": [
276+
{
277+
"command": "svn.checkout",
278+
"when": "config.svn.enabled"
279+
},
271280
{
272281
"command": "svn.add",
273282
"when": "config.svn.enabled && svnOpenRepositoryCount != 0"
@@ -588,6 +597,11 @@
588597
"default": null,
589598
"isExecutable": true
590599
},
600+
"svn.defaultCheckoutDirectory": {
601+
"type": "string",
602+
"default": null,
603+
"description": "The default location to checkout a svn repository."
604+
},
591605
"svn.ignoreMissingSvnWarning": {
592606
"type": "boolean",
593607
"description": "Ignores the warning when SVN is missing",

src/commands.ts

Lines changed: 154 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import * as fs from "fs";
2+
import * as os from "os";
23
import * as path from "path";
34
import {
45
commands,
56
Disposable,
67
LineChange,
78
Position,
9+
ProgressLocation,
810
Range,
911
SourceControlResourceState,
1012
TextDocumentShowOptions,
@@ -21,20 +23,23 @@ import {
2123
inputSwitchChangelist
2224
} from "./changelistItems";
2325
import {
26+
IAuth,
2427
ICommand,
2528
ICommandOptions,
29+
ICpOptions,
2630
Status,
2731
SvnUriAction
2832
} from "./common/types";
2933
import { getConflictPickOptions } from "./conflictItems";
30-
import { selectBranch } from "./helpers/branch";
34+
import { getBranchName, selectBranch } from "./helpers/branch";
3135
import { configuration } from "./helpers/configuration";
3236
import { inputIgnoreList } from "./ignoreitems";
3337
import { applyLineChanges } from "./lineChanges";
3438
import { inputCommitMessage } from "./messages";
3539
import { Model } from "./model";
3640
import { Repository } from "./repository";
3741
import { Resource } from "./resource";
42+
import { Svn, svnErrorCodes } from "./svn";
3843
import IncommingChangeNode from "./treeView/nodes/incomingChangeNode";
3944
import IncomingChangeNode from "./treeView/nodes/incomingChangeNode";
4045
import { fromSvnUri, toSvnUri } from "./uri";
@@ -62,7 +67,7 @@ function command(
6267
export class SvnCommands implements IDisposable {
6368
private disposables: Disposable[];
6469

65-
constructor(private model: Model) {
70+
constructor(private model: Model, private svn: Svn) {
6671
this.disposables = svnCommands.map(({ commandId, method, options }) => {
6772
const command = this.createCommand(method, options);
6873
if (options.diff && hasSupportToRegisterDiffCommand()) {
@@ -121,32 +126,170 @@ export class SvnCommands implements IDisposable {
121126
await commands.executeCommand("vscode.open", resourceUri);
122127
}
123128

124-
@command("svn.promptAuth", { repository: true })
125-
public async promptAuth(repository: Repository): Promise<boolean> {
129+
@command("svn.promptAuth")
130+
public async promptAuth(
131+
prevUsername?: string,
132+
prevPassword?: string
133+
): Promise<IAuth | undefined> {
126134
const username = await window.showInputBox({
127135
placeHolder: "Svn repository username",
128136
prompt: "Please enter your username",
129-
value: repository.username
137+
value: prevUsername
130138
});
131139

132140
if (username === undefined) {
133-
return false;
141+
return;
134142
}
135143

136144
const password = await window.showInputBox({
137145
placeHolder: "Svn repository password",
138146
prompt: "Please enter your password",
147+
value: prevPassword,
139148
password: true
140149
});
141150

142-
if (username === undefined) {
143-
return false;
151+
if (password === undefined) {
152+
return;
153+
}
154+
155+
const auth: IAuth = {
156+
username,
157+
password
158+
};
159+
160+
return auth;
161+
}
162+
163+
@command("svn.checkout")
164+
public async checkout(url?: string): Promise<void> {
165+
if (!url) {
166+
url = await window.showInputBox({
167+
prompt: "Repository URL",
168+
ignoreFocusOut: true
169+
});
170+
}
171+
172+
if (!url) {
173+
return;
174+
}
175+
176+
let defaultCheckoutDirectory =
177+
configuration.get<string>("defaultCheckoutDirectory") || os.homedir();
178+
defaultCheckoutDirectory = defaultCheckoutDirectory.replace(
179+
/^~/,
180+
os.homedir()
181+
);
182+
183+
const uris = await window.showOpenDialog({
184+
canSelectFiles: false,
185+
canSelectFolders: true,
186+
canSelectMany: false,
187+
defaultUri: Uri.file(defaultCheckoutDirectory),
188+
openLabel: "Select Repository Location"
189+
});
190+
191+
if (!uris || uris.length === 0) {
192+
return;
193+
}
194+
195+
const uri = uris[0];
196+
const parentPath = uri.fsPath;
197+
198+
let folderName: string | undefined;
199+
200+
// Get folder name from branch
201+
const branch = getBranchName(url);
202+
if (branch) {
203+
const baseUrl = url.replace(/\//g, "/").replace(branch.path, "");
204+
folderName = path.basename(baseUrl);
205+
}
206+
207+
folderName = await window.showInputBox({
208+
prompt: "Folder name",
209+
value: folderName,
210+
ignoreFocusOut: true
211+
});
212+
213+
if (!folderName) {
214+
return;
144215
}
145216

146-
repository.username = username;
147-
repository.password = password;
217+
const repositoryPath = path.join(parentPath, folderName);
218+
219+
// Use Notification location if supported
220+
let location = ProgressLocation.Window;
221+
if ((ProgressLocation as any).Notification) {
222+
location = (ProgressLocation as any).Notification;
223+
}
224+
225+
const progressOptions = {
226+
location,
227+
title: `Checkout svn repository '${url}'...`,
228+
cancellable: true
229+
};
230+
231+
let attempt = 0;
148232

149-
return true;
233+
const opt: ICpOptions = {};
234+
235+
while (true) {
236+
attempt++;
237+
try {
238+
await window.withProgress(progressOptions, async () => {
239+
const args = ["checkout", url, repositoryPath];
240+
await this.svn.exec(parentPath, args, opt);
241+
});
242+
break;
243+
} catch (err) {
244+
if (
245+
err.svnErrorCode === svnErrorCodes.AuthorizationFailed &&
246+
attempt <= 3
247+
) {
248+
const auth = (await commands.executeCommand(
249+
"svn.promptAuth",
250+
opt.username
251+
)) as IAuth;
252+
if (auth) {
253+
opt.username = auth.username;
254+
opt.password = auth.password;
255+
continue;
256+
}
257+
}
258+
throw err;
259+
}
260+
}
261+
262+
const choices = [];
263+
let message = "Would you like to open the checked out repository?";
264+
const open = "Open Repository";
265+
choices.push(open);
266+
267+
const addToWorkspace = "Add to Workspace";
268+
if (
269+
workspace.workspaceFolders &&
270+
(workspace as any).updateWorkspaceFolders // For VSCode >= 1.21
271+
) {
272+
message =
273+
"Would you like to open the checked out repository, or add it to the current workspace?";
274+
choices.push(addToWorkspace);
275+
}
276+
277+
const result = await window.showInformationMessage(message, ...choices);
278+
279+
const openFolder = result === open;
280+
281+
if (openFolder) {
282+
commands.executeCommand("vscode.openFolder", Uri.file(repositoryPath));
283+
} else if (result === addToWorkspace) {
284+
// For VSCode >= 1.21
285+
(workspace as any).updateWorkspaceFolders(
286+
workspace.workspaceFolders!.length,
287+
0,
288+
{
289+
uri: Uri.file(repositoryPath)
290+
}
291+
);
292+
}
150293
}
151294

152295
@command("svn.commitWithMessage", { repository: true })

src/common/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,3 +250,8 @@ export interface IOperations {
250250
isIdle(): boolean;
251251
isRunning(operation: Operation): boolean;
252252
}
253+
254+
export interface IAuth {
255+
username: string;
256+
password: string;
257+
}

src/extension.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ async function init(
3535
const svn = new Svn({ svnPath: info.path, version: info.version });
3636
const model = new Model(svn);
3737
const contentProvider = new SvnContentProvider(model);
38-
const svnCommands = new SvnCommands(model);
38+
const svnCommands = new SvnCommands(model, svn);
3939
disposables.push(model, contentProvider, svnCommands);
4040

4141
const svnProvider = new SvnProvider(model);

src/repository.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
workspace
1717
} from "vscode";
1818
import {
19+
IAuth,
1920
IFileStatus,
2021
IOperations,
2122
ISvnResourceGroup,
@@ -71,7 +72,7 @@ export class Repository {
7172
private remoteChangedUpdateInterval?: NodeJS.Timer;
7273
private deletedUris: Uri[] = [];
7374

74-
private lastPromptAuth?: Thenable<boolean | undefined>;
75+
private lastPromptAuth?: Thenable<IAuth | undefined>;
7576

7677
private _onDidChangeRepository = new EventEmitter<Uri>();
7778
public readonly onDidChangeRepository: Event<Uri> = this
@@ -864,14 +865,20 @@ export class Repository {
864865
);
865866
}
866867

867-
public async promptAuth(): Promise<boolean | undefined> {
868+
public async promptAuth(): Promise<IAuth | undefined> {
868869
// Prevent multiple prompts for auth
869870
if (this.lastPromptAuth) {
870871
return this.lastPromptAuth;
871872
}
872873

873874
this.lastPromptAuth = commands.executeCommand("svn.promptAuth");
874875
const result = await this.lastPromptAuth;
876+
877+
if (result) {
878+
this.username = result.username;
879+
this.password = result.password;
880+
}
881+
875882
this.lastPromptAuth = undefined;
876883
return result;
877884
}

0 commit comments

Comments
 (0)