Skip to content

Commit bed3cba

Browse files
authored
Add DVC and Git initialization watcher (#3025)
1 parent 9aca903 commit bed3cba

File tree

13 files changed

+273
-90
lines changed

13 files changed

+273
-90
lines changed

extension/src/cli/dvc/constants.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { join } from 'path'
22

33
export const UNEXPECTED_ERROR_CODE = 255
4+
export const DOT_DVC = '.dvc'
45

5-
export const TEMP_PLOTS_DIR = join('.dvc', 'tmp', 'plots')
6+
export const TEMP_PLOTS_DIR = join(DOT_DVC, 'tmp', 'plots')
67
export const DVCLIVE_ONLY_RUNNING_SIGNAL_FILE = join(
7-
'.dvc',
8+
DOT_DVC,
89
'tmp',
910
'exps',
1011
'run',

extension/src/cli/dvc/reader.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { join } from 'path'
22
import { EventEmitter } from 'vscode'
33
import { Disposable, Disposer } from '@hediet/std/disposable'
4-
import { UNEXPECTED_ERROR_CODE } from './constants'
4+
import { DOT_DVC, UNEXPECTED_ERROR_CODE } from './constants'
55
import { EXPERIMENT_WORKSPACE_ID } from './contract'
66
import { DvcReader } from './reader'
77
import { CliResult, CliStarted } from '..'
@@ -168,7 +168,7 @@ describe('CliReader', () => {
168168
'diff',
169169
'HEAD',
170170
'-o',
171-
join('.dvc', 'tmp', 'plots'),
171+
join(DOT_DVC, 'tmp', 'plots'),
172172
'--split',
173173
JSON_FLAG
174174
],

extension/src/experiments/data/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ import {
1313
import { AvailableCommands, InternalCommands } from '../../commands/internal'
1414
import { ExperimentsOutput } from '../../cli/dvc/contract'
1515
import { BaseData } from '../../data'
16-
import { ExperimentFlag } from '../../cli/dvc/constants'
16+
import { DOT_DVC, ExperimentFlag } from '../../cli/dvc/constants'
1717
import { gitPath } from '../../cli/git/constants'
1818
import { getGitPath } from '../../fileSystem'
1919

20-
export const QUEUED_EXPERIMENT_PATH = join('.dvc', 'tmp', 'exps')
20+
export const QUEUED_EXPERIMENT_PATH = join(DOT_DVC, 'tmp', 'exps')
2121

2222
export class ExperimentsData extends BaseData<ExperimentsOutput> {
2323
constructor(
@@ -36,7 +36,7 @@ export class ExperimentsData extends BaseData<ExperimentsOutput> {
3636
},
3737
{ name: 'fullUpdate', process: () => this.update() }
3838
],
39-
['dvc.lock', 'dvc.yaml', 'params.yaml', '.dvc']
39+
['dvc.lock', 'dvc.yaml', 'params.yaml', DOT_DVC]
4040
)
4141

4242
this.watchExpGitRefs()

extension/src/fileSystem/index.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
getModifiedTime
1010
} from '.'
1111
import { dvcDemoPath } from '../test/util'
12+
import { DOT_DVC } from '../cli/dvc/constants'
1213

1314
jest.mock('../cli/dvc/reader')
1415

@@ -26,7 +27,7 @@ describe('findDvcRootPaths', () => {
2627
it('should find multiple roots if available one directory below the given folder', async () => {
2728
const parentDir = resolve(dvcDemoPath, '..')
2829
const mockDvcRoot = join(parentDir, 'mockDvc')
29-
ensureDirSync(join(mockDvcRoot, '.dvc'))
30+
ensureDirSync(join(mockDvcRoot, DOT_DVC))
3031

3132
const dvcRoots = await findDvcRootPaths(parentDir)
3233

extension/src/fileSystem/index.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { gitPath } from '../cli/git/constants'
1717
import { createValidInteger } from '../util/number'
1818
import { processExists } from '../processExecution'
1919
import { getFirstWorkspaceFolder } from '../vscode/workspaceFolders'
20+
import { DOT_DVC } from '../cli/dvc/constants'
2021

2122
export const exists = (path: string): boolean => existsSync(path)
2223

@@ -43,13 +44,11 @@ export const findSubRootPaths = async (
4344
}
4445

4546
export const findDvcRootPaths = async (cwd: string): Promise<string[]> => {
46-
const dotDir = '.dvc'
47-
48-
if (isDirectory(join(cwd, dotDir))) {
47+
if (isDirectory(join(cwd, DOT_DVC))) {
4948
return [cwd]
5049
}
5150

52-
const subRoots = await findSubRootPaths(cwd, '.dvc')
51+
const subRoots = await findSubRootPaths(cwd, DOT_DVC)
5352

5453
if (definedAndNonEmpty(subRoots)) {
5554
return subRoots
@@ -120,7 +119,7 @@ export type PartialDvcYaml = {
120119
export const isAnyDvcYaml = (path?: string): boolean =>
121120
!!(
122121
path &&
123-
(extname(path) === '.dvc' ||
122+
(extname(path) === DOT_DVC ||
124123
basename(path) === 'dvc.lock' ||
125124
basename(path) === 'dvc.yaml')
126125
)

extension/src/repository/model/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { Disposable } from '../../class/dispose'
2323
import { sameContents } from '../../util/array'
2424
import { Data } from '../data'
2525
import { isDirectory } from '../../fileSystem'
26+
import { DOT_DVC } from '../../cli/dvc/constants'
2627

2728
export class RepositoryModel extends Disposable {
2829
private readonly dvcRoot: string
@@ -156,7 +157,7 @@ export class RepositoryModel extends Disposable {
156157
),
157158
untracked: [...untracked]
158159
.filter(
159-
path => extname(path) !== '.dvc' && basename(path) !== '.gitignore'
160+
path => extname(path) !== DOT_DVC && basename(path) !== '.gitignore'
160161
)
161162
.map(path => ({
162163
contextValue: SourceControlDataStatus.UNTRACKED,

extension/src/repository/model/tree.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import {
4646
getErrorTooltip
4747
} from '../../tree'
4848
import { getWorkspaceFolders } from '../../vscode/workspaceFolders'
49+
import { DOT_DVC } from '../../cli/dvc/constants'
4950

5051
export class RepositoriesTree
5152
extends Disposable
@@ -174,7 +175,7 @@ export class RepositoriesTree
174175
}
175176

176177
private getDataPlaceholder({ fsPath }: { fsPath: string }): string {
177-
return fsPath.trim() + '.dvc'
178+
return fsPath.trim() + DOT_DVC
178179
}
179180

180181
private getContextValue({ fsPath }: Uri, isDirectory: boolean): string {

extension/src/setup/index.ts

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Event, EventEmitter, ViewColumn, commands } from 'vscode'
2+
import { Disposable, Disposer } from '@hediet/std/disposable'
23
import isEmpty from 'lodash.isempty'
34
import { SetupData as TSetupData } from './webview/contract'
45
import { WebviewMessages } from './webview/messages'
@@ -35,9 +36,14 @@ import { WorkspaceExperiments } from '../experiments/workspace'
3536
import { DvcRunner } from '../cli/dvc/runner'
3637
import { sendTelemetryEvent, sendTelemetryEventAndThrow } from '../telemetry'
3738
import { StopWatch } from '../util/time'
38-
import { createFileSystemWatcher } from '../fileSystem/watcher'
39+
import {
40+
createFileSystemWatcher,
41+
getRelativePattern
42+
} from '../fileSystem/watcher'
3943
import { EventName } from '../telemetry/constants'
4044
import { WorkspaceScale } from '../telemetry/collect'
45+
import { gitPath } from '../cli/git/constants'
46+
import { DOT_DVC } from '../cli/dvc/constants'
4147

4248
export type SetupWebviewWebview = BaseWebview<TSetupData>
4349

@@ -74,6 +80,8 @@ export class Setup
7480
private cliAccessible = false
7581
private cliCompatible: boolean | undefined
7682

83+
private dotFolderWatcher?: Disposer
84+
7785
constructor(
7886
stopWatch: StopWatch,
7987
config: Config,
@@ -143,6 +151,7 @@ export class Setup
143151
)
144152
this.watchForVenvChanges()
145153
this.watchExecutionDetailsForChanges()
154+
this.watchDotFolderForChanges()
146155
this.watchPathForChanges(stopWatch)
147156
}
148157

@@ -280,13 +289,16 @@ export class Setup
280289
private setProjectAvailability() {
281290
const available = this.hasRoots()
282291
setContextValue('dvc.project.available', available)
292+
if (available && this.dotFolderWatcher && !this.dotFolderWatcher.disposed) {
293+
this.dispose.untrack(this.dotFolderWatcher)
294+
this.dotFolderWatcher.dispose()
295+
}
283296
}
284297

285298
private async initializeDvc() {
286299
const root = getFirstWorkspaceFolder()
287300
if (root) {
288301
await this.dvcExecutor.init(root)
289-
this.workspaceChanged.fire()
290302
}
291303
}
292304

@@ -324,7 +336,6 @@ export class Setup
324336
const cwd = getFirstWorkspaceFolder()
325337
if (cwd) {
326338
this.gitExecutor.init(cwd)
327-
this.workspaceChanged.fire()
328339
}
329340
}
330341

@@ -449,12 +460,46 @@ export class Setup
449460
previousPythonBinPath !== this.config.getPythonBinPath()
450461

451462
if (!this.cliAccessible || !this.cliCompatible || trySetupWithVenv) {
452-
run(this)
463+
this.workspaceChanged.fire()
453464
}
454465
}
455466
)
456467
}
457468

469+
private watchDotFolderForChanges() {
470+
const cwd = getFirstWorkspaceFolder()
471+
472+
if (!cwd) {
473+
return
474+
}
475+
476+
const disposer = Disposable.fn()
477+
this.dotFolderWatcher = disposer
478+
this.dispose.track(this.dotFolderWatcher)
479+
480+
return createFileSystemWatcher(
481+
disposable => disposer.track(disposable),
482+
getRelativePattern(cwd, '**'),
483+
path => this.dotFolderListener(disposer, path)
484+
)
485+
}
486+
487+
private dotFolderListener(disposer: Disposer, path: string) {
488+
if (
489+
!(path && (path.endsWith(gitPath.DOT_GIT) || path.includes(DOT_DVC))) ||
490+
disposer.disposed
491+
) {
492+
return
493+
}
494+
495+
if (path.includes(DOT_DVC)) {
496+
this.dispose.untrack(disposer)
497+
disposer.dispose()
498+
}
499+
500+
return this.workspaceChanged.fire()
501+
}
502+
458503
private async getEventProperties() {
459504
return {
460505
...(this.cliAccessible ? await this.collectWorkspaceScale() : {}),

extension/src/test/cli/util.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { exists } from '../../fileSystem'
77
import { getVenvBinPath } from '../../python/path'
88
import { dvcDemoPath } from '../util'
99
import { GitExecutor } from '../../cli/git/executor'
10+
import { DOT_DVC } from '../../cli/dvc/constants'
1011

1112
const config = {
1213
getCliPath: () => '',
@@ -26,7 +27,7 @@ export const initializeDemoRepo = (): Promise<string> => {
2627
}
2728

2829
export const initializeEmptyRepo = async (): Promise<string> => {
29-
if (exists(join(TEMP_DIR, '.dvc'))) {
30+
if (exists(join(TEMP_DIR, DOT_DVC))) {
3031
return ''
3132
}
3233

extension/src/test/suite/extension.test.ts

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -343,33 +343,6 @@ suite('Extension Test Suite', () => {
343343
})
344344
})
345345

346-
describe('dvc.init', () => {
347-
it('should be able to run dvc.init without error', async () => {
348-
const mockInit = stub(DvcExecutor.prototype, 'init').resolves('')
349-
const mockSetup = stub(Setup, 'run')
350-
const mockSetupCalled = new Promise(resolve =>
351-
mockSetup.callsFake(() => {
352-
resolve(undefined)
353-
return Promise.resolve(undefined)
354-
})
355-
)
356-
357-
await commands.executeCommand(RegisteredCliCommands.INIT)
358-
await mockSetupCalled
359-
expect(mockInit).to.be.calledOnce
360-
expect(mockSetup).to.be.calledOnce
361-
362-
mockInit.resetHistory()
363-
mockSetup.resetHistory()
364-
stub(WorkspaceFolders, 'getFirstWorkspaceFolder').returns(undefined)
365-
366-
await commands.executeCommand(RegisteredCliCommands.INIT)
367-
368-
expect(mockInit).not.to.be.called
369-
expect(mockSetup).not.to.be.called
370-
})
371-
})
372-
373346
describe('dvc.showCommands', () => {
374347
it('should show all of the dvc commands without error', async () => {
375348
await expect(

0 commit comments

Comments
 (0)