Skip to content

Commit 270c032

Browse files
authored
Replace git diff call with git extension state (#4444)
1 parent dde8ae6 commit 270c032

File tree

10 files changed

+209
-118
lines changed

10 files changed

+209
-118
lines changed

extension/src/cli/git/reader.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ export const autoRegisteredCommands = {
1313
GIT_GET_REMOTE_EXPERIMENT_REFS: 'getRemoteExperimentRefs',
1414
GIT_GET_REMOTE_URL: 'getRemoteUrl',
1515
GIT_GET_REPOSITORY_ROOT: 'getGitRepositoryRoot',
16-
GIT_HAS_CHANGES: 'hasChanges',
1716
GIT_HAS_NO_COMMITS: 'hasNoCommits',
1817
GIT_LIST_UNTRACKED: 'listUntracked',
1918
GIT_VERSION: 'gitVersion'
@@ -25,16 +24,6 @@ export class GitReader extends GitCli {
2524
this
2625
)
2726

28-
public async hasChanges(cwd: string) {
29-
const options = getOptions({
30-
args: [Command.DIFF, Flag.NAME_ONLY, Flag.RAW_WITH_NUL],
31-
cwd
32-
})
33-
const output = await this.executeProcess(options)
34-
35-
return !!output
36-
}
37-
3827
public async hasNoCommits(cwd: string) {
3928
const options = getOptions({
4029
args: [Command.REV_LIST, Flag.NUMBER, '1', Flag.ALL],

extension/src/extensions/git.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { Event, Uri } from 'vscode'
2+
import { getExtensionAPI } from '../vscode/extensions'
3+
4+
const enum Status {
5+
INDEX_MODIFIED,
6+
INDEX_ADDED,
7+
INDEX_DELETED,
8+
INDEX_RENAMED,
9+
INDEX_COPIED,
10+
11+
MODIFIED,
12+
DELETED,
13+
UNTRACKED,
14+
IGNORED,
15+
INTENT_TO_ADD,
16+
INTENT_TO_RENAME,
17+
TYPE_CHANGED,
18+
19+
ADDED_BY_US,
20+
ADDED_BY_THEM,
21+
DELETED_BY_US,
22+
DELETED_BY_THEM,
23+
BOTH_ADDED,
24+
BOTH_DELETED,
25+
BOTH_MODIFIED
26+
}
27+
28+
type Change = {
29+
readonly uri: Uri
30+
readonly originalUri: Uri
31+
readonly renameUri: Uri | undefined
32+
readonly status: Status
33+
}
34+
35+
export type RepositoryState = {
36+
readonly mergeChanges: Change[]
37+
readonly indexChanges: Change[]
38+
readonly workingTreeChanges: Change[]
39+
readonly onDidChange: Event<void>
40+
}
41+
42+
type Repository = {
43+
readonly rootUri: Uri
44+
readonly state: RepositoryState
45+
}
46+
47+
enum APIState {
48+
INITIALIZED = 'initialized',
49+
UNINITIALIZED = 'uninitialized'
50+
}
51+
52+
type ExtensionAPI = {
53+
readonly state: APIState
54+
readonly onDidChangeState: Event<APIState>
55+
getRepository(uri: Uri): Repository | null
56+
}
57+
58+
type VscodeGit = {
59+
readonly enabled: boolean
60+
readonly onDidChangeEnablement: Event<boolean>
61+
62+
getAPI(version: number): Thenable<ExtensionAPI>
63+
}
64+
65+
const isReady = (api: ExtensionAPI) =>
66+
new Promise(resolve => {
67+
const listener = api.onDidChangeState(state => {
68+
if (state === APIState.INITIALIZED) {
69+
listener.dispose()
70+
resolve(undefined)
71+
}
72+
})
73+
74+
if (api.state === APIState.INITIALIZED) {
75+
listener.dispose()
76+
resolve(undefined)
77+
}
78+
})
79+
80+
export const getGitExtensionAPI = async (): Promise<
81+
ExtensionAPI | undefined
82+
> => {
83+
const extension = await getExtensionAPI<VscodeGit>('vscode.git')
84+
if (!extension) {
85+
return
86+
}
87+
const api = await extension.getAPI(1)
88+
89+
await isReady(api)
90+
return api
91+
}

extension/src/repository/data/index.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import { getGitPath, isPathInProject } from '../../fileSystem'
1717

1818
export type Data = {
1919
dataStatus: DataStatusOutput | DvcError
20-
hasGitChanges: boolean
2120
untracked: Set<string>
2221
}
2322

@@ -88,15 +87,11 @@ export class RepositoryData extends DeferredDisposable {
8887
}
8988

9089
private async update() {
91-
const [dataStatus, hasGitChanges, untracked] = await Promise.all([
90+
const [dataStatus, untracked] = await Promise.all([
9291
this.internalCommands.executeCommand<DataStatusOutput | DvcError>(
9392
AvailableCommands.DATA_STATUS,
9493
this.dvcRoot
9594
),
96-
this.internalCommands.executeCommand<boolean>(
97-
AvailableCommands.GIT_HAS_CHANGES,
98-
this.dvcRoot
99-
),
10095
this.internalCommands.executeCommand<Set<string>>(
10196
AvailableCommands.GIT_LIST_UNTRACKED,
10297
this.dvcRoot
@@ -105,7 +100,6 @@ export class RepositoryData extends DeferredDisposable {
105100

106101
return this.notifyChanged({
107102
dataStatus,
108-
hasGitChanges,
109103
untracked
110104
})
111105
}

extension/src/repository/index.ts

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
import { EventEmitter } from 'vscode'
1+
import { EventEmitter, Uri } from 'vscode'
22
import {
33
DecorationProvider as ScmDecorationProvider,
44
ScmDecorationState
55
} from './sourceControlManagement/decorationProvider'
66
import { RepositoryData } from './data'
77
import { RepositoryModel } from './model'
88
import { SourceControlManagement, SCMState } from './sourceControlManagement'
9-
import { InternalCommands } from '../commands/internal'
9+
import { AvailableCommands, InternalCommands } from '../commands/internal'
1010
import { DeferredDisposable } from '../class/deferred'
1111
import { ErrorDecorationProvider } from '../tree/decorationProvider/error'
1212
import { DecoratableTreeItemScheme } from '../tree'
13+
import { getGitExtensionAPI } from '../extensions/git'
1314

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

3637
this.dvcRoot = dvcRoot
38+
3739
this.model = this.dispose.track(new RepositoryModel(dvcRoot))
3840
this.data = this.dispose.track(
3941
new RepositoryData(dvcRoot, internalCommands, subProjects)
@@ -54,7 +56,10 @@ export class Repository extends DeferredDisposable {
5456

5557
this.treeDataChanged = treeDataChanged
5658

57-
void this.initialize()
59+
void Promise.all([
60+
this.watchGitExtension(internalCommands),
61+
this.initializeData()
62+
]).then(() => this.deferred.resolve())
5863
}
5964

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

76-
private async initialize() {
81+
private initializeData() {
7782
this.dispose.track(
7883
this.data.onDidUpdate(data => {
79-
const state = this.model.transformAndSet(data)
84+
const state = this.model.transformAndSetCli(data)
8085
this.notifyChanged(state)
8186
})
8287
)
8388

84-
await this.data.isReady()
85-
return this.deferred.resolve()
89+
return this.data.isReady()
8690
}
8791

8892
private notifyChanged({
@@ -99,4 +103,28 @@ export class Repository extends DeferredDisposable {
99103
this.scmDecorationProvider.setState(scmDecorationState)
100104
this.errorDecorationProvider.setState(errorDecorationState)
101105
}
106+
107+
private async watchGitExtension(internalCommands: InternalCommands) {
108+
const gitRoot = await internalCommands.executeCommand(
109+
AvailableCommands.GIT_GET_REPOSITORY_ROOT,
110+
this.dvcRoot
111+
)
112+
113+
const api = await getGitExtensionAPI()
114+
if (!api) {
115+
return
116+
}
117+
const repository = api.getRepository(Uri.file(gitRoot))
118+
if (!repository) {
119+
return
120+
}
121+
122+
this.dispose.track(
123+
repository.state.onDidChange(() =>
124+
this.model.transformAndSetGit(repository.state)
125+
)
126+
)
127+
128+
return this.model.transformAndSetGit(repository.state)
129+
}
102130
}

extension/src/repository/model/index.test.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,8 @@ describe('RepositoryModel', () => {
5858

5959
const model = new RepositoryModel(dvcDemoPath)
6060
const { scmDecorationState, sourceControlManagementState } =
61-
model.transformAndSet({
61+
model.transformAndSetCli({
6262
dataStatus,
63-
hasGitChanges: true,
6463
untracked: new Set()
6564
})
6665

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

155154
const model = new RepositoryModel(dvcDemoPath)
156155
const { scmDecorationState, sourceControlManagementState } =
157-
model.transformAndSet({
156+
model.transformAndSetCli({
158157
dataStatus,
159-
hasGitChanges: false,
160158
untracked: new Set()
161159
})
162160

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

204202
const model = new RepositoryModel(dvcDemoPath)
205203
const { scmDecorationState, sourceControlManagementState } =
206-
model.transformAndSet({
204+
model.transformAndSetCli({
207205
dataStatus,
208-
hasGitChanges: true,
209206
untracked: new Set()
210207
})
211208

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

252-
model.transformAndSet(emptyRepoData)
249+
model.transformAndSetCli(emptyRepoData)
253250
expect(model.getChildren(dvcDemoPath)).toStrictEqual([])
254251

255-
model.transformAndSet(emptyRepoError)
252+
model.transformAndSetCli(emptyRepoError)
256253
expect(model.getChildren(dvcDemoPath)).toStrictEqual([{ error: msg }])
257254

258-
model.transformAndSet(emptyRepoData)
255+
model.transformAndSetCli(emptyRepoData)
259256
expect(model.getChildren(dvcDemoPath)).toStrictEqual([])
260257
})
261258
})

extension/src/repository/model/index.ts

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,16 @@ import { isDvcError } from '../../cli/dvc/reader'
2222
import { Disposable } from '../../class/dispose'
2323
import { sameContents } from '../../util/array'
2424
import { Data } from '../data'
25-
import { isDirectory } from '../../fileSystem'
25+
import { isDirectory, isSameOrChild } from '../../fileSystem'
2626
import { DOT_DVC } from '../../cli/dvc/constants'
2727
import { getCliErrorLabel } from '../../tree'
28+
import { RepositoryState as GitRepositoryState } from '../../extensions/git'
2829

2930
export class RepositoryModel extends Disposable {
3031
private readonly dvcRoot: string
3132

32-
private hasChanges = false
33+
private hasGitChanges = false
34+
private hasDvcChanges = false
3335

3436
private tracked = new Set<string>()
3537

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

52-
public transformAndSet({ dataStatus, hasGitChanges, untracked }: Data) {
54+
public async transformAndSetGit(state: GitRepositoryState) {
55+
const [workingTreeChanges, indexChanges, mergeChanges] = await Promise.all([
56+
state.workingTreeChanges.filter(({ uri }) =>
57+
isSameOrChild(this.dvcRoot, uri.fsPath)
58+
),
59+
state.indexChanges.filter(({ uri }) =>
60+
isSameOrChild(this.dvcRoot, uri.fsPath)
61+
),
62+
state.mergeChanges.filter(({ uri }) =>
63+
isSameOrChild(this.dvcRoot, uri.fsPath)
64+
)
65+
])
66+
67+
this.hasGitChanges =
68+
workingTreeChanges.length > 0 ||
69+
indexChanges.length > 0 ||
70+
mergeChanges.length > 0
71+
}
72+
73+
public transformAndSetCli({ dataStatus, untracked }: Data) {
5374
if (isDvcError(dataStatus)) {
5475
return this.handleCliError(dataStatus)
5576
}
@@ -59,7 +80,7 @@ export class RepositoryModel extends Disposable {
5980
untracked: [...untracked].map(path => relative(this.dvcRoot, path))
6081
})
6182
this.collectTree(data.tracked)
62-
this.collectHasChanges(data, hasGitChanges)
83+
this.collectHasDvcChanges(data)
6384

6485
return {
6586
scmDecorationState: this.getScmDecorationState(data),
@@ -68,12 +89,12 @@ export class RepositoryModel extends Disposable {
6889
}
6990

7091
public getHasChanges(): boolean {
71-
return this.hasChanges
92+
return this.hasGitChanges || this.hasDvcChanges
7293
}
7394

7495
private handleCliError({ error: { msg } }: DvcError) {
7596
const emptyState = createDataStatusAccumulator()
76-
this.hasChanges = true
97+
this.hasDvcChanges = true
7798

7899
this.tracked = new Set()
79100
this.tree = createTreeFromError(this.dvcRoot, msg)
@@ -97,12 +118,8 @@ export class RepositoryModel extends Disposable {
97118
}
98119
}
99120

100-
private collectHasChanges(
101-
data: DataStatusAccumulator,
102-
hasGitChanges: boolean
103-
) {
104-
this.hasChanges = !!(
105-
hasGitChanges ||
121+
private collectHasDvcChanges(data: DataStatusAccumulator) {
122+
this.hasDvcChanges = !!(
106123
data.committedAdded.size > 0 ||
107124
data.committedDeleted.size > 0 ||
108125
data.committedModified.size > 0 ||

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,6 @@ suite('Extension Test Suite', () => {
153153
'isReady'
154154
)
155155

156-
stub(GitReader.prototype, 'hasChanges').resolves(false)
157156
stub(GitReader.prototype, 'listUntracked').resolves(new Set())
158157

159158
const workspaceExperimentsAreReady = new Promise(resolve =>

0 commit comments

Comments
 (0)