Browse Source

Adds structure to handle paging of branches/tags

Actual paging will have to be implemented later
main
Eric Amodio 3 years ago
parent
commit
0bf5477122
21 changed files with 350 additions and 394 deletions
  1. +1
    -1
      src/commands/git/switch.ts
  2. +34
    -15
      src/commands/quickCommand.steps.ts
  3. +18
    -35
      src/git/gitProvider.ts
  4. +16
    -65
      src/git/gitProviderService.ts
  5. +9
    -25
      src/git/models/repository.ts
  6. +102
    -148
      src/git/providers/localGitProvider.ts
  7. +14
    -11
      src/git/remotes/bitbucket-server.ts
  8. +14
    -11
      src/git/remotes/bitbucket.ts
  9. +34
    -16
      src/git/remotes/gerrit.ts
  10. +15
    -13
      src/git/remotes/gitea.ts
  11. +14
    -11
      src/git/remotes/github.ts
  12. +14
    -11
      src/git/remotes/gitlab.ts
  13. +10
    -8
      src/quickpicks/remoteProviderPicker.ts
  14. +34
    -10
      src/terminal/linkProvider.ts
  15. +2
    -2
      src/views/branchesView.ts
  16. +3
    -2
      src/views/nodes/branchesNode.ts
  17. +4
    -2
      src/views/nodes/fileHistoryTrackerNode.ts
  18. +4
    -2
      src/views/nodes/lineHistoryTrackerNode.ts
  19. +3
    -2
      src/views/nodes/remoteNode.ts
  20. +3
    -2
      src/views/nodes/tagsNode.ts
  21. +2
    -2
      src/views/tagsView.ts

+ 1
- 1
src/commands/git/switch.ts View File

@ -146,7 +146,7 @@ export class SwitchGitCommand extends QuickCommand {
if (GitReference.isBranch(state.reference) && state.reference.remote) {
context.title = `Create Branch and ${this.title}`;
const branches = await Container.instance.git.getBranches(state.reference.repoPath, {
const { values: branches } = await Container.instance.git.getBranches(state.reference.repoPath, {
filter: b => b.upstream?.name === state.reference!.name,
sort: { orderBy: BranchSorting.DateDesc },
});

+ 34
- 15
src/commands/quickCommand.steps.ts View File

@ -25,6 +25,7 @@ import {
SearchPattern,
TagSortOptions,
} from '../git/git';
import { PagedResult } from '../git/gitProvider';
import { GitUri } from '../git/gitUri';
import {
BranchQuickPickItem,
@ -163,7 +164,8 @@ export async function getBranchesAndOrTags(
singleRepo = true;
const repo = repos instanceof Repository ? repos : repos[0];
[branches, tags] = await Promise.all([
// TODO@eamodio handle paging
const [branchesResult, tagsResult] = await Promise.allSettled([
include.includes('branches')
? repo.getBranches({
filter: filter?.branches,
@ -172,10 +174,14 @@ export async function getBranchesAndOrTags(
: undefined,
include.includes('tags') ? repo.getTags({ filter: filter?.tags, sort: true }) : undefined,
]);
branches = (branchesResult.status === 'fulfilled' ? branchesResult.value?.values : undefined) ?? [];
tags = (tagsResult.status === 'fulfilled' ? tagsResult.value?.values : undefined) ?? [];
} else {
const [branchesByRepo, tagsByRepo] = await Promise.all([
// TODO@eamodio handle paging
const [branchesByRepoResult, tagsByRepoResult] = await Promise.allSettled([
include.includes('branches')
? Promise.all(
? Promise.allSettled(
repos.map(r =>
r.getBranches({
filter: filter?.branches,
@ -185,7 +191,7 @@ export async function getBranchesAndOrTags(
)
: undefined,
include.includes('tags')
? Promise.all(
? Promise.allSettled(
repos.map(r =>
r.getTags({ filter: filter?.tags, sort: typeof sort === 'boolean' ? sort : sort?.tags }),
),
@ -193,22 +199,35 @@ export async function getBranchesAndOrTags(
: undefined,
]);
if (include.includes('branches')) {
const branchesByRepo =
branchesByRepoResult.status === 'fulfilled'
? branchesByRepoResult.value
?.filter((r): r is PromiseFulfilledResult<PagedResult<GitBranch>> => r.status === 'fulfilled')
?.map(r => r.value.values)
: undefined;
const tagsByRepo =
tagsByRepoResult.status === 'fulfilled'
? tagsByRepoResult.value
?.filter((r): r is PromiseFulfilledResult<PagedResult<GitTag>> => r.status === 'fulfilled')
?.map(r => r.value.values)
: undefined;
if (include.includes('branches') && branchesByRepo != null) {
branches = GitBranch.sort(
Arrays.intersection(...branchesByRepo!, ((b1: GitBranch, b2: GitBranch) => b1.name === b2.name) as any),
Arrays.intersection(...branchesByRepo, (b1: GitBranch, b2: GitBranch) => b1.name === b2.name),
);
}
if (include.includes('tags')) {
tags = GitTag.sort(
Arrays.intersection(...tagsByRepo!, ((t1: GitTag, t2: GitTag) => t1.name === t2.name) as any),
);
if (include.includes('tags') && tagsByRepo != null) {
tags = GitTag.sort(Arrays.intersection(...tagsByRepo, (t1: GitTag, t2: GitTag) => t1.name === t2.name));
}
}
if (include.includes('branches') && !include.includes('tags')) {
if ((branches == null || branches.length === 0) && (tags == null || tags.length === 0)) return [];
if (branches != null && branches.length !== 0 && (tags == null || tags.length === 0)) {
return Promise.all(
branches!.map(b =>
branches.map(b =>
BranchQuickPickItem.create(
b,
picked != null && (typeof picked === 'string' ? b.ref === picked : picked.includes(b.ref)),
@ -224,9 +243,9 @@ export async function getBranchesAndOrTags(
);
}
if (include.includes('tags') && !include.includes('branches')) {
if (tags != null && tags.length !== 0 && (branches == null || branches.length === 0)) {
return Promise.all(
tags!.map(t =>
tags.map(t =>
TagQuickPickItem.create(
t,
picked != null && (typeof picked === 'string' ? t.ref === picked : picked.includes(t.ref)),
@ -329,7 +348,7 @@ export function getValidateGitReferenceFn(
if (!inRefMode) {
if (
await Container.instance.git.hasBranchesAndOrTags(repos.path, {
await Container.instance.git.hasBranchOrTag(repos.path, {
filter: { branches: b => b.name.includes(value), tags: t => t.name.includes(value) },
})
) {

+ 18
- 35
src/git/gitProvider.ts View File

@ -57,12 +57,18 @@ export interface ScmRepository {
push(remoteName?: string, branchName?: string, setUpstream?: boolean): Promise<void>;
}
export interface PagedResult<T> {
readonly paging?: {
readonly cursor: string;
readonly more: boolean;
};
readonly values: NonNullable<T>[];
}
export interface GitProvider {
get onDidChangeRepository(): Event<RepositoryChangeEvent>;
readonly descriptor: GitProviderDescriptor;
// get readonly(): boolean;
// get useCaching(): boolean;
discoverRepositories(uri: Uri): Promise<Repository[]>;
createRepository(
@ -99,9 +105,6 @@ export interface GitProvider {
remote?: string | undefined;
},
): Promise<void>;
// getActiveRepository(editor?: TextEditor): Promise<Repository | undefined>;
// getActiveRepoPath(editor?: TextEditor): Promise<string | undefined>;
// getHighlanderRepoPath(): string | undefined;
getBlameForFile(uri: GitUri): Promise<GitBlame | undefined>;
getBlameForFileContents(uri: GitUri, contents: string): Promise<GitBlame | undefined>;
getBlameForLine(
@ -119,30 +122,10 @@ export interface GitProvider {
getBlameForRangeContents(uri: GitUri, range: Range, contents: string): Promise<GitBlameLines | undefined>;
getBlameForRangeSync(blame: GitBlame, uri: GitUri, range: Range): GitBlameLines | undefined;
getBranch(repoPath: string): Promise<GitBranch | undefined>;
// getBranchAheadRange(branch: GitBranch): Promise<string | undefined>;
getBranches(
repoPath: string,
options?: { filter?: ((b: GitBranch) => boolean) | undefined; sort?: boolean | BranchSortOptions | undefined },
): Promise<GitBranch[]>;
// getBranchesAndOrTags(
// repoPath: string | undefined,
// options?: {
// filter?:
// | { branches?: ((b: GitBranch) => boolean) | undefined; tags?: ((t: GitTag) => boolean) | undefined }
// | undefined;
// include?: 'branches' | 'tags' | 'all' | undefined;
// sort?:
// | boolean
// | { branches?: BranchSortOptions | undefined; tags?: TagSortOptions | undefined }
// | undefined;
// },
// ): Promise<any>;
// getBranchesAndTagsTipsFn(
// repoPath: string | undefined,
// currentName?: string,
// ): Promise<
// (sha: string, options?: { compact?: boolean | undefined; icons?: boolean | undefined }) => string | undefined
// >;
): Promise<PagedResult<GitBranch>>;
getChangedFilesCount(repoPath: string, ref?: string): Promise<GitDiffShortStat | undefined>;
getCommit(repoPath: string, ref: string): Promise<GitLogCommit | undefined>;
getCommitBranches(
@ -394,21 +377,21 @@ export interface GitProvider {
getTags(
repoPath: string | undefined,
options?: { filter?: ((t: GitTag) => boolean) | undefined; sort?: boolean | TagSortOptions | undefined },
): Promise<GitTag[]>;
): Promise<PagedResult<GitTag>>;
getTreeFileForRevision(repoPath: string, fileName: string, ref: string): Promise<GitTree | undefined>;
getTreeForRevision(repoPath: string, ref: string): Promise<GitTree[]>;
getVersionedFileBuffer(repoPath: string, fileName: string, ref: string): Promise<Buffer | undefined>;
getVersionedUri(repoPath: string | undefined, fileName: string, ref: string | undefined): Promise<Uri | undefined>;
getWorkingUri(repoPath: string, uri: Uri): Promise<Uri | undefined>;
// hasBranchesAndOrTags(
// repoPath: string | undefined,
// options?: {
// filter?:
// | { branches?: ((b: GitBranch) => boolean) | undefined; tags?: ((t: GitTag) => boolean) | undefined }
// | undefined;
// },
// ): Promise<boolean>;
hasBranchOrTag(
repoPath: string | undefined,
options?: {
filter?:
| { branches?: ((b: GitBranch) => boolean) | undefined; tags?: ((t: GitTag) => boolean) | undefined }
| undefined;
},
): Promise<boolean>;
hasRemotes(repoPath: string | undefined): Promise<boolean>;
hasTrackingBranch(repoPath: string | undefined): Promise<boolean>;
isActiveRepoPath(repoPath: string | undefined, editor?: TextEditor): Promise<boolean>;

+ 16
- 65
src/git/gitProviderService.ts View File

@ -64,7 +64,7 @@ import {
SearchPattern,
TagSortOptions,
} from './git';
import { GitProvider, GitProviderDescriptor, GitProviderId, ScmRepository } from './gitProvider';
import { GitProvider, GitProviderDescriptor, GitProviderId, PagedResult, ScmRepository } from './gitProvider';
import { GitUri } from './gitUri';
import { RemoteProvider, RemoteProviders, RichRemoteProvider } from './remotes/factory';
@ -753,7 +753,7 @@ export class GitProviderService implements Disposable {
if (branch.upstream == null) {
// If we have no upstream branch, try to find a best guess branch to use as the "base"
const branches = await this.getBranches(branch.repoPath, {
const { values: branches } = await this.getBranches(branch.repoPath, {
filter: b => weightedDefaultBranches.has(b.name),
});
if (branches.length > 0) {
@ -788,55 +788,13 @@ export class GitProviderService implements Disposable {
filter?: (b: GitBranch) => boolean;
sort?: boolean | BranchSortOptions;
},
): Promise<GitBranch[]> {
if (repoPath == null) return [];
): Promise<PagedResult<GitBranch>> {
if (repoPath == null) return { values: [] };
const { provider, path } = this.getProvider(repoPath);
return provider.getBranches(path, options);
}
@log({
args: {
1: () => false,
},
})
async getBranchesAndOrTags(
repoPath: string | Uri | undefined,
{
filter,
include,
sort,
...options
}: {
filter?: { branches?: (b: GitBranch) => boolean; tags?: (t: GitTag) => boolean };
include?: 'all' | 'branches' | 'tags';
sort?: boolean | { branches?: BranchSortOptions; tags?: TagSortOptions };
} = {},
): Promise<(GitBranch | GitTag)[] | GitBranch[] | GitTag[]> {
const [branches, tags] = await Promise.all([
include == null || include === 'all' || include === 'branches'
? this.getBranches(repoPath, {
...options,
filter: filter?.branches,
sort: typeof sort === 'boolean' ? undefined : sort?.branches,
})
: undefined,
include == null || include === 'all' || include === 'tags'
? this.getTags(repoPath, {
...options,
filter: filter?.tags,
sort: typeof sort === 'boolean' ? undefined : sort?.tags,
})
: undefined,
]);
if (branches != null && tags != null) {
return [...branches.filter(b => !b.remote), ...tags, ...branches.filter(b => b.remote)];
}
return branches ?? tags ?? [];
}
@log()
async getBranchesAndTagsTipsFn(
repoPath: string | Uri | undefined,
@ -844,7 +802,10 @@ export class GitProviderService implements Disposable {
): Promise<
(sha: string, options?: { compact?: boolean | undefined; icons?: boolean | undefined }) => string | undefined
> {
const [branches, tags] = await Promise.all([this.getBranches(repoPath), this.getTags(repoPath)]);
const [{ values: branches }, { values: tags }] = await Promise.all([
this.getBranches(repoPath),
this.getTags(repoPath),
]);
const branchesAndTagsBySha = Arrays.groupByFilterMap(
(branches as (GitBranch | GitTag)[]).concat(tags as (GitBranch | GitTag)[]),
@ -1577,8 +1538,8 @@ export class GitProviderService implements Disposable {
async getTags(
repoPath: string | Uri | undefined,
options?: { filter?: (t: GitTag) => boolean; sort?: boolean | TagSortOptions },
): Promise<GitTag[]> {
if (repoPath == null) return [];
): Promise<PagedResult<GitTag>> {
if (repoPath == null) return { values: [] };
const { provider, path } = this.getProvider(repoPath);
return provider.getTags(path, options);
@ -1629,26 +1590,16 @@ export class GitProviderService implements Disposable {
}
@log()
async hasBranchesAndOrTags(
async hasBranchOrTag(
repoPath: string | Uri | undefined,
{
filter,
}: {
options?: {
filter?: { branches?: (b: GitBranch) => boolean; tags?: (t: GitTag) => boolean };
} = {},
},
): Promise<boolean> {
const [branches, tags] = await Promise.all([
this.getBranches(repoPath, {
filter: filter?.branches,
sort: false,
}),
this.getTags(repoPath, {
filter: filter?.tags,
sort: false,
}),
]);
if (repoPath == null) return false;
return (branches != null && branches.length !== 0) || (tags != null && tags.length !== 0);
const { provider, path } = this.getProvider(repoPath);
return provider.hasBranchOrTag(path, options);
}
@log()

+ 9
- 25
src/git/models/repository.ts View File

@ -427,13 +427,11 @@ export class Repository implements Disposable {
}
}
@gate()
@log()
branch(...args: string[]) {
this.runTerminalCommand('branch', ...args);
}
@gate()
@log()
branchDelete(
branches: GitBranchReference | GitBranchReference[],
@ -485,7 +483,6 @@ export class Repository implements Disposable {
}
}
@gate(() => '')
@log()
cherryPick(...args: string[]) {
this.runTerminalCommand('cherry-pick', ...args);
@ -541,7 +538,9 @@ export class Repository implements Disposable {
async getBranch(name?: string): Promise<GitBranch | undefined> {
if (name) {
const [branch] = await this.getBranches({ filter: b => b.name === name });
const {
values: [branch],
} = await this.getBranches({ filter: b => b.name === name });
return branch;
}
@ -554,20 +553,11 @@ export class Repository implements Disposable {
getBranches(
options: {
filter?: (b: GitBranch) => boolean;
paging?: { cursor?: string; limit?: number };
sort?: boolean | BranchSortOptions;
} = {},
): Promise<GitBranch[]> {
return this.container.git.getBranches(this.path, options);
}
getBranchesAndOrTags(
options: {
filter?: { branches?: (b: GitBranch) => boolean; tags?: (t: GitTag) => boolean };
include?: 'all' | 'branches' | 'tags';
sort?: boolean | { branches?: BranchSortOptions; tags?: TagSortOptions };
} = {},
) {
return this.container.git.getBranchesAndOrTags(this.path, options);
return this.container.git.getBranches(this.path, options);
}
getChangedFilesCount(sha?: string): Promise<GitDiffShortStat | undefined> {
@ -657,7 +647,7 @@ export class Repository implements Disposable {
return this.container.git.getStatusForRepo(this.path);
}
getTags(options?: { filter?: (t: GitTag) => boolean; sort?: boolean | TagSortOptions }): Promise<GitTag[]> {
getTags(options?: { filter?: (t: GitTag) => boolean; sort?: boolean | TagSortOptions }) {
return this.container.git.getTags(this.path, options);
}
@ -676,7 +666,6 @@ export class Repository implements Disposable {
return branch?.upstream != null;
}
@gate(() => '')
@log()
merge(...args: string[]) {
this.runTerminalCommand('merge', ...args);
@ -826,7 +815,6 @@ export class Repository implements Disposable {
}
}
@gate(() => '')
@log()
rebase(configs: string[] | undefined, ...args: string[]) {
this.runTerminalCommand(
@ -835,7 +823,6 @@ export class Repository implements Disposable {
);
}
@gate(() => '')
@log()
reset(...args: string[]) {
this.runTerminalCommand('reset', ...args);
@ -869,7 +856,6 @@ export class Repository implements Disposable {
}
}
@gate()
@log()
revert(...args: string[]) {
this.runTerminalCommand('revert', ...args);
@ -891,7 +877,7 @@ export class Repository implements Disposable {
return this.updateStarred(true, branch);
}
@gate(() => '')
@gate()
@log()
async stashApply(stashName: string, options: { deleteAfter?: boolean } = {}) {
void (await this.container.git.stashApply(this.path, stashName, options));
@ -899,7 +885,7 @@ export class Repository implements Disposable {
this.fireChange(RepositoryChange.Stash);
}
@gate(() => '')
@gate()
@log()
async stashDelete(stashName: string, ref?: string) {
void (await this.container.git.stashDelete(this.path, stashName, ref));
@ -907,7 +893,7 @@ export class Repository implements Disposable {
this.fireChange(RepositoryChange.Stash);
}
@gate(() => '')
@gate()
@log()
async stashSave(message?: string, uris?: Uri[], options: { includeUntracked?: boolean; keepIndex?: boolean } = {}) {
void (await this.container.git.stashSave(this.path, message, uris, options));
@ -1008,13 +994,11 @@ export class Repository implements Disposable {
this._suspended = true;
}
@gate()
@log()
tag(...args: string[]) {
this.runTerminalCommand('tag', ...args);
}
@gate()
@log()
tagDelete(tags: GitTagReference | GitTagReference[]) {
if (!Array.isArray(tags)) {

+ 102
- 148
src/git/providers/localGitProvider.ts View File

@ -17,7 +17,7 @@ import {
} from 'vscode';
import type { API as BuiltInGitApi, Repository as BuiltInGitRepository, GitExtension } from '../../@types/vscode.git';
import { configuration } from '../../configuration';
import { BuiltInGitConfiguration, DocumentSchemes, GlyphChars } from '../../constants';
import { BuiltInGitConfiguration, DocumentSchemes } from '../../constants';
import { Container } from '../../container';
import { LogCorrelationContext, Logger } from '../../logger';
import { Messages } from '../../messages';
@ -81,7 +81,7 @@ import {
SearchPattern,
TagSortOptions,
} from '../git';
import { GitProvider, GitProviderId, RepositoryInitWatcher, ScmRepository } from '../gitProvider';
import { GitProvider, GitProviderId, PagedResult, RepositoryInitWatcher, ScmRepository } from '../gitProvider';
import { GitProviderService } from '../gitProviderService';
import { GitUri } from '../gitUri';
import { InvalidGitConfigError, UnableToFindGitError } from '../locator';
@ -103,15 +103,6 @@ const mappedAuthorRegex = /(.+)\s<(.+)>/;
const emptyPromise: Promise<GitBlame | GitDiff | GitLog | undefined> = Promise.resolve(undefined);
const reflogCommands = ['merge', 'pull'];
const maxDefaultBranchWeight = 100;
const weightedDefaultBranches = new Map<string, number>([
['master', maxDefaultBranchWeight],
['main', 15],
['default', 10],
['develop', 5],
['development', 1],
]);
export class LocalGitProvider implements GitProvider, Disposable {
descriptor = { id: GitProviderId.Git, name: 'Git' };
@ -916,7 +907,9 @@ export class LocalGitProvider implements GitProvider, Disposable {
@log()
async getBranch(repoPath: string): Promise<GitBranch | undefined> {
let [branch] = await this.getBranches(repoPath, { filter: b => b.current });
let {
values: [branch],
} = await this.getBranches(repoPath, { filter: b => b.current });
if (branch != null) return branch;
const data = await Git.rev_parse__currentBranch(repoPath, this.container.config.advanced.commitOrdering);
@ -947,41 +940,41 @@ export class LocalGitProvider implements GitProvider, Disposable {
return branch;
}
@log({
args: {
0: b => b.name,
},
})
async getBranchAheadRange(branch: GitBranch) {
if (branch.state.ahead > 0) {
return GitRevision.createRange(branch.upstream?.name, branch.ref);
}
if (branch.upstream == null) {
// If we have no upstream branch, try to find a best guess branch to use as the "base"
const branches = await this.getBranches(branch.repoPath, {
filter: b => weightedDefaultBranches.has(b.name),
});
if (branches.length > 0) {
let weightedBranch: { weight: number; branch: GitBranch } | undefined;
for (const branch of branches) {
const weight = weightedDefaultBranches.get(branch.name)!;
if (weightedBranch == null || weightedBranch.weight < weight) {
weightedBranch = { weight: weight, branch: branch };
}
if (weightedBranch.weight === maxDefaultBranchWeight) break;
}
const possibleBranch = weightedBranch!.branch.upstream?.name ?? weightedBranch!.branch.ref;
if (possibleBranch !== branch.ref) {
return GitRevision.createRange(possibleBranch, branch.ref);
}
}
}
return undefined;
}
// @log({
// args: {
// 0: b => b.name,
// },
// })
// async getBranchAheadRange(branch: GitBranch) {
// if (branch.state.ahead > 0) {
// return GitRevision.createRange(branch.upstream?.name, branch.ref);
// }
// if (branch.upstream == null) {
// // If we have no upstream branch, try to find a best guess branch to use as the "base"
// const { values: branches } = await this.getBranches(branch.repoPath, {
// filter: b => weightedDefaultBranches.has(b.name),
// });
// if (branches.length > 0) {
// let weightedBranch: { weight: number; branch: GitBranch } | undefined;
// for (const branch of branches) {
// const weight = weightedDefaultBranches.get(branch.name)!;
// if (weightedBranch == null || weightedBranch.weight < weight) {
// weightedBranch = { weight: weight, branch: branch };
// }
// if (weightedBranch.weight === maxDefaultBranchWeight) break;
// }
// const possibleBranch = weightedBranch!.branch.upstream?.name ?? weightedBranch!.branch.ref;
// if (possibleBranch !== branch.ref) {
// return GitRevision.createRange(possibleBranch, branch.ref);
// }
// }
// }
// return undefined;
// }
@log({
args: {
@ -994,8 +987,8 @@ export class LocalGitProvider implements GitProvider, Disposable {
filter?: (b: GitBranch) => boolean;
sort?: boolean | BranchSortOptions;
} = {},
): Promise<GitBranch[]> {
if (repoPath == null) return [];
): Promise<PagedResult<GitBranch>> {
if (repoPath == null) return { values: [] };
let branchesPromise = this.useCaching ? this._branchesCache.get(repoPath) : undefined;
if (branchesPromise == null) {
@ -1064,99 +1057,60 @@ export class LocalGitProvider implements GitProvider, Disposable {
GitBranch.sort(branches, typeof options.sort === 'boolean' ? undefined : options.sort);
}
return branches;
}
@log({
args: {
1: () => false,
},
})
async getBranchesAndOrTags(
repoPath: string | undefined,
{
filter,
include,
sort,
...options
}: {
filter?: { branches?: (b: GitBranch) => boolean; tags?: (t: GitTag) => boolean };
include?: 'all' | 'branches' | 'tags';
sort?: boolean | { branches?: BranchSortOptions; tags?: TagSortOptions };
} = {},
) {
const [branches, tags] = await Promise.all([
include == null || include === 'all' || include === 'branches'
? this.getBranches(repoPath, {
...options,
filter: filter?.branches,
sort: typeof sort === 'boolean' ? undefined : sort?.branches,
})
: undefined,
include == null || include === 'all' || include === 'tags'
? this.getTags(repoPath, {
...options,
filter: filter?.tags,
sort: typeof sort === 'boolean' ? undefined : sort?.tags,
})
: undefined,
]);
if (branches != null && tags != null) {
return [...branches.filter(b => !b.remote), ...tags, ...branches.filter(b => b.remote)];
}
return branches ?? tags;
}
@log()
async getBranchesAndTagsTipsFn(repoPath: string | undefined, currentName?: string) {
const [branches, tags] = await Promise.all([this.getBranches(repoPath), this.getTags(repoPath)]);
const branchesAndTagsBySha = Arrays.groupByFilterMap(
(branches as (GitBranch | GitTag)[]).concat(tags as (GitBranch | GitTag)[]),
bt => bt.sha,
bt => {
if (currentName) {
if (bt.name === currentName) return undefined;
if (bt.refType === 'branch' && bt.getNameWithoutRemote() === currentName) {
return { name: bt.name, compactName: bt.getRemoteName(), type: bt.refType };
}
}
return { name: bt.name, compactName: undefined, type: bt.refType };
},
);
return (sha: string, options?: { compact?: boolean; icons?: boolean }): string | undefined => {
const branchesAndTags = branchesAndTagsBySha.get(sha);
if (branchesAndTags == null || branchesAndTags.length === 0) return undefined;
if (!options?.compact) {
return branchesAndTags
.map(
bt => `${options?.icons ? `${bt.type === 'tag' ? '$(tag)' : '$(git-branch)'} ` : ''}${bt.name}`,
)
.join(', ');
}
if (branchesAndTags.length > 1) {
const [bt] = branchesAndTags;
return `${options?.icons ? `${bt.type === 'tag' ? '$(tag)' : '$(git-branch)'} ` : ''}${
bt.compactName ?? bt.name
}, ${GlyphChars.Ellipsis}`;
}
return branchesAndTags
.map(
bt =>
`${options?.icons ? `${bt.type === 'tag' ? '$(tag)' : '$(git-branch)'} ` : ''}${
bt.compactName ?? bt.name
}`,
)
.join(', ');
};
}
return { values: branches };
}
// @log()
// async getBranchesAndTagsTipsFn(repoPath: string | undefined, currentName?: string) {
// const [{ values: branches }, { values: tags }] = await Promise.all([
// this.getBranches(repoPath),
// this.getTags(repoPath),
// ]);
// const branchesAndTagsBySha = Arrays.groupByFilterMap(
// (branches as (GitBranch | GitTag)[]).concat(tags as (GitBranch | GitTag)[]),
// bt => bt.sha,
// bt => {
// if (currentName) {
// if (bt.name === currentName) return undefined;
// if (bt.refType === 'branch' && bt.getNameWithoutRemote() === currentName) {
// return { name: bt.name, compactName: bt.getRemoteName(), type: bt.refType };
// }
// }
// return { name: bt.name, compactName: undefined, type: bt.refType };
// },
// );
// return (sha: string, options?: { compact?: boolean; icons?: boolean }): string | undefined => {
// const branchesAndTags = branchesAndTagsBySha.get(sha);
// if (branchesAndTags == null || branchesAndTags.length === 0) return undefined;
// if (!options?.compact) {
// return branchesAndTags
// .map(
// bt => `options?.icons?$bt.type===tag?$(tag):$(gitbranch):{bt.name}`,
// )
// .join(', ');
// }
// if (branchesAndTags.length > 1) {
// const [bt] = branchesAndTags;
// return `options?.icons?$bt.type===tag?$(tag):$(gitbranch):{
// bt.compactName ?? bt.name
// }, ${GlyphChars.Ellipsis}`;
// }
// return branchesAndTags
// .map(
// bt =>
// `options?.icons?$bt.type===tag?$(tag):$(gitbranch):{
// bt.compactName ?? bt.name
// }`,
// )
// .join(', ');
// };
// }
@log()
async getChangedFilesCount(repoPath: string, ref?: string): Promise<GitDiffShortStat | undefined> {
@ -3249,8 +3203,8 @@ export class LocalGitProvider implements GitProvider, Disposable {
async getTags(
repoPath: string | undefined,
options: { filter?: (t: GitTag) => boolean; sort?: boolean | TagSortOptions } = {},
): Promise<GitTag[]> {
if (repoPath == null) return [];
): Promise<PagedResult<GitTag>> {
if (repoPath == null) return { values: [] };
let tagsPromise = this.useCaching ? this._tagsCache.get(repoPath) : undefined;
if (tagsPromise == null) {
@ -3286,7 +3240,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
GitTag.sort(tags, typeof options.sort === 'boolean' ? undefined : options.sort);
}
return tags;
return { values: tags };
}
@log()
@ -3384,7 +3338,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
}
@log()
async hasBranchesAndOrTags(
async hasBranchOrTag(
repoPath: string | undefined,
{
filter,
@ -3392,7 +3346,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
filter?: { branches?: (b: GitBranch) => boolean; tags?: (t: GitTag) => boolean };
} = {},
) {
const [branches, tags] = await Promise.all([
const [{ values: branches }, { values: tags }] = await Promise.all([
this.getBranches(repoPath, {
filter: filter?.branches,
sort: false,
@ -3403,7 +3357,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
}),
]);
return (branches != null && branches.length !== 0) || (tags != null && tags.length !== 0);
return branches.length !== 0 || tags.length !== 0;
}
@log()

+ 14
- 11
src/git/remotes/bitbucket-server.ts View File

@ -90,26 +90,29 @@ export class BitbucketServerRemote extends RemoteProvider {
}
}
const branches = new Set<string>(
(
await repository.getBranches({
filter: b => b.remote,
})
).map(b => b.getNameWithoutRemote()),
);
// Check for a link with branch (and deal with branch names with /)
let branch;
const possibleBranches = new Map<string, string>();
index = path.length;
do {
index = path.lastIndexOf('/', index - 1);
branch = path.substring(1, index);
if (branches.has(branch)) {
const uri = repository.toAbsoluteUri(path.substr(index), { validate: options?.validate });
possibleBranches.set(branch, path.substr(index));
} while (index > 0);
if (possibleBranches.size !== 0) {
const { values: branches } = await repository.getBranches({
filter: b => b.remote && possibleBranches.has(b.getNameWithoutRemote()),
});
for (const branch of branches) {
const path = possibleBranches.get(branch.getNameWithoutRemote());
if (path == null) continue;
const uri = repository.toAbsoluteUri(path, { validate: options?.validate });
if (uri != null) return { uri: uri, startLine: startLine, endLine: endLine };
}
} while (index > 0);
}
return undefined;
}

+ 14
- 11
src/git/remotes/bitbucket.ts View File

@ -83,26 +83,29 @@ export class BitbucketRemote extends RemoteProvider {
}
}
const branches = new Set<string>(
(
await repository.getBranches({
filter: b => b.remote,
})
).map(b => b.getNameWithoutRemote()),
);
// Check for a link with branch (and deal with branch names with /)
let branch;
const possibleBranches = new Map<string, string>();
index = path.length;
do {
index = path.lastIndexOf('/', index - 1);
branch = path.substring(1, index);
if (branches.has(branch)) {
const uri = repository.toAbsoluteUri(path.substr(index), { validate: options?.validate });
possibleBranches.set(branch, path.substr(index));
} while (index > 0);
if (possibleBranches.size !== 0) {
const { values: branches } = await repository.getBranches({
filter: b => b.remote && possibleBranches.has(b.getNameWithoutRemote()),
});
for (const branch of branches) {
const path = possibleBranches.get(branch.getNameWithoutRemote());
if (path == null) continue;
const uri = repository.toAbsoluteUri(path, { validate: options?.validate });
if (uri != null) return { uri: uri, startLine: startLine, endLine: endLine };
}
} while (index > 0);
}
return undefined;
}

+ 34
- 16
src/git/remotes/gerrit.ts View File

@ -86,42 +86,60 @@ export class GerritRemote extends RemoteProvider {
// Check for a link with branch (and deal with branch names with /)
if (path.startsWith('/refs/heads/')) {
const branches = new Set<string>(
(
await repository.getBranches({
filter: b => b.remote,
})
).map(b => b.getNameWithoutRemote()),
);
const branchPath = path.substr('/refs/heads/'.length);
let branch;
const possibleBranches = new Map<string, string>();
index = branchPath.length;
do {
index = branchPath.lastIndexOf('/', index - 1);
const branch = branchPath.substring(0, index);
branch = branchPath.substring(1, index);
if (branches.has(branch)) {
const uri = repository.toAbsoluteUri(branchPath.substr(index), { validate: options?.validate });
possibleBranches.set(branch, branchPath.substr(index));
} while (index > 0);
if (possibleBranches.size !== 0) {
const { values: branches } = await repository.getBranches({
filter: b => b.remote && possibleBranches.has(b.getNameWithoutRemote()),
});
for (const branch of branches) {
const path = possibleBranches.get(branch.getNameWithoutRemote());
if (path == null) continue;
const uri = repository.toAbsoluteUri(path, { validate: options?.validate });
if (uri != null) return { uri: uri, startLine: startLine };
}
} while (index > 0);
}
return undefined;
}
// Check for a link with tag (and deal with tag names with /)
if (path.startsWith('/refs/tags/')) {
const tags = new Set<string>((await repository.getTags()).map(t => t.name));
const tagPath = path.substr('/refs/tags/'.length);
let tag;
const possibleTags = new Map<string, string>();
index = tagPath.length;
do {
index = tagPath.lastIndexOf('/', index - 1);
const tag = tagPath.substring(0, index);
tag = tagPath.substring(1, index);
if (tags.has(tag)) {
const uri = repository.toAbsoluteUri(tagPath.substr(index), { validate: options?.validate });
possibleTags.set(tag, tagPath.substr(index));
} while (index > 0);
if (possibleTags.size !== 0) {
const { values: tags } = await repository.getTags({
filter: t => possibleTags.has(t.name),
});
for (const tag of tags) {
const path = possibleTags.get(tag.name);
if (path == null) continue;
const uri = repository.toAbsoluteUri(path, { validate: options?.validate });
if (uri != null) return { uri: uri, startLine: startLine };
}
} while (index > 0);
}
return undefined;
}

+ 15
- 13
src/git/remotes/gitea.ts View File

@ -83,29 +83,31 @@ export class GiteaRemote extends RemoteProvider {
}
}
const branches = new Set<string>(
(
await repository.getBranches({
filter: b => b.remote,
})
).map(b => b.getNameWithoutRemote()),
);
// Check for a link with branch (and deal with branch names with /)
if (path.startsWith('/branch/')) {
let branch;
const possibleBranches = new Map<string, string>();
offset = '/branch/'.length;
index = offset;
do {
branch = path.substring(offset, index);
if (branches.has(branch)) {
const uri = repository.toAbsoluteUri(path.substr(index), { validate: options?.validate });
if (uri != null) return { uri: uri, startLine: startLine, endLine: endLine };
}
possibleBranches.set(branch, path.substr(index));
index = path.indexOf('/', index + 1);
} while (index < path.length && index !== -1);
if (possibleBranches.size !== 0) {
const { values: branches } = await repository.getBranches({
filter: b => b.remote && possibleBranches.has(b.getNameWithoutRemote()),
});
for (const branch of branches) {
const path = possibleBranches.get(branch.getNameWithoutRemote());
if (path == null) continue;
const uri = repository.toAbsoluteUri(path, { validate: options?.validate });
if (uri != null) return { uri: uri, startLine: startLine, endLine: endLine };
}
}
}
return undefined;

+ 14
- 11
src/git/remotes/github.ts View File

@ -111,26 +111,29 @@ export class GitHubRemote extends RichRemoteProvider {
}
}
const branches = new Set<string>(
(
await repository.getBranches({
filter: b => b.remote,
})
).map(b => b.getNameWithoutRemote()),
);
// Check for a link with branch (and deal with branch names with /)
let branch;
const possibleBranches = new Map<string, string>();
index = path.length;
do {
index = path.lastIndexOf('/', index - 1);
branch = path.substring(1, index);
if (branches.has(branch)) {
const uri = repository.toAbsoluteUri(path.substr(index), { validate: options?.validate });
possibleBranches.set(branch, path.substr(index));
} while (index > 0);
if (possibleBranches.size !== 0) {
const { values: branches } = await repository.getBranches({
filter: b => b.remote && possibleBranches.has(b.getNameWithoutRemote()),
});
for (const branch of branches) {
const path = possibleBranches.get(branch.getNameWithoutRemote());
if (path == null) continue;
const uri = repository.toAbsoluteUri(path, { validate: options?.validate });
if (uri != null) return { uri: uri, startLine: startLine, endLine: endLine };
}
} while (index > 0);
}
return undefined;
}

+ 14
- 11
src/git/remotes/gitlab.ts View File

@ -78,26 +78,29 @@ export class GitLabRemote extends RemoteProvider {
}
}
const branches = new Set<string>(
(
await repository.getBranches({
filter: b => b.remote,
})
).map(b => b.getNameWithoutRemote()),
);
// Check for a link with branch (and deal with branch names with /)
let branch;
const possibleBranches = new Map<string, string>();
index = path.length;
do {
index = path.lastIndexOf('/', index - 1);
branch = path.substring(1, index);
if (branches.has(branch)) {
const uri = repository.toAbsoluteUri(path.substr(index), { validate: options?.validate });
possibleBranches.set(branch, path.substr(index));
} while (index > 0);
if (possibleBranches.size !== 0) {
const { values: branches } = await repository.getBranches({
filter: b => b.remote && possibleBranches.has(b.getNameWithoutRemote()),
});
for (const branch of branches) {
const path = possibleBranches.get(branch.getNameWithoutRemote());
if (path == null) continue;
const uri = repository.toAbsoluteUri(path, { validate: options?.validate });
if (uri != null) return { uri: uri, startLine: startLine, endLine: endLine };
}
} while (index > 0);
}
return undefined;
}

+ 10
- 8
src/quickpicks/remoteProviderPicker.ts View File

@ -73,14 +73,16 @@ export class CopyOrOpenRemoteCommandQuickPickItem extends CommandQuickPickItem {
// Since Bitbucket can't support branch names in the url (other than with the default branch),
// turn this into a `Revision` request
const { branchOrTag } = resource;
const branchesOrTags = await Container.instance.git.getBranchesAndOrTags(this.remote.repoPath, {
filter: {
branches: b => b.name === branchOrTag || GitBranch.getNameWithoutRemote(b.name) === branchOrTag,
tags: b => b.name === branchOrTag,
},
});
const sha = branchesOrTags?.[0]?.sha;
const [branches, tags] = await Promise.allSettled([
Container.instance.git.getBranches(this.remote.repoPath, {
filter: b => b.name === branchOrTag || b.getNameWithoutRemote() === branchOrTag,
}),
Container.instance.git.getTags(this.remote.repoPath, { filter: t => t.name === branchOrTag }),
]);
const sha =
(branches.status === 'fulfilled' ? branches.value.values[0]?.sha : undefined) ??
(tags.status === 'fulfilled' ? tags.value.values[0]?.sha : undefined);
if (sha) {
resource = { ...resource, type: RemoteResourceType.Revision, sha: sha };
}

+ 34
- 10
src/terminal/linkProvider.ts View File

@ -7,7 +7,8 @@ import {
ShowQuickCommitCommandArgs,
} from '../commands';
import { Container } from '../container';
import { GitReference } from '../git/git';
import { GitBranch, GitReference, GitTag } from '../git/git';
import { PagedResult } from '../git/gitProvider';
const commandsRegexShared =
/\b(g(?:it)?\b\s*)\b(branch|checkout|cherry-pick|fetch|grep|log|merge|pull|push|rebase|reset|revert|show|stash|status|tag)\b/gi;
@ -42,7 +43,8 @@ export class GitTerminalLinkProvider implements Disposable, TerminalLinkProvider
const links: GitTerminalLink[] = [];
const branchesAndTags = await this.container.git.getBranchesAndOrTags(repoPath);
let branchResults: PagedResult<GitBranch> | undefined;
let tagResults: PagedResult<GitTag> | undefined;
// Don't use the shared regex instance directly, because we can be called reentrantly (because of the awaits below)
const refRegex = new RegExp(refRegexShared, refRegexShared.flags);
@ -91,19 +93,41 @@ export class GitTerminalLinkProvider implements Disposable, TerminalLinkProvider
continue;
}
const branchOrTag = branchesAndTags?.find(r => r.name === ref);
if (branchOrTag != null) {
if (branchResults === undefined) {
branchResults = await this.container.git.getBranches(repoPath);
// TODO@eamodio handle paging
}
const branch = branchResults.values.find(r => r.name === ref);
if (branch != null) {
const link: GitTerminalLink<ShowQuickBranchHistoryCommandArgs> = {
startIndex: match.index,
length: ref.length,
tooltip: branchOrTag.refType === 'branch' ? 'Show Branch' : 'Show Tag',
tooltip: 'Show Branch',
command: {
command: Commands.ShowQuickBranchHistory,
args: {
branch: branchOrTag.refType === 'branch' ? branchOrTag.name : undefined,
tag: branchOrTag.refType === 'tag' ? branchOrTag.name : undefined,
repoPath: repoPath,
},
args: { repoPath: repoPath, branch: branch.name },
},
};
links.push(link);
continue;
}
if (tagResults === undefined) {
tagResults = await this.container.git.getTags(repoPath);
// TODO@eamodio handle paging
}
const tag = tagResults.values.find(r => r.name === ref);
if (tag != null) {
const link: GitTerminalLink<ShowQuickBranchHistoryCommandArgs> = {
startIndex: match.index,
length: ref.length,
tooltip: 'Show Tag',
command: {
command: Commands.ShowQuickBranchHistory,
args: { repoPath: repoPath, tag: tag.name },
},
};
links.push(link);

+ 2
- 2
src/views/branchesView.ts View File

@ -89,7 +89,7 @@ export class BranchesViewNode extends RepositoriesSubscribeableNode
}
const branches = await child.repo.getBranches({ filter: b => !b.remote });
if (branches.length === 0) {
if (branches.values.length === 0) {
this.view.message = 'No branches could be found.';
this.view.title = 'Branches';
@ -99,7 +99,7 @@ export class BranchesViewNode extends RepositoriesSubscribeableNode
}
this.view.message = undefined;
this.view.title = `Branches (${branches.length})`;
this.view.title = `Branches (${branches.values.length})`;
return child.getChildren();
}

+ 3
- 2
src/views/nodes/branchesNode.ts View File

@ -44,9 +44,10 @@ export class BranchesNode extends ViewNode {
filter: b => !b.remote,
sort: { current: false },
});
if (branches.length === 0) return [new MessageNode(this.view, this, 'No branches could be found.')];
if (branches.values.length === 0) return [new MessageNode(this.view, this, 'No branches could be found.')];
const branchNodes = branches.map(
// TODO@eamodio handle paging
const branchNodes = branches.values.map(
b =>
new BranchNode(GitUri.fromRepoPath(this.uri.repoPath!, b.ref), this.view, this, b, false, {
showComparison:

+ 4
- 2
src/views/nodes/fileHistoryTrackerNode.ts View File

@ -66,9 +66,11 @@ export class FileHistoryTrackerNode extends SubscribeableViewNode
if (!commitish.sha || commitish.sha === 'HEAD') {
branch = await this.view.container.git.getBranch(this.uri.repoPath);
} else if (!GitRevision.isSha(commitish.sha)) {
[branch] = await this.view.container.git.getBranches(this.uri.repoPath, {
({
values: [branch],
} = await this.view.container.git.getBranches(this.uri.repoPath, {
filter: b => b.name === commitish.sha,
});
}));
}
this._child = new FileHistoryNode(fileUri, this.view, this, folder, branch);
}

+ 4
- 2
src/views/nodes/lineHistoryTrackerNode.ts View File

@ -76,9 +76,11 @@ export class LineHistoryTrackerNode extends SubscribeableViewNode
if (!commitish.sha || commitish.sha === 'HEAD') {
branch = await this.view.container.git.getBranch(this.uri.repoPath);
} else if (!GitRevision.isSha(commitish.sha)) {
[branch] = await this.view.container.git.getBranches(this.uri.repoPath, {
({
values: [branch],
} = await this.view.container.git.getBranches(this.uri.repoPath, {
filter: b => b.name === commitish.sha,
});
}));
}
this._child = new LineHistoryNode(fileUri, this.view, this, branch, this._selection, this._editorContents);
}

+ 3
- 2
src/views/nodes/remoteNode.ts View File

@ -43,9 +43,10 @@ export class RemoteNode extends ViewNode {
filter: b => b.remote && b.name.startsWith(this.remote.name),
sort: true,
});
if (branches.length === 0) return [new MessageNode(this.view, this, 'No branches could be found.')];
if (branches.values.length === 0) return [new MessageNode(this.view, this, 'No branches could be found.')];
const branchNodes = branches.map(
// TODO@eamodio handle paging
const branchNodes = branches.values.map(
b =>
new BranchNode(GitUri.fromRepoPath(this.uri.repoPath!, b.ref), this.view, this, b, false, {
showComparison: false,

+ 3
- 2
src/views/nodes/tagsNode.ts View File

@ -35,9 +35,10 @@ export class TagsNode extends ViewNode {
async getChildren(): Promise<ViewNode[]> {
if (this._children == null) {
const tags = await this.repo.getTags({ sort: true });
if (tags.length === 0) return [new MessageNode(this.view, this, 'No tags could be found.')];
if (tags.values.length === 0) return [new MessageNode(this.view, this, 'No tags could be found.')];
const tagNodes = tags.map(
// TODO@eamodio handle paging
const tagNodes = tags.values.map(
t => new TagNode(GitUri.fromRepoPath(this.uri.repoPath!, t.ref), this.view, this, t),
);
if (this.view.config.branches.layout === ViewBranchesLayout.List) return tagNodes;

+ 2
- 2
src/views/tagsView.ts View File

@ -71,7 +71,7 @@ export class TagsViewNode extends RepositoriesSubscribeableNode
}
const tags = await child.repo.getTags();
if (tags.length === 0) {
if (tags.values.length === 0) {
this.view.message = 'No tags could be found.';
this.view.title = 'Tags';
@ -81,7 +81,7 @@ export class TagsViewNode extends RepositoriesSubscribeableNode
}
this.view.message = undefined;
this.view.title = `Tags (${tags.length})`;
this.view.title = `Tags (${tags.values.length})`;
return child.getChildren();
}

||||||
x
 
000:0
Loading…
Cancel
Save