Skip to content

Commit 621edae

Browse files
committed
Fixes #4078 deadlock on paused operation detection
- Adds operation priority ordering for proper detection
1 parent 6d19b7e commit 621edae

File tree

8 files changed

+163
-76
lines changed

8 files changed

+163
-76
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
66

77
## [Unreleased]
88

9+
### Fixed
10+
11+
- Fixes GitLens gets stuck after rebase ([#4078](https://github.com/gitkraken/vscode-gitlens/issues/4078))
12+
913
## [16.3.2] - 2025-02-21
1014

1115
## [16.3.1] - 2025-02-20

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

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,16 @@ import { parseMergeTreeConflict } from '../../../../git/parsers/mergeTreeParser'
1717
import { getReferenceFromBranch } from '../../../../git/utils/-webview/reference.utils';
1818
import type { BranchSortOptions } from '../../../../git/utils/-webview/sorting';
1919
import { sortBranches, sortContributors } from '../../../../git/utils/-webview/sorting';
20-
import { getLocalBranchByUpstream, isDetachedHead } from '../../../../git/utils/branch.utils';
20+
import {
21+
formatDetachedHeadName,
22+
getBranchId,
23+
getLocalBranchByUpstream,
24+
isDetachedHead,
25+
} from '../../../../git/utils/branch.utils';
26+
import { createReference } from '../../../../git/utils/reference.utils';
2127
import { createRevisionRange } from '../../../../git/utils/revision.utils';
2228
import { configuration } from '../../../../system/-webview/configuration';
2329
import { filterMap } from '../../../../system/array';
24-
import { gate } from '../../../../system/decorators/-webview/gate';
2530
import { log } from '../../../../system/decorators/log';
2631
import { Logger } from '../../../../system/logger';
2732
import { getLogScope } from '../../../../system/logger.scope';
@@ -40,7 +45,6 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
4045
private readonly provider: LocalGitProvider,
4146
) {}
4247

43-
@gate()
4448
@log()
4549
async getBranch(repoPath: string, name?: string): Promise<GitBranch | undefined> {
4650
if (name != null) {
@@ -67,15 +71,13 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
6771
}
6872

6973
private async getCurrentBranch(repoPath: string): Promise<GitBranch | undefined> {
70-
const commitOrdering = configuration.get('advanced.commitOrdering');
74+
const ref = await this.getCurrentBranchReferenceCore(repoPath);
75+
if (ref == null) return undefined;
7176

72-
const data = await this.git.rev_parse__currentBranch(repoPath, commitOrdering);
73-
if (data == null) return undefined;
74-
75-
const [name, upstream] = data[0].split('\n');
77+
const commitOrdering = configuration.get('advanced.commitOrdering');
7678

7779
const [pausedOpStatusResult, committerDateResult] = await Promise.allSettled([
78-
isDetachedHead(name) ? this.provider.status?.getPausedOperationStatus(repoPath) : undefined,
80+
isDetachedHead(ref.name) ? this.provider.status?.getPausedOperationStatus(repoPath) : undefined,
7981
this.git.log__recent_committerdate(repoPath, commitOrdering),
8082
]);
8183

@@ -86,12 +88,12 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
8688
return new GitBranch(
8789
this.container,
8890
repoPath,
89-
rebaseStatus?.incoming.name ?? name,
91+
rebaseStatus?.incoming.name ?? ref.name,
9092
false,
9193
true,
9294
committerDate != null ? new Date(Number(committerDate) * 1000) : undefined,
93-
data[1],
94-
upstream ? { name: upstream, missing: false } : undefined,
95+
ref.sha,
96+
ref.upstream,
9597
undefined,
9698
undefined,
9799
undefined,
@@ -269,6 +271,40 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
269271
return filterMap(data.split('\n'), b => b.trim() || undefined);
270272
}
271273

274+
@log()
275+
async getCurrentBranchReference(repoPath: string): Promise<GitBranchReference | undefined> {
276+
let ref = await this.getCurrentBranchReferenceCore(repoPath);
277+
if (ref != null && isDetachedHead(ref.name)) {
278+
ref = createReference(ref.sha!, repoPath, {
279+
refType: 'branch',
280+
name: formatDetachedHeadName(ref.sha!),
281+
id: getBranchId(repoPath, ref.remote, ref.sha!),
282+
remote: ref.remote,
283+
upstream: ref.upstream,
284+
sha: ref.sha,
285+
});
286+
}
287+
return ref;
288+
}
289+
290+
private async getCurrentBranchReferenceCore(repoPath: string): Promise<GitBranchReference | undefined> {
291+
const commitOrdering = configuration.get('advanced.commitOrdering');
292+
293+
const data = await this.git.rev_parse__currentBranch(repoPath, commitOrdering);
294+
if (data == null) return undefined;
295+
296+
const [name, upstream] = data[0].split('\n');
297+
298+
return createReference(name, repoPath, {
299+
refType: 'branch',
300+
name: name,
301+
id: getBranchId(repoPath, false, name),
302+
remote: false,
303+
upstream: upstream ? { name: upstream, missing: false } : undefined,
304+
sha: data[1],
305+
});
306+
}
307+
272308
@log({ exit: true })
273309
async getDefaultBranchName(repoPath: string | undefined, remote?: string): Promise<string | undefined> {
274310
if (repoPath == null) return undefined;

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

Lines changed: 56 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,22 @@ 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';
2827
import { gate } from '../../../../system/decorators/-webview/gate';
2928
import { log } from '../../../../system/decorators/log';
3029
import { Logger } from '../../../../system/logger';
30+
import { getLogScope, setLogScopeExit } from '../../../../system/logger.scope';
3131
import { getSettledValue } from '../../../../system/promise';
3232
import type { Git } from '../git';
3333
import { GitErrors } from '../git';
3434
import type { LocalGitProvider } from '../localGitProvider';
3535

36+
type Operation = 'cherry-pick' | 'merge' | 'rebase-apply' | 'rebase-merge' | 'revert';
37+
38+
const orderedOperations: Operation[] = ['rebase-apply', 'rebase-merge', 'merge', 'cherry-pick', 'revert'];
39+
3640
export class StatusGitSubProvider implements GitStatusSubProvider {
3741
constructor(
3842
private readonly container: Container,
@@ -44,21 +48,23 @@ export class StatusGitSubProvider implements GitStatusSubProvider {
4448
@gate()
4549
@log()
4650
async getPausedOperationStatus(repoPath: string): Promise<GitPausedOperationStatus | undefined> {
51+
const scope = getLogScope();
52+
4753
let status = this.cache.pausedOperationStatus?.get(repoPath);
4854
if (status == null) {
4955
async function getCore(this: StatusGitSubProvider): Promise<GitPausedOperationStatus | undefined> {
5056
const gitDir = await this.provider.getGitDir(repoPath);
5157

52-
type Operation = 'cherry-pick' | 'merge' | 'rebase-apply' | 'rebase-merge' | 'revert';
53-
const operation = await new Promise<Operation | undefined>((resolve, _) => {
58+
const operations = await new Promise<Set<Operation>>((resolve, _) => {
5459
readdir(gitDir.uri.fsPath, { withFileTypes: true }, (err, entries) => {
60+
const operations = new Set<Operation>();
5561
if (err != null) {
56-
resolve(undefined);
62+
resolve(operations);
5763
return;
5864
}
5965

6066
if (entries.length === 0) {
61-
resolve(undefined);
67+
resolve(operations);
6268
return;
6369
}
6470

@@ -67,33 +73,39 @@ export class StatusGitSubProvider implements GitStatusSubProvider {
6773
if (entry.isFile()) {
6874
switch (entry.name) {
6975
case 'CHERRY_PICK_HEAD':
70-
resolve('cherry-pick');
71-
return;
76+
operations.add('cherry-pick');
77+
break;
7278
case 'MERGE_HEAD':
73-
resolve('merge');
74-
return;
79+
operations.add('merge');
80+
break;
7581
case 'REVERT_HEAD':
76-
resolve('revert');
77-
return;
82+
operations.add('revert');
83+
break;
7884
}
7985
} else if (entry.isDirectory()) {
8086
switch (entry.name) {
8187
case 'rebase-apply':
82-
resolve('rebase-apply');
83-
return;
88+
operations.add('rebase-apply');
89+
break;
8490
case 'rebase-merge':
85-
resolve('rebase-merge');
86-
return;
91+
operations.add('rebase-merge');
92+
break;
8793
}
8894
}
8995
}
9096

91-
resolve(undefined);
97+
resolve(operations);
9298
});
9399
});
94100

95-
if (operation == null) return undefined;
101+
if (!operations.size) return undefined;
102+
103+
const sortedOperations = [...operations].sort(
104+
(a, b) => orderedOperations.indexOf(a) - orderedOperations.indexOf(b),
105+
);
106+
Logger.log(`Detected paused operations: ${sortedOperations.join(', ')}`);
96107

108+
const operation = sortedOperations[0];
97109
switch (operation) {
98110
case 'cherry-pick': {
99111
const cherryPickHead = (
@@ -105,16 +117,19 @@ export class StatusGitSubProvider implements GitStatusSubProvider {
105117
'CHERRY_PICK_HEAD',
106118
)
107119
)?.trim();
108-
if (!cherryPickHead) return undefined;
120+
if (!cherryPickHead) {
121+
setLogScopeExit(scope, 'No CHERRY_PICK_HEAD found');
122+
return undefined;
123+
}
109124

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

112127
return {
113128
type: 'cherry-pick',
114129
repoPath: repoPath,
115130
// TODO: Validate that these are correct
116131
HEAD: createReference(cherryPickHead, repoPath, { refType: 'revision' }),
117-
current: getReferenceFromBranch(branch),
132+
current: current,
118133
incoming: createReference(cherryPickHead, repoPath, { refType: 'revision' }),
119134
} satisfies GitCherryPickStatus;
120135
}
@@ -128,18 +143,21 @@ export class StatusGitSubProvider implements GitStatusSubProvider {
128143
'MERGE_HEAD',
129144
)
130145
)?.trim();
131-
if (!mergeHead) return undefined;
146+
if (!mergeHead) {
147+
setLogScopeExit(scope, 'No MERGE_HEAD found');
148+
return undefined;
149+
}
132150

133151
const [branchResult, mergeBaseResult, possibleSourceBranchesResult] = await Promise.allSettled([
134-
this.provider.branches.getBranch(repoPath),
152+
this.provider.branches.getCurrentBranchReference(repoPath),
135153
this.provider.branches.getMergeBase(repoPath, 'MERGE_HEAD', 'HEAD'),
136154
this.provider.branches.getBranchesWithCommits(repoPath, ['MERGE_HEAD'], undefined, {
137155
all: true,
138156
mode: 'pointsAt',
139157
}),
140158
]);
141159

142-
const branch = getSettledValue(branchResult)!;
160+
const current = getSettledValue(branchResult)!;
143161
const mergeBase = getSettledValue(mergeBaseResult);
144162
const possibleSourceBranches = getSettledValue(possibleSourceBranchesResult);
145163

@@ -148,7 +166,7 @@ export class StatusGitSubProvider implements GitStatusSubProvider {
148166
repoPath: repoPath,
149167
mergeBase: mergeBase,
150168
HEAD: createReference(mergeHead, repoPath, { refType: 'revision' }),
151-
current: getReferenceFromBranch(branch),
169+
current: current,
152170
incoming:
153171
possibleSourceBranches?.length === 1
154172
? createReference(possibleSourceBranches[0], repoPath, {
@@ -169,22 +187,28 @@ export class StatusGitSubProvider implements GitStatusSubProvider {
169187
'REVERT_HEAD',
170188
)
171189
)?.trim();
172-
if (!revertHead) return undefined;
190+
if (!revertHead) {
191+
setLogScopeExit(scope, 'No REVERT_HEAD found');
192+
return undefined;
193+
}
173194

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

176197
return {
177198
type: 'revert',
178199
repoPath: repoPath,
179200
HEAD: createReference(revertHead, repoPath, { refType: 'revision' }),
180-
current: getReferenceFromBranch(branch),
201+
current: current,
181202
incoming: createReference(revertHead, repoPath, { refType: 'revision' }),
182203
} satisfies GitRevertStatus;
183204
}
184205
case 'rebase-apply':
185206
case 'rebase-merge': {
186207
let branch = await this.git.readDotGitFile(gitDir, [operation, 'head-name']);
187-
if (!branch) return undefined;
208+
if (!branch) {
209+
setLogScopeExit(scope, `No '${operation}/head-name' found`);
210+
return undefined;
211+
}
188212

189213
const [
190214
rebaseHeadResult,
@@ -212,7 +236,10 @@ export class StatusGitSubProvider implements GitStatusSubProvider {
212236

213237
const origHead = getSettledValue(origHeadResult);
214238
const onto = getSettledValue(ontoResult);
215-
if (origHead == null || onto == null) return undefined;
239+
if (origHead == null || onto == null) {
240+
setLogScopeExit(scope, `Neither '${operation}/orig-head' nor '${operation}/onto' found`);
241+
return undefined;
242+
}
216243

217244
const rebaseHead = getSettledValue(rebaseHeadResult)?.trim();
218245

src/git/gitProvider.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,8 @@ export interface GitBranchesSubProvider {
321321
branch: GitBranchReference,
322322
into: GitBranchReference,
323323
): Promise<GitBranchMergedStatus>;
324+
/** @internal not intended to be used outside of the sub-providers */
325+
getCurrentBranchReference?(repoPath: string): Promise<GitBranchReference | undefined>;
324326
getLocalBranchByUpstream?(repoPath: string, remoteBranchName: string): Promise<GitBranch | undefined>;
325327
getPotentialMergeOrRebaseConflict?(
326328
repoPath: string,

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)