Skip to content

Commit

Permalink
Replace git diff call with git extension state (#4444)
Browse files Browse the repository at this point in the history
  • Loading branch information
mattseddon committed Aug 8, 2023
1 parent dde8ae6 commit 270c032
Show file tree
Hide file tree
Showing 10 changed files with 209 additions and 118 deletions.
11 changes: 0 additions & 11 deletions extension/src/cli/git/reader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ export const autoRegisteredCommands = {
GIT_GET_REMOTE_EXPERIMENT_REFS: 'getRemoteExperimentRefs',
GIT_GET_REMOTE_URL: 'getRemoteUrl',
GIT_GET_REPOSITORY_ROOT: 'getGitRepositoryRoot',
GIT_HAS_CHANGES: 'hasChanges',
GIT_HAS_NO_COMMITS: 'hasNoCommits',
GIT_LIST_UNTRACKED: 'listUntracked',
GIT_VERSION: 'gitVersion'
Expand All @@ -25,16 +24,6 @@ export class GitReader extends GitCli {
this
)

public async hasChanges(cwd: string) {
const options = getOptions({
args: [Command.DIFF, Flag.NAME_ONLY, Flag.RAW_WITH_NUL],
cwd
})
const output = await this.executeProcess(options)

return !!output
}

public async hasNoCommits(cwd: string) {
const options = getOptions({
args: [Command.REV_LIST, Flag.NUMBER, '1', Flag.ALL],
Expand Down
91 changes: 91 additions & 0 deletions extension/src/extensions/git.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Event, Uri } from 'vscode'
import { getExtensionAPI } from '../vscode/extensions'

const enum Status {
INDEX_MODIFIED,
INDEX_ADDED,
INDEX_DELETED,
INDEX_RENAMED,
INDEX_COPIED,

MODIFIED,
DELETED,
UNTRACKED,
IGNORED,
INTENT_TO_ADD,
INTENT_TO_RENAME,
TYPE_CHANGED,

ADDED_BY_US,
ADDED_BY_THEM,
DELETED_BY_US,
DELETED_BY_THEM,
BOTH_ADDED,
BOTH_DELETED,
BOTH_MODIFIED
}

type Change = {
readonly uri: Uri
readonly originalUri: Uri
readonly renameUri: Uri | undefined
readonly status: Status
}

export type RepositoryState = {
readonly mergeChanges: Change[]
readonly indexChanges: Change[]
readonly workingTreeChanges: Change[]
readonly onDidChange: Event<void>
}

type Repository = {
readonly rootUri: Uri
readonly state: RepositoryState
}

enum APIState {
INITIALIZED = 'initialized',
UNINITIALIZED = 'uninitialized'
}

type ExtensionAPI = {
readonly state: APIState
readonly onDidChangeState: Event<APIState>
getRepository(uri: Uri): Repository | null
}

type VscodeGit = {
readonly enabled: boolean
readonly onDidChangeEnablement: Event<boolean>

getAPI(version: number): Thenable<ExtensionAPI>
}

const isReady = (api: ExtensionAPI) =>
new Promise(resolve => {
const listener = api.onDidChangeState(state => {
if (state === APIState.INITIALIZED) {
listener.dispose()
resolve(undefined)
}
})

if (api.state === APIState.INITIALIZED) {
listener.dispose()
resolve(undefined)
}
})

export const getGitExtensionAPI = async (): Promise<
ExtensionAPI | undefined
> => {
const extension = await getExtensionAPI<VscodeGit>('vscode.git')
if (!extension) {
return
}
const api = await extension.getAPI(1)

await isReady(api)
return api
}
8 changes: 1 addition & 7 deletions extension/src/repository/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { getGitPath, isPathInProject } from '../../fileSystem'

export type Data = {
dataStatus: DataStatusOutput | DvcError
hasGitChanges: boolean
untracked: Set<string>
}

Expand Down Expand Up @@ -88,15 +87,11 @@ export class RepositoryData extends DeferredDisposable {
}

private async update() {
const [dataStatus, hasGitChanges, untracked] = await Promise.all([
const [dataStatus, untracked] = await Promise.all([
this.internalCommands.executeCommand<DataStatusOutput | DvcError>(
AvailableCommands.DATA_STATUS,
this.dvcRoot
),
this.internalCommands.executeCommand<boolean>(
AvailableCommands.GIT_HAS_CHANGES,
this.dvcRoot
),
this.internalCommands.executeCommand<Set<string>>(
AvailableCommands.GIT_LIST_UNTRACKED,
this.dvcRoot
Expand All @@ -105,7 +100,6 @@ export class RepositoryData extends DeferredDisposable {

return this.notifyChanged({
dataStatus,
hasGitChanges,
untracked
})
}
Expand Down
42 changes: 35 additions & 7 deletions extension/src/repository/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { EventEmitter } from 'vscode'
import { EventEmitter, Uri } from 'vscode'
import {
DecorationProvider as ScmDecorationProvider,
ScmDecorationState
} from './sourceControlManagement/decorationProvider'
import { RepositoryData } from './data'
import { RepositoryModel } from './model'
import { SourceControlManagement, SCMState } from './sourceControlManagement'
import { InternalCommands } from '../commands/internal'
import { AvailableCommands, InternalCommands } from '../commands/internal'
import { DeferredDisposable } from '../class/deferred'
import { ErrorDecorationProvider } from '../tree/decorationProvider/error'
import { DecoratableTreeItemScheme } from '../tree'
import { getGitExtensionAPI } from '../extensions/git'

export const RepositoryScale = {
TRACKED: 'tracked'
Expand All @@ -34,6 +35,7 @@ export class Repository extends DeferredDisposable {
super()

this.dvcRoot = dvcRoot

this.model = this.dispose.track(new RepositoryModel(dvcRoot))
this.data = this.dispose.track(
new RepositoryData(dvcRoot, internalCommands, subProjects)
Expand All @@ -54,7 +56,10 @@ export class Repository extends DeferredDisposable {

this.treeDataChanged = treeDataChanged

void this.initialize()
void Promise.all([
this.watchGitExtension(internalCommands),
this.initializeData()
]).then(() => this.deferred.resolve())
}

public getChildren(path: string) {
Expand All @@ -73,16 +78,15 @@ export class Repository extends DeferredDisposable {
return { tracked: this.model.getScale() }
}

private async initialize() {
private initializeData() {
this.dispose.track(
this.data.onDidUpdate(data => {
const state = this.model.transformAndSet(data)
const state = this.model.transformAndSetCli(data)
this.notifyChanged(state)
})
)

await this.data.isReady()
return this.deferred.resolve()
return this.data.isReady()
}

private notifyChanged({
Expand All @@ -99,4 +103,28 @@ export class Repository extends DeferredDisposable {
this.scmDecorationProvider.setState(scmDecorationState)
this.errorDecorationProvider.setState(errorDecorationState)
}

private async watchGitExtension(internalCommands: InternalCommands) {
const gitRoot = await internalCommands.executeCommand(
AvailableCommands.GIT_GET_REPOSITORY_ROOT,
this.dvcRoot
)

const api = await getGitExtensionAPI()
if (!api) {
return
}
const repository = api.getRepository(Uri.file(gitRoot))
if (!repository) {
return
}

this.dispose.track(
repository.state.onDidChange(() =>
this.model.transformAndSetGit(repository.state)
)
)

return this.model.transformAndSetGit(repository.state)
}
}
15 changes: 6 additions & 9 deletions extension/src/repository/model/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,8 @@ describe('RepositoryModel', () => {

const model = new RepositoryModel(dvcDemoPath)
const { scmDecorationState, sourceControlManagementState } =
model.transformAndSet({
model.transformAndSetCli({
dataStatus,
hasGitChanges: true,
untracked: new Set()
})

Expand Down Expand Up @@ -154,9 +153,8 @@ describe('RepositoryModel', () => {

const model = new RepositoryModel(dvcDemoPath)
const { scmDecorationState, sourceControlManagementState } =
model.transformAndSet({
model.transformAndSetCli({
dataStatus,
hasGitChanges: false,
untracked: new Set()
})

Expand Down Expand Up @@ -203,9 +201,8 @@ describe('RepositoryModel', () => {

const model = new RepositoryModel(dvcDemoPath)
const { scmDecorationState, sourceControlManagementState } =
model.transformAndSet({
model.transformAndSetCli({
dataStatus,
hasGitChanges: true,
untracked: new Set()
})

Expand Down Expand Up @@ -249,13 +246,13 @@ describe('RepositoryModel', () => {
const emptyRepoError = { ...emptyRepoData, dataStatus: { error } }
const model = new RepositoryModel(dvcDemoPath)

model.transformAndSet(emptyRepoData)
model.transformAndSetCli(emptyRepoData)
expect(model.getChildren(dvcDemoPath)).toStrictEqual([])

model.transformAndSet(emptyRepoError)
model.transformAndSetCli(emptyRepoError)
expect(model.getChildren(dvcDemoPath)).toStrictEqual([{ error: msg }])

model.transformAndSet(emptyRepoData)
model.transformAndSetCli(emptyRepoData)
expect(model.getChildren(dvcDemoPath)).toStrictEqual([])
})
})
Expand Down
41 changes: 29 additions & 12 deletions extension/src/repository/model/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,16 @@ import { isDvcError } from '../../cli/dvc/reader'
import { Disposable } from '../../class/dispose'
import { sameContents } from '../../util/array'
import { Data } from '../data'
import { isDirectory } from '../../fileSystem'
import { isDirectory, isSameOrChild } from '../../fileSystem'
import { DOT_DVC } from '../../cli/dvc/constants'
import { getCliErrorLabel } from '../../tree'
import { RepositoryState as GitRepositoryState } from '../../extensions/git'

export class RepositoryModel extends Disposable {
private readonly dvcRoot: string

private hasChanges = false
private hasGitChanges = false
private hasDvcChanges = false

private tracked = new Set<string>()

Expand All @@ -49,7 +51,26 @@ export class RepositoryModel extends Disposable {
return this.tree.get(path) || []
}

public transformAndSet({ dataStatus, hasGitChanges, untracked }: Data) {
public async transformAndSetGit(state: GitRepositoryState) {
const [workingTreeChanges, indexChanges, mergeChanges] = await Promise.all([
state.workingTreeChanges.filter(({ uri }) =>
isSameOrChild(this.dvcRoot, uri.fsPath)
),
state.indexChanges.filter(({ uri }) =>
isSameOrChild(this.dvcRoot, uri.fsPath)
),
state.mergeChanges.filter(({ uri }) =>
isSameOrChild(this.dvcRoot, uri.fsPath)
)
])

this.hasGitChanges =
workingTreeChanges.length > 0 ||
indexChanges.length > 0 ||
mergeChanges.length > 0
}

public transformAndSetCli({ dataStatus, untracked }: Data) {
if (isDvcError(dataStatus)) {
return this.handleCliError(dataStatus)
}
Expand All @@ -59,7 +80,7 @@ export class RepositoryModel extends Disposable {
untracked: [...untracked].map(path => relative(this.dvcRoot, path))
})
this.collectTree(data.tracked)
this.collectHasChanges(data, hasGitChanges)
this.collectHasDvcChanges(data)

return {
scmDecorationState: this.getScmDecorationState(data),
Expand All @@ -68,12 +89,12 @@ export class RepositoryModel extends Disposable {
}

public getHasChanges(): boolean {
return this.hasChanges
return this.hasGitChanges || this.hasDvcChanges
}

private handleCliError({ error: { msg } }: DvcError) {
const emptyState = createDataStatusAccumulator()
this.hasChanges = true
this.hasDvcChanges = true

this.tracked = new Set()
this.tree = createTreeFromError(this.dvcRoot, msg)
Expand All @@ -97,12 +118,8 @@ export class RepositoryModel extends Disposable {
}
}

private collectHasChanges(
data: DataStatusAccumulator,
hasGitChanges: boolean
) {
this.hasChanges = !!(
hasGitChanges ||
private collectHasDvcChanges(data: DataStatusAccumulator) {
this.hasDvcChanges = !!(
data.committedAdded.size > 0 ||
data.committedDeleted.size > 0 ||
data.committedModified.size > 0 ||
Expand Down
1 change: 0 additions & 1 deletion extension/src/test/suite/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,6 @@ suite('Extension Test Suite', () => {
'isReady'
)

stub(GitReader.prototype, 'hasChanges').resolves(false)
stub(GitReader.prototype, 'listUntracked').resolves(new Set())

const workspaceExperimentsAreReady = new Promise(resolve =>
Expand Down
Loading

0 comments on commit 270c032

Please sign in to comment.