Skip to content

Commit 73e07c2

Browse files
committed
Fixes #4078 deadlock on paused operation detection
- Adds operation priority ordering for proper detection
1 parent f4d6203 commit 73e07c2

File tree

8 files changed

+132
-70
lines changed

8 files changed

+132
-70
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
2525

2626
### Fixed
2727

28-
- Fixes [#3218](https://github.com/gitkraken/vscode-gitlens/issues/3218) - Bitbucket Server remote - "scm/" path prefix not removed (regression)
28+
- Fixes GitLens gets stuck after rebase ([#4078](https://github.com/gitkraken/vscode-gitlens/issues/4078))
29+
- Fixes Bitbucket Server remote - "scm/" path prefix not removed (regression) ([#3218](https://github.com/gitkraken/vscode-gitlens/issues/3218))
2930
- Fixes avoid eagerly getting "full" commit details for inline blame ([#4115])(https://github.com/gitkraken/vscode-gitlens/issues/4115))
3031
- Fixes large commit messages work poorly on Commit Graph ([#4100](https://github.com/gitkraken/vscode-gitlens/issues/4100))
3132
- Fixes _Show \* View_ commands fail intermittently ([#4127](https://github.com/gitkraken/vscode-gitlens/issues/4127))

src/env/node/git/sub-providers/branches.ts

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,17 @@ import { getReferenceFromBranch } from '../../../../git/utils/-webview/reference
1818
import type { BranchSortOptions } from '../../../../git/utils/-webview/sorting';
1919
import { sortBranches, sortContributors } from '../../../../git/utils/-webview/sorting';
2020
import {
21+
formatDetachedHeadName,
22+
getBranchId,
2123
getLocalBranchByUpstream,
2224
isDetachedHead,
2325
isRemoteHEAD,
2426
parseUpstream,
2527
} from '../../../../git/utils/branch.utils';
28+
import { createReference } from '../../../../git/utils/reference.utils';
2629
import { createRevisionRange } from '../../../../git/utils/revision.utils';
2730
import { configuration } from '../../../../system/-webview/configuration';
2831
import { filterMap } from '../../../../system/array';
29-
import { gate } from '../../../../system/decorators/-webview/gate';
3032
import { log } from '../../../../system/decorators/log';
3133
import { Logger } from '../../../../system/logger';
3234
import { getLogScope } from '../../../../system/logger.scope';
@@ -49,7 +51,6 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
4951
private readonly provider: LocalGitProvider,
5052
) {}
5153

52-
@gate()
5354
@log()
5455
async getBranch(repoPath: string, name?: string): Promise<GitBranch | undefined> {
5556
if (name != null) {
@@ -76,15 +77,13 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
7677
}
7778

7879
private async getCurrentBranch(repoPath: string): Promise<GitBranch | undefined> {
79-
const commitOrdering = configuration.get('advanced.commitOrdering');
80+
const ref = await this.getCurrentBranchReferenceCore(repoPath);
81+
if (ref == null) return undefined;
8082

81-
const data = await this.git.rev_parse__currentBranch(repoPath, commitOrdering);
82-
if (data == null) return undefined;
83-
84-
const [name, upstream] = data[0].split('\n');
83+
const commitOrdering = configuration.get('advanced.commitOrdering');
8584

8685
const [pausedOpStatusResult, committerDateResult, defaultWorktreePathResult] = await Promise.allSettled([
87-
isDetachedHead(name) ? this.provider.status?.getPausedOperationStatus(repoPath) : undefined,
86+
isDetachedHead(ref.name) ? this.provider.status?.getPausedOperationStatus(repoPath) : undefined,
8887
this.git
8988
.exec(
9089
{ cwd: repoPath, configs: gitLogDefaultConfigs, errors: GitErrorHandling.Ignore },
@@ -106,11 +105,11 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
106105
return new GitBranch(
107106
this.container,
108107
repoPath,
109-
rebaseStatus?.incoming.name ?? `refs/heads/${name}`,
108+
rebaseStatus?.incoming.name ?? `refs/heads/${ref.name}`,
110109
true,
111110
committerDate != null ? new Date(Number(committerDate) * 1000) : undefined,
112-
data[1],
113-
upstream ? { name: upstream, missing: false, state: { ahead: 0, behind: 0 } } : undefined,
111+
ref.sha,
112+
ref.upstream ? { ...ref.upstream, state: { ahead: 0, behind: 0 } } : undefined,
114113
{ path: repoPath, isDefault: repoPath === defaultWorktreePath },
115114
undefined,
116115
rebaseStatus != null,
@@ -338,6 +337,40 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
338337
return filterMap(data.split('\n'), b => b.trim() || undefined);
339338
}
340339

340+
@log()
341+
async getCurrentBranchReference(repoPath: string): Promise<GitBranchReference | undefined> {
342+
let ref = await this.getCurrentBranchReferenceCore(repoPath);
343+
if (ref != null && isDetachedHead(ref.name)) {
344+
ref = createReference(ref.sha!, repoPath, {
345+
refType: 'branch',
346+
name: formatDetachedHeadName(ref.sha!),
347+
id: getBranchId(repoPath, ref.remote, ref.sha!),
348+
remote: ref.remote,
349+
upstream: ref.upstream,
350+
sha: ref.sha,
351+
});
352+
}
353+
return ref;
354+
}
355+
356+
private async getCurrentBranchReferenceCore(repoPath: string): Promise<GitBranchReference | undefined> {
357+
const commitOrdering = configuration.get('advanced.commitOrdering');
358+
359+
const data = await this.git.rev_parse__currentBranch(repoPath, commitOrdering);
360+
if (data == null) return undefined;
361+
362+
const [name, upstream] = data[0].split('\n');
363+
364+
return createReference(name, repoPath, {
365+
refType: 'branch',
366+
name: name,
367+
id: getBranchId(repoPath, false, name),
368+
remote: false,
369+
upstream: upstream ? { name: upstream, missing: false } : undefined,
370+
sha: data[1],
371+
});
372+
}
373+
341374
@log({ exit: true })
342375
async getDefaultBranchName(repoPath: string | undefined, remote?: string): Promise<string | undefined> {
343376
if (repoPath == null) return undefined;

src/env/node/git/sub-providers/status.ts

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import type { GitBranchReference, GitTagReference } from '../../../../git/models
2121
import { GitStatus } from '../../../../git/models/status';
2222
import type { GitStatusFile } from '../../../../git/models/statusFile';
2323
import { parseGitStatus } from '../../../../git/parsers/statusParser';
24-
import { getReferenceFromBranch } from '../../../../git/utils/-webview/reference.utils';
2524
import { createReference } from '../../../../git/utils/reference.utils';
2625
import { configuration } from '../../../../system/-webview/configuration';
2726
import { splitPath } from '../../../../system/-webview/path';
@@ -33,6 +32,10 @@ import type { Git } from '../git';
3332
import { GitErrors } from '../git';
3433
import type { LocalGitProvider } from '../localGitProvider';
3534

35+
type Operation = 'cherry-pick' | 'merge' | 'rebase-apply' | 'rebase-merge' | 'revert';
36+
37+
const orderedOperations: Operation[] = ['rebase-apply', 'rebase-merge', 'merge', 'cherry-pick', 'revert'];
38+
3639
export class StatusGitSubProvider implements GitStatusSubProvider {
3740
constructor(
3841
private readonly container: Container,
@@ -49,16 +52,16 @@ export class StatusGitSubProvider implements GitStatusSubProvider {
4952
async function getCore(this: StatusGitSubProvider): Promise<GitPausedOperationStatus | undefined> {
5053
const gitDir = await this.provider.config.getGitDir(repoPath);
5154

52-
type Operation = 'cherry-pick' | 'merge' | 'rebase-apply' | 'rebase-merge' | 'revert';
53-
const operation = await new Promise<Operation | undefined>((resolve, _) => {
55+
const operations = await new Promise<Set<Operation>>((resolve, _) => {
5456
readdir(gitDir.uri.fsPath, { withFileTypes: true }, (err, entries) => {
57+
const operations = new Set<Operation>();
5558
if (err != null) {
56-
resolve(undefined);
59+
resolve(operations);
5760
return;
5861
}
5962

6063
if (entries.length === 0) {
61-
resolve(undefined);
64+
resolve(operations);
6265
return;
6366
}
6467

@@ -67,32 +70,36 @@ export class StatusGitSubProvider implements GitStatusSubProvider {
6770
if (entry.isFile()) {
6871
switch (entry.name) {
6972
case 'CHERRY_PICK_HEAD':
70-
resolve('cherry-pick');
71-
return;
73+
operations.add('cherry-pick');
74+
break;
7275
case 'MERGE_HEAD':
73-
resolve('merge');
74-
return;
76+
operations.add('merge');
77+
break;
7578
case 'REVERT_HEAD':
76-
resolve('revert');
77-
return;
79+
operations.add('revert');
80+
break;
7881
}
7982
} else if (entry.isDirectory()) {
8083
switch (entry.name) {
8184
case 'rebase-apply':
82-
resolve('rebase-apply');
83-
return;
85+
operations.add('rebase-apply');
86+
break;
8487
case 'rebase-merge':
85-
resolve('rebase-merge');
86-
return;
88+
operations.add('rebase-merge');
89+
break;
8790
}
8891
}
8992
}
9093

91-
resolve(undefined);
94+
resolve(operations);
9295
});
9396
});
9497

95-
if (operation == null) return undefined;
98+
if (!operations.size) return undefined;
99+
100+
const operation = [...operations].sort(
101+
(a, b) => orderedOperations.indexOf(a) - orderedOperations.indexOf(b),
102+
)[0];
96103

97104
switch (operation) {
98105
case 'cherry-pick': {
@@ -107,14 +114,14 @@ export class StatusGitSubProvider implements GitStatusSubProvider {
107114
)?.trim();
108115
if (!cherryPickHead) return undefined;
109116

110-
const branch = (await this.provider.branches.getBranch(repoPath))!;
117+
const current = (await this.provider.branches.getCurrentBranchReference(repoPath))!;
111118

112119
return {
113120
type: 'cherry-pick',
114121
repoPath: repoPath,
115122
// TODO: Validate that these are correct
116123
HEAD: createReference(cherryPickHead, repoPath, { refType: 'revision' }),
117-
current: getReferenceFromBranch(branch),
124+
current: current,
118125
incoming: createReference(cherryPickHead, repoPath, { refType: 'revision' }),
119126
} satisfies GitCherryPickStatus;
120127
}
@@ -131,15 +138,15 @@ export class StatusGitSubProvider implements GitStatusSubProvider {
131138
if (!mergeHead) return undefined;
132139

133140
const [branchResult, mergeBaseResult, possibleSourceBranchesResult] = await Promise.allSettled([
134-
this.provider.branches.getBranch(repoPath),
141+
this.provider.branches.getCurrentBranchReference(repoPath),
135142
this.provider.refs.getMergeBase(repoPath, 'MERGE_HEAD', 'HEAD'),
136143
this.provider.branches.getBranchesWithCommits(repoPath, ['MERGE_HEAD'], undefined, {
137144
all: true,
138145
mode: 'pointsAt',
139146
}),
140147
]);
141148

142-
const branch = getSettledValue(branchResult)!;
149+
const current = getSettledValue(branchResult)!;
143150
const mergeBase = getSettledValue(mergeBaseResult);
144151
const possibleSourceBranches = getSettledValue(possibleSourceBranchesResult);
145152

@@ -148,7 +155,7 @@ export class StatusGitSubProvider implements GitStatusSubProvider {
148155
repoPath: repoPath,
149156
mergeBase: mergeBase,
150157
HEAD: createReference(mergeHead, repoPath, { refType: 'revision' }),
151-
current: getReferenceFromBranch(branch),
158+
current: current,
152159
incoming:
153160
possibleSourceBranches?.length === 1
154161
? createReference(possibleSourceBranches[0], repoPath, {
@@ -171,13 +178,13 @@ export class StatusGitSubProvider implements GitStatusSubProvider {
171178
)?.trim();
172179
if (!revertHead) return undefined;
173180

174-
const branch = (await this.provider.branches.getBranch(repoPath))!;
181+
const current = (await this.provider.branches.getCurrentBranchReference(repoPath))!;
175182

176183
return {
177184
type: 'revert',
178185
repoPath: repoPath,
179186
HEAD: createReference(revertHead, repoPath, { refType: 'revision' }),
180-
current: getReferenceFromBranch(branch),
187+
current: current,
181188
incoming: createReference(revertHead, repoPath, { refType: 'revision' }),
182189
} satisfies GitRevertStatus;
183190
}

src/git/gitProvider.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,8 @@ export interface GitBranchesSubProvider {
232232
branch: GitBranchReference,
233233
into: GitBranchReference,
234234
): Promise<GitBranchMergedStatus>;
235+
/** @internal not intended to be used outside of the sub-providers */
236+
getCurrentBranchReference?(repoPath: string): Promise<GitBranchReference | undefined>;
235237
getLocalBranchByUpstream?(repoPath: string, remoteBranchName: string): Promise<GitBranch | undefined>;
236238
getPotentialMergeOrRebaseConflict?(
237239
repoPath: string,

src/git/models/branch.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,12 @@ export class GitBranch implements GitBranchReference {
5454
) {
5555
({ name: this._name, remote: this._remote } = parseRefName(refName));
5656

57-
this.id = getBranchId(repoPath, this._remote, this._name);
58-
5957
this.detached = detached || (this.current ? isDetachedHead(this._name) : false);
6058
if (this.detached) {
59+
this.id = getBranchId(repoPath, this._remote, this.sha!);
6160
this._name = formatDetachedHeadName(this.sha!);
61+
} else {
62+
this.id = getBranchId(repoPath, this._remote, this._name);
6263
}
6364

6465
this.upstream = upstream?.name ? upstream : undefined;

src/git/utils/-webview/reference.utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export function getReferenceFromBranch(branch: GitBranch): GitBranchReference {
1111
name: branch.name,
1212
remote: branch.remote,
1313
upstream: branch.upstream,
14+
sha: branch.sha,
1415
});
1516
}
1617

@@ -39,5 +40,6 @@ export function getReferenceFromTag(tag: GitTag): GitTagReference {
3940
id: tag.id,
4041
refType: tag.refType,
4142
name: tag.name,
43+
sha: tag.sha,
4244
});
4345
}

0 commit comments

Comments
 (0)