Browse Source

Adds graph searching for virtual GitHub repos

main
Eric Amodio 2 years ago
parent
commit
142a1b7c6b
7 changed files with 201 additions and 100 deletions
  1. +1
    -1
      src/env/node/git/localGitProvider.ts
  2. +1
    -1
      src/git/gitProvider.ts
  3. +2
    -2
      src/git/gitProviderService.ts
  4. +2
    -2
      src/git/models/repository.ts
  5. +68
    -0
      src/plus/github/github.ts
  6. +126
    -93
      src/plus/github/githubGitProvider.ts
  7. +1
    -1
      src/plus/webviews/graph/graphWebview.ts

+ 1
- 1
src/env/node/git/localGitProvider.ts View File

@ -2637,7 +2637,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
}
@log()
async searchForCommitsSimple(
async searchForCommitShas(
repoPath: string,
search: SearchQuery,
options?: { cancellation?: CancellationToken; limit?: number; ordering?: 'date' | 'author-date' | 'topo' },

+ 1
- 1
src/git/gitProvider.ts View File

@ -296,7 +296,7 @@ export interface GitProvider extends Disposable {
since?: string | undefined;
},
): Promise<Set<string> | undefined>;
searchForCommitsSimple(
searchForCommitShas(
repoPath: string | Uri,
search: SearchQuery,
options?: { cancellation?: CancellationToken; limit?: number; ordering?: 'date' | 'author-date' | 'topo' },

+ 2
- 2
src/git/gitProviderService.ts View File

@ -1466,13 +1466,13 @@ export class GitProviderService implements Disposable {
}
@log()
searchForCommitsSimple(
searchForCommitShas(
repoPath: string | Uri,
search: SearchQuery,
options?: { cancellation?: CancellationToken; limit?: number; ordering?: 'date' | 'author-date' | 'topo' },
): Promise<GitSearch> {
const { provider, path } = this.getProvider(repoPath);
return provider.searchForCommitsSimple(path, search, options);
return provider.searchForCommitShas(path, search, options);
}
@log()

+ 2
- 2
src/git/models/repository.ts View File

@ -859,11 +859,11 @@ export class Repository implements Disposable {
return this.container.git.getLogForSearch(this.path, search, options);
}
searchForCommitsSimple(
searchForCommitShas(
search: SearchQuery,
options?: { cancellation?: CancellationToken; limit?: number; ordering?: 'date' | 'author-date' | 'topo' },
): Promise<GitSearch> {
return this.container.git.searchForCommitsSimple(this.path, search, options);
return this.container.git.searchForCommitShas(this.path, search, options);
}
async setRemoteAsDefault(remote: GitRemote, value: boolean = true) {

+ 68
- 0
src/plus/github/github.ts View File

@ -1900,6 +1900,74 @@ export class GitHubApi implements Disposable {
}
}
@debug<GitHubApi['searchCommitShas']>({ args: { 0: '<token>' } })
async searchCommitShas(
token: string,
query: string,
options?: {
cursor?: string;
limit?: number;
order?: 'asc' | 'desc' | undefined;
sort?: 'author-date' | 'committer-date' | undefined;
},
): Promise<GitHubPagedResult<string> | undefined> {
const scope = getLogScope();
const limit = Math.min(100, options?.limit ?? 100);
let page;
let pageSize;
let previousCount;
if (options?.cursor != null) {
[page, pageSize, previousCount] = options.cursor.split(' ', 3);
page = parseInt(page, 10);
// TODO@eamodio need to figure out how allow different page sizes if the limit changes
pageSize = parseInt(pageSize, 10);
previousCount = parseInt(previousCount, 10);
} else {
page = 1;
pageSize = limit;
previousCount = 0;
}
try {
const rsp = await this.request(
undefined,
token,
'GET /search/commits',
{
q: query,
sort: options?.sort,
order: options?.order,
per_page: pageSize,
page: page,
},
scope,
);
const data = rsp?.data;
if (data == null || data.items.length === 0) return undefined;
const count = previousCount + data.items.length;
const hasMore = data.incomplete_results || data.total_count > count;
return {
pageInfo: {
startCursor: `${page} ${pageSize} ${previousCount}`,
endCursor: hasMore ? `${page + 1} ${pageSize} ${count}` : undefined,
hasPreviousPage: data.total_count > 0 && page > 1,
hasNextPage: hasMore,
},
totalCount: data.total_count,
values: data.items.map(r => r.sha),
};
} catch (ex) {
if (ex instanceof ProviderRequestNotFoundError) return undefined;
throw this.handleException(ex, undefined, scope);
}
}
private _enterpriseVersions = new Map<string, Version | null>();
@debug<GitHubApi['getEnterpriseVersion']>({ args: { 0: '<token>' } })

+ 126
- 93
src/plus/github/githubGitProvider.ts View File

@ -90,6 +90,7 @@ import { getRemoteHubApi } from '../remotehub';
import type { GitHubApi } from './github';
import { fromCommitFileStatus } from './models';
const doubleQuoteRegex = /"/g;
const emptyPagedResult: PagedResult<any> = Object.freeze({ values: [] });
const emptyPromise: Promise<GitBlame | GitDiff | GitLog | undefined> = Promise.resolve(undefined);
@ -1070,8 +1071,12 @@ export class GitHubGitProvider implements GitProvider, Disposable {
ref?: string;
},
): Promise<GitGraph> {
const defaultLimit = options?.limit ?? configuration.get('graph.defaultItemLimit') ?? 5000;
// const defaultPageLimit = configuration.get('graph.pageItemLimit') ?? 1000;
const ordering = configuration.get('graph.commitOrdering', undefined, 'date');
const [logResult, branchResult, remotesResult, tagsResult] = await Promise.allSettled([
this.getLog(repoPath, { all: true, ordering: 'date', limit: options?.limit }),
this.getLog(repoPath, { all: true, ordering: ordering, limit: defaultLimit }),
this.getBranch(repoPath),
this.getRemotes(repoPath),
this.getTags(repoPath),
@ -1213,7 +1218,6 @@ export class GitHubGitProvider implements GitProvider, Disposable {
paging: {
limit: log.limit,
// endingCursor: log.endingCursor,
startingCursor: log.startingCursor,
hasMore: log.hasMore,
},
@ -1582,104 +1586,133 @@ export class GitHubGitProvider implements GitProvider, Disposable {
}
@log()
async searchForCommitsSimple(
async searchForCommitShas(
repoPath: string,
search: SearchQuery,
_options?: { cancellation?: CancellationToken; limit?: number; ordering?: 'date' | 'author-date' | 'topo' },
options?: { cancellation?: CancellationToken; limit?: number; ordering?: 'date' | 'author-date' | 'topo' },
): Promise<GitSearch> {
// const scope = getLogScope();
search = { matchAll: false, matchCase: false, matchRegex: true, ...search };
const comparisonKey = getSearchQueryComparisonKey(search);
return {
repoPath: repoPath,
query: search,
comparisonKey: comparisonKey,
results: new Set<string>(),
};
// try {
// const { args: searchArgs, files, commits } = this.getArgsFromSearchPattern(search);
// if (commits?.length) {
// return {
// repoPath: repoPath,
// pattern: search,
// results: commits,
// };
// }
// const refParser = getGraphRefParser();
// const limit = options?.limit ?? configuration.get('advanced.maxSearchItems') ?? 0;
// const similarityThreshold = configuration.get('advanced.similarityThreshold');
// const args = [
// 'log',
// ...refParser.arguments,
// `-M${similarityThreshold == null ? '' : `${similarityThreshold}%`}`,
// '--use-mailmap',
// ];
// if (limit) {
// args.push(`-n${limit + 1}`);
// }
// if (options?.ordering) {
// args.push(`--${options.ordering}-order`);
// }
// searchArgs.push(`-M${similarityThreshold == null ? '' : `${similarityThreshold}%`}`, '--');
// if (files.length !== 0) {
// searchArgs.push(...files);
// }
// async function searchForCommitsCore(
// this: LocalGitProvider,
// limit: number,
// cursor?: { sha: string; skip: number },
// ): Promise<GitSearch> {
// const data = await this.git.log2(
// repoPath,
// undefined,
// ...args,
// ...(cursor?.skip ? [`--skip=${cursor.skip}`] : []),
// ...searchArgs,
// '--',
// ...files,
// );
// const results = [...refParser.parse(data)];
// const last = results[results.length - 1];
// cursor =
// last != null
// ? {
// sha: last,
// skip: results.length,
// }
// : undefined;
// return {
// repoPath: repoPath,
// pattern: search,
// results: results,
// paging:
// limit !== 0 && results.length > limit
// ? {
// limit: limit,
// startingCursor: cursor?.sha,
// more: true,
// }
// : undefined,
// more: async (limit: number): Promise<GitSearch | undefined> =>
// searchForCommitsCore.call(this, limit, cursor),
// };
// }
// return searchForCommitsCore.call(this, limit);
// } catch (ex) {
// // TODO@eamodio handle error reporting -- just invalid patterns? or more detailed?
// return {
// repoPath: repoPath,
// pattern: search,
// results: [],
// };
// }
try {
const results = new Set<string>();
const operations = parseSearchQuery(search.query);
let op;
let values = operations.get('commit:');
if (values != null) {
for (const value of values) {
results.add(value.replace(doubleQuoteRegex, ''));
}
return {
repoPath: repoPath,
query: search,
comparisonKey: comparisonKey,
results: results,
};
}
const queryValues: string[] = [];
for ([op, values] of operations.entries()) {
switch (op) {
case 'message:':
queryValues.push(...values.map(m => m.replace(/ /g, '+')));
break;
case 'author:':
queryValues.push(
...values.map(a => {
a = a.replace(/ /g, '+');
if (a.startsWith('@')) return `author:${a.slice(1)}`;
if (a.startsWith('"@')) return `author:"${a.slice(2)}`;
if (a.includes('@')) return `author-email:${a}`;
return `author-name:${a}`;
}),
);
break;
// case 'change:':
// case 'file:':
// break;
}
}
if (queryValues.length === 0) {
return {
repoPath: repoPath,
query: search,
comparisonKey: comparisonKey,
results: results,
};
}
const { metadata, github, session } = await this.ensureRepositoryContext(repoPath);
const query = `repo:${metadata.repo.owner}/${metadata.repo.name}+${queryValues.join('+').trim()}`;
async function searchForCommitsCore(
this: GitHubGitProvider,
limit: number | undefined,
cursor?: string,
): Promise<GitSearch> {
if (options?.cancellation?.isCancellationRequested) {
// TODO@eamodio: Should we throw an error here?
return { repoPath: repoPath, query: search, comparisonKey: comparisonKey, results: results };
}
limit = this.getPagingLimit(limit ?? configuration.get('advanced.maxSearchItems'));
const result = await github.searchCommitShas(session.accessToken, query, {
cursor: cursor,
limit: limit,
sort:
options?.ordering === 'date'
? 'committer-date'
: options?.ordering === 'author-date'
? 'author-date'
: undefined,
});
if (result == null || options?.cancellation?.isCancellationRequested) {
// TODO@eamodio: Should we throw an error if cancelled?
return { repoPath: repoPath, query: search, comparisonKey: comparisonKey, results: results };
}
for (const sha of result.values) {
results.add(sha);
}
cursor = result.pageInfo?.endCursor ?? undefined;
return {
repoPath: repoPath,
query: search,
comparisonKey: comparisonKey,
results: results,
paging: result.pageInfo?.hasNextPage
? {
limit: limit,
hasMore: true,
}
: undefined,
more: async (limit: number): Promise<GitSearch> => searchForCommitsCore.call(this, limit, cursor),
};
}
return searchForCommitsCore.call(this, options?.limit);
} catch (ex) {
// TODO@eamodio: Should we throw an error here?
// TODO@eamodio handle error reporting -- just invalid queries? or more detailed?
return {
repoPath: repoPath,
query: search,
comparisonKey: comparisonKey,
results: new Set<string>(),
};
}
}
@log()

+ 1
- 1
src/plus/webviews/graph/graphWebview.ts View File

@ -478,7 +478,7 @@ export class GraphWebview extends WebviewBase {
const cancellation = new CancellationTokenSource();
this._searchCancellation = cancellation;
search = await this._repository.searchForCommitsSimple(e.search, {
search = await this._repository.searchForCommitShas(e.search, {
limit: configuration.get('graph.searchItemLimit') ?? 100,
ordering: configuration.get('graph.commitOrdering'),
cancellation: cancellation.token,

Loading…
Cancel
Save