diff --git a/README.md b/README.md
index 3490d22b0..a73233ce0 100644
--- a/README.md
+++ b/README.md
@@ -173,7 +173,7 @@ The continued development and maintenance of GitHub1s is made possible by these
- [Mr-B0b/TamperMonkeyScripts/vscode.js](https://github.com/Mr-B0b/TamperMonkeyScripts/blob/main/vscode.js)
-### Maintainers! :blush:
+## Maintainers! :blush:
@@ -182,3 +182,7 @@ The continued development and maintenance of GitHub1s is made possible by these
Siddhant Khare 💻 🖋 |
+
+## Stargazers over time
+
+[![Stargazers over time](https://starchart.cc/conwnet/github1s.svg)](https://starchart.cc/conwnet/github1s)
diff --git a/extensions/github1s/package.json b/extensions/github1s/package.json
index a592b1ef8..2a589bc57 100644
--- a/extensions/github1s/package.json
+++ b/extensions/github1s/package.json
@@ -19,6 +19,7 @@
"onCommand:github1s.switch-to-commit",
"onCommand:github1s.diff-view-open-left-file",
"onCommand:github1s.diff-view-open-right-file",
+ "onCommand:github1s.open-on-github",
"onView:github1s"
],
"browser": "./dist/extension",
@@ -198,6 +199,11 @@
"light": "assets/icons/light/close-blame.svg"
},
"enablement": "!isInDiffEditor && resourceScheme =~ /^github1s$/"
+ },
+ {
+ "command": "github1s.open-on-github",
+ "title": "Open on GitHub",
+ "category": "GitHub1s"
}
],
"colors": [
diff --git a/extensions/github1s/src/commands/commit.ts b/extensions/github1s/src/commands/commit.ts
index 8f3941b26..289ca6e27 100644
--- a/extensions/github1s/src/commands/commit.ts
+++ b/extensions/github1s/src/commands/commit.ts
@@ -10,11 +10,12 @@ import {
CommitTreeItem,
getCommitTreeItemDescription,
} from '@/views/commit-list-view';
+import { commitTreeDataProvider } from '@/views';
import { RequestNotFoundError } from '@/helpers/fetch';
const checkCommitExists = async (commitSha: string) => {
try {
- return !!(await repository.getCommit(commitSha));
+ return !!(await repository.getCommitManager().getItem(commitSha));
} catch (e) {
vscode.window.showErrorMessage(
e instanceof RequestNotFoundError
@@ -37,7 +38,7 @@ export const commandSwitchToCommit = async (commitSha?: string) => {
};
// use the commit list as the candidates
const commitItems: vscode.QuickPickItem[] = (
- await repository.getCommits((await router.getState()).ref)
+ await repository.getCommitManager().getList((await router.getState()).ref)
).map((commit) => ({
commitSha: commit.sha,
label: commit.commit.message,
@@ -97,3 +98,13 @@ export const commandCommitViewItemOpenOnGitHub = async (
const commitSha = viewItem?.commit?.sha;
commitSha && commandOpenCommitOnGitHub(commitSha);
};
+
+export const commandCommitViewRefreshCommitList = (forceUpdate = true) => {
+ return commitTreeDataProvider.updateTree(forceUpdate);
+};
+
+export const commandCommitViewLoadMoreCommits = async () => {
+ const { ref } = await router.getState();
+ repository.getCommitManager().loadMore(ref);
+ return commandCommitViewRefreshCommitList(false);
+};
diff --git a/extensions/github1s/src/commands/editor.ts b/extensions/github1s/src/commands/editor.ts
index d4ac2dc50..de10e62f7 100644
--- a/extensions/github1s/src/commands/editor.ts
+++ b/extensions/github1s/src/commands/editor.ts
@@ -94,7 +94,9 @@ const getLatestFileUri = async (fileUri: vscode.Uri) => {
// to router.getAuthority() in this case
const fileAuthority = fileUri.authority || (await router.getAuthority());
const [owner, repo, ref] = fileAuthority.split('+').filter(Boolean);
- const latestCommitSha = await repository.getFileCommitSha(fileUri.path, ref);
+ const latestCommitSha = await repository
+ .getCommitManager()
+ .getFileCommitSha(fileUri.path, ref);
return fileUri.with({
authority: `${owner}+${repo}+${latestCommitSha}`,
@@ -116,10 +118,9 @@ export const commandEditorViewOpenPrevRevision = async (
const [owner, repo, rightCommitSha] = rightFileUri.authority
.split('+')
.filter(Boolean);
- const leftCommitSha = await repository.getFilePrevCommitSha(
- rightFileUri.path,
- rightCommitSha
- );
+ const leftCommitSha = await repository
+ .getCommitManager()
+ .getFilePrevCommitSha(rightFileUri.path, rightCommitSha);
// if we can't find prevCommitSha, use the the `emptyFileUri` as the leftFileUri
const leftFileUri = leftCommitSha
@@ -130,10 +131,9 @@ export const commandEditorViewOpenPrevRevision = async (
? FileChangeType.MODIFIED
: FileChangeType.ADDED;
- const hasNextRevision = !!(await repository.getFileNextCommitSha(
- rightFileUri.path,
- rightCommitSha
- ));
+ const hasNextRevision = !!(await repository
+ .getCommitManager()
+ .getFileNextCommitSha(rightFileUri.path, rightCommitSha));
const query = queryString.stringify({
base: leftFileUri.with({ query: '' }).toString(),
@@ -166,10 +166,9 @@ export const commandEditorViewOpenNextRevision = async (
const [owner, repo, leftCommitSha] = leftFileUri.authority
.split('+')
.filter(Boolean);
- const rightCommitSha = await repository.getFileNextCommitSha(
- leftFileUri.path,
- leftCommitSha
- );
+ const rightCommitSha = await repository
+ .getCommitManager()
+ .getFileNextCommitSha(leftFileUri.path, leftCommitSha);
if (!rightCommitSha) {
return vscode.window.showInformationMessage(
@@ -181,10 +180,9 @@ export const commandEditorViewOpenNextRevision = async (
authority: `${owner}+${repo}+${rightCommitSha}`,
});
- const hasNextRevision = !!(await repository.getFileNextCommitSha(
- rightFileUri.path,
- rightCommitSha
- ));
+ const hasNextRevision = !!(await repository
+ .getCommitManager()
+ .getFileNextCommitSha(rightFileUri.path, rightCommitSha));
const query = queryString.stringify({
base: leftFileUri.with({ query: '' }).toString(),
head: rightFileUri.with({ query: '' }).toString(),
diff --git a/extensions/github1s/src/commands/global.ts b/extensions/github1s/src/commands/global.ts
new file mode 100644
index 000000000..9b12ed8a4
--- /dev/null
+++ b/extensions/github1s/src/commands/global.ts
@@ -0,0 +1,19 @@
+/**
+ * @file GitHub1s Ref Related Commands
+ * @author netcon
+ */
+
+import * as vscode from 'vscode';
+import router from '@/router';
+
+export const commandOpenOnGitHub = async () => {
+ const location = router.history.location;
+ const githubPath =
+ location.pathname === '/'
+ ? '/conwnet/github1s'
+ : `${location.pathname}${location.search}${location.hash}`;
+ const GITHUB_ORIGIN = 'https://github.com';
+ const gitHubUri = vscode.Uri.parse(GITHUB_ORIGIN + githubPath);
+
+ return vscode.commands.executeCommand('vscode.open', gitHubUri);
+};
diff --git a/extensions/github1s/src/commands/index.ts b/extensions/github1s/src/commands/index.ts
index 101a3ef62..871e6c8f2 100644
--- a/extensions/github1s/src/commands/index.ts
+++ b/extensions/github1s/src/commands/index.ts
@@ -5,7 +5,6 @@
import * as vscode from 'vscode';
import { getExtensionContext } from '@/helpers/context';
-import { pullRequestTreeDataProvider, commitTreeDataProvider } from '@/views';
import {
commandValidateToken,
commandUpdateToken,
@@ -16,12 +15,16 @@ import {
commandSwitchToPull,
commandPullViewItemSwitchToPull,
commandPullViewItemOpenOnGitHub,
+ commandPullViewRefreshPullList,
+ commandPullViewLoadMorePulls,
} from './pull';
import {
commandSwitchToCommit,
commandOpenCommitOnGitHub,
commandCommitViewItemSwitchToCommit,
commandCommitViewItemOpenOnGitHub,
+ commandCommitViewRefreshCommitList,
+ commandCommitViewLoadMoreCommits,
} from './commit';
import { commandOpenGitpod } from './gitpod';
import {
@@ -36,6 +39,7 @@ import {
commandOpenEditorGutterBlame,
commandCloseEditorGutterBlame,
} from './blame';
+import { commandOpenOnGitHub } from './global';
const commands: { id: string; callback: (...args: any[]) => any }[] = [
// validate GitHub OAuth Token
@@ -53,7 +57,9 @@ const commands: { id: string; callback: (...args: any[]) => any }[] = [
// switch to a pull request & input pull number manually
{ id: 'github1s.switch-to-pull', callback: commandSwitchToPull },
// update the pull request list in the pull requests view
- { id: 'github1s.pull-view-refresh-pull-list', callback: () => pullRequestTreeDataProvider.updateTree() }, // prettier-ignore
+ { id: 'github1s.pull-view-refresh-pull-list', callback: commandPullViewRefreshPullList }, // prettier-ignore
+ // load more pulls in the pull requests tree view
+ { id: 'github1s.pull-view-load-more-pulls', callback: commandPullViewLoadMorePulls }, // prettier-ignore
// switch to a pull request in the pull requests view
{ id: 'github1s.pull-view-item-switch-to-pull', callback: commandPullViewItemSwitchToPull }, // prettier-ignore
// open pull on github in the pull requests view
@@ -64,7 +70,9 @@ const commands: { id: string; callback: (...args: any[]) => any }[] = [
// open a commit on GitHub's website
{ id: 'github1s.open-commit-on-github', callback: commandOpenCommitOnGitHub },
// update the commit list in the commits view
- { id: 'github1s.commit-view-refresh-commit-list', callback: () => commitTreeDataProvider.updateTree() }, // prettier-ignore
+ { id: 'github1s.commit-view-refresh-commit-list', callback: commandCommitViewRefreshCommitList }, // prettier-ignore
+ // load more commits in the commits tree view
+ { id: 'github1s.commit-view-load-more-commits', callback: commandCommitViewLoadMoreCommits }, // prettier-ignore
// switch to a commit in the commits view
{ id: 'github1s.commit-view-item-switch-to-commit', callback: commandCommitViewItemSwitchToCommit }, // prettier-ignore
// open commit on github in the commits view
@@ -90,6 +98,9 @@ const commands: { id: string; callback: (...args: any[]) => any }[] = [
{ id: 'github1s.open-editor-gutter-blame', callback: commandOpenEditorGutterBlame }, // prettier-ignore
// close the gutter blame of a editor
{ id: 'github1s.close-editor-gutter-blame', callback: commandCloseEditorGutterBlame }, // prettier-ignore
+
+ // open current page on GitHub
+ { id: 'github1s.open-on-github', callback: commandOpenOnGitHub },
];
export const registerGitHub1sCommands = () => {
diff --git a/extensions/github1s/src/commands/pull.ts b/extensions/github1s/src/commands/pull.ts
index d90da6db7..86edb412b 100644
--- a/extensions/github1s/src/commands/pull.ts
+++ b/extensions/github1s/src/commands/pull.ts
@@ -11,11 +11,12 @@ import {
getPullTreeItemLabel,
getPullTreeItemDescription,
} from '@/views/pull-list-view';
+import { pullRequestTreeDataProvider } from '@/views';
import { RequestNotFoundError } from '@/helpers/fetch';
const checkPullExists = async (pullNumber: number) => {
try {
- return !!(await repository.getPull(pullNumber));
+ return !!(await repository.getPullManager().getItem(pullNumber));
} catch (e) {
vscode.window.showErrorMessage(
e instanceof RequestNotFoundError
@@ -38,7 +39,7 @@ export const commandSwitchToPull = async (pullNumber?: number) => {
};
// use the pull list as the candidates
const pullRequestItems: vscode.QuickPickItem[] = (
- await repository.getPulls()
+ await repository.getPullManager().getList()
).map((pull) => ({
pullNumber: pull.number,
label: getPullTreeItemLabel(pull),
@@ -98,3 +99,12 @@ export const commandPullViewItemOpenOnGitHub = async (
);
}
};
+
+export const commandPullViewRefreshPullList = (forceUpdate = true) => {
+ return pullRequestTreeDataProvider.updateTree(forceUpdate);
+};
+
+export const commandPullViewLoadMorePulls = () => {
+ repository.getPullManager().loadMore();
+ return commandPullViewRefreshPullList(false);
+};
diff --git a/extensions/github1s/src/interfaces/github-api-rest.ts b/extensions/github1s/src/interfaces/github-api-rest.ts
index 2b1b8fd57..33ba719c5 100644
--- a/extensions/github1s/src/interfaces/github-api-rest.ts
+++ b/extensions/github1s/src/interfaces/github-api-rest.ts
@@ -138,11 +138,13 @@ export const getGitHubAllFiles = (
export const getGitHubPulls = (
owner: string,
repo: string,
+ pageNumber = 0,
+ pageSize = 100,
options?: RequestInit
) => {
// TODO: only recent 100 pull requests are supported now
return fetch(
- `https://api.github.com/repos/${owner}/${repo}/pulls?state=all&order=created&per_page=100`,
+ `https://api.github.com/repos/${owner}/${repo}/pulls?state=all&order=created&per_page=${pageSize}&page=${pageNumber}`,
options
);
};
@@ -176,10 +178,12 @@ export const getGitHubCommits = (
owner: string,
repo: string,
sha: string,
+ pageNumber = 0,
+ pageSize = 100,
options?: ResponseInit
) => {
return fetch(
- `https://api.github.com/repos/${owner}/${repo}/commits?sha=${sha}&per_page=100`,
+ `https://api.github.com/repos/${owner}/${repo}/commits?sha=${sha}&per_page=${pageSize}&page=${pageNumber}`,
options
);
};
diff --git a/extensions/github1s/src/providers/changedFileDecorationProvider.ts b/extensions/github1s/src/providers/changedFileDecorationProvider.ts
index c53549d82..49fef98fd 100644
--- a/extensions/github1s/src/providers/changedFileDecorationProvider.ts
+++ b/extensions/github1s/src/providers/changedFileDecorationProvider.ts
@@ -73,7 +73,9 @@ const getFileDecorationForPull = async (
uri: Uri,
pullNumber: number
): Promise => {
- const changedFiles = await repository.getPullFiles(pullNumber);
+ const changedFiles = await repository
+ .getPullManager()
+ .getPullFiles(pullNumber);
return getFileDecorationFromChangeFiles(uri, changedFiles);
};
@@ -81,7 +83,9 @@ const getFileDecorationForCommit = async (
uri: Uri,
commitSha: string
): Promise => {
- const changedFiles = await repository.getCommitFiles(commitSha);
+ const changedFiles = await repository
+ .getCommitManager()
+ .getCommitFiles(commitSha);
return getFileDecorationFromChangeFiles(uri, changedFiles);
};
diff --git a/extensions/github1s/src/repository/github-commit-manager.ts b/extensions/github1s/src/repository/github-commit-manager.ts
new file mode 100644
index 000000000..88e51aa15
--- /dev/null
+++ b/extensions/github1s/src/repository/github-commit-manager.ts
@@ -0,0 +1,182 @@
+/**
+ * @file GitHub pull manager
+ * @author netcon
+ */
+
+import {
+ getGitHubCommits,
+ getGitHubCommitDetail,
+ getGitHubFileCommits,
+} from '@/interfaces/github-api-rest';
+import { getFetchOptions } from '@/helpers/fetch';
+import { reuseable } from '@/helpers/func';
+import { Barrier } from '@/helpers/async';
+import { LinkedList, LinkedListDirection } from './linked-list';
+import { Repository } from './index';
+import {
+ CommitManager,
+ RepositoryCommit,
+ RepositoryChangedFile,
+} from './types';
+
+export class GitHubCommitManager implements CommitManager {
+ private _commitMap = new Map();
+ private _fileCommitIdListMap = new Map();
+ private _commitListMap = new Map>();
+ private _pageSize = 100;
+ private _currentPageNumber = 1; // page number is begin from 1
+ private _hasMore = true;
+ private _loadingBarrier = new Barrier();
+
+ constructor(public repository: Repository) {
+ this._loadingBarrier.open();
+ }
+
+ getList = reuseable(
+ async (
+ commitSha: string,
+ forceUpdate: boolean = false
+ ): Promise => {
+ if (forceUpdate || !this._commitListMap.has(commitSha)) {
+ this._commitListMap.set(commitSha, []);
+ this._currentPageNumber = 0;
+ this.loadMore(commitSha);
+ }
+ await this._loadingBarrier.wait();
+ return this._commitListMap.get(commitSha);
+ }
+ );
+
+ getItem = reuseable(
+ async (
+ commitSha: string,
+ forceUpdate: boolean = false
+ ): Promise => {
+ if (forceUpdate || !this._commitMap.has(commitSha)) {
+ const commit = await getGitHubCommitDetail(
+ this.repository.getOwner(),
+ this.repository.getRepo(),
+ commitSha,
+ getFetchOptions(forceUpdate)
+ );
+ this._commitMap.set(commitSha, commit);
+ }
+ return this._commitMap.get(commitSha);
+ }
+ );
+
+ async loadMore(commitSha: string) {
+ this._loadingBarrier = new Barrier();
+ const fetchOptions = getFetchOptions(true);
+ const commits = await getGitHubCommits(
+ this.repository.getOwner(),
+ this.repository.getRepo(),
+ commitSha,
+ this._currentPageNumber,
+ this._pageSize,
+ fetchOptions
+ );
+
+ commits.forEach((commit) => this._commitMap.set(commit.sha, commit));
+ this._commitListMap.get(commitSha).push(...commits);
+ this._currentPageNumber += 1;
+ this._hasMore = commits.length === this._pageSize;
+ this._loadingBarrier.open();
+
+ return this._hasMore;
+ }
+
+ async hasMore() {
+ await this._loadingBarrier.wait();
+ return this._hasMore;
+ }
+
+ public getCommitFiles = reuseable(
+ async (
+ commitSha: string,
+ forceUpdate: boolean = false
+ ): Promise => {
+ return (
+ // the commit maybe updated by fetch commit **list** which
+ // won't have the file list data, so we will fallback to
+ // fetch single commit data to get the file list data
+ (await this.getItem(commitSha, forceUpdate)).files ||
+ (await this.getItem(commitSha, true)).files
+ );
+ }
+ );
+
+ // get this commits for a specified file
+ public getFileCommits = reuseable(
+ async (
+ filePath: string,
+ commitSha: string,
+ forceUpdate: boolean = false
+ ): Promise => {
+ const commits = await getGitHubFileCommits(
+ this.repository.getOwner(),
+ this.repository.getRepo(),
+ filePath,
+ commitSha,
+ getFetchOptions(forceUpdate)
+ );
+ // `this.getCommit` can be benefited from the cache
+ commits.forEach((commit) => this._commitMap.set(commit.sha, commit));
+ return commits;
+ }
+ );
+
+ // get the commit sha of a file with direction, default get
+ // the latest commit sha for the file, note this the latest
+ // commit sha maybe not equal the `commitSha` in arguments
+ public getFileCommitSha = reuseable(
+ async (
+ filePath: string,
+ commitShaOrRef: string,
+ direction: LinkedListDirection = LinkedListDirection.CURRENT,
+ forceUpdate: boolean = false
+ ): Promise => {
+ if (!this._fileCommitIdListMap.has(filePath)) {
+ this._fileCommitIdListMap.set(filePath, new LinkedList());
+ }
+ const commitIdList = this._fileCommitIdListMap.get(filePath);
+ if (!commitIdList.getNodeId(commitShaOrRef, direction)) {
+ const commits = await this.getFileCommits(
+ filePath,
+ commitShaOrRef,
+ forceUpdate
+ );
+ commitIdList.update(commits.map((item) => item.sha).reverse());
+ // Actually the latest commit for `filePath` maybe not equal the
+ // `commitSha` in arguments, we should use commits[0].sha in this case
+ return commitIdList.getNodeId(commits[0]?.sha, direction);
+ }
+ return commitIdList.getNodeId(commitShaOrRef, direction);
+ }
+ );
+
+ public getFilePrevCommitSha = reuseable(
+ async (
+ filePath: string,
+ commitSha: string,
+ forceUpdate: boolean = false
+ ): Promise => {
+ return this.getFileCommitSha(
+ filePath,
+ commitSha,
+ LinkedListDirection.PREVIOUS,
+ forceUpdate
+ );
+ }
+ );
+
+ public getFileNextCommitSha = reuseable(
+ async (filePath: string, commitSha: string): Promise => {
+ // because we can not find the next commit by GitHub API,
+ // we can only try to find the next commit from the cache
+ return this._fileCommitIdListMap
+ .get(filePath)
+ ?.getNodeId(commitSha, LinkedListDirection.NEXT);
+ }
+ );
+}
diff --git a/extensions/github1s/src/repository/github-pull-manager.ts b/extensions/github1s/src/repository/github-pull-manager.ts
new file mode 100644
index 000000000..c396a33d8
--- /dev/null
+++ b/extensions/github1s/src/repository/github-pull-manager.ts
@@ -0,0 +1,97 @@
+/**
+ * @file GitHub pull manager
+ * @author netcon
+ */
+
+import {
+ getGitHubPulls,
+ getGitHubPullFiles,
+ getGitHubPullDetail,
+} from '@/interfaces/github-api-rest';
+import { getFetchOptions } from '@/helpers/fetch';
+import { reuseable } from '@/helpers/func';
+import { Barrier } from '@/helpers/async';
+import { Repository } from './index';
+import { PullManager, RepositoryPull, RepositoryChangedFile } from './types';
+
+export class GitHubPullManager implements PullManager {
+ private _pullMap = new Map();
+ private _pullList = null;
+ private _pageSize = 100;
+ private _currentPageNumber = 1; // page number is begin from 1
+ private _hasMore = true;
+ private _loadingBarrier = new Barrier();
+
+ constructor(public repository: Repository) {
+ this._loadingBarrier.open();
+ }
+
+ getList = reuseable(
+ async (forceUpdate: boolean = false): Promise => {
+ if (forceUpdate || !this._pullList) {
+ this._pullList = [];
+ this._currentPageNumber = 0;
+ this.loadMore();
+ }
+ await this._loadingBarrier.wait();
+ return this._pullList;
+ }
+ );
+
+ getItem = reuseable(
+ async (
+ pullNumber: number,
+ forceUpdate: boolean = false
+ ): Promise => {
+ if (forceUpdate || !this._pullMap.has(pullNumber)) {
+ const pull = await getGitHubPullDetail(
+ this.repository.getOwner(),
+ this.repository.getRepo(),
+ pullNumber,
+ getFetchOptions(forceUpdate)
+ );
+ this._pullMap.set(pullNumber, pull);
+ }
+ return this._pullMap.get(pullNumber);
+ }
+ );
+
+ async loadMore() {
+ this._loadingBarrier = new Barrier();
+ const fetchOptions = getFetchOptions(true);
+ const pulls = await getGitHubPulls(
+ this.repository.getOwner(),
+ this.repository.getRepo(),
+ this._currentPageNumber,
+ this._pageSize,
+ fetchOptions
+ );
+
+ pulls.forEach((pull) => this._pullMap.set(pull.number, pull));
+ this._pullList.push(...pulls);
+ this._currentPageNumber += 1;
+ this._hasMore = pulls.length === this._pageSize;
+ this._loadingBarrier.open();
+
+ return this._hasMore;
+ }
+
+ async hasMore() {
+ await this._loadingBarrier.wait();
+ return this._hasMore;
+ }
+
+ public getPullFiles = reuseable(
+ async (
+ pullNumber: number,
+ forceUpdate: boolean = false
+ ): Promise => {
+ return getGitHubPullFiles(
+ this.repository.getOwner(),
+ this.repository.getRepo(),
+ pullNumber,
+ getFetchOptions(forceUpdate)
+ );
+ }
+ );
+}
diff --git a/extensions/github1s/src/repository/index.ts b/extensions/github1s/src/repository/index.ts
index ed9099136..ae833e2b0 100644
--- a/extensions/github1s/src/repository/index.ts
+++ b/extensions/github1s/src/repository/index.ts
@@ -7,32 +7,20 @@ import { reuseable } from '@/helpers/func';
import router from '@/router';
import {
getGitHubBranchRefs,
- getGitHubPullDetail,
getGitHubTagRefs,
- getGitHubPullFiles,
- getGitHubPulls,
- getGitHubCommitDetail,
- getGitHubCommits,
- getGitHubFileCommits,
} from '@/interfaces/github-api-rest';
import { apolloClient } from '@/interfaces/client';
import { githubFileBlameQuery } from '@/interfaces/github-api-gql';
import { getFetchOptions } from '@/helpers/fetch';
-import { LinkedList, LinkedListDirection } from './linked-list';
-import {
- RepositoryChangedFile,
- RepositoryCommit,
- RepositoryPull,
- RepositoryRef,
- BlameRange,
-} from './types';
+import { GitHubPullManager } from './github-pull-manager';
+import { GitHubCommitManager } from './github-commit-manager';
+import { RepositoryRef, BlameRange, PullManager } from './types';
export class Repository {
private static instance: Repository;
- private _fileCommitIdListMap: Map;
- private _pullMap: Map;
- private _commitMap: Map;
private _fileBlameMap: Map;
+ private _pullManager: PullManager;
+ private _commitManager: GitHubCommitManager;
public static getInstance() {
if (Repository.instance) {
@@ -42,12 +30,19 @@ export class Repository {
}
constructor() {
- this._fileCommitIdListMap = new Map();
- this._pullMap = new Map();
- this._commitMap = new Map();
+ this._pullManager = new GitHubPullManager(this);
+ this._commitManager = new GitHubCommitManager(this);
this._fileBlameMap = new Map();
}
+ public getPullManager(): GitHubPullManager {
+ return this._pullManager as GitHubPullManager;
+ }
+
+ public getCommitManager(): GitHubCommitManager {
+ return this._commitManager as GitHubCommitManager;
+ }
+
// get current repo owner
public getOwner() {
const pathname = router.history.location.pathname;
@@ -64,11 +59,7 @@ export class Repository {
public getBranches = reuseable(
async (forceUpdate: boolean = false): Promise => {
const [owner, repo] = [this.getOwner(), this.getRepo()];
- return await getGitHubBranchRefs(
- owner,
- repo,
- getFetchOptions(forceUpdate)
- );
+ return getGitHubBranchRefs(owner, repo, getFetchOptions(forceUpdate));
}
);
@@ -80,174 +71,6 @@ export class Repository {
}
);
- public getPulls = reuseable(
- async (forceUpdate: boolean = false): Promise => {
- const [owner, repo] = [this.getOwner(), this.getRepo()];
- const fetchOptions = getFetchOptions(forceUpdate);
- const pulls = await getGitHubPulls(owner, repo, fetchOptions);
- // `this.getPull` can be benefited from the cache
- pulls.forEach((pull) => this._pullMap.set(pull.number, pull));
- return pulls;
- }
- );
-
- public getPull = reuseable(
- async (
- pullNumber: number,
- forceUpdate: boolean = false
- ): Promise => {
- const [owner, repo] = [this.getOwner(), this.getRepo()];
- if (forceUpdate || !this._pullMap.has(pullNumber)) {
- const pull = await getGitHubPullDetail(
- owner,
- repo,
- pullNumber,
- getFetchOptions(forceUpdate)
- );
- this._pullMap.set(pullNumber, pull);
- }
- return this._pullMap.get(pullNumber);
- }
- );
-
- public getPullFiles = reuseable(
- async (
- pullNumber: number,
- forceUpdate: boolean = false
- ): Promise => {
- const [owner, repo] = [this.getOwner(), this.getRepo()];
- return getGitHubPullFiles(
- owner,
- repo,
- pullNumber,
- getFetchOptions(forceUpdate)
- );
- }
- );
-
- public getCommits = reuseable(
- async (
- sha: string,
- forceUpdate: boolean = false
- ): Promise => {
- const [owner, repo] = [this.getOwner(), this.getRepo()];
- const fetchOptions = getFetchOptions(forceUpdate);
- const commits = await getGitHubCommits(owner, repo, sha, fetchOptions);
- // `this.getCommit` can be benefited from the cache
- commits.forEach((commit) => this._commitMap.set(commit.sha, commit));
- return commits;
- }
- );
-
- public getCommit = reuseable(
- async (
- commitSha: string,
- forceUpdate: boolean = false
- ): Promise => {
- const [owner, repo] = [this.getOwner(), this.getRepo()];
- if (forceUpdate || !this._commitMap.has(commitSha)) {
- const commit = await getGitHubCommitDetail(
- owner,
- repo,
- commitSha,
- getFetchOptions(forceUpdate)
- );
- this._commitMap.set(commitSha, commit);
- }
- return this._commitMap.get(commitSha);
- }
- );
-
- public getCommitFiles = reuseable(
- async (
- commitSha: string,
- forceUpdate: boolean = false
- ): Promise => {
- return (
- // the commit maybe updated by fetch commit **list** which
- // won't have the file list data, so we will fallback to
- // fetch single commit data to get the file list data
- (await this.getCommit(commitSha, forceUpdate)).files ||
- (await this.getCommit(commitSha, true)).files
- );
- }
- );
-
- // get this commits for a specified file
- public getFileCommits = reuseable(
- async (
- filePath: string,
- commitSha: string,
- forceUpdate: boolean = false
- ): Promise => {
- const [owner, repo] = [this.getOwner(), this.getRepo()];
- const commits = await getGitHubFileCommits(
- owner,
- repo,
- filePath,
- commitSha,
- getFetchOptions(forceUpdate)
- );
- // `this.getCommit` can be benefited from the cache
- commits.forEach((commit) => this._commitMap.set(commit.sha, commit));
- return commits;
- }
- );
-
- // get the commit sha of a file with direction, default get
- // the latest commit sha for the file, note this the latest
- // commit sha maybe not equal the `commitSha` in arguments
- public getFileCommitSha = reuseable(
- async (
- filePath: string,
- commitShaOrRef: string,
- direction: LinkedListDirection = LinkedListDirection.CURRENT,
- forceUpdate: boolean = false
- ): Promise => {
- if (!this._fileCommitIdListMap.has(filePath)) {
- this._fileCommitIdListMap.set(filePath, new LinkedList());
- }
- const commitIdList = this._fileCommitIdListMap.get(filePath);
- if (!commitIdList.getNodeId(commitShaOrRef, direction)) {
- const commits = await this.getFileCommits(
- filePath,
- commitShaOrRef,
- forceUpdate
- );
- commitIdList.update(commits.map((item) => item.sha).reverse());
- // Actually the latest commit for `filePath` maybe not equal the
- // `commitSha` in arguments, we should use commits[0].sha in this case
- return commitIdList.getNodeId(commits[0]?.sha, direction);
- }
- return commitIdList.getNodeId(commitShaOrRef, direction);
- }
- );
-
- public getFilePrevCommitSha = reuseable(
- async (
- filePath: string,
- commitSha: string,
- forceUpdate: boolean = false
- ): Promise => {
- return this.getFileCommitSha(
- filePath,
- commitSha,
- LinkedListDirection.PREVIOUS,
- forceUpdate
- );
- }
- );
-
- public getFileNextCommitSha = reuseable(
- async (filePath: string, commitSha: string): Promise => {
- // because we can not find the next commit by GitHub API,
- // we can only try to find the next commit from the cache
- return this._fileCommitIdListMap
- .get(filePath)
- ?.getNodeId(commitSha, LinkedListDirection.NEXT);
- }
- );
-
public getFileBlame = reuseable(
async (filePath: string, commitSha: string): Promise => {
const cacheKey = `${commitSha}:${filePath}`;
diff --git a/extensions/github1s/src/repository/types.ts b/extensions/github1s/src/repository/types.ts
index 542c3fe80..550e594a4 100644
--- a/extensions/github1s/src/repository/types.ts
+++ b/extensions/github1s/src/repository/types.ts
@@ -80,3 +80,23 @@ export interface BlameRange {
};
};
}
+
+export interface ObjectManager {
+ getList(...args: any[]): T[] | Promise;
+ getItem(...args: any[]): T | Promise;
+ // return boolean indicate that if there are more results
+ loadMore(...args: any[]): boolean | Promise;
+}
+
+export interface PullManager extends ObjectManager {
+ getPullFiles(
+ ...args: any[]
+ ): RepositoryChangedFile[] | Promise;
+}
+
+export interface CommitManager extends ObjectManager {
+ getCommitFiles(
+ ...args: any[]
+ ): RepositoryChangedFile[] | Promise;
+ getFileCommitSha(...args: any[]);
+}
diff --git a/extensions/github1s/src/router/parser/pull.ts b/extensions/github1s/src/router/parser/pull.ts
index b82b90d63..ca3d08736 100644
--- a/extensions/github1s/src/router/parser/pull.ts
+++ b/extensions/github1s/src/router/parser/pull.ts
@@ -10,7 +10,7 @@ import { RouterState, PageType } from '../types';
export const parsePullUrl = async (path: string): Promise => {
const pathParts = parsePath(path).pathname.split('/').filter(Boolean);
const [owner, repo, _pageType, pullNumber] = pathParts;
- const repositoryPull = await repository.getPull(+pullNumber);
+ const repositoryPull = await repository.getPullManager().getItem(+pullNumber);
return {
owner,
diff --git a/extensions/github1s/src/router/parser/tree.ts b/extensions/github1s/src/router/parser/tree.ts
index e9fbd5c2a..9f8427907 100644
--- a/extensions/github1s/src/router/parser/tree.ts
+++ b/extensions/github1s/src/router/parser/tree.ts
@@ -28,8 +28,15 @@ const detectRefFormPathParts = async (pathParts: string[]): Promise => {
if (!pathParts[3] || pathParts[3].toUpperCase() === 'HEAD') {
return 'HEAD';
}
- const branchRefs = await repository.getBranches();
- const tagRefs = await repository.getTags();
+ // the ref will be pathParts[3] if there is no other parts after it
+ if (!pathParts[4]) {
+ return pathParts[3];
+ }
+ // use Promise.all to fetch all refs in parallel as soon as possible
+ const [branchRefs, tagRefs] = await Promise.all([
+ repository.getBranches(),
+ repository.getTags(),
+ ]);
const refNames = [...branchRefs, ...tagRefs].map((item) => item.name);
// fallback to pathParts[3] because it also can be a commit ID
return findMatchedBranchOrTag(refNames, pathParts) || pathParts[3];
diff --git a/extensions/github1s/src/source-control/changes.ts b/extensions/github1s/src/source-control/changes.ts
index 557118734..5ee8d06d0 100644
--- a/extensions/github1s/src/source-control/changes.ts
+++ b/extensions/github1s/src/source-control/changes.ts
@@ -35,7 +35,7 @@ export const getPullChangedFiles = async (pull: RepositoryPull) => {
const headRootUri = baseRootUri.with({
authority: `${owner}+${repo}+${pull.head.sha}`,
});
- const pullFiles = await repository.getPullFiles(pull.number);
+ const pullFiles = await repository.getPullManager().getPullFiles(pull.number);
return pullFiles.map((pullFile) => {
// the `previous_filename` field only exists in `RENAMED` file,
@@ -64,7 +64,9 @@ export const getCommitChangedFiles = async (commit: RepositoryCommit) => {
const headRootUri = baseRootUri.with({
authority: `${owner}+${repo}+${commit.sha || 'HEAD'}`,
});
- const commitFiles = await repository.getCommitFiles(commit.sha);
+ const commitFiles = await repository
+ .getCommitManager()
+ .getCommitFiles(commit.sha);
return commitFiles.map((commitFile) => {
// the `previous_filename` field only exists in `RENAMED` file,
@@ -84,12 +86,16 @@ export const getChangedFiles = async (): Promise => {
// github pull page
if (routerState.pageType === PageType.PULL) {
- const pull = await repository.getPull(routerState.pullNumber);
+ const pull = await repository
+ .getPullManager()
+ .getItem(routerState.pullNumber);
return pull ? getPullChangedFiles(pull) : [];
}
// github commit page
else if (routerState.pageType === PageType.COMMIT) {
- const commit = await repository.getCommit(routerState.commitSha);
+ const commit = await repository
+ .getCommitManager()
+ .getItem(routerState.commitSha);
return commit ? getCommitChangedFiles(commit) : [];
}
return [];
diff --git a/extensions/github1s/src/source-control/quickDiffProviders.ts b/extensions/github1s/src/source-control/quickDiffProviders.ts
index 76d8cd4ca..98a00bea6 100644
--- a/extensions/github1s/src/source-control/quickDiffProviders.ts
+++ b/extensions/github1s/src/source-control/quickDiffProviders.ts
@@ -16,7 +16,9 @@ const getOriginalResourceForPull = async (
uri: vscode.Uri,
pullNumber: number
): Promise => {
- const changedFiles = await repository.getPullFiles(pullNumber);
+ const changedFiles = await repository
+ .getPullManager()
+ .getPullFiles(pullNumber);
const changedFile = changedFiles?.find(
(changedFile) => changedFile.filename === uri.path.slice(1)
);
@@ -29,7 +31,7 @@ const getOriginalResourceForPull = async (
return emptyFileUri;
}
- const pull = await repository.getPull(pullNumber);
+ const pull = await repository.getPullManager().getItem(pullNumber);
const { owner, repo } = await router.getState();
const originalAuthority = `${owner}+${repo}+${pull.base.sha}`;
const originalPath = changedFile.previous_filename
@@ -44,7 +46,9 @@ const getOriginalResourceForCommit = async (
uri: vscode.Uri,
commitSha: string
) => {
- const changedFiles = await repository.getCommitFiles(commitSha);
+ const changedFiles = await repository
+ .getCommitManager()
+ .getCommitFiles(commitSha);
const changedFile = changedFiles?.find(
(changedFile) => changedFile.filename === uri.path.slice(1)
);
@@ -57,7 +61,7 @@ const getOriginalResourceForCommit = async (
return emptyFileUri;
}
- const commit = await repository.getCommit(commitSha);
+ const commit = await repository.getCommitManager().getItem(commitSha);
const { owner, repo } = await router.getState();
const parentCommitSha = commit.parents?.[0]?.sha;
diff --git a/extensions/github1s/src/views/commit-list-view.ts b/extensions/github1s/src/views/commit-list-view.ts
index 7b6d4d5f9..acd803d68 100644
--- a/extensions/github1s/src/views/commit-list-view.ts
+++ b/extensions/github1s/src/views/commit-list-view.ts
@@ -29,6 +29,16 @@ export interface CommitTreeItem extends vscode.TreeItem {
commit: RepositoryCommit;
}
+const loadMoreCommitItem: vscode.TreeItem = {
+ label: 'Load more',
+ tooltip: 'Load more commits',
+ command: {
+ title: 'Load more commits',
+ command: 'github1s.commit-view-load-more-commits',
+ tooltip: 'Load more commits',
+ },
+};
+
export class CommitTreeDataProvider
implements vscode.TreeDataProvider {
public static viewType = 'github1s.views.commit-list';
@@ -37,21 +47,18 @@ export class CommitTreeDataProvider
private _onDidChangeTreeData = new vscode.EventEmitter();
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
- public updateTree() {
- this.forceUpdate = true;
+ public updateTree(forceUpdate = true) {
+ this.forceUpdate = forceUpdate;
this._onDidChangeTreeData.fire(undefined);
}
- async getCommitItems(): Promise {
+ async getCommitItems(): Promise {
const { ref } = await router.getState();
- // only recent 100 commits will be list here
- // TODO: implement pagination
- const repositoryCommits = await repository.getCommits(
- ref,
- this.forceUpdate
- );
+ const repositoryCommits = await repository
+ .getCommitManager()
+ .getList(ref, this.forceUpdate);
this.forceUpdate = false;
- return repositoryCommits.map((commit) => {
+ const commitTreeItems = repositoryCommits.map((commit) => {
const label = `${commit.commit.message}`;
const description = getCommitTreeItemDescription(commit);
const tooltip = `${label} (${description})`;
@@ -74,6 +81,9 @@ export class CommitTreeDataProvider
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
};
});
+ return (await repository.getCommitManager().hasMore())
+ ? [...commitTreeItems, loadMoreCommitItem]
+ : commitTreeItems;
}
async getCommitFileItems(
diff --git a/extensions/github1s/src/views/pull-list-view.ts b/extensions/github1s/src/views/pull-list-view.ts
index d296eb2ce..6f1b3a2a5 100644
--- a/extensions/github1s/src/views/pull-list-view.ts
+++ b/extensions/github1s/src/views/pull-list-view.ts
@@ -67,6 +67,16 @@ export interface PullTreeItem extends vscode.TreeItem {
pull: RepositoryPull;
}
+const loadMorePullItem: vscode.TreeItem = {
+ label: 'Load more',
+ tooltip: 'Load more pull requests',
+ command: {
+ title: 'Load more pull requests',
+ command: 'github1s.pull-view-load-more-pulls',
+ tooltip: 'Load more pull requests',
+ },
+};
+
export class PullRequestTreeDataProvider
implements vscode.TreeDataProvider {
public static viewType = 'github1s.views.pull-request-list';
@@ -75,17 +85,17 @@ export class PullRequestTreeDataProvider
private _onDidChangeTreeData = new vscode.EventEmitter();
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
- public updateTree() {
- this.forceUpdate = true;
+ public updateTree(forceUpdate = true) {
+ this.forceUpdate = forceUpdate;
this._onDidChangeTreeData.fire(undefined);
}
- async getPullItems(): Promise {
- // only recent 100 pull requests will be list here
- // TODO: implement pagination
- const repositoryPulls = await repository.getPulls(this.forceUpdate);
+ async getPullItems(): Promise {
+ const repositoryPulls = await repository
+ .getPullManager()
+ .getList(this.forceUpdate);
this.forceUpdate = false;
- return repositoryPulls.map((pull) => {
+ const pullTreeItems = repositoryPulls.map((pull) => {
const label = getPullTreeItemLabel(pull);
const description = getPullTreeItemDescription(pull);
const tooltip = `${label} (${description})`;
@@ -106,6 +116,9 @@ export class PullRequestTreeDataProvider
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
};
});
+ return (await repository.getPullManager().hasMore())
+ ? [...pullTreeItems, loadMorePullItem]
+ : pullTreeItems;
}
async getPullFileItems(pull: RepositoryPull): Promise {