Skip to content

Commit af03565

Browse files
committed
Adds new ref parser
Adopts new ref parser for branches - Adds basic worktree prop to branches Adopts new ref parser for tags
1 parent 49f40ef commit af03565

File tree

14 files changed

+293
-172
lines changed

14 files changed

+293
-172
lines changed

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

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,17 @@ import type {
1212
import { GitBranch } from '../../../../git/models/branch';
1313
import type { MergeConflict } from '../../../../git/models/mergeConflict';
1414
import type { GitBranchReference } from '../../../../git/models/reference';
15-
import { parseGitBranches, parseGitBranchesDefaultFormat } from '../../../../git/parsers/branchParser';
1615
import { parseMergeTreeConflict } from '../../../../git/parsers/mergeTreeParser';
16+
import { getBranchParser } from '../../../../git/parsers/refParser';
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+
getLocalBranchByUpstream,
22+
isDetachedHead,
23+
isRemoteHEAD,
24+
parseUpstream,
25+
} from '../../../../git/utils/branch.utils';
2126
import { createRevisionRange } from '../../../../git/utils/revision.utils';
2227
import { configuration } from '../../../../system/-webview/configuration';
2328
import { filterMap } from '../../../../system/array';
@@ -26,7 +31,9 @@ import { log } from '../../../../system/decorators/log';
2631
import { Logger } from '../../../../system/logger';
2732
import { getLogScope } from '../../../../system/logger.scope';
2833
import { PageableResult } from '../../../../system/paging';
34+
import { normalizePath } from '../../../../system/path';
2935
import { getSettledValue } from '../../../../system/promise';
36+
import { maybeStopWatch } from '../../../../system/stopwatch';
3037
import type { Git } from '../git';
3138
import { GitErrors, gitLogDefaultConfigs } from '../git';
3239
import type { LocalGitProvider } from '../localGitProvider';
@@ -76,7 +83,7 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
7683

7784
const [name, upstream] = data[0].split('\n');
7885

79-
const [pausedOpStatusResult, committerDateResult] = await Promise.allSettled([
86+
const [pausedOpStatusResult, committerDateResult, defaultWorktreePathResult] = await Promise.allSettled([
8087
isDetachedHead(name) ? this.provider.status?.getPausedOperationStatus(repoPath) : undefined,
8188
this.git
8289
.exec(
@@ -88,21 +95,23 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
8895
'--',
8996
)
9097
.then(data => (!data.length ? undefined : data.trim())),
98+
this.provider.config.getDefaultWorktreePath?.(repoPath),
9199
]);
92100

93101
const committerDate = getSettledValue(committerDateResult);
94102
const pausedOpStatus = getSettledValue(pausedOpStatusResult);
95103
const rebaseStatus = pausedOpStatus?.type === 'rebase' ? pausedOpStatus : undefined;
104+
const defaultWorktreePath = getSettledValue(defaultWorktreePathResult);
96105

97106
return new GitBranch(
98107
this.container,
99108
repoPath,
100-
rebaseStatus?.incoming.name ?? name,
101-
false,
109+
rebaseStatus?.incoming.name ?? `refs/heads/${name}`,
102110
true,
103111
committerDate != null ? new Date(Number(committerDate) * 1000) : undefined,
104112
data[1],
105113
upstream ? { name: upstream, missing: false, state: { ahead: 0, behind: 0 } } : undefined,
114+
{ path: repoPath, isDefault: repoPath === defaultWorktreePath },
106115
undefined,
107116
rebaseStatus != null,
108117
);
@@ -119,14 +128,18 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
119128
): Promise<PagedResult<GitBranch>> {
120129
if (repoPath == null) return emptyPagedResult;
121130

131+
const scope = getLogScope();
132+
122133
let resultsPromise = this.cache.branches?.get(repoPath);
123134
if (resultsPromise == null) {
124135
async function load(this: BranchesGitSubProvider): Promise<PagedResult<GitBranch>> {
125136
try {
137+
const parser = getBranchParser();
138+
126139
const data = await this.git.exec(
127140
{ cwd: repoPath },
128141
'for-each-ref',
129-
`--format=${parseGitBranchesDefaultFormat}`,
142+
...parser.arguments,
130143
'refs/heads',
131144
'refs/remotes',
132145
);
@@ -136,11 +149,52 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
136149
return current != null ? { values: [current] } : emptyPagedResult;
137150
}
138151

139-
const branches = parseGitBranches(this.container, data, repoPath);
152+
const defaultWorktreePath = await this.provider.config.getDefaultWorktreePath?.(repoPath);
153+
154+
using sw = maybeStopWatch(scope, { log: false, logLevel: 'debug' });
155+
156+
const branches: GitBranch[] = [];
157+
158+
let hasCurrent = false;
159+
160+
for (const entry of parser.parse(data)) {
161+
// Skip HEAD refs in remote branches
162+
if (isRemoteHEAD(entry.name)) continue;
163+
164+
const upstream = parseUpstream(entry.upstream, entry.upstreamTracking);
165+
166+
const current = entry.current === '*';
167+
if (current) {
168+
hasCurrent = true;
169+
}
170+
171+
const worktreePath = normalizePath(entry.worktreePath);
172+
173+
branches.push(
174+
new GitBranch(
175+
this.container,
176+
repoPath,
177+
entry.name,
178+
current,
179+
entry.date ? new Date(entry.date) : undefined,
180+
entry.sha,
181+
upstream,
182+
worktreePath
183+
? {
184+
path: worktreePath,
185+
isDefault: worktreePath === defaultWorktreePath,
186+
}
187+
: undefined,
188+
),
189+
);
190+
}
191+
192+
sw?.stop({ suffix: ` parsed ${branches.length} branches` });
193+
140194
if (!branches.length) return emptyPagedResult;
141195

142196
// If we don't have a current branch, check if we can find it another way (likely detached head)
143-
if (!branches.some(b => b.current)) {
197+
if (!hasCurrent) {
144198
const current = await this.getCurrentBranch(repoPath);
145199
if (current != null) {
146200
// replace the current branch if it already exists and add it first if not

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { GitCache } from '../../../../git/cache';
77
import { GitErrorHandling } from '../../../../git/commandOptions';
88
import type { GitConfigSubProvider, GitDir } from '../../../../git/gitProvider';
99
import type { GitUser } from '../../../../git/models/user';
10+
import { getBestPath } from '../../../../system/-webview/path';
1011
import { gate } from '../../../../system/decorators/-webview/gate';
1112
import { debug, log } from '../../../../system/decorators/log';
1213
import { Logger } from '../../../../system/logger';
@@ -112,6 +113,13 @@ export class ConfigGitSubProvider implements GitConfigSubProvider {
112113
}
113114
}
114115

116+
@gate()
117+
@debug<NonNullable<ConfigGitSubProvider>['getDefaultWorktreePath']>({ exit: r => `returned ${r}` })
118+
async getDefaultWorktreePath(repoPath: string): Promise<string | undefined> {
119+
const gitDir = await this.getGitDir(repoPath);
120+
return getBestPath(Uri.joinPath(gitDir.commonUri ?? gitDir.uri, '..'));
121+
}
122+
115123
@gate()
116124
@debug<NonNullable<ConfigGitSubProvider>['getGitDir']>({
117125
exit: r => `returned ${r.uri.toString(true)}, commonUri=${r.commonUri?.toString(true)}`,

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

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import type { Container } from '../../../../container';
22
import type { GitCache } from '../../../../git/cache';
33
import { TagError } from '../../../../git/errors';
44
import type { GitTagsSubProvider, PagedResult, PagingOptions } from '../../../../git/gitProvider';
5-
import type { GitTag } from '../../../../git/models/tag';
6-
import { parseGitTags, parseGitTagsDefaultFormat } from '../../../../git/parsers/tagParser';
5+
import { GitTag } from '../../../../git/models/tag';
6+
import { getTagParser } from '../../../../git/parsers/refParser';
77
import type { TagSortOptions } from '../../../../git/utils/-webview/sorting';
88
import { sortTags } from '../../../../git/utils/-webview/sorting';
99
import { filterMap } from '../../../../system/array';
1010
import { log } from '../../../../system/decorators/log';
11+
import { getLogScope } from '../../../../system/logger.scope';
12+
import { maybeStopWatch } from '../../../../system/stopwatch';
1113
import type { Git } from '../git';
1214

1315
const emptyPagedResult: PagedResult<any> = Object.freeze({ values: [] });
@@ -38,12 +40,44 @@ export class TagsGitSubProvider implements GitTagsSubProvider {
3840
): Promise<PagedResult<GitTag>> {
3941
if (repoPath == null) return emptyPagedResult;
4042

43+
const scope = getLogScope();
44+
4145
let resultsPromise = this.cache.tags?.get(repoPath);
4246
if (resultsPromise == null) {
4347
async function load(this: TagsGitSubProvider): Promise<PagedResult<GitTag>> {
4448
try {
45-
const data = await this.git.tag(repoPath, '-l', `--format=${parseGitTagsDefaultFormat}`);
46-
return { values: parseGitTags(this.container, data, repoPath) };
49+
const parser = getTagParser();
50+
51+
const data = await this.git.exec(
52+
{ cwd: repoPath },
53+
'for-each-ref',
54+
...parser.arguments,
55+
'refs/tags',
56+
);
57+
58+
if (!data?.length) return emptyPagedResult;
59+
60+
using sw = maybeStopWatch(scope, { log: false, logLevel: 'debug' });
61+
62+
const tags: GitTag[] = [];
63+
64+
for (const entry of parser.parse(data)) {
65+
tags.push(
66+
new GitTag(
67+
this.container,
68+
repoPath,
69+
entry.name,
70+
entry.sha,
71+
entry.message,
72+
entry.date ? new Date(entry.date) : undefined,
73+
entry.commitDate ? new Date(entry.commitDate) : undefined,
74+
),
75+
);
76+
}
77+
78+
sw?.stop({ suffix: ` parsed ${tags.length} tags` });
79+
80+
return { values: tags };
4781
} catch (_ex) {
4882
this.cache.tags?.delete(repoPath);
4983

src/git/gitProvider.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ export interface GitConfigSubProvider {
338338
getConfig?(repoPath: string, key: GitConfigKeys): Promise<string | undefined>;
339339
setConfig?(repoPath: string, key: GitConfigKeys, value: string | undefined): Promise<void>;
340340
getCurrentUser(repoPath: string): Promise<GitUser | undefined>;
341+
getDefaultWorktreePath?(repoPath: string): Promise<string | undefined>;
341342
getGitDir?(repoPath: string): Promise<GitDir | undefined>;
342343
}
343344

src/git/models/branch.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
getRemoteNameFromBranchName,
1414
getRemoteNameSlashIndex,
1515
isDetachedHead,
16+
parseRefName,
1617
} from '../utils/branch.utils';
1718
import { getUpstreamStatus } from '../utils/status.utils';
1819
import type { PullRequest, PullRequestState } from './pullRequest';
@@ -28,23 +29,35 @@ export class GitBranch implements GitBranchReference {
2829
readonly detached: boolean;
2930
readonly id: string;
3031

32+
private readonly _name: string;
33+
get name(): string {
34+
return this._name;
35+
}
36+
37+
private readonly _remote: boolean;
38+
get remote(): boolean {
39+
return this._remote;
40+
}
41+
3142
constructor(
3243
private readonly container: Container,
3344
public readonly repoPath: string,
34-
public readonly name: string,
35-
public readonly remote: boolean,
45+
public readonly refName: string,
3646
public readonly current: boolean,
3747
public readonly date: Date | undefined,
3848
public readonly sha?: string,
3949
public readonly upstream?: GitTrackingUpstream,
50+
public readonly worktree?: { path: string; isDefault: boolean },
4051
detached: boolean = false,
4152
public readonly rebasing: boolean = false,
4253
) {
43-
this.id = getBranchId(repoPath, remote, name);
54+
({ name: this._name, remote: this._remote } = parseRefName(refName));
55+
56+
this.id = getBranchId(repoPath, this._remote, this._name);
4457

45-
this.detached = detached || (this.current ? isDetachedHead(name) : false);
58+
this.detached = detached || (this.current ? isDetachedHead(this._name) : false);
4659
if (this.detached) {
47-
this.name = formatDetachedHeadName(this.sha!);
60+
this._name = formatDetachedHeadName(this.sha!);
4861
}
4962

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

src/git/models/tag.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { Container } from '../../container';
33
import { formatDate, fromNow } from '../../system/date';
44
import { memoize } from '../../system/decorators/-webview/memoize';
55
import { getLoggableName } from '../../system/logger';
6-
import { getTagId } from '../utils/tag.utils';
6+
import { getTagId, parseRefName } from '../utils/tag.utils';
77
import type { GitTagReference } from './reference';
88

99
export function isTag(tag: unknown): tag is GitTag {
@@ -14,16 +14,23 @@ export class GitTag implements GitTagReference {
1414
readonly refType = 'tag';
1515
readonly id: string;
1616

17+
private readonly _name: string;
18+
get name(): string {
19+
return this._name;
20+
}
21+
1722
constructor(
1823
private readonly container: Container,
1924
public readonly repoPath: string,
20-
public readonly name: string,
25+
public readonly refName: string,
2126
public readonly sha: string,
2227
public readonly message: string,
2328
public readonly date: Date | undefined,
2429
public readonly commitDate: Date | undefined,
2530
) {
26-
this.id = getTagId(repoPath, name);
31+
({ name: this._name } = parseRefName(refName));
32+
33+
this.id = getTagId(repoPath, this._name);
2734
}
2835

2936
toString(): string {

0 commit comments

Comments
 (0)