Skip to content

Commit e56850a

Browse files
committed
Changes to provider specific graph data querying
- Allows for more performant querying Optimizes local git graph data to avoid extra lookups
1 parent 680e23c commit e56850a

File tree

15 files changed

+575
-324
lines changed

15 files changed

+575
-324
lines changed

src/env/node/git/git.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -712,7 +712,7 @@ export class Git {
712712
},
713713
) {
714714
if (argsOrFormat == null) {
715-
argsOrFormat = ['--name-status', `--format=${GitLogParser.defaultFormat}`];
715+
argsOrFormat = ['--name-status', `--format=${all ? GitLogParser.allFormat : GitLogParser.defaultFormat}`];
716716
}
717717

718718
if (typeof argsOrFormat === 'string') {
@@ -808,7 +808,7 @@ export class Git {
808808
const [file, root] = splitPath(fileName, repoPath, true);
809809

810810
if (argsOrFormat == null) {
811-
argsOrFormat = [`--format=${GitLogParser.defaultFormat}`];
811+
argsOrFormat = [`--format=${all ? GitLogParser.allFormat : GitLogParser.defaultFormat}`];
812812
}
813813

814814
if (typeof argsOrFormat === 'string') {

src/env/node/git/localGitProvider.ts

Lines changed: 208 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
import { configuration } from '../../../configuration';
1616
import { CoreGitConfiguration, GlyphChars, Schemes } from '../../../constants';
1717
import type { Container } from '../../../container';
18+
import { emojify } from '../../../emojis';
1819
import { Features } from '../../../features';
1920
import {
2021
StashApplyError,
@@ -42,20 +43,34 @@ import { GitProviderService } from '../../../git/gitProviderService';
4243
import { encodeGitLensRevisionUriAuthority, GitUri } from '../../../git/gitUri';
4344
import type { GitBlame, GitBlameAuthor, GitBlameLine, GitBlameLines } from '../../../git/models/blame';
4445
import type { BranchSortOptions } from '../../../git/models/branch';
45-
import { GitBranch, isDetachedHead, sortBranches } from '../../../git/models/branch';
46+
import {
47+
getBranchNameWithoutRemote,
48+
getRemoteNameFromBranchName,
49+
GitBranch,
50+
isDetachedHead,
51+
sortBranches,
52+
} from '../../../git/models/branch';
4653
import type { GitStashCommit } from '../../../git/models/commit';
47-
import { GitCommit, GitCommitIdentity } from '../../../git/models/commit';
54+
import { GitCommit, GitCommitIdentity, isStash } from '../../../git/models/commit';
4855
import { GitContributor } from '../../../git/models/contributor';
4956
import type { GitDiff, GitDiffFilter, GitDiffHunkLine, GitDiffShortStat } from '../../../git/models/diff';
5057
import type { GitFile, GitFileStatus } from '../../../git/models/file';
5158
import { GitFileChange } from '../../../git/models/file';
59+
import type {
60+
GitGraph,
61+
GitGraphRow,
62+
GitGraphRowHead,
63+
GitGraphRowRemoteHead,
64+
GitGraphRowTag,
65+
} from '../../../git/models/graph';
66+
import { GitGraphRowType } from '../../../git/models/graph';
5267
import type { GitLog } from '../../../git/models/log';
5368
import type { GitMergeStatus } from '../../../git/models/merge';
5469
import type { GitRebaseStatus } from '../../../git/models/rebase';
5570
import type { GitBranchReference } from '../../../git/models/reference';
5671
import { GitReference, GitRevision } from '../../../git/models/reference';
5772
import type { GitReflog } from '../../../git/models/reflog';
58-
import { GitRemote } from '../../../git/models/remote';
73+
import { getRemoteIconUri, GitRemote } from '../../../git/models/remote';
5974
import type { RepositoryChangeEvent } from '../../../git/models/repository';
6075
import { Repository, RepositoryChange, RepositoryChangeComparisonMode } from '../../../git/models/repository';
6176
import type { GitStash } from '../../../git/models/stash';
@@ -1583,6 +1598,179 @@ export class LocalGitProvider implements GitProvider, Disposable {
15831598
}
15841599
}
15851600

1601+
@log()
1602+
async getCommitsForGraph(
1603+
repoPath: string,
1604+
asWebviewUri: (uri: Uri) => Uri,
1605+
options?: {
1606+
branch?: string;
1607+
limit?: number;
1608+
mode?: 'single' | 'local' | 'all';
1609+
ref?: string;
1610+
},
1611+
): Promise<GitGraph> {
1612+
const [logResult, stashResult, remotesResult] = await Promise.allSettled([
1613+
this.getLog(repoPath, { all: true, ordering: 'date', limit: options?.limit }),
1614+
this.getStash(repoPath),
1615+
this.getRemotes(repoPath),
1616+
]);
1617+
1618+
return this.getCommitsForGraphCore(
1619+
repoPath,
1620+
asWebviewUri,
1621+
getSettledValue(logResult),
1622+
getSettledValue(stashResult),
1623+
getSettledValue(remotesResult),
1624+
options,
1625+
);
1626+
}
1627+
1628+
private async getCommitsForGraphCore(
1629+
repoPath: string,
1630+
asWebviewUri: (uri: Uri) => Uri,
1631+
log: GitLog | undefined,
1632+
stash: GitStash | undefined,
1633+
remotes: GitRemote[] | undefined,
1634+
options?: {
1635+
ref?: string;
1636+
mode?: 'single' | 'local' | 'all';
1637+
branch?: string;
1638+
},
1639+
): Promise<GitGraph> {
1640+
if (log == null) {
1641+
return {
1642+
repoPath: repoPath,
1643+
rows: [],
1644+
};
1645+
}
1646+
1647+
const commits = (log.pagedCommits?.() ?? log.commits)?.values();
1648+
if (commits == null) {
1649+
return {
1650+
repoPath: repoPath,
1651+
rows: [],
1652+
};
1653+
}
1654+
1655+
const rows: GitGraphRow[] = [];
1656+
1657+
let current = false;
1658+
let refHeads: GitGraphRowHead[];
1659+
let refRemoteHeads: GitGraphRowRemoteHead[];
1660+
let refTags: GitGraphRowTag[];
1661+
let parents: string[];
1662+
let remoteName: string;
1663+
let isStashCommit: boolean;
1664+
1665+
const remoteMap = remotes != null ? new Map(remotes.map(r => [r.name, r])) : new Map();
1666+
1667+
const skipStashParents = new Set();
1668+
1669+
for (const commit of commits) {
1670+
if (skipStashParents.has(commit.sha)) continue;
1671+
1672+
refHeads = [];
1673+
refRemoteHeads = [];
1674+
refTags = [];
1675+
1676+
if (commit.tips != null) {
1677+
for (let tip of commit.tips) {
1678+
if (tip === 'refs/stash' || tip === 'HEAD') continue;
1679+
1680+
if (tip.startsWith('tag: ')) {
1681+
refTags.push({
1682+
name: tip.substring(5),
1683+
// Not currently used, so don't bother filling it out
1684+
annotated: false,
1685+
});
1686+
1687+
continue;
1688+
}
1689+
1690+
current = tip.startsWith('HEAD -> ');
1691+
if (current) {
1692+
tip = tip.substring(8);
1693+
}
1694+
1695+
remoteName = getRemoteNameFromBranchName(tip);
1696+
if (remoteName) {
1697+
const remote = remoteMap.get(remoteName);
1698+
if (remote != null) {
1699+
const branchName = getBranchNameWithoutRemote(tip);
1700+
if (branchName === 'HEAD') continue;
1701+
1702+
refRemoteHeads.push({
1703+
name: branchName,
1704+
owner: remote.name,
1705+
url: remote.url,
1706+
avatarUrl: (
1707+
remote.provider?.avatarUri ?? getRemoteIconUri(this.container, remote, asWebviewUri)
1708+
)?.toString(true),
1709+
});
1710+
1711+
continue;
1712+
}
1713+
}
1714+
1715+
refHeads.push({
1716+
name: tip,
1717+
isCurrentHead: current,
1718+
});
1719+
}
1720+
}
1721+
1722+
isStashCommit = isStash(commit) || (stash?.commits.has(commit.sha) ?? false);
1723+
1724+
parents = commit.parents;
1725+
// Remove the second & third parent, if exists, from each stash commit as it is a Git implementation for the index and untracked files
1726+
if (isStashCommit && parents.length > 1) {
1727+
// Copy the array to avoid mutating the original
1728+
parents = [...parents];
1729+
1730+
// Skip the "index commit" (e.g. contains staged files) of the stash
1731+
skipStashParents.add(parents[1]);
1732+
// Skip the "untracked commit" (e.g. contains untracked files) of the stash
1733+
skipStashParents.add(parents[2]);
1734+
parents.splice(1, 2);
1735+
}
1736+
1737+
rows.push({
1738+
sha: commit.sha,
1739+
parents: parents,
1740+
author: commit.author.name,
1741+
avatarUrl: !isStashCommit ? (await commit.getAvatarUri())?.toString(true) : undefined,
1742+
email: commit.author.email ?? '',
1743+
date: commit.committer.date.getTime(),
1744+
message: emojify(commit.message && String(commit.message).length ? commit.message : commit.summary),
1745+
// TODO: review logic for stash, wip, etc
1746+
type: isStashCommit
1747+
? GitGraphRowType.Stash
1748+
: commit.parents.length > 1
1749+
? GitGraphRowType.MergeCommit
1750+
: GitGraphRowType.Commit,
1751+
heads: refHeads,
1752+
remotes: refRemoteHeads,
1753+
tags: refTags,
1754+
});
1755+
}
1756+
1757+
return {
1758+
repoPath: repoPath,
1759+
paging: {
1760+
limit: log.limit,
1761+
endingCursor: log.endingCursor,
1762+
startingCursor: log.startingCursor,
1763+
more: log.hasMore,
1764+
},
1765+
rows: rows,
1766+
1767+
more: async (limit: number | { until: string } | undefined): Promise<GitGraph | undefined> => {
1768+
const moreLog = await log.more?.(limit);
1769+
return this.getCommitsForGraphCore(repoPath, asWebviewUri, moreLog, stash, remotes, options);
1770+
},
1771+
};
1772+
}
1773+
15861774
@log()
15871775
async getOldestUnpushedRefForFile(repoPath: string, uri: Uri): Promise<string | undefined> {
15881776
const [relativePath, root] = splitPath(uri, repoPath);
@@ -2242,14 +2430,15 @@ export class LocalGitProvider implements GitProvider, Disposable {
22422430
count: commits.size,
22432431
limit: moreUntil == null ? (log.limit ?? 0) + moreLimit : undefined,
22442432
hasMore: moreUntil == null ? moreLog.hasMore : true,
2433+
startingCursor: last(log.commits)?.[0],
2434+
endingCursor: moreLog.endingCursor,
22452435
pagedCommits: () => {
22462436
// Remove any duplicates
22472437
for (const sha of log.commits.keys()) {
22482438
moreLog.commits.delete(sha);
22492439
}
22502440
return moreLog.commits;
22512441
},
2252-
previousCursor: last(log.commits)?.[0],
22532442
query: (limit: number | undefined) => this.getLog(log.repoPath, { ...options, limit: limit }),
22542443
};
22552444
mergedLog.more = this.getLogMoreFn(mergedLog, options);
@@ -3201,7 +3390,13 @@ export class LocalGitProvider implements GitProvider, Disposable {
32013390
@log()
32023391
async getIncomingActivity(
32033392
repoPath: string,
3204-
options?: { all?: boolean; branch?: string; limit?: number; ordering?: 'date' | 'author-date' | 'topo' | null; skip?: number },
3393+
options?: {
3394+
all?: boolean;
3395+
branch?: string;
3396+
limit?: number;
3397+
ordering?: 'date' | 'author-date' | 'topo' | null;
3398+
skip?: number;
3399+
},
32053400
): Promise<GitReflog | undefined> {
32063401
const scope = getLogScope();
32073402

@@ -3229,7 +3424,13 @@ export class LocalGitProvider implements GitProvider, Disposable {
32293424

32303425
private getReflogMoreFn(
32313426
reflog: GitReflog,
3232-
options?: { all?: boolean; branch?: string; limit?: number; ordering?: 'date' | 'author-date' | 'topo' | null; skip?: number },
3427+
options?: {
3428+
all?: boolean;
3429+
branch?: string;
3430+
limit?: number;
3431+
ordering?: 'date' | 'author-date' | 'topo' | null;
3432+
skip?: number;
3433+
},
32333434
): (limit: number) => Promise<GitReflog> {
32343435
return async (limit: number | undefined) => {
32353436
limit = limit ?? configuration.get('advanced.maxSearchItems') ?? 0;
@@ -3358,6 +3559,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
33583559
) ?? [],
33593560
undefined,
33603561
[],
3562+
undefined,
33613563
s.stashName,
33623564
onRef,
33633565
) as GitStashCommit,

src/git/gitProvider.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type { GitCommit } from './models/commit';
99
import type { GitContributor } from './models/contributor';
1010
import type { GitDiff, GitDiffFilter, GitDiffHunkLine, GitDiffShortStat } from './models/diff';
1111
import type { GitFile } from './models/file';
12+
import type { GitGraph } from './models/graph';
1213
import type { GitLog } from './models/log';
1314
import type { GitMergeStatus } from './models/merge';
1415
import type { GitRebaseStatus } from './models/rebase';
@@ -217,6 +218,16 @@ export interface GitProvider extends Disposable {
217218
range?: Range | undefined;
218219
},
219220
): Promise<GitCommit | undefined>;
221+
getCommitsForGraph(
222+
repoPath: string,
223+
asWebviewUri: (uri: Uri) => Uri,
224+
options?: {
225+
branch?: string;
226+
limit?: number;
227+
mode?: 'single' | 'local' | 'all';
228+
ref?: string;
229+
},
230+
): Promise<GitGraph>;
220231
getOldestUnpushedRefForFile(repoPath: string, uri: Uri): Promise<string | undefined>;
221232
getContributors(
222233
repoPath: string,

src/git/gitProviderService.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import type { GitCommit } from './models/commit';
4949
import type { GitContributor } from './models/contributor';
5050
import type { GitDiff, GitDiffFilter, GitDiffHunkLine, GitDiffShortStat } from './models/diff';
5151
import type { GitFile } from './models/file';
52+
import type { GitGraph } from './models/graph';
5253
import type { GitLog } from './models/log';
5354
import type { GitMergeStatus } from './models/merge';
5455
import type { PullRequest, PullRequestState } from './models/pullRequest';
@@ -1312,6 +1313,21 @@ export class GitProviderService implements Disposable {
13121313
return provider.getCommitForFile(path, uri, options);
13131314
}
13141315

1316+
@log()
1317+
getCommitsForGraph(
1318+
repoPath: string | Uri,
1319+
asWebviewUri: (uri: Uri) => Uri,
1320+
options?: {
1321+
branch?: string;
1322+
limit?: number;
1323+
mode?: 'single' | 'local' | 'all';
1324+
ref?: string;
1325+
},
1326+
): Promise<GitGraph> {
1327+
const { provider, path } = this.getProvider(repoPath);
1328+
return provider.getCommitsForGraph(path, asWebviewUri, options);
1329+
}
1330+
13151331
@log()
13161332
async getOldestUnpushedRefForFile(repoPath: string | Uri, uri: Uri): Promise<string | undefined> {
13171333
const { provider, path } = this.getProvider(repoPath);

src/git/models/commit.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export class GitCommit implements GitRevisionReference {
3434
// TODO@eamodio rename to stashNumber
3535
readonly number: string | undefined;
3636
readonly stashOnRef: string | undefined;
37+
readonly tips: string[] | undefined;
3738

3839
constructor(
3940
private readonly container: Container,
@@ -47,11 +48,13 @@ export class GitCommit implements GitRevisionReference {
4748
files?: GitFileChange | GitFileChange[] | { file?: GitFileChange; files?: GitFileChange[] } | undefined,
4849
stats?: GitCommitStats,
4950
lines?: GitCommitLine | GitCommitLine[] | undefined,
51+
tips?: string[],
5052
stashName?: string | undefined,
5153
stashOnRef?: string | undefined,
5254
) {
5355
this.ref = this.sha;
5456
this.shortSha = this.sha.substring(0, this.container.CommitShaFormatting.length);
57+
this.tips = tips;
5558

5659
if (stashName) {
5760
this.refType = 'stash';
@@ -562,6 +565,7 @@ export class GitCommit implements GitRevisionReference {
562565
files,
563566
this.stats,
564567
this.getChangedValue(changes.lines, this.lines),
568+
this.tips,
565569
this.stashName,
566570
this.stashOnRef,
567571
);

0 commit comments

Comments
 (0)