diff --git a/extensions/github1s/package-lock.json b/extensions/github1s/package-lock.json index 7bf1546e9..8266fd138 100644 --- a/extensions/github1s/package-lock.json +++ b/extensions/github1s/package-lock.json @@ -31,7 +31,7 @@ "webpack-cli": "^4.9.2" }, "engines": { - "vscode": "^1.48.0" + "vscode": "^1.96.0" } }, "node_modules/@apollo/client": { diff --git a/extensions/github1s/package.json b/extensions/github1s/package.json index 7cf57d414..325daaa83 100644 --- a/extensions/github1s/package.json +++ b/extensions/github1s/package.json @@ -25,7 +25,7 @@ ], "browser": "./dist/extension", "engines": { - "vscode": "^1.48.0" + "vscode": "^1.96.0" }, "contributes": { "resourceLabelFormatters": [ @@ -244,7 +244,7 @@ "dark": "assets/icons/dark/open-left-file.svg", "light": "assets/icons/light/open-left-file.svg" }, - "enablement": "isInDiffEditor && resourceScheme =~ /^(github1s|gitlab1s|bitbucket1s)/ && resource =~ /^(?![^?]*\\?[^#]*(%26|\\b)base(=|%3D|%3d)github1s-empty-file)/" + "enablement": "isInDiffEditor && diffEditorOriginalUri && !(diffEditorOriginalUri =~ /^github1s-empty-file:/)" }, { "command": "github1s.commands.diffViewOpenRightFile", @@ -254,21 +254,21 @@ "dark": "assets/icons/dark/open-right-file.svg", "light": "assets/icons/light/open-right-file.svg" }, - "enablement": "isInDiffEditor && resourceScheme =~ /^(github1s|gitlab1s|bitbucket1s)/ && resource =~ /^(?![^?]*\\?[^#]*(%26|\\b)head(=|%3D|%3d)github1s-empty-file)/" + "enablement": "isInDiffEditor && diffEditorModifiedUri && !(diffEditorModifiedUri =~ /^github1s-empty-file:/)" }, { "command": "github1s.commands.openFilePreviousRevision", "title": "Open Previous Revision", "category": "GitHub1s", "icon": "$(arrow-left)", - "enablement": "resourceScheme =~ /^(github1s|gitlab1s|bitbucket1s)$/ && resource =~ /^(?![^?]*\\?[^#]*(%26|\\b)base(=|%3D|%3d)github1s-empty-file)/" + "enablement": "(!isInDiffEditor && resourceScheme =~ /^(github1s|gitlab1s|bitbucket1s)$/) || (isInDiffEditor && diffEditorOriginalUri =~ /^(github1s|gitlab1s|bitbucket1s):/)" }, { "command": "github1s.commands.openFileNextRevision", "title": "Open Next Revision", "category": "GitHub1s", "icon": "$(arrow-right)", - "enablement": "resource =~ /^[^?]*\\?[^#]*(%26|\\b)hasNextRevision(=|%3D|%3d)true/" + "enablement": "resourceScheme =~ /^(github1s|gitlab1s|bitbucket1s)$/ && resource =~ /^[^?]*\\?([^#]*(&|%26))?hasNext(=|%3D|%3d)true/" }, { "command": "github1s.commands.toggleEditorGutterBlame", @@ -588,12 +588,12 @@ }, { "command": "github1s.commands.openFilePreviousRevision", - "when": "resourceScheme =~ /^(github1s|gitlab1s|bitbucket1s)/", + "when": "resourceScheme =~ /^(github1s|gitlab1s|bitbucket1s)$/ || (isInDiffEditor && diffEditorOriginalUri =~ /^(github1s|gitlab1s|bitbucket1s):/)", "group": "navigation@4" }, { "command": "github1s.commands.openFileNextRevision", - "when": "resourceScheme =~ /^(github1s|gitlab1s|bitbucket1s)/", + "when": "resourceScheme =~ /^(github1s|gitlab1s|bitbucket1s)$/ || (isInDiffEditor && diffEditorOriginalUri =~ /^(github1s|gitlab1s|bitbucket1s):/)", "group": "navigation@5" }, { diff --git a/extensions/github1s/src/adapters/github1s/data-source.ts b/extensions/github1s/src/adapters/github1s/data-source.ts index 971befe02..c3c0c3b0d 100644 --- a/extensions/github1s/src/adapters/github1s/data-source.ts +++ b/extensions/github1s/src/adapters/github1s/data-source.ts @@ -281,7 +281,7 @@ export class GitHub1sDataSource extends DataSource { page: options?.page, per_page: options?.pageSize, sha: options?.from, - path: isString(options?.path) ? trimStart(options.path, '/') : undefined, + path: trimStart(options?.path || '', '/') || undefined, author: options?.author, }; const requestParams = { owner, repo, ...queryParams }; diff --git a/extensions/github1s/src/adapters/gitlab1s/data-source.ts b/extensions/github1s/src/adapters/gitlab1s/data-source.ts index 9b79a2a95..73cff23b9 100644 --- a/extensions/github1s/src/adapters/gitlab1s/data-source.ts +++ b/extensions/github1s/src/adapters/gitlab1s/data-source.ts @@ -251,7 +251,7 @@ export class GitLab1sDataSource extends DataSource { page: options?.page, per_page: options?.pageSize, sha: options?.from, - path: isString(options?.path) ? trimStart(options.path, '/') : undefined, + path: trimStart(options?.path || '', '/') || undefined, author: options?.author, }; const requestParams = { repo, ...queryParams }; diff --git a/extensions/github1s/src/adapters/sourcegraph/data-source.ts b/extensions/github1s/src/adapters/sourcegraph/data-source.ts index 1d42f437d..bf82b0a51 100644 --- a/extensions/github1s/src/adapters/sourcegraph/data-source.ts +++ b/extensions/github1s/src/adapters/sourcegraph/data-source.ts @@ -191,7 +191,7 @@ export class SourcegraphDataSource extends DataSource { let commits = await getCommits( this.buildRepository(repo), options?.from || 'HEAD', - options?.path === undefined ? undefined : trimStart(options.path, '/'), + trimStart(options?.path || '', '/') || undefined, options?.pageSize ? options.pageSize * (options.page || 1) : undefined, ); if (options?.path && options.path !== '/' && commits.length) { diff --git a/extensions/github1s/src/changes/files.ts b/extensions/github1s/src/changes/files.ts index 915bcee45..8594edc94 100644 --- a/extensions/github1s/src/changes/files.ts +++ b/extensions/github1s/src/changes/files.ts @@ -4,7 +4,6 @@ */ import * as vscode from 'vscode'; -import queryString from 'query-string'; import * as adapterTypes from '@/adapters/types'; import router from '@/router'; import { basename } from '@/helpers/util'; @@ -117,15 +116,10 @@ export const getChangedFileDiffCommand = (changedFile: VSCodeChangedFile): vscod } const title = getChangedFileDiffTitle(baseFileUri, headFileUri, status); - const query = queryString.stringify({ - status, - base: baseFileUri.with({ query: '' }).toString(), - head: headFileUri.with({ query: '' }).toString(), - }); return { title: 'Diff', command: 'vscode.diff', - arguments: [baseFileUri.with({ query }), headFileUri.with({ query }), title], + arguments: [baseFileUri, headFileUri, title], }; }; diff --git a/extensions/github1s/src/changes/history.ts b/extensions/github1s/src/changes/history.ts index 9fde57e47..742953637 100644 --- a/extensions/github1s/src/changes/history.ts +++ b/extensions/github1s/src/changes/history.ts @@ -95,21 +95,12 @@ export class GitHub1sHistoryProvider implements vscode.SourceControlHistoryProvi // A missing side makes VS Code open the existing file directly for additions/deletions. const originalUri = !parentId || file.status === FileChangeStatus.Added ? undefined : file.baseFileUri; const modifiedUri = file.status === FileChangeStatus.Removed ? undefined : file.headFileUri; - // Carry context for our diff editor commands (open either side, previous/next revision). - const query = - originalUri && modifiedUri - ? queryString.stringify({ - base: originalUri.with({ query: '' }).toString(), - head: modifiedUri.with({ query: '' }).toString(), - status: file.status, - }) - : ''; return { // Display resource for the file label and status badge, including deleted files. uri: file.headFileUri.with({ query: queryString.stringify({ changeStatus: file.status }) }), // Content resources for the diff's left (before) and right (after) sides. - originalUri: originalUri?.with({ query }), - modifiedUri: modifiedUri?.with({ query }), + originalUri, + modifiedUri, }; }); } diff --git a/extensions/github1s/src/commands/commit.ts b/extensions/github1s/src/commands/commit.ts index cd244916a..bc4aca509 100644 --- a/extensions/github1s/src/commands/commit.ts +++ b/extensions/github1s/src/commands/commit.ts @@ -99,9 +99,11 @@ const commandDiffCommitFile = async (commitItem: CommitTreeItem) => { if (!activeDocumentUri) { return; } - const fileUri = router.buildUri({ ref: commitSha }, activeDocumentUri).with({ - query: queryString.stringify({ from: router.getState().ref }), - }); + const from = await Repository.getCurrentInstance().getCommitItem(router.getState().ref); + if (!from) { + return; + } + const fileUri = router.buildUri({ ref: commitSha }, activeDocumentUri); return vscode.commands.executeCommand('github1s.commands.openFilePreviousRevision', fileUri); }; diff --git a/extensions/github1s/src/commands/editor.ts b/extensions/github1s/src/commands/editor.ts index 819e7ac9d..193a41761 100644 --- a/extensions/github1s/src/commands/editor.ts +++ b/extensions/github1s/src/commands/editor.ts @@ -10,6 +10,7 @@ import { emptyFileUri } from '@/providers'; import { FileChangeStatus } from '@/adapters/types'; import { Repository } from '@/repository'; import { getChangedFiles, getChangedFileDiffCommand, getChangedFileDiffTitle } from '@/changes/files'; +import { omit } from '@/helpers/util'; export const getChangedFileFromSourceControl = async (fileUri: vscode.Uri) => { // the file should belong to current workspace @@ -35,114 +36,136 @@ const commandDiffChangedFile = async (fileUri: vscode.Uri) => { vscode.commands.executeCommand(command.command, ...(command.arguments || [])); }; -const openFileToEditor = async (fileUri) => { - return vscode.commands.executeCommand('vscode.open', fileUri, { preview: false }); +const isRepositoryFileUri = (uri: vscode.Uri | undefined): uri is vscode.Uri => { + return !!uri && /^(github1s|gitlab1s|bitbucket1s)$/.test(uri.scheme); }; -// open the left file in the diff editor title -const commandDiffViewOpenLeftFile = async (fileUri: vscode.Uri) => { - const query = queryString.parse(fileUri?.query || ''); - return query.base ? openFileToEditor(vscode.Uri.parse(query.base as string)) : null; -}; - -// open the right file in the diff editor title -const commandDiffViewOpenRightFile = async (fileUri: vscode.Uri) => { - const query = queryString.parse(fileUri?.query || ''); - return query.head ? openFileToEditor(vscode.Uri.parse(query.head as string)) : null; -}; - -// get the file uri with the concrete commit sha, the `ref` in -// `fileUri.authority` maybe newer but not related this file -const getConcreteFileUri = async (fileUri: vscode.Uri) => { - const { scheme, repo, ref, path } = router.parseUri(fileUri); - const repository = Repository.getInstance(scheme, repo); - const commit = await repository.getFileLatestCommit(ref, path); - const latestCommitSha = commit?.sha || (await repository.getCommitItem(ref))?.sha; +const getActiveDiffInput = (resource?: vscode.Uri): vscode.TabInputTextDiff | undefined => { + const input = vscode.window.tabGroups.activeTabGroup.activeTab?.input; + if (!(input instanceof vscode.TabInputTextDiff)) { + return; + } - return router.buildUri({ ref: latestCommitSha }, fileUri); + // Title actions receive the modified URI. Ignore actions targeting another diff. + if (resource && resource.toString() !== input.modified.toString()) { + return; + } + return input; }; -// show the file's diff between current commit and previous commit -const commandOpenFilePreviousRevision = async (fileUri: vscode.Uri) => { - const queryParams = queryString.parse(fileUri.query); - const queryBaseUriStr = queryParams.base; - const headFileUri = typeof queryParams.head === 'string' ? vscode.Uri.parse(queryParams.head) : undefined; - const from = - typeof queryParams.from === 'string' - ? queryParams.from - : router.parseUri(headFileUri && headFileUri.scheme !== emptyFileUri.scheme ? headFileUri : fileUri).ref; - const rightFileUri = await getConcreteFileUri( - // if the `queryBaseUriStr` is empty, which means this command is called from - // a normal file editor (not a diff editor), just use `fileUri` in this case - queryBaseUriStr ? vscode.Uri.parse(queryBaseUriStr as string) : fileUri, - ); - const { scheme, repo, ref: rightCommitSha } = router.parseUri(rightFileUri); - const repository = Repository.getInstance(scheme, repo); - const leftCommit = await repository.getPreviousCommit(rightCommitSha, rightFileUri.path, from); - // if we can't find previous commit, use the `emptyFileUri` as the leftFileUri - const leftFileUri = leftCommit ? router.buildUri({ ref: leftCommit.sha }, rightFileUri) : emptyFileUri; - - const changedStatus = leftCommit ? FileChangeStatus.Modified : FileChangeStatus.Added; - const hasNextRevision = !!(await repository.getNextCommit(rightCommitSha, rightFileUri.path, from)); - - const query = queryString.stringify({ - base: leftFileUri.with({ query: '' }).toString(), - head: rightFileUri.with({ query: '' }).toString(), - from, - status: changedStatus, - // if we can't find a newer commit for this file, - // the `Show Next Commit` Button would be disabled. - hasNextRevision, - }); - - return vscode.commands.executeCommand( - 'vscode.diff', - leftFileUri.with({ query }), - rightFileUri.with({ query }), - getChangedFileDiffTitle(leftFileUri, rightFileUri, changedStatus), - ); +const createCommandDiffViewOpenFile = (side: 'original' | 'modified') => async (resource?: vscode.Uri) => { + const fileUri = getActiveDiffInput(resource)?.[side]; + if (fileUri && fileUri?.scheme !== emptyFileUri.scheme) { + await vscode.commands.executeCommand('workbench.action.keepEditor'); + return vscode.commands.executeCommand('vscode.open', fileUri, {}); + } }; -// show the file's diff between current commit and next commit -const commandOpenFileNextRevision = async (fileUri: vscode.Uri) => { - const queryParams = queryString.parse(fileUri.query); - const headFileUri = typeof queryParams.head === 'string' ? vscode.Uri.parse(queryParams.head) : fileUri; - const from = typeof queryParams.from === 'string' ? queryParams.from : router.parseUri(headFileUri).ref; - const leftFileUri = await getConcreteFileUri(headFileUri); - - const { scheme, repo, ref: leftCommitSha } = router.parseUri(leftFileUri); - const repository = Repository.getInstance(scheme, repo); - const rightCommit = await repository.getNextCommit(leftCommitSha, leftFileUri.path, from); +const resolveOpenFileRevisionArgs = async ( + fileUri: vscode.Uri | undefined, + direction: 'previous' | 'next', +): Promise<[vscode.Uri, string]> => { + let baseUri: vscode.Uri | undefined, from: string | undefined; + const getQueryFrom = (uri: vscode.Uri): string | undefined => { + return queryString.parse(uri.query).from as string | undefined; + }; + + const textDiffInput = getActiveDiffInput(fileUri); + if (textDiffInput) { + // this is a diff editor + const { original, modified } = textDiffInput; + const hasLeftFile = isRepositoryFileUri(original); + const hasRightFile = isRepositoryFileUri(modified); + + if (direction === 'previous' && hasLeftFile) { + baseUri = original; + } + if (direction === 'next' && hasRightFile) { + baseUri = modified; + } + if (hasRightFile) { + from = getQueryFrom(modified); + } + } else if (isRepositoryFileUri(fileUri)) { + // this is a single file editor + from = getQueryFrom(fileUri); + baseUri = fileUri; + } - if (!rightCommit) { - return vscode.window.showInformationMessage('There is no next commit found.'); + if (!baseUri) { + throw new Error('Unable to resolve the target file.'); } - const rightFileUri = router.buildUri({ ref: rightCommit.sha }, leftFileUri); - const hasNextRevision = !!(await repository.getNextCommit(rightCommit.sha, rightFileUri.path, from)); + if (!from) { + // If 'from' cannot be obtained in the query, use the ref of baseUri as 'from' + const { scheme, repo, ref, path } = router.parseUri(baseUri); + const repository = Repository.getInstance(scheme, repo); + from = (await repository.getFileLatestCommit(ref, path))?.sha; + if (!from) { + throw new Error('Unable to resolve the latest commit for this file.'); + } + baseUri = router.buildUri({ ref: from }, baseUri); + } - const query = queryString.stringify({ - base: leftFileUri.with({ query: '' }).toString(), - head: rightFileUri.with({ query: '' }).toString(), - from, - status: FileChangeStatus.Modified, - hasNextRevision, - }); + return [baseUri, from]; +}; - return vscode.commands.executeCommand( - 'vscode.diff', - leftFileUri.with({ query }), - rightFileUri.with({ query }), - getChangedFileDiffTitle(leftFileUri, rightFileUri, FileChangeStatus.Modified), - ); +const createCommandOpenFileRevision = (direction: 'previous' | 'next') => async (fileUri?: vscode.Uri) => { + try { + const [baseUri, from] = await resolveOpenFileRevisionArgs(fileUri, direction); + const { scheme, repo, ref, path } = router.parseUri(baseUri); + const repository = Repository.getInstance(scheme, repo); + const baseSha = (await repository.getCommitItem(ref))?.sha; + if (!baseSha) { + throw new Error('Unable to resolve the commit for this file.'); + } + + let leftFileUri: vscode.Uri | undefined, rightFileUri: vscode.Uri | undefined; + if (direction === 'previous') { + const prevCommit = await repository.getPreviousCommit(baseSha, path, from); + leftFileUri = prevCommit ? router.buildUri({ ref: prevCommit.sha }, baseUri) : emptyFileUri; + rightFileUri = baseUri; + } else { + const nextCommit = await repository.getNextCommit(baseSha, path, from); + if (!nextCommit) throw new Error('Unable to find next commit for this file.'); + leftFileUri = baseUri; + rightFileUri = router.buildUri({ ref: nextCommit.sha }, baseUri); + } + + const hasNext = router.parseUri(rightFileUri).ref !== from || undefined; + const leftQuery = queryString.stringify(omit(queryString.parse(baseUri.query), ['from'])); + const rightQuery = queryString.stringify({ ...queryString.parse(baseUri.query), from, hasNext }); + + if (fileUri && !queryString.parse(fileUri.query).from) { + await vscode.commands.executeCommand('workbench.action.keepEditor'); + } + + return await vscode.commands.executeCommand( + 'vscode.diff', + leftFileUri.with({ query: leftQuery }), + rightFileUri.with({ query: rightQuery }), + getChangedFileDiffTitle(leftFileUri, rightFileUri, FileChangeStatus.Modified), + ); + } catch (error) { + return vscode.window.showErrorMessage(`Unable to open file revision: ${error.message}`); + } }; export const registerEditorCommands = (context: vscode.ExtensionContext) => { return context.subscriptions.push( vscode.commands.registerCommand('github1s.commands.diffChangedFile', commandDiffChangedFile), - vscode.commands.registerCommand('github1s.commands.diffViewOpenLeftFile', commandDiffViewOpenLeftFile), - vscode.commands.registerCommand('github1s.commands.diffViewOpenRightFile', commandDiffViewOpenRightFile), - vscode.commands.registerCommand('github1s.commands.openFilePreviousRevision', commandOpenFilePreviousRevision), - vscode.commands.registerCommand('github1s.commands.openFileNextRevision', commandOpenFileNextRevision), + vscode.commands.registerCommand( + 'github1s.commands.diffViewOpenLeftFile', + createCommandDiffViewOpenFile('original'), + ), + vscode.commands.registerCommand( + 'github1s.commands.diffViewOpenRightFile', + createCommandDiffViewOpenFile('modified'), + ), + vscode.commands.registerCommand( + 'github1s.commands.openFilePreviousRevision', + createCommandOpenFileRevision('previous'), + ), + vscode.commands.registerCommand('github1s.commands.openFileNextRevision', createCommandOpenFileRevision('next')), ); }; diff --git a/extensions/github1s/src/helpers/util.ts b/extensions/github1s/src/helpers/util.ts index bf491fca9..675d7b4e7 100644 --- a/extensions/github1s/src/helpers/util.ts +++ b/extensions/github1s/src/helpers/util.ts @@ -65,3 +65,11 @@ export const prop = (obj: object, path: (string | number)[] = []): any => { export const last = (array: readonly T[]): T => { return array[array.length - 1]; }; + +export const omit = (obj: T, keys: K[]): Omit => { + const result = { ...obj }; + for (const key of keys) { + delete result[key]; + } + return result as Omit; +}; diff --git a/extensions/github1s/src/repository/commit-manager.ts b/extensions/github1s/src/repository/commit-manager.ts index 7569ce53b..87cfad0a2 100644 --- a/extensions/github1s/src/repository/commit-manager.ts +++ b/extensions/github1s/src/repository/commit-manager.ts @@ -68,9 +68,8 @@ const historyKey = (from: string, filePath: string) => `${from} ${filePath}`; export class CommitManager { private static instancesMap = new Map(); - private _refMap = new Map(); // ref -> sha private _shaMap = new Map(); // sha -> commit - // `from filePath` -> [sha[], page, hasMore] + // historyKey -> [sha[], page, hasMore] private _historyMap = new Map(); private _pageSize = 100; @@ -95,31 +94,33 @@ export class CommitManager { pageSize: this._pageSize, ...(!filePath || filePath === '/' ? {} : { path: filePath }), }); - if (page == 1 && commits.length && (!filePath || filePath === '/')) { - this._refMap.set(from, commits[0]?.sha); - } commits.forEach((commit) => this._shaMap.set(commit.sha, commit)); return commits; } getList = reuseable(async (from: string, filePath: string, forceUpdate = false): Promise => { - const history = this._historyMap.get(historyKey(from, filePath)); + let history = this._historyMap.get(historyKey(from, filePath)); if (forceUpdate || !history) { const commits = await this.fetchCommits(from, 1, filePath); const hasMore = commits.length >= this._pageSize; - this._historyMap.set(historyKey(from, filePath), [commits.map((commit) => commit.sha), 1, hasMore]); - return commits; + history = [commits.map((commit) => commit.sha), 1, hasMore] as [string[], number, boolean]; + this._historyMap.set(historyKey(from, filePath), history); + if (commits.length > 0 && from !== commits[0].sha) { + // also cache the history for the latest commit sha, so that + // we can get the history from the latest commit sha directly + // Note when filePath is not empty, ref may not be history[0][0] + this._historyMap.set(historyKey(commits[0].sha, filePath), history); + } } return history[0].map((sha) => this._shaMap.get(sha)!).filter(Boolean); }); getItem = reuseable(async (ref: string, forceUpdate = false): Promise => { - const sha = this._refMap.get(ref) || ref; + const sha = this._historyMap.get(historyKey(ref, '/'))?.[0]?.[0] || ref; let commit: Commit | null | undefined = this._shaMap.get(sha); if (forceUpdate || !commit) { const dataSource = await getAdapter(this._scheme).resolveDataSource(); commit = await dataSource.provideCommit(this._repo, ref); - commit && this._refMap.set(ref, commit.sha); commit && this._shaMap.set(commit.sha, commit); } return commit; @@ -127,24 +128,23 @@ export class CommitManager { loadMore = reuseable(async (from: string, filePath: string): Promise => { const key = historyKey(from, filePath); - const history = this._historyMap.get(key); + let history = this._historyMap.get(key); if (history && !history[2]) { // no more commits to load return Promise.resolve([]); } + const fromSha = history?.[0]?.[0] || from; const nextPage = (history?.[1] || 0) + 1; - // TODO: Pin a root SHA per history and use it from the first page onward. - // File history queries may leave _refMap unset, while other queries can - // overwrite it between pages, causing duplicate or inconsistent results. - const fromSha = this._refMap.get(from) || from; const commits = await this.fetchCommits(fromSha, nextPage, filePath); - // A refresh may have replaced this history while the page was loading. - if (this._historyMap.get(key) !== history) { + // Ignore responses if a refresh replaced the history or another request already loaded this page. + if (this._historyMap.get(key) !== history || (history && history[1] >= nextPage)) { return []; } - const hasMore = commits.length >= this._pageSize; - const allCommits = [...(history?.[0] || []), ...commits.map((commit) => commit.sha)]; - this._historyMap.set(key, [allCommits, nextPage, hasMore]); + history = history || ([[], 0, true] as [string[], number, boolean]); + history[0].push(...commits.map((commit) => commit.sha)); + history[1] = nextPage; + history[2] = commits.length >= this._pageSize; + this._historyMap.set(key, history); return commits; }); @@ -180,7 +180,7 @@ export class CommitManager { // Keep the original history's `from`: at merges, the previous file revision // in that list can differ from the one found by querying from `sha`. public getPreviousCommit = reuseable(async (sha: string, filePath: string, from: string): Promise => { - let key = historyKey(from, filePath); + const key = historyKey(from, filePath); let history = this._historyMap.get(key); if (!history) { // no history for the file, fetch the history first. @@ -189,12 +189,9 @@ export class CommitManager { } if (history[0].indexOf(sha) < 0) { - // If history exists, but sha is not in it. We use `sha` as - // `from` to request commitList again. This might not be very - // accurate, but it avoids an unknown number of fetch requests. - key = historyKey((from = sha), filePath); - await this.getList(from, filePath); - history = this._historyMap.get(key)!; + // Should appear only after moving forward a lot (exceeds page size) + // and already lost the history (For example, refreshed the page) + throw new Error(`Commit not found in the history`); } let index = history[0].indexOf(sha); @@ -205,15 +202,22 @@ export class CommitManager { history = this._historyMap.get(key); index = history?.[0].indexOf(sha) ?? -1; } - const targetSha = index >= 0 ? history?.[0][index + 1] : null; + // use the first commit if index is -1 + const targetSha = history?.[0][index + 1]; return targetSha ? this._shaMap.get(targetSha) || null : null; }); // Newer entries depend on the original history's `from`; a commit // SHA and file path alone do not provide that navigation context. - public getNextCommit = reuseable(async (ref: string, filePath: string, from: string): Promise => { - const history = this._historyMap.get(historyKey(from || ref, filePath)); - const index = history?.[0].indexOf(ref) ?? -1; + public getNextCommit = reuseable(async (sha: string, filePath: string, from: string): Promise => { + const key = historyKey(from, filePath); + let history = this._historyMap.get(key); + if (!history) { + // no history for the file, fetch the history first. + await this.getList(from, filePath); + history = this._historyMap.get(key)!; + } + const index = history?.[0].indexOf(sha) ?? -1; return index > 0 ? this._shaMap.get(history![0][index - 1]) || null : null; }); } diff --git a/extensions/github1s/src/repository/index.ts b/extensions/github1s/src/repository/index.ts index 7cc0f5c63..ae8336983 100644 --- a/extensions/github1s/src/repository/index.ts +++ b/extensions/github1s/src/repository/index.ts @@ -104,12 +104,12 @@ export class Repository { return this._commitManager.getLatestCommit(ref, filePath); } - getPreviousCommit(ref: string, filePath: string, from: string) { - return this._commitManager.getPreviousCommit(ref, filePath, from); + getPreviousCommit(sha: string, filePath: string, fromSha: string) { + return this._commitManager.getPreviousCommit(sha, filePath, fromSha); } - getNextCommit(ref: string, filePath: string, from: string) { - return this._commitManager.getNextCommit(ref, filePath, from); + getNextCommit(sha: string, filePath: string, fromSha: string) { + return this._commitManager.getNextCommit(sha, filePath, fromSha); } getCodeReviewList(...args: Parameters) { diff --git a/extensions/github1s/src/views/commit-list.ts b/extensions/github1s/src/views/commit-list.ts index 5e37412a6..0b787b35a 100644 --- a/extensions/github1s/src/views/commit-list.ts +++ b/extensions/github1s/src/views/commit-list.ts @@ -74,9 +74,11 @@ export class CommitTreeDataProvider implements vscode.TreeDataProvider { this._loadingBarrier && (await this._loadingBarrier.wait()); const filePath = await this.resolveFilePath(); - const { ref } = router.getState(); const repository = Repository.getCurrentInstance(); + // Use the same pinned history as revision navigation, including loaded pages. + const commit = await repository.getCommitItem(router.getState().ref, this._forceUpdate); + if (!commit) { + return []; + } + const ref = commit.sha; const repositoryCommits = await repository.getCommitList(ref, filePath, this._forceUpdate); const commitTreeItems = repositoryCommits.map((commit) => { const label = commit.message.split(/[\r\n]/)[0];