Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions extension/.prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ dist
.wdio-vscode-service
coverage
src/test/fixtures/plotsDiff/vega.ts
CHANGELOG.md
17 changes: 16 additions & 1 deletion extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,12 @@
"category": "DVC",
"icon": "$(repo-push)"
},
{
"title": "%command.views.experiments.shareExperimentAsCommit%",
"command": "dvc.views.experiments.shareExperimentAsCommit",
"category": "DVC",
"icon": "$(repo-push)"
},
{
"title": "%command.views.experimentsTree.selectExperiments%",
"command": "dvc.views.experimentsTree.selectExperiments",
Expand Down Expand Up @@ -803,6 +809,10 @@
"command": "dvc.views.experiments.shareExperimentAsBranch",
"when": "false"
},
{
"command": "dvc.views.experiments.shareExperimentAsCommit",
"when": "false"
},
{
"command": "dvc.views.experimentsFilterByTree.removeAllFilters",
"when": "false"
Expand Down Expand Up @@ -1090,10 +1100,15 @@
"when": "view == dvc.views.experimentsTree && dvc.commands.available && viewItem =~ /^(experiment|queued)$/ && !dvc.experiment.running"
},
{
"command": "dvc.views.experiments.shareExperimentAsBranch",
"command": "dvc.views.experiments.shareExperimentAsCommit",
"group": "1_share@1",
"when": "view == dvc.views.experimentsTree && dvc.commands.available && viewItem =~ /^(checkpoint|experiment)$/ && !dvc.experiment.running"
},
{
"command": "dvc.views.experiments.shareExperimentAsBranch",
"group": "1_share@2",
"when": "view == dvc.views.experimentsTree && dvc.commands.available && viewItem =~ /^(checkpoint|experiment)$/ && !dvc.experiment.running"
},
{
"command": "dvc.views.experiments.runExperiment",
"group": "2_modify@1",
Expand Down
1 change: 1 addition & 0 deletions extension/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"command.views.experiments.resumeCheckpointExperiment": "Modify Param(s) and Resume",
"command.views.experiments.resetAndRunCheckpointExperiment": "Modify Param(s), Reset and Run",
"command.views.experiments.shareExperimentAsBranch": "Share as Branch",
"command.views.experiments.shareExperimentAsCommit": "Commit and Share",
"command.views.experimentsTree.selectExperiments": "Select Experiments to Display in Plots",
"command.views.plotsPathsTree.selectPlots": "Select Plots to Display",
"command.views.plotsPathsTree.refreshPlots": "Refresh Plots for Selected Experiments",
Expand Down
2 changes: 2 additions & 0 deletions extension/src/cli/git/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const HEADS_GIT_REFS = join(GIT_REFS, 'heads')
export enum Command {
ADD = 'add',
CLEAN = 'clean',
COMMIT = 'commit',
DIFF = 'diff',
LS_FILES = 'ls-files',
PUSH = 'push',
Expand All @@ -24,6 +25,7 @@ export enum Flag {
EXCLUDE_STANDARD = '--exclude-standard',
FORCE = '-f',
HARD = '--hard',
MESSAGE = '-m',
NAME_ONLY = '--name-only',
NO_EMPTY_DIRECTORY = '--no-empty-directory',
OTHERS = '--others',
Expand Down
99 changes: 99 additions & 0 deletions extension/src/cli/git/executor.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { EventEmitter } from 'vscode'
import { Disposable, Disposer } from '@hediet/std/disposable'
import { GitExecutor } from './executor'
import { createProcess } from '../../processExecution'
import { CliResult, CliStarted } from '..'
import { getMockedProcess } from '../../test/util/jest'

jest.mock('vscode')
jest.mock('@hediet/std/disposable')
jest.mock('../../processExecution')

const mockedDisposable = jest.mocked(Disposable)

const mockedCreateProcess = jest.mocked(createProcess)

beforeEach(() => {
jest.resetAllMocks()
})

describe('GitExecutor', () => {
mockedDisposable.fn.mockReturnValueOnce({
track: function <T>(disposable: T): T {
return disposable
},
untrack: function <T>(disposable: T): T {
return disposable
}
} as unknown as (() => void) & Disposer)

const gitExecutor = new GitExecutor({
processCompleted: {
event: jest.fn(),
fire: jest.fn()
} as unknown as EventEmitter<CliResult>,
processStarted: {
event: jest.fn(),
fire: jest.fn()
} as unknown as EventEmitter<CliStarted>
})

describe('pushBranch', () => {
it('should call createProcess with the correct parameters to push a branch', async () => {
const cwd = __dirname
const branchName = 'my-branch'
mockedCreateProcess.mockReturnValueOnce(
getMockedProcess(
`branch '${branchName}' set up to track 'origin/${branchName}'.`
)
)

await gitExecutor.pushBranch(cwd, branchName)
expect(mockedCreateProcess).toBeCalledWith({
args: ['push', '--set-upstream', 'origin', branchName],
cwd,
executable: 'git'
})
})

it('should call createProcess with the correct parameters to push the current branch', async () => {
const cwd = __dirname
mockedCreateProcess.mockReturnValueOnce(
getMockedProcess('Everything up-to-date')
)

await gitExecutor.pushBranch(cwd)
expect(mockedCreateProcess).toBeCalledWith({
args: ['push', '--set-upstream', 'origin', 'HEAD'],
cwd,
executable: 'git'
})
})
})

describe('stageAndCommit', () => {
it('should call createProcess with the correct parameters to stage all files and then commit', async () => {
const cwd = __dirname
const message = 'best experiment'
mockedCreateProcess.mockReturnValueOnce(getMockedProcess(cwd))
mockedCreateProcess
.mockReturnValueOnce(getMockedProcess(''))
.mockReturnValueOnce(
getMockedProcess(`[current-branch 67effdbc] ${message}`)
)

await gitExecutor.stageAndCommit(cwd, message)
expect(mockedCreateProcess).toBeCalledTimes(3)
expect(mockedCreateProcess).toBeCalledWith({
args: ['add', '.'],
cwd,
executable: 'git'
})
expect(mockedCreateProcess).toBeCalledWith({
args: ['commit', '-m', message],
cwd,
executable: 'git'
})
})
})
})
26 changes: 17 additions & 9 deletions extension/src/cli/git/executor.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { GitCli } from '.'
import { Command, Commit, DEFAULT_REMOTE, Flag } from './constants'
import { Args, Command, Commit, DEFAULT_REMOTE, Flag } from './constants'
import { getOptions } from './options'
import { typeCheckCommands } from '..'

export const autoRegisteredCommands = {
GIT_PUSH_BRANCH: 'pushBranch',
GIT_RESET_WORKSPACE: 'resetWorkspace',
GIT_STAGE_ALL: 'stageAll',
GIT_STAGE_AND_COMMIT: 'stageAndCommit',
GIT_UNSTAGE_ALL: 'reset'
} as const

Expand All @@ -16,6 +17,16 @@ export class GitExecutor extends GitCli {
this
)

public pushBranch(cwd: string, branchName?: string) {
const args: Args = [Command.PUSH, Flag.SET_UPSTREAM, DEFAULT_REMOTE]

args.push((branchName || Commit.HEAD) as Commit)

const options = getOptions(cwd, ...args)

return this.executeProcess(options)
}

public reset(cwd: string, ...args: (Flag | Commit)[]) {
const options = getOptions(cwd, Command.RESET, ...args)

Expand Down Expand Up @@ -43,14 +54,11 @@ export class GitExecutor extends GitCli {
return this.executeProcess(options)
}

public pushBranch(cwd: string, branchName: string) {
const options = getOptions(
cwd,
Command.PUSH,
Flag.SET_UPSTREAM,
DEFAULT_REMOTE,
branchName as Commit
)
public async stageAndCommit(cwd: string, message: string) {
await this.stageAll(cwd)

const args = [Command.COMMIT, Flag.MESSAGE, message] as Args
const options = getOptions(cwd, ...args)

return this.executeProcess(options)
}
Expand Down
1 change: 1 addition & 0 deletions extension/src/commands/external.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export enum RegisteredCliCommands {
EXPERIMENT_VIEW_BRANCH = 'dvc.views.experiments.branchExperiment',
EXPERIMENT_VIEW_REMOVE = 'dvc.views.experiments.removeExperiment',
EXPERIMENT_VIEW_SHARE_AS_BRANCH = 'dvc.views.experiments.shareExperimentAsBranch',
EXPERIMENT_VIEW_SHARE_AS_COMMIT = 'dvc.views.experiments.shareExperimentAsCommit',

EXPERIMENT_VIEW_QUEUE = 'dvc.views.experiments.queueExperiment',
EXPERIMENT_VIEW_RESUME = 'dvc.views.experiments.resumeCheckpointExperiment',
Expand Down
16 changes: 16 additions & 0 deletions extension/src/experiments/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,19 @@ export const getShareExperimentAsBranchCommand =

return experiments.runCommand(AvailableCommands.GIT_PUSH_BRANCH, cwd, input)
}

export const getShareExperimentAsCommitCommand =
(experiments: WorkspaceExperiments) =>
async (cwd: string, name: string, input: string) => {
await experiments.runCommand(AvailableCommands.EXPERIMENT_APPLY, cwd, name)

await experiments.runCommand(
AvailableCommands.GIT_STAGE_AND_COMMIT,
cwd,
input
)

await experiments.runCommand(AvailableCommands.PUSH, cwd)

return experiments.runCommand(AvailableCommands.GIT_PUSH_BRANCH, cwd)
}
14 changes: 13 additions & 1 deletion extension/src/experiments/commands/register.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
getBranchExperimentCommand,
getShareExperimentAsBranchCommand
getShareExperimentAsBranchCommand,
getShareExperimentAsCommitCommand
} from '.'
import { pickGarbageCollectionFlags } from '../quickPick'
import { WorkspaceExperiments } from '../workspace'
Expand Down Expand Up @@ -192,6 +193,17 @@ const registerExperimentInputCommands = (
id
)
)

internalCommands.registerExternalCliCommand(
RegisteredCliCommands.EXPERIMENT_VIEW_SHARE_AS_COMMIT,
({ dvcRoot, id }: ExperimentDetails) =>
experiments.getExpNameAndInputThenRun(
getShareExperimentAsCommitCommand(experiments),
Title.ENTER_COMMIT_MESSAGE,
dvcRoot,
id
)
)
}

const registerExperimentQuickPickCommands = (
Expand Down
5 changes: 5 additions & 0 deletions extension/src/experiments/webview/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ export class WebviewMessages {
RegisteredCliCommands.EXPERIMENT_VIEW_SHARE_AS_BRANCH,
{ dvcRoot: this.dvcRoot, id: message.payload }
)
case MessageFromWebviewType.SHARE_EXPERIMENT_AS_COMMIT:
return commands.executeCommand(
RegisteredCliCommands.EXPERIMENT_VIEW_SHARE_AS_COMMIT,
{ dvcRoot: this.dvcRoot, id: message.payload }
)

default:
Logger.error(`Unexpected message: ${JSON.stringify(message)}`)
Expand Down
1 change: 1 addition & 0 deletions extension/src/telemetry/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ export interface IEventNamePropertyMapping {
[EventName.EXPERIMENT_VIEW_BRANCH]: undefined
[EventName.EXPERIMENT_VIEW_REMOVE]: undefined
[EventName.EXPERIMENT_VIEW_SHARE_AS_BRANCH]: undefined
[EventName.EXPERIMENT_VIEW_SHARE_AS_COMMIT]: undefined
[EventName.EXPERIMENT_TOGGLE]: undefined

[EventName.EXPERIMENT_VIEW_QUEUE]: undefined
Expand Down
56 changes: 56 additions & 0 deletions extension/src/test/suite/experiments/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,62 @@ suite('Experiments Test Suite', () => {
expect(mockGitPush).to.be.calledWithExactly(dvcDemoPath, mockBranch)
}).timeout(WEBVIEW_TEST_TIMEOUT)

it('should handle a message to share an experiment as a commit', async () => {
const { experiments } = buildExperiments(disposable)
await experiments.isReady()

const testCheckpointId = 'd1343a87c6ee4a2e82d19525964d2fb2cb6756c9'
const testCheckpointLabel = shortenForLabel(testCheckpointId)
const mockCommitMessage =
'this is the very best version that I could come up with'
const inputEvent = getInputBoxEvent(mockCommitMessage)

const mockExperimentApply = stub(
DvcExecutor.prototype,
'experimentApply'
).resolves(
`Changes for experiment '${testCheckpointId}' have been applied to your current workspace.`
)
const mockStageAndCommit = stub(
GitExecutor.prototype,
'stageAndCommit'
).resolves(`[current-branch 67effdbc] ${mockCommitMessage}`)

const mockPush = stub(DvcExecutor.prototype, 'push').resolves(
'100000 files updated.'
)
const mockGitPush = stub(GitExecutor.prototype, 'pushBranch')
const branchPushedToRemote = new Promise(resolve =>
mockGitPush.callsFake(() => {
resolve(undefined)
return Promise.resolve('current-branch pushed to remote')
})
)

stubWorkspaceExperimentsGetters(dvcDemoPath, experiments)

const webview = await experiments.showWebview()
const mockMessageReceived = getMessageReceivedEmitter(webview)

mockMessageReceived.fire({
payload: testCheckpointId,
type: MessageFromWebviewType.SHARE_EXPERIMENT_AS_COMMIT
})

await inputEvent
await branchPushedToRemote
expect(mockStageAndCommit).to.be.calledWithExactly(
dvcDemoPath,
mockCommitMessage
)
expect(mockExperimentApply).to.be.calledWithExactly(
dvcDemoPath,
testCheckpointLabel
)
expect(mockPush).to.be.calledWithExactly(dvcDemoPath)
expect(mockGitPush).to.be.calledWithExactly(dvcDemoPath)
}).timeout(WEBVIEW_TEST_TIMEOUT)

it("should be able to handle a message to modify an experiment's params and queue an experiment", async () => {
const { experiments, dvcExecutor } = buildExperiments(disposable)

Expand Down
1 change: 1 addition & 0 deletions extension/src/vscode/title.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export enum Title {
CHOOSE_RESOURCES = 'Choose Resources to Add to the Dataset',
ENTER_BRANCH_NAME = 'Enter a Name for the New Branch',
ENTER_COMMIT_MESSAGE = 'Enter a Commit Message',
ENTER_FILTER_VALUE = 'Enter a Filter Value',
ENTER_RELATIVE_DESTINATION = 'Enter a Destination Relative to the Root',
GARBAGE_COLLECT_EXPERIMENTS = 'Garbage Collect Experiments',
Expand Down
5 changes: 5 additions & 0 deletions extension/src/webview/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export enum MessageFromWebviewType {
SELECT_COLUMNS = 'select-columns',
SELECT_PLOTS = 'select-plots',
SHARE_EXPERIMENT_AS_BRANCH = 'share-experiment-as-branch',
SHARE_EXPERIMENT_AS_COMMIT = 'share-experiment-as-commit',
TOGGLE_METRIC = 'toggle-metric',
TOGGLE_PLOTS_SECTION = 'toggle-plots-section',
MODIFY_EXPERIMENT_PARAMS_AND_QUEUE = 'modify-experiment-params-and-queue',
Expand Down Expand Up @@ -151,6 +152,10 @@ export type MessageFromWebview =
type: MessageFromWebviewType.SHARE_EXPERIMENT_AS_BRANCH
payload: string
}
| {
type: MessageFromWebviewType.SHARE_EXPERIMENT_AS_COMMIT
payload: string
}

export type MessageToWebview<T extends WebviewData> = {
type: MessageToWebviewType.SET_DATA
Expand Down
1 change: 1 addition & 0 deletions webview/src/experiments/components/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,7 @@ describe('App', () => {
expect(itemLabels).toStrictEqual([
'Apply to Workspace',
'Create new Branch',
'Commit and Share',
'Share as Branch',
'Modify, Reset and Run',
'Modify and Resume',
Expand Down
Loading