Skip to content

Commit ff95cf9

Browse files
committed
feat: Prompt action for deleted file (Close #314)
1 parent 5393373 commit ff95cf9

File tree

5 files changed

+134
-2
lines changed

5 files changed

+134
-2
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ Example:
110110
|`svn.showOutput`|Show the output window when the extension starts|`false`|
111111
|`svn.conflicts.autoResolve`|Set file to status resolved after fix conflictss|`false`|
112112
|`svn.update.ignoreExternals`|Set to ignore externals definitions on update (add --ignore-externals)|`true`|
113+
|`svn.delete.actionForDeletedFiles`|When a file is deleted, what SVN should do? `none` - Do nothing, `prompt` - Ask the action, `remove` - automatically remove from SVN|`"prompt"`|
114+
|`svn.delete.ignoredRulesForDeletedFiles`|Ignored files/rules for `svn.delete.actionForDeletedFiles`(Ex.: file.txt or \*\*/\*.txt)|`[]`|
113115
|`svn.default.encoding`|Encoding of svn output if the output is not utf-8. When this parameter is null, the encoding is automatically detected. Example: 'windows-1252'.|`null`|
114116
|`svn.showUpdateMessage`|Show the update message when update is run|`true`|
115117
|`svn.remoteChanges.checkFrequency`|Set the interval in seconds to check changed files on remote repository and show in statusbar. 0 to disable|`300`|

package.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,24 @@
726726
"description": "Set to ignore externals definitions on update (add --ignore-externals)",
727727
"default": true
728728
},
729+
"svn.delete.actionForDeletedFiles": {
730+
"type": "string",
731+
"enum": [
732+
"none",
733+
"prompt",
734+
"remove"
735+
],
736+
"description": "When a file is deleted, what SVN should do? `none` - Do nothing, `prompt` - Ask the action, `remove` - automatically remove from SVN",
737+
"default": "prompt"
738+
},
739+
"svn.delete.ignoredRulesForDeletedFiles": {
740+
"type": "array",
741+
"items": {
742+
"type": "string"
743+
},
744+
"description": "Ignored files/rules for `svn.delete.actionForDeletedFiles`(Ex.: file.txt or **/*.txt)",
745+
"default": []
746+
},
729747
"svn.default.encoding": {
730748
"type": [
731749
"string",

src/commands.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1319,6 +1319,42 @@ export class SvnCommands implements IDisposable {
13191319
return;
13201320
}
13211321

1322+
@command("svn.promptRemove", { repository: true })
1323+
public async promptRemove(repository: Repository, ...uris: Uri[]) {
1324+
const files = uris.map(uri => uri.fsPath);
1325+
1326+
const relativeList = files
1327+
.map(file => repository.repository.removeAbsolutePath(file))
1328+
.sort();
1329+
1330+
const ignoreText = "Add to ignored list";
1331+
1332+
const resp = await window.showInformationMessage(
1333+
`The file(s) "${relativeList.join(
1334+
", "
1335+
)}" are removed from disk.\nWould you like remove from SVN?`,
1336+
{ modal: true },
1337+
"Yes",
1338+
ignoreText,
1339+
"No"
1340+
);
1341+
1342+
if (resp === "Yes") {
1343+
await repository.removeFiles(files, false);
1344+
} else if (resp === ignoreText) {
1345+
let ignoreList = configuration.get<string[]>(
1346+
"delete.ignoredRulesForDeletedFiles",
1347+
[]
1348+
);
1349+
1350+
ignoreList.push(...relativeList);
1351+
1352+
ignoreList = [...new Set(ignoreList)]; // Remove duplicates
1353+
1354+
configuration.update("delete.ignoredRulesForDeletedFiles", ignoreList);
1355+
}
1356+
}
1357+
13221358
private getSCMResource(uri?: Uri): Resource | undefined {
13231359
uri = uri
13241360
? uri

src/repository.ts

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export class Repository {
6969
public isIncomplete: boolean = false;
7070
public needCleanUp: boolean = false;
7171
private remoteChangedUpdateInterval?: NodeJS.Timer;
72+
private deletedUris: Uri[] = [];
7273

7374
private lastPromptAuth?: Thenable<boolean | undefined>;
7475

@@ -245,6 +246,17 @@ export class Repository {
245246
})
246247
);
247248

249+
// For each deleted file, append to list
250+
const onFsDelete = filterEvent(
251+
fsWatcher.onDidDelete,
252+
uri => !/[\\\/]\.svn[\\\/]/.test(uri.path)
253+
);
254+
255+
onFsDelete(uri => this.deletedUris.push(uri), this, this.disposables);
256+
257+
// Only check deleted files after the status list is fully updated
258+
this.onDidChangeStatus(this.actionForDeletedFiles, this, this.disposables);
259+
248260
this.createRemoteChangedInterval();
249261

250262
this.updateRemoteChangedFiles();
@@ -286,6 +298,70 @@ export class Repository {
286298
}, 1000 * updateFreq);
287299
}
288300

301+
/**
302+
* Check all recently deleted files and compare with svn status "missing"
303+
*/
304+
@debounce(1000)
305+
private async actionForDeletedFiles() {
306+
if (!this.deletedUris.length) {
307+
return;
308+
}
309+
310+
const allUris = this.deletedUris;
311+
this.deletedUris = [];
312+
313+
const actionForDeletedFiles = configuration.get<string>(
314+
"delete.actionForDeletedFiles",
315+
"prompt"
316+
);
317+
318+
if (actionForDeletedFiles === "none") {
319+
return;
320+
}
321+
322+
const resources = allUris
323+
.map(uri => this.getResourceFromFile(uri))
324+
.filter(
325+
resource => resource && resource.type === Status.MISSING
326+
) as Resource[];
327+
328+
let uris = resources.map(resource => resource.resourceUri);
329+
330+
if (!uris.length) {
331+
return;
332+
}
333+
334+
const ignoredRulesForDeletedFiles = configuration.get<string[]>(
335+
"delete.ignoredRulesForDeletedFiles",
336+
[]
337+
);
338+
const rules = ignoredRulesForDeletedFiles.map(
339+
ignored => new Minimatch(ignored)
340+
);
341+
342+
if (rules.length) {
343+
uris = uris.filter(uri => {
344+
// Check first for relative URL (Better for workspace configuration)
345+
const relativePath = this.repository.removeAbsolutePath(uri.fsPath);
346+
347+
// If some match, remove from list
348+
return !rules.some(
349+
rule => rule.match(relativePath) || rule.match(uri.fsPath)
350+
);
351+
});
352+
}
353+
354+
if (!uris.length) {
355+
return;
356+
}
357+
358+
if (actionForDeletedFiles === "remove") {
359+
return await this.removeFiles(uris.map(uri => uri.fsPath), false);
360+
} else if (actionForDeletedFiles === "prompt") {
361+
return await commands.executeCommand("svn.promptRemove", ...uris);
362+
}
363+
}
364+
289365
@debounce(1000)
290366
public async updateRemoteChangedFiles() {
291367
const updateFreq = configuration.get<number>(
@@ -727,7 +803,7 @@ export class Repository {
727803
);
728804
}
729805

730-
public async removeFiles(files: any[], keepLocal: boolean) {
806+
public async removeFiles(files: string[], keepLocal: boolean) {
731807
return this.run(Operation.Remove, () =>
732808
this.repository.removeFiles(files, keepLocal)
733809
);

src/svnRepository.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ export class Repository {
432432
return message;
433433
}
434434

435-
public async removeFiles(files: any[], keepLocal: boolean) {
435+
public async removeFiles(files: string[], keepLocal: boolean) {
436436
files = files.map(file => this.removeAbsolutePath(file));
437437
const args = ["remove"];
438438

0 commit comments

Comments
 (0)