diff --git a/extension/package.json b/extension/package.json index bb5834c76e..f855e3395c 100644 --- a/extension/package.json +++ b/extension/package.json @@ -202,6 +202,11 @@ "command": "dvc.copyRelativeFilePath", "category": "DVC" }, + { + "title": "%command.renameTarget%", + "command": "dvc.renameTarget", + "category": "DVC" + }, { "title": "%command.addExperimentsTableSort%", "command": "dvc.addExperimentsTableSort", @@ -383,6 +388,10 @@ "command": "dvc.copyRelativeFilePath", "when": "false" }, + { + "command": "dvc.renameTarget", + "when": "false" + }, { "command": "dvc.views.experimentsFilterByTree.removeAllFilters", "when": "false" @@ -489,23 +498,13 @@ ], "view/item/context": [ { - "command": "dvc.deleteTarget", - "group": "DVC", - "when": "viewItem =~ /^dvcTracked/" - }, - { - "command": "dvc.removeTarget", - "when": "viewItem == dvcTrackedData", - "group": "DVC" - }, - { - "command": "dvc.pushTarget", - "group": "DVC", + "command": "dvc.pullTarget", + "group": "1_DVC@1", "when": "viewItem == dvcTrackedData || viewItem == dvcTrackedHasRemote" }, { - "command": "dvc.pullTarget", - "group": "DVC", + "command": "dvc.pushTarget", + "group": "1_DVC@2", "when": "viewItem == dvcTrackedData || viewItem == dvcTrackedHasRemote" }, { @@ -518,6 +517,21 @@ "group": "6_copypath@2", "when": "viewItem =~ /^dvcTracked/" }, + { + "command": "dvc.renameTarget", + "group": "7_modification@1", + "when": "viewItem == dvcTrackedData" + }, + { + "command": "dvc.deleteTarget", + "group": "7_modification@2", + "when": "viewItem =~ /^dvcTracked/" + }, + { + "command": "dvc.removeTarget", + "when": "viewItem == dvcTrackedData", + "group": "7_modification@2" + }, { "command": "dvc.addExperimentsTableSort", "group": "inline", diff --git a/extension/package.nls.json b/extension/package.nls.json index 9108aa1dfa..c0e416130d 100644 --- a/extension/package.nls.json +++ b/extension/package.nls.json @@ -21,6 +21,7 @@ "command.push": "Push", "command.pushTarget": "Push", "command.removeTarget": "Remove", + "command.renameTarget": "Rename", "command.views.experimentsFilterByTree.removeFilter": "Remove Filter From Experiments Table", "command.views.experimentsFilterByTree.removeAllFilters": "Remove All Filters From Experiments Table", "command.views.experimentsSortByTree.removeSort": "Remove Sort From Experiments Table", diff --git a/extension/src/cli/args.ts b/extension/src/cli/args.ts index edc820f996..044999b0b4 100644 --- a/extension/src/cli/args.ts +++ b/extension/src/cli/args.ts @@ -6,6 +6,7 @@ export enum Command { EXPERIMENT = 'exp', INITIALIZE = 'init', LIST = 'list', + MOVE = 'move', PULL = 'pull', PUSH = 'push', REMOVE = 'remove', diff --git a/extension/src/cli/executor.test.ts b/extension/src/cli/executor.test.ts index 0ea82e619a..1ea67aeb2c 100644 --- a/extension/src/cli/executor.test.ts +++ b/extension/src/cli/executor.test.ts @@ -374,6 +374,30 @@ describe('CliExecutor', () => { }) }) + describe('move', () => { + it('should call createProcess with the correct parameters to move a DVC tracked target', async () => { + const cwd = __dirname + const target = 'data/data.xml.dvc' + const destination = 'data/data1.xml.dvc' + const stdout = ` + To track the changes with git, run: + + git add ${destination} data/.gitignore` + + mockedCreateProcess.mockReturnValueOnce(getMockedProcess(stdout)) + + const output = await cliExecutor.move(cwd, target, destination) + expect(output).toEqual(stdout) + + expect(mockedCreateProcess).toBeCalledWith({ + args: ['move', target, destination], + cwd: cwd, + env: mockedEnv, + executable: 'dvc' + }) + }) + }) + describe('pull', () => { it('should call createProcess with the correct parameters to pull the entire repository', async () => { const cwd = __dirname diff --git a/extension/src/cli/executor.ts b/extension/src/cli/executor.ts index 0d1b5a34d9..4ad8736dbc 100644 --- a/extension/src/cli/executor.ts +++ b/extension/src/cli/executor.ts @@ -18,6 +18,7 @@ export const autoRegisteredCommands = { EXPERIMENT_QUEUE: 'experimentRunQueue', EXPERIMENT_REMOVE: 'experimentRemove', INIT: 'init', + MOVE: 'move', PULL: 'pull', PUSH: 'push', REMOVE: 'remove' @@ -94,6 +95,10 @@ export class CliExecutor extends Cli { return this.executeProcess(cwd, Command.INITIALIZE, Flag.SUBDIRECTORY) } + public move(cwd: string, target: string, destination: string) { + return this.executeProcess(cwd, Command.MOVE, target, destination) + } + public pull(cwd: string, ...args: Args) { return this.executeProcess(cwd, Command.PULL, ...args) } diff --git a/extension/src/cli/index.ts b/extension/src/cli/index.ts index 6c7143e0d3..8aec34cb7e 100644 --- a/extension/src/cli/index.ts +++ b/extension/src/cli/index.ts @@ -80,7 +80,6 @@ export class Cli implements ICli { public async executeProcess(cwd: string, ...args: Args): Promise { const { command, ...options } = this.getOptions(cwd, ...args) - const baseEvent: CliEvent = { command, cwd, pid: undefined } const stopWatch = new StopWatch() try { diff --git a/extension/src/commands/external.ts b/extension/src/commands/external.ts index cf812fefb7..c9acf94b7d 100644 --- a/extension/src/commands/external.ts +++ b/extension/src/commands/external.ts @@ -36,6 +36,7 @@ export enum RegisteredCommands { PUSH_TARGET = 'dvc.pushTarget', DELETE_TARGET = 'dvc.deleteTarget', REMOVE_TARGET = 'dvc.removeTarget', + RENAME_TARGET = 'dvc.renameTarget', EXTENSION_DESELECT_DEFAULT_PROJECT = 'dvc.deselectDefaultProject', EXTENSION_SELECT_DEFAULT_PROJECT = 'dvc.selectDefaultProject', diff --git a/extension/src/fileSystem/tree.ts b/extension/src/fileSystem/tree.ts index 1543fee8fa..c5e5f13aa1 100644 --- a/extension/src/fileSystem/tree.ts +++ b/extension/src/fileSystem/tree.ts @@ -27,6 +27,7 @@ import { } from '../commands/external' import { sendViewOpenedTelemetryEvent } from '../telemetry' import { EventName } from '../telemetry/constants' +import { getInput } from '../vscode/inputBox' export class TrackedExplorerTree implements TreeDataProvider { public dispose = Disposable.fn() @@ -287,6 +288,30 @@ export class TrackedExplorerTree implements TreeDataProvider { ) ) + this.dispose.track( + registerInstrumentedCommand( + RegisteredCommands.RENAME_TARGET, + async path => { + const dvcRoot = this.pathRoots[path] + const relPath = relative(dvcRoot, path) + const relDestination = await getInput( + 'enter a destination relative to the root', + relPath + ) + if (!relDestination || relDestination === relPath) { + return + } + + return this.internalCommands.executeCommand( + AvailableCommands.MOVE, + dvcRoot, + relPath, + relDestination + ) + } + ) + ) + this.dispose.track( registerInstrumentedCommand( RegisteredCommands.PULL_TARGET, diff --git a/extension/src/telemetry/constants.ts b/extension/src/telemetry/constants.ts index 98cfd3acfd..257cb605bb 100644 --- a/extension/src/telemetry/constants.ts +++ b/extension/src/telemetry/constants.ts @@ -90,6 +90,7 @@ export interface IEventNamePropertyMapping { [EventName.PUSH_TARGET]: undefined [EventName.PUSH]: undefined [EventName.REMOVE_TARGET]: undefined + [EventName.RENAME_TARGET]: undefined [EventName.TRACKED_EXPLORER_OPEN_FILE]: undefined [EventName.TRACKED_EXPLORER_COPY_FILE_PATH]: undefined diff --git a/extension/src/test/suite/fileSystem/tree.test.ts b/extension/src/test/suite/fileSystem/tree.test.ts index 71e3456a41..1b5e265564 100644 --- a/extension/src/test/suite/fileSystem/tree.test.ts +++ b/extension/src/test/suite/fileSystem/tree.test.ts @@ -231,6 +231,27 @@ suite('Tracked Explorer Tree Test Suite', () => { expect(mockRemove).to.be.calledOnce }) + it('should be able to run dvc.renameTarget without error', async () => { + const relPath = join('mock', 'data', 'MNIST', 'raw') + const absPath = join(dvcDemoPath, relPath) + stub(path, 'relative').returns(relPath) + const mockMove = stub(CliExecutor.prototype, 'move').resolves( + 'target moved to new destination' + ) + + const mockInputBox = stub(window, 'showInputBox').resolves( + relPath + 'est' + ) + + await commands.executeCommand(RegisteredCommands.RENAME_TARGET, absPath) + expect(mockMove).to.be.calledOnce + expect(mockInputBox).to.be.calledOnce + expect(mockInputBox).to.be.calledWith({ + prompt: 'enter a destination relative to the root', + value: relPath + }) + }) + it('should be able to run dvc.pullTarget without error', async () => { const relPath = 'data' const absPath = join(dvcDemoPath, relPath) diff --git a/extension/src/vscode/inputBox.ts b/extension/src/vscode/inputBox.ts index fb8d277841..22dff5878f 100644 --- a/extension/src/vscode/inputBox.ts +++ b/extension/src/vscode/inputBox.ts @@ -1,6 +1,7 @@ import { window } from 'vscode' -export const getInput = (prompt: string) => +export const getInput = (prompt: string, value?: string) => window.showInputBox({ - prompt + prompt, + value })