From 9b5aa94b9bd07f71f2f4ba5cefb57e5baef3be6e Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sun, 7 Mar 2021 05:05:41 +0900 Subject: [PATCH 01/54] #462 Pipeline/Workflow status for GitLab/GitHub --- package.json | 29 ++++- src/config.ts | 19 +++- src/dataSource.ts | 195 +++++++++++++++++++++++++++++++++- src/extensionState.ts | 1 + src/gitGraphView.ts | 3 +- src/types.ts | 50 +++++++++ web/dialog.ts | 14 ++- web/main.ts | 26 +++-- web/settingsWidget.ts | 121 +++++++++++++++++++++ web/styles/dialog.css | 6 +- web/styles/settingsWidget.css | 6 +- 11 files changed, 445 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 4a75fff0..9c352ecb 100644 --- a/package.json +++ b/package.json @@ -431,6 +431,31 @@ "default": [], "markdownDescription": "An array of custom Pull Request providers that can be used in the \"Pull Request Creation\" Integration. For information on how to configure this setting, see the documentation [here](https://github.com/mhutchie/vscode-git-graph/wiki/Configuring-a-custom-Pull-Request-Provider)." }, + "git-graph.customPipelineProviders": { + "type": "array", + "items": { + "type": "object", + "title": "Pipeline Request Provider", + "required": [ + "name", + "templateUrl" + ], + "properties": { + "name": { + "type": "string", + "title": "Name of the Provider", + "description": "A unique, identifying, display name for the provider." + }, + "templateUrl": { + "type": "string", + "title": "Template URL", + "markdownDescription": "A template URL that can be used to create a Pipeline, after the $1 - $8 variables have been substituted to construct the final URL. For information on how to configure this setting, see the documentation [here](https://github.com/mhutchie/vscode-git-graph/wiki/Configuring-a-custom-Pull-Request-Provider)." + } + } + }, + "default": [], + "markdownDescription": "An array of custom Pipeline providers that can be used in the \"Pipeline Status\" Integration. For information on how to configure this setting, see the documentation [here](https://github.com/mhutchie/vscode-git-graph/wiki/Configuring-a-custom-Pull-Request-Provider)." + }, "git-graph.date.format": { "type": "string", "enum": [ @@ -1451,7 +1476,8 @@ "test-and-report-coverage": "jest --verbose --coverage" }, "dependencies": { - "iconv-lite": "0.5.0" + "iconv-lite": "0.5.0", + "request-promise": "^4.2.5" }, "devDependencies": { "@types/jest": "26.0.19", @@ -1463,6 +1489,7 @@ "jest": "26.6.3", "ts-jest": "26.4.4", "typescript": "4.0.2", + "@types/request-promise": "^4.1.46", "uglify-js": "3.10.0" } } diff --git a/src/config.ts b/src/config.ts index 4e957745..f4b0cdf6 100644 --- a/src/config.ts +++ b/src/config.ts @@ -6,6 +6,7 @@ import { ContextMenuActionsVisibility, CustomBranchGlobPattern, CustomEmojiShortcodeMapping, + CustomPipelineProvider, CustomPullRequestProvider, DateFormat, DateFormatType, @@ -132,6 +133,18 @@ class Config { : []; } + /** + * Get the value of the `git-graph.customPipelineProviders` Extension Setting. + */ + get customPipelineProviders(): CustomPipelineProvider[] { + let providers = this.config.get('customPipelineProviders', []); + return Array.isArray(providers) + ? providers + .filter((provider) => typeof provider.name === 'string' && typeof provider.templateUrl === 'string') + .map((provider) => ({ name: provider.name, templateUrl: provider.templateUrl })) + : []; + } + /** * Get the value of the `git-graph.date.format` Extension Setting. */ @@ -160,10 +173,10 @@ class Config { */ get defaultColumnVisibility(): DefaultColumnVisibility { let obj: any = this.config.get('defaultColumnVisibility', {}); - if (typeof obj === 'object' && obj !== null && typeof obj['Date'] === 'boolean' && typeof obj['Author'] === 'boolean' && typeof obj['Commit'] === 'boolean') { - return { author: obj['Author'], commit: obj['Commit'], date: obj['Date'] }; + if (typeof obj === 'object' && obj !== null && typeof obj['Date'] === 'boolean' && typeof obj['Author'] === 'boolean' && typeof obj['Commit'] === 'boolean' && typeof obj['Pipeline'] === 'boolean') { + return { author: obj['Author'], commit: obj['Commit'], date: obj['Date'], pipeline: obj['Pileline']}; } else { - return { author: true, commit: true, date: true }; + return { author: true, commit: true, date: true, pipeline: true }; } } diff --git a/src/dataSource.ts b/src/dataSource.ts index 4ee39e21..52c28b51 100644 --- a/src/dataSource.ts +++ b/src/dataSource.ts @@ -6,10 +6,11 @@ import * as vscode from 'vscode'; import { AskpassEnvironment, AskpassManager } from './askpass/askpassManager'; import { getConfig } from './config'; import { Logger } from './logger'; -import { CommitOrdering, DateType, DeepWriteable, ErrorInfo, GitCommit, GitCommitDetails, GitCommitStash, GitConfigLocation, GitFileChange, GitFileStatus, GitPushBranchMode, GitRepoConfig, GitRepoConfigBranches, GitResetMode, GitSignatureStatus, GitStash, MergeActionOn, RebaseActionOn, SquashMessageFormat, TagType, Writeable } from './types'; +import { CommitOrdering, DateType, DeepWriteable, ErrorInfo, GitCommit, GitCommitDetails, GitCommitStash, GitConfigLocation, GitFileChange, GitFileStatus, GitPipelinesData, GitPushBranchMode, GitRepoConfig, GitRepoConfigBranches, GitResetMode, GitSignatureStatus, GitStash, MergeActionOn, PipelineConfig, PipelineProvider, RebaseActionOn, SquashMessageFormat, TagType, Writeable } from './types'; import { GitExecutable, UNABLE_TO_FIND_GIT_MSG, UNCOMMITTED, abbrevCommit, constructIncompatibleGitVersionMessage, getPathFromStr, getPathFromUri, isGitAtLeastVersion, openGitTerminal, pathWithTrailingSlash, realpath, resolveSpawnOutput, showErrorMessage } from './utils'; import { Disposable } from './utils/disposable'; import { Event } from './utils/event'; +import * as request from 'request-promise'; const DRIVE_LETTER_PATH_REGEX = /^[a-z]:\//; const EOL_REGEX = /\r\n|\r|\n/g; @@ -158,13 +159,15 @@ export class DataSource extends Disposable { * @param stashes An array of all stashes in the repository. * @returns The commits in the repository. */ - public getCommits(repo: string, branches: ReadonlyArray | null, maxCommits: number, showTags: boolean, showRemoteBranches: boolean, includeCommitsMentionedByReflogs: boolean, onlyFollowFirstParent: boolean, commitOrdering: CommitOrdering, remotes: ReadonlyArray, hideRemotes: ReadonlyArray, stashes: ReadonlyArray): Promise { + public getCommits(repo: string, branches: ReadonlyArray | null, maxCommits: number, showTags: boolean, showRemoteBranches: boolean, includeCommitsMentionedByReflogs: boolean, onlyFollowFirstParent: boolean, commitOrdering: CommitOrdering, remotes: ReadonlyArray, hideRemotes: ReadonlyArray, stashes: ReadonlyArray, pipelineConfigs: PipelineConfig[] | null): Promise { const config = getConfig(); return Promise.all([ this.getLog(repo, branches, maxCommits + 1, showTags && config.showCommitsOnlyReferencedByTags, showRemoteBranches, includeCommitsMentionedByReflogs, onlyFollowFirstParent, commitOrdering, remotes, hideRemotes, stashes), - this.getRefs(repo, showRemoteBranches, config.showRemoteHeads, hideRemotes).then((refData: GitRefData) => refData, (errorMessage: string) => errorMessage) + this.getRefs(repo, showRemoteBranches, config.showRemoteHeads, hideRemotes).then((refData: GitRefData) => refData, (errorMessage: string) => errorMessage), + this.getPipelines(pipelineConfigs).then((refData: GitPipelinesData[] | string | undefined) => refData, (errorMessage: string) => errorMessage) ]).then(async (results) => { let commits: GitCommitRecord[] = results[0], refData: GitRefData | string = results[1], i; + let pipelines: GitPipelinesData[] | string | undefined = results[2]; let moreCommitsAvailable = commits.length === maxCommits + 1; if (moreCommitsAvailable) commits.pop(); @@ -197,7 +200,7 @@ export class DataSource extends Disposable { for (i = 0; i < commits.length; i++) { commitLookup[commits[i].hash] = i; - commitNodes.push({ ...commits[i], heads: [], tags: [], remotes: [], stash: null }); + commitNodes.push({ ...commits[i], heads: [], tags: [], remotes: [], stash: null, pipeline: null }); } /* Insert Stashes */ @@ -217,6 +220,7 @@ export class DataSource extends Disposable { for (i = toAdd.length - 1; i >= 0; i--) { let stash = toAdd[i].data; commitNodes.splice(toAdd[i].index, 0, { + pipeline: null, hash: stash.hash, parents: [stash.baseHash], author: stash.author, @@ -257,6 +261,16 @@ export class DataSource extends Disposable { } } + if (typeof pipelines === 'string' || typeof pipelines === 'undefined') { + pipelines = []; + } + /* Annotate Pipelines */ + for (i = 0; i < pipelines.length; i++) { + if (typeof commitLookup[pipelines[i].sha] === 'number') { + commitNodes[commitLookup[pipelines[i].sha]].pipeline = pipelines[i]; + } + } + return { commits: commitNodes, head: refData.head, @@ -1497,6 +1511,179 @@ export class DataSource extends Disposable { }); } + /** + * Get the result in a Pipelines. + * @param pipelineConfigs pipeline configuration. + * @returns The references data. + */ + private async getPipelines(pipelineConfigs: PipelineConfig[] | null) { + if (pipelineConfigs === null) { + return ''; + } + + return await Promise.all( + pipelineConfigs.map(async pipelineConfig => { + if (pipelineConfig.provider === PipelineProvider.GitHubV3) { + + const match1 = pipelineConfig.gitUrl.match(/^(https?:\/\/|git@)((?=[^/]+@)[^@]+@|(?![^/]+@))([^/:]+)/); + let hostRootUrl = match1 !== null ? 'https://api.' + match1[3] : ''; + + const match2 = pipelineConfig.gitUrl.match(/^(https?:\/\/|git@)[^/:]+[/:]([^/]+)\/([^/]*?)(.git|)$/); + let sourceOwner = match2 !== null ? match2[2] : ''; + let sourceRepo = match2 !== null ? match2[3] : ''; + + const apiRoot = `${hostRootUrl}`; + const pipelinesRootPath = `/repos/${sourceOwner}/${sourceRepo.replace(/\//g, '%2F')}/actions/runs?per_page=100`; + + const config: request.RequestPromiseOptions = { + method: 'GET', + headers: { + 'Authorization': `token ${pipelineConfig.glToken}`, + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'vscode-git-graph' + } + }; + + config.transform = (body, response) => { + try { + let res: any = JSON.parse(body); + let last = 1; + let next = 2; + if (typeof response.headers['link'] === 'string') { + next = 3; + const DELIM_LINKS = ','; + const DELIM_LINK_PARAM = ';'; + let links = response.headers['link'].split(DELIM_LINKS); + links.forEach(link => { + let segments = link.split(DELIM_LINK_PARAM); + + let linkPart = segments[0].trim(); + if (!linkPart.startsWith('<') || !linkPart.endsWith('>')) { + return true; + } + linkPart = linkPart.substring(1, linkPart.length - 1); + let match3 = linkPart.match(/&page=(\d+).*$/); + let linkPage = match3 !== null ? match3[1] : '0'; + + for (let i = 1; i < segments.length; i++) { + let rel = segments[i].trim().split('='); + if (rel.length < 2) { + continue; + } + + let relValue = rel[1]; + if (relValue.startsWith('"') && relValue.endsWith('"')) { + relValue = relValue.substring(1, relValue.length - 1); + } + + if (relValue === 'last') { + last = parseInt(linkPage); + } else if (relValue === 'next') { + next = parseInt(linkPage); + } + } + }); + } + if (typeof res['workflow_runs'] !== 'undefined' && res['workflow_runs'].length >= 1) { // url found + let ret: GitPipelinesData[] = res['workflow_runs'].map( (elm: { [x: string]: any; }) => { + return { + id: elm['id'], + status: elm['conclusion'], + ref: elm['name'], + sha: elm['head_sha'], + web_url: elm['html_url'], + created_at: elm['created_at'], + updated_at: elm['updated_at'] + }; + }); + if (next === 2) { + return { x_total_pages: last, ret: ret }; + } + return ret; + } + return { x_total_pages: 0, ret: 'error' }; + } catch (e) { + return { x_total_pages: 0, ret: e }; + } + }; + return request(`${apiRoot}${pipelinesRootPath}`, config).then(async (result1st) => { + let promises = []; + promises.push(result1st.ret); + for (let i = 1; i < result1st.x_total_pages; i++) { + promises.push(request(`${apiRoot}${pipelinesRootPath}&page=${i + 1}`, config)); + } + return await Promise.all(promises); + }).then((resultAll) => { + let retAll: GitPipelinesData[] = []; + for (let i = 0; i < resultAll.length; i++) { + retAll = retAll.concat(resultAll[i]); + } + return retAll; + }); + } + if (pipelineConfig.provider === PipelineProvider.GitLabV4) { + + const match1 = pipelineConfig.gitUrl.match(/^(https?:\/\/|git@)((?=[^/]+@)[^@]+@|(?![^/]+@))([^/:]+)/); + let hostRootUrl = match1 !== null ? 'https://' + match1[3] : ''; + + const match2 = pipelineConfig.gitUrl.match(/^(https?:\/\/|git@)[^/:]+[/:]([^/]+)\/([^/]*?)(.git|)$/); + let sourceOwner = match2 !== null ? match2[2] : ''; + let sourceRepo = match2 !== null ? match2[3] : ''; + + const apiRoot = `${hostRootUrl}/api/v4`; + const pipelinesRootPath = `/projects/${sourceOwner}%2F${sourceRepo.replace(/\//g, '%2F')}/pipelines?per_page=100`; + + const config: request.RequestPromiseOptions = { + method: 'GET', + headers: { + 'PRIVATE-TOKEN': pipelineConfig.glToken, + 'User-Agent': 'vscode-git-graph' + } + }; + + config.transform = (body, response) => { + try { + if (typeof response.headers['x-page'] === 'string' && typeof response.headers['x-total-pages'] === 'string' && typeof response.headers['x-total'] === 'string') { + let res: any = JSON.parse(body); + if (parseInt(response.headers['x-total']) !== 0 && res.length && res[0].id) { // url found + let ret: GitPipelinesData[] = res; + if (parseInt(response.headers['x-page']) === 1) { + return { x_total_pages: parseInt(response.headers['x-total-pages']), ret: ret }; + } + return ret; + } + } + return { x_total_pages: 0, ret: 'error' }; + } catch (e) { + return { x_total_pages: 0, ret: e }; + } + }; + return request(`${apiRoot}${pipelinesRootPath}`, config).then(async (result1st) => { + let promises = []; + promises.push(result1st.ret); + for (let i = 1; i < result1st.x_total_pages; i++) { + promises.push(request(`${apiRoot}${pipelinesRootPath}&page=${i + 1}`, config)); + } + return await Promise.all(promises); + }).then((resultAll) => { + let retAll: GitPipelinesData[] = []; + for (let i = 0; i < resultAll.length; i++) { + retAll = retAll.concat(resultAll[i]); + } + return retAll; + }); + } + }) + ).then((resultAll2) => { + let retAll: GitPipelinesData[] = []; + resultAll2.forEach(resultList => { + resultList?.forEach(result => { + retAll = retAll.concat(result); + }); + }); + return retAll; + }); + } /** * Get the references in a repository. * @param repo The path of the repository. diff --git a/src/extensionState.ts b/src/extensionState.ts index 3ccfaf2b..36f67bb7 100644 --- a/src/extensionState.ts +++ b/src/extensionState.ts @@ -32,6 +32,7 @@ export const DEFAULT_REPO_STATE: GitRepoState = { onRepoLoadShowCheckedOutBranch: BooleanOverride.Default, onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, + pipelineConfigs: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, diff --git a/src/gitGraphView.ts b/src/gitGraphView.ts index 5af41d16..78ef33b3 100644 --- a/src/gitGraphView.ts +++ b/src/gitGraphView.ts @@ -404,7 +404,7 @@ export class GitGraphView extends Disposable { command: 'loadCommits', refreshId: msg.refreshId, onlyFollowFirstParent: msg.onlyFollowFirstParent, - ...await this.dataSource.getCommits(msg.repo, msg.branches, msg.maxCommits, msg.showTags, msg.showRemoteBranches, msg.includeCommitsMentionedByReflogs, msg.onlyFollowFirstParent, msg.commitOrdering, msg.remotes, msg.hideRemotes, msg.stashes) + ...await this.dataSource.getCommits(msg.repo, msg.branches, msg.maxCommits, msg.showTags, msg.showRemoteBranches, msg.includeCommitsMentionedByReflogs, msg.onlyFollowFirstParent, msg.commitOrdering, msg.remotes, msg.hideRemotes, msg.stashes, msg.pipelineConfigs) }); break; case 'loadConfig': @@ -628,6 +628,7 @@ export class GitGraphView extends Disposable { customBranchGlobPatterns: config.customBranchGlobPatterns, customEmojiShortcodeMappings: config.customEmojiShortcodeMappings, customPullRequestProviders: config.customPullRequestProviders, + customPipelineProviders: config.customPipelineProviders, dateFormat: config.dateFormat, defaultColumnVisibility: config.defaultColumnVisibility, dialogDefaults: config.dialogDefaults, diff --git a/src/types.ts b/src/types.ts index 39afdf57..3d91f799 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,7 @@ /* Git Interfaces / Types */ export interface GitCommit { + readonly pipeline: GitPipelinesData | null; readonly hash: string; readonly parents: ReadonlyArray; readonly author: string; @@ -13,6 +14,16 @@ export interface GitCommit { readonly stash: GitCommitStash | null; // null => not a stash, otherwise => stash info } +export interface GitPipelinesData { + id: string; + status: string; + ref: string; + sha: string; + web_url: string; + created_at: string; + updated_at: string; +} + export interface GitCommitTag { readonly name: string; readonly annotated: boolean; @@ -188,6 +199,36 @@ interface PullRequestConfigCustom extends PullRequestConfigBase { export type PullRequestConfig = PullRequestConfigBuiltIn | PullRequestConfigCustom; + + +export interface PipelineConfigBase { + readonly gitUrl: string; + readonly glToken: string; +} + +export const enum PipelineProvider { + Bitbucket, + Custom, + GitHubV3, + GitLabV4, + Jenkins +} + +interface PipelineConfigBuiltIn extends PipelineConfigBase { + readonly provider: PipelineProvider; + readonly custom: null; +} + +interface PipelineConfigCustom extends PipelineConfigBase { + readonly provider: PipelineProvider.Custom; + readonly custom: { + readonly name: string, + readonly templateUrl: string + }; +} + +export type PipelineConfig = PipelineConfigBuiltIn | PipelineConfigCustom; + export interface GitRepoState { cdvDivider: number; cdvHeight: number; @@ -203,6 +244,7 @@ export interface GitRepoState { onRepoLoadShowCheckedOutBranch: BooleanOverride; onRepoLoadShowSpecificBranches: string[] | null; pullRequestConfig: PullRequestConfig | null; + pipelineConfigs: PipelineConfig[] | null; showRemoteBranches: boolean; showRemoteBranchesV2: BooleanOverride; showStashes: BooleanOverride; @@ -228,6 +270,7 @@ export interface GitGraphViewConfig { readonly customBranchGlobPatterns: ReadonlyArray; readonly customEmojiShortcodeMappings: ReadonlyArray; readonly customPullRequestProviders: ReadonlyArray; + readonly customPipelineProviders: ReadonlyArray; readonly dateFormat: DateFormat; readonly defaultColumnVisibility: DefaultColumnVisibility; readonly dialogDefaults: DialogDefaults; @@ -407,6 +450,11 @@ export interface CustomPullRequestProvider { readonly templateUrl: string; } +export interface CustomPipelineProvider { + readonly name: string; + readonly templateUrl: string; +} + export interface DateFormat { readonly type: DateFormatType; readonly iso: boolean; @@ -427,6 +475,7 @@ export interface DefaultColumnVisibility { readonly date: boolean; readonly author: boolean; readonly commit: boolean; + readonly pipeline: boolean; } export interface DialogDefaults { @@ -884,6 +933,7 @@ export interface RequestLoadCommits extends RepoRequest { readonly remotes: ReadonlyArray; readonly hideRemotes: ReadonlyArray; readonly stashes: ReadonlyArray; + readonly pipelineConfigs: PipelineConfig[] | null; } export interface ResponseLoadCommits extends ResponseWithErrorInfo { readonly command: 'loadCommits'; diff --git a/web/dialog.ts b/web/dialog.ts index e1385a99..8fe08538 100644 --- a/web/dialog.ts +++ b/web/dialog.ts @@ -11,6 +11,7 @@ const enum DialogType { const enum DialogInputType { Text, TextRef, + PasswordRef, Select, Radio, Checkbox @@ -31,6 +32,13 @@ interface DialogTextRefInput { readonly info?: string; } +interface DialogPasswordRefInput { + readonly type: DialogInputType.PasswordRef; + readonly name: string; + readonly default: string; + readonly info?: string; +} + type DialogSelectInput = { readonly type: DialogInputType.Select; readonly name: string; @@ -71,7 +79,7 @@ interface DialogRadioInputOption { readonly value: string; } -type DialogInput = DialogTextInput | DialogTextRefInput | DialogSelectInput | DialogRadioInput | DialogCheckboxInput; +type DialogInput = DialogTextInput | DialogTextRefInput | DialogPasswordRefInput | DialogSelectInput | DialogRadioInput | DialogCheckboxInput; type DialogInputValue = string | string[] | boolean; type DialogTarget = { @@ -212,6 +220,8 @@ class Dialog { inputHtml = '
' + (infoColRequired ? '' + infoHtml + '' : ''); } else if (input.type === DialogInputType.Checkbox) { inputHtml = ''; + } else if (input.type === DialogInputType.PasswordRef) { + inputHtml = '' + (infoColRequired ? '' + infoHtml + '' : ''); } else { inputHtml = '' + (infoColRequired ? '' + infoHtml + '' : ''); } @@ -265,7 +275,7 @@ class Dialog { }); // If the dialog contains a TextRef input, attach event listeners for validation - const textRefInput = inputs.findIndex((input) => input.type === DialogInputType.TextRef); + const textRefInput = inputs.findIndex((input) => input.type === DialogInputType.TextRef || input.type === DialogInputType.PasswordRef); if (textRefInput > -1) { let dialogInput = document.getElementById('dialogInput' + textRefInput), dialogAction = document.getElementById('dialogAction')!; if (dialogInput.value === '') this.elem!.classList.add(CLASS_DIALOG_NO_INPUT); diff --git a/web/main.ts b/web/main.ts index 7852ba34..e6fad767 100644 --- a/web/main.ts +++ b/web/main.ts @@ -615,7 +615,8 @@ class GitGraphView { commitOrdering: getCommitOrdering(repoState.commitOrdering), remotes: this.gitRemotes, hideRemotes: repoState.hideRemotes, - stashes: this.gitStashes + stashes: this.gitStashes, + pipelineConfigs: repoState.pipelineConfigs }); } @@ -735,7 +736,7 @@ class GitGraphView { } private saveColumnWidths(columnWidths: GG.ColumnWidth[]) { - this.gitRepos[this.currentRepo].columnWidths = [columnWidths[0], columnWidths[2], columnWidths[3], columnWidths[4]]; + this.gitRepos[this.currentRepo].columnWidths = [columnWidths[0], columnWidths[2], columnWidths[3], columnWidths[4], columnWidths[5]]; this.saveRepoState(); } @@ -820,6 +821,7 @@ class GitGraphView { (colVisibility.date ? 'Date' : '') + (colVisibility.author ? 'Author' : '') + (colVisibility.commit ? 'Commit' : '') + + (colVisibility.pipeline ? 'Pipeline' : '') + ''; for (let i = 0; i < this.commits.length; i++) { @@ -868,6 +870,7 @@ class GitGraphView { (colVisibility.date ? '' + date.formatted + '' : '') + (colVisibility.author ? '' + (this.config.fetchAvatars ? '' + (typeof this.avatars[commit.email] === 'string' ? '' : '') + '' : '') + escapeHtml(commit.author) + '' : '') + (colVisibility.commit ? '' + abbrevCommit(commit.hash) + '' : '') + + (colVisibility.pipeline ? (commit.pipeline ? '' + '' + commit.pipeline.status + '' : '*') : '') + ''; } this.tableElem.innerHTML = '' + html + '
'; @@ -925,7 +928,8 @@ class GitGraphView { document.getElementById('uncommittedChanges')!.innerHTML = '' + escapeHtml(this.commits[0].message) + '' + (colVisibility.date ? '' + date.formatted + '' : '') + (colVisibility.author ? '*' : '') + - (colVisibility.commit ? '*' : ''); + (colVisibility.commit ? '*' : '') + + (colVisibility.pipeline ? '*' : ''); } private renderFetchButton() { @@ -1654,10 +1658,10 @@ class GitGraphView { let cWidths = this.gitRepos[this.currentRepo].columnWidths; if (cWidths === null) { // Initialise auto column layout if it is the first time viewing the repo. let defaults = this.config.defaultColumnVisibility; - columnWidths = [COLUMN_AUTO, COLUMN_AUTO, defaults.date ? COLUMN_AUTO : COLUMN_HIDDEN, defaults.author ? COLUMN_AUTO : COLUMN_HIDDEN, defaults.commit ? COLUMN_AUTO : COLUMN_HIDDEN]; + columnWidths = [COLUMN_AUTO, COLUMN_AUTO, defaults.date ? COLUMN_AUTO : COLUMN_HIDDEN, defaults.author ? COLUMN_AUTO : COLUMN_HIDDEN, defaults.commit ? COLUMN_AUTO : COLUMN_HIDDEN, defaults.pipeline ? COLUMN_AUTO : COLUMN_HIDDEN]; this.saveColumnWidths(columnWidths); } else { - columnWidths = [cWidths[0], COLUMN_AUTO, cWidths[1], cWidths[2], cWidths[3]]; + columnWidths = [cWidths[0], COLUMN_AUTO, cWidths[1], cWidths[2], cWidths[3], cWidths[4]]; } if (columnWidths[0] !== COLUMN_AUTO) { @@ -1772,6 +1776,12 @@ class GitGraphView { visible: true, checked: columnWidths[4] !== COLUMN_HIDDEN, onClick: () => toggleColumnState(4, 80) + }, + { + title: 'Pepeline', + visible: true, + checked: columnWidths[5] !== COLUMN_HIDDEN, + onClick: () => toggleColumnState(5, 80) } ], [ @@ -1801,16 +1811,16 @@ class GitGraphView { public getColumnVisibility() { let colWidths = this.gitRepos[this.currentRepo].columnWidths; if (colWidths !== null) { - return { date: colWidths[1] !== COLUMN_HIDDEN, author: colWidths[2] !== COLUMN_HIDDEN, commit: colWidths[3] !== COLUMN_HIDDEN }; + return { date: colWidths[1] !== COLUMN_HIDDEN, author: colWidths[2] !== COLUMN_HIDDEN, commit: colWidths[3] !== COLUMN_HIDDEN, pipeline: colWidths[4] !== COLUMN_HIDDEN }; } else { let defaults = this.config.defaultColumnVisibility; - return { date: defaults.date, author: defaults.author, commit: defaults.commit }; + return { date: defaults.date, author: defaults.author, commit: defaults.commit, pipeline: defaults.pipeline }; } } private getNumColumns() { let colVisibility = this.getColumnVisibility(); - return 2 + (colVisibility.date ? 1 : 0) + (colVisibility.author ? 1 : 0) + (colVisibility.commit ? 1 : 0); + return 2 + (colVisibility.date ? 1 : 0) + (colVisibility.author ? 1 : 0) + (colVisibility.commit ? 1 : 0) + (colVisibility.pipeline ? 1 : 0); } /** diff --git a/web/settingsWidget.ts b/web/settingsWidget.ts index 4841f535..c74d35ce 100644 --- a/web/settingsWidget.ts +++ b/web/settingsWidget.ts @@ -239,6 +239,27 @@ class SettingsWidget { html += ''; } + if (this.config !== null) { + html += '

Pipeline Status Configuration

'; + const pipelineConfigs = this.repo.pipelineConfigs; + if (pipelineConfigs !== null && pipelineConfigs.length !== 0) { + pipelineConfigs.forEach((pipelineConfig, i) => { + let providerOptions:any = {}; + providerOptions[(GG.PipelineProvider.GitLabV4).toString()] = 'GitLabV4'; + providerOptions[(GG.PipelineProvider.GitHubV3).toString()] = 'GitHubV3'; + const gitUrl = escapeHtml(pipelineConfig.gitUrl || 'Not Set'); + html += '' + + '' + + '' + + '' + + ''; + }); + } else { + html += ''; + } + html += '
ProviderURLAction
' + escapeHtml(providerOptions[pipelineConfig.provider]) + '' + gitUrl + '
' + SVG_ICONS.pencil + '
' + SVG_ICONS.close + '
There are no pipelines configured for this repository.
' + SVG_ICONS.plus + 'Add Pipeline
'; + } + html += '

Git Graph Configuration

' + '
' + SVG_ICONS.gear + 'Open Git Graph Extension Settings

' + '
' + SVG_ICONS.package + 'Export Repository Configuration
' + @@ -450,6 +471,85 @@ class SettingsWidget { this.view.saveRepoStateValue(this.currentRepo, 'hideRemotes', this.repo.hideRemotes); this.view.refresh(true); }); + + const updateConfigWithFormValues = (values: DialogInputValue[]) => { + let config: GG.PipelineConfig = { + provider: parseInt(values[0]), gitUrl: values[1], + glToken: values[2], + custom: null + }; + return config; + }; + const copyConfigs = () => { + if (this.repo === null) return []; + let configs: GG.PipelineConfig[]; + if (this.repo.pipelineConfigs === null) { + configs = []; + } else { + configs = Object.assign([], this.repo.pipelineConfigs); + } + return configs; + }; + + document.getElementById('settingsAddPipeline')!.addEventListener('click', () => { + let defaultProvider = GG.PipelineProvider.GitHubV3.toString(); + let providerOptions = [ + // { name: 'Bitbucket', value: (GG.PipelineProvider.Bitbucket).toString() }, + { name: 'GitHubV3', value: (GG.PipelineProvider.GitHubV3).toString() }, + { name: 'GitLabV4', value: (GG.PipelineProvider.GitLabV4).toString() } + ]; + dialog.showForm('Add a new pipeline to this repository:', [ + { + type: DialogInputType.Select, name: 'Provider', + options: providerOptions, default: defaultProvider, + info: 'In addition to the built-in publicly hosted Pipeline providers, custom providers can be configured using the Extension Setting "git-graph.customPipelineProviders" (e.g. for use with privately hosted Pipeline providers).' + }, + { type: DialogInputType.Text, name: 'Git URL', default: '', placeholder: null, info: 'The Pipeline provider\'s Git URL (e.g. https://gitlab.com/OWNER/REPO.git).' }, + { type: DialogInputType.PasswordRef, name: 'Access Token', default: '', info: 'The GitLab personal access token or project access token.' } + ], 'Add Pipeline', (values) => { + let configs: GG.PipelineConfig[] = copyConfigs(); + let config: GG.PipelineConfig = updateConfigWithFormValues(values); + configs.push(config); + this.setPipelineConfig(configs); + }, { type: TargetType.Repo }); + }); + + addListenerToClass('editPipeline', 'click', (e) => { + const pipelineConfig = this.getPipelineForBtnEvent(e); + if (pipelineConfig === null) return; + let providerOptions = [ + // { name: 'Bitbucket', value: (GG.PipelineProvider.Bitbucket).toString() }, + { name: 'GitHubV3', value: (GG.PipelineProvider.GitHubV3).toString() }, + { name: 'GitLabV4', value: (GG.PipelineProvider.GitLabV4).toString() } + ]; + dialog.showForm('Edit the Pipeline ' + escapeHtml(pipelineConfig.gitUrl || 'Not Set') + ':', [ + { + type: DialogInputType.Select, name: 'Provider', + options: providerOptions, default: pipelineConfig.provider.toString(), + info: 'In addition to the built-in publicly hosted Pipeline providers, custom providers can be configured using the Extension Setting "git-graph.customPipelineProviders" (e.g. for use with privately hosted Pipeline providers).' + }, + { type: DialogInputType.Text, name: 'Git URL', default: pipelineConfig.gitUrl || '', placeholder: null, info: 'The Pipeline provider\'s Git URL (e.g. https://gitlab.com/OWNER/REPO.git).' }, + { type: DialogInputType.PasswordRef, name: 'Personal Access Token', default: pipelineConfig.glToken, info: 'The GitLab personal access token.' } + ], 'Save Changes', (values) => { + let index = parseInt(((e.target).closest('.pipelineBtns')!).dataset.index!); + let configs: GG.PipelineConfig[] = copyConfigs(); + let config: GG.PipelineConfig = updateConfigWithFormValues(values); + configs[index] = config; + this.setPipelineConfig(configs); + }, { type: TargetType.Repo }); + }); + + addListenerToClass('deletePipeline', 'click', (e) => { + const pipelineConfig = this.getPipelineForBtnEvent(e); + if (pipelineConfig === null) return; + dialog.showConfirmation('Are you sure you want to delete the Pipeline ' + escapeHtml(pipelineConfig.gitUrl) + '?', 'Yes, delete', () => { + let index = parseInt(((e.target).closest('.pipelineBtns')!).dataset.index!); + let configs: GG.PipelineConfig[] = copyConfigs(); + configs.splice(index, 1); + this.setPipelineConfig(configs); + }, { type: TargetType.Repo }); + }); + } document.getElementById('editIssueLinking')!.addEventListener('click', () => { @@ -570,6 +670,16 @@ class SettingsWidget { this.render(); } + /** + * Save the pull request configuration for this repository. + * @param config The pull request configuration to save. + */ + private setPipelineConfig(config: GG.PipelineConfig[] | null) { + if (this.currentRepo === null) return; + this.view.saveRepoStateValue(this.currentRepo, 'pipelineConfigs', config); + this.render(); + } + /** * Show the dialog allowing the user to configure the issue linking for this repository. * @param defaultIssueRegex The default regular expression used to match issue numbers. @@ -800,6 +910,17 @@ class SettingsWidget { }); } + /** + * Get the pipeline details corresponding to a mouse event. + * @param e The mouse event. + * @returns The details of the pipeline. + */ + private getPipelineForBtnEvent(e: Event) { + return this.repo !== null && this.repo.pipelineConfigs !== null + ? this.repo.pipelineConfigs[parseInt(((e.target).closest('.pipelineBtns')!).dataset.index!)] + : null; + } + /** * Get the remote details corresponding to a mouse event. * @param e The mouse event. diff --git a/web/styles/dialog.css b/web/styles/dialog.css index 88f34ed1..19024318 100644 --- a/web/styles/dialog.css +++ b/web/styles/dialog.css @@ -83,7 +83,7 @@ body.vscode-high-contrast .dialog{ background-color:var(--vscode-menu-foreground); } -.dialogContent > table.dialogForm input[type=text]{ +.dialogContent > table.dialogForm input[type=text], .dialogContent > table.dialogForm input[type=password]{ width:100%; padding:4px 6px; box-sizing:border-box; @@ -96,10 +96,10 @@ body.vscode-high-contrast .dialog{ font-size:13px; line-height:17px; } -.dialogContent > table.dialogForm input[type=text]:focus, .dialogContent .dialogFormCheckbox > label > input[type=checkbox]:focus ~ .customCheckbox, .dialogContent .dialogFormRadio > label > input[type=radio]:focus ~ .customRadio{ +.dialogContent > table.dialogForm input[type=text]:focus, .dialogContent > table.dialogForm input[type=password]:focus, .dialogContent .dialogFormCheckbox > label > input[type=checkbox]:focus ~ .customCheckbox, .dialogContent .dialogFormRadio > label > input[type=radio]:focus ~ .customRadio{ border-color:var(--vscode-focusBorder); } -.dialogContent > table.dialogForm input[type=text]::placeholder{ +.dialogContent > table.dialogForm input[type=text]::placeholder, .dialogContent > table.dialogForm input[type=password]::placeholder{ color:var(--vscode-menu-foreground); opacity:0.4; } diff --git a/web/styles/settingsWidget.css b/web/styles/settingsWidget.css index cc26941b..b894e47f 100644 --- a/web/styles/settingsWidget.css +++ b/web/styles/settingsWidget.css @@ -140,17 +140,17 @@ vertical-align:top; cursor:pointer; } -.settingsSection > table td.btns.remoteBtns div{ +.settingsSection > table td.btns.remoteBtns div, .settingsSection > table td.btns.pipelineBtns div{ vertical-align:middle; } -.settingsSection > table #editRepoName svg, .settingsSection > table #editInitialBranches svg, .settingsSection > table .editRemote svg{ +.settingsSection > table #editRepoName svg, .settingsSection > table #editInitialBranches svg, .settingsSection > table .editRemote svg, .settingsSection > table .editPipeline svg{ position:absolute; left:1.5px; top:1.5px; width:14px !important; height:14px !important; } -.settingsSection > table #deleteRepoName svg, .settingsSection > table #clearInitialBranches svg, .settingsSection > table .deleteRemote svg, .settingsSection > table .fetchRemote svg, .settingsSection > table .pruneRemote svg{ +.settingsSection > table #deleteRepoName svg, .settingsSection > table #clearInitialBranches svg, .settingsSection > table .deleteRemote svg, .settingsSection > table .fetchRemote svg, .settingsSection > table .pruneRemote svg, .settingsSection > table .deletePipeline svg{ position:absolute; left:0.5px; top:0.5px; From 008c80dfa92a170817ed3deb7f297f2236aab044 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sun, 7 Mar 2021 09:50:52 +0900 Subject: [PATCH 02/54] #462 Removed CustomPipeline. --- package.json | 25 ------------------------- src/config.ts | 13 ------------- src/gitGraphView.ts | 1 - src/types.ts | 6 ------ web/settingsWidget.ts | 4 ++-- 5 files changed, 2 insertions(+), 47 deletions(-) diff --git a/package.json b/package.json index 9c352ecb..1229962c 100644 --- a/package.json +++ b/package.json @@ -431,31 +431,6 @@ "default": [], "markdownDescription": "An array of custom Pull Request providers that can be used in the \"Pull Request Creation\" Integration. For information on how to configure this setting, see the documentation [here](https://github.com/mhutchie/vscode-git-graph/wiki/Configuring-a-custom-Pull-Request-Provider)." }, - "git-graph.customPipelineProviders": { - "type": "array", - "items": { - "type": "object", - "title": "Pipeline Request Provider", - "required": [ - "name", - "templateUrl" - ], - "properties": { - "name": { - "type": "string", - "title": "Name of the Provider", - "description": "A unique, identifying, display name for the provider." - }, - "templateUrl": { - "type": "string", - "title": "Template URL", - "markdownDescription": "A template URL that can be used to create a Pipeline, after the $1 - $8 variables have been substituted to construct the final URL. For information on how to configure this setting, see the documentation [here](https://github.com/mhutchie/vscode-git-graph/wiki/Configuring-a-custom-Pull-Request-Provider)." - } - } - }, - "default": [], - "markdownDescription": "An array of custom Pipeline providers that can be used in the \"Pipeline Status\" Integration. For information on how to configure this setting, see the documentation [here](https://github.com/mhutchie/vscode-git-graph/wiki/Configuring-a-custom-Pull-Request-Provider)." - }, "git-graph.date.format": { "type": "string", "enum": [ diff --git a/src/config.ts b/src/config.ts index f4b0cdf6..85cfe580 100644 --- a/src/config.ts +++ b/src/config.ts @@ -6,7 +6,6 @@ import { ContextMenuActionsVisibility, CustomBranchGlobPattern, CustomEmojiShortcodeMapping, - CustomPipelineProvider, CustomPullRequestProvider, DateFormat, DateFormatType, @@ -133,18 +132,6 @@ class Config { : []; } - /** - * Get the value of the `git-graph.customPipelineProviders` Extension Setting. - */ - get customPipelineProviders(): CustomPipelineProvider[] { - let providers = this.config.get('customPipelineProviders', []); - return Array.isArray(providers) - ? providers - .filter((provider) => typeof provider.name === 'string' && typeof provider.templateUrl === 'string') - .map((provider) => ({ name: provider.name, templateUrl: provider.templateUrl })) - : []; - } - /** * Get the value of the `git-graph.date.format` Extension Setting. */ diff --git a/src/gitGraphView.ts b/src/gitGraphView.ts index 78ef33b3..4b23e525 100644 --- a/src/gitGraphView.ts +++ b/src/gitGraphView.ts @@ -628,7 +628,6 @@ export class GitGraphView extends Disposable { customBranchGlobPatterns: config.customBranchGlobPatterns, customEmojiShortcodeMappings: config.customEmojiShortcodeMappings, customPullRequestProviders: config.customPullRequestProviders, - customPipelineProviders: config.customPipelineProviders, dateFormat: config.dateFormat, defaultColumnVisibility: config.defaultColumnVisibility, dialogDefaults: config.dialogDefaults, diff --git a/src/types.ts b/src/types.ts index 3d91f799..e8ec3914 100644 --- a/src/types.ts +++ b/src/types.ts @@ -270,7 +270,6 @@ export interface GitGraphViewConfig { readonly customBranchGlobPatterns: ReadonlyArray; readonly customEmojiShortcodeMappings: ReadonlyArray; readonly customPullRequestProviders: ReadonlyArray; - readonly customPipelineProviders: ReadonlyArray; readonly dateFormat: DateFormat; readonly defaultColumnVisibility: DefaultColumnVisibility; readonly dialogDefaults: DialogDefaults; @@ -450,11 +449,6 @@ export interface CustomPullRequestProvider { readonly templateUrl: string; } -export interface CustomPipelineProvider { - readonly name: string; - readonly templateUrl: string; -} - export interface DateFormat { readonly type: DateFormatType; readonly iso: boolean; diff --git a/web/settingsWidget.ts b/web/settingsWidget.ts index c74d35ce..acc37dbe 100644 --- a/web/settingsWidget.ts +++ b/web/settingsWidget.ts @@ -502,7 +502,7 @@ class SettingsWidget { { type: DialogInputType.Select, name: 'Provider', options: providerOptions, default: defaultProvider, - info: 'In addition to the built-in publicly hosted Pipeline providers, custom providers can be configured using the Extension Setting "git-graph.customPipelineProviders" (e.g. for use with privately hosted Pipeline providers).' + info: 'In addition to the built-in publicly hosted Pipeline providers.' }, { type: DialogInputType.Text, name: 'Git URL', default: '', placeholder: null, info: 'The Pipeline provider\'s Git URL (e.g. https://gitlab.com/OWNER/REPO.git).' }, { type: DialogInputType.PasswordRef, name: 'Access Token', default: '', info: 'The GitLab personal access token or project access token.' } @@ -526,7 +526,7 @@ class SettingsWidget { { type: DialogInputType.Select, name: 'Provider', options: providerOptions, default: pipelineConfig.provider.toString(), - info: 'In addition to the built-in publicly hosted Pipeline providers, custom providers can be configured using the Extension Setting "git-graph.customPipelineProviders" (e.g. for use with privately hosted Pipeline providers).' + info: 'In addition to the built-in publicly hosted Pipeline providers.' }, { type: DialogInputType.Text, name: 'Git URL', default: pipelineConfig.gitUrl || '', placeholder: null, info: 'The Pipeline provider\'s Git URL (e.g. https://gitlab.com/OWNER/REPO.git).' }, { type: DialogInputType.PasswordRef, name: 'Personal Access Token', default: pipelineConfig.glToken, info: 'The GitLab personal access token.' } From c97c25514d8cc5f1a071c4e434a2ead1e0dd7df3 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sun, 7 Mar 2021 14:48:21 +0900 Subject: [PATCH 03/54] #462 Renamed "Pipeline Status" to "CI/CD Status" --- package.json | 7 ++- src/config.ts | 6 +- src/dataSource.ts | 72 ++++++++++----------- src/extensionState.ts | 2 +- src/gitGraphView.ts | 2 +- src/types.ts | 24 +++---- web/main.ts | 18 +++--- web/settingsWidget.ts | 114 +++++++++++++++++----------------- web/styles/settingsWidget.css | 6 +- 9 files changed, 128 insertions(+), 123 deletions(-) diff --git a/package.json b/package.json index 1229962c..df527744 100644 --- a/package.json +++ b/package.json @@ -477,12 +477,17 @@ "Commit": { "type": "boolean", "title": "Visibility of the Commit column" + }, + "CIDI": { + "type": "boolean", + "title": "Visibility of the CI/DI Status column" } }, "default": { "Date": true, "Author": true, - "Commit": true + "Commit": true, + "CIDI": true }, "description": "An object specifying the default visibility of the Date, Author & Commit columns. Example: {\"Date\": true, \"Author\": true, \"Commit\": true}" }, diff --git a/src/config.ts b/src/config.ts index 85cfe580..f63855f2 100644 --- a/src/config.ts +++ b/src/config.ts @@ -160,10 +160,10 @@ class Config { */ get defaultColumnVisibility(): DefaultColumnVisibility { let obj: any = this.config.get('defaultColumnVisibility', {}); - if (typeof obj === 'object' && obj !== null && typeof obj['Date'] === 'boolean' && typeof obj['Author'] === 'boolean' && typeof obj['Commit'] === 'boolean' && typeof obj['Pipeline'] === 'boolean') { - return { author: obj['Author'], commit: obj['Commit'], date: obj['Date'], pipeline: obj['Pileline']}; + if (typeof obj === 'object' && obj !== null && typeof obj['Date'] === 'boolean' && typeof obj['Author'] === 'boolean' && typeof obj['Commit'] === 'boolean' && typeof obj['CIDI'] === 'boolean') { + return { author: obj['Author'], commit: obj['Commit'], date: obj['Date'], cidi: obj['CIDI']}; } else { - return { author: true, commit: true, date: true, pipeline: true }; + return { author: true, commit: true, date: true, cidi: true }; } } diff --git a/src/dataSource.ts b/src/dataSource.ts index 52c28b51..6e7e9838 100644 --- a/src/dataSource.ts +++ b/src/dataSource.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode'; import { AskpassEnvironment, AskpassManager } from './askpass/askpassManager'; import { getConfig } from './config'; import { Logger } from './logger'; -import { CommitOrdering, DateType, DeepWriteable, ErrorInfo, GitCommit, GitCommitDetails, GitCommitStash, GitConfigLocation, GitFileChange, GitFileStatus, GitPipelinesData, GitPushBranchMode, GitRepoConfig, GitRepoConfigBranches, GitResetMode, GitSignatureStatus, GitStash, MergeActionOn, PipelineConfig, PipelineProvider, RebaseActionOn, SquashMessageFormat, TagType, Writeable } from './types'; +import { CIDIConfig, CIDIProvider, CommitOrdering, DateType, DeepWriteable, ErrorInfo, GitCIDIData, GitCommit, GitCommitDetails, GitCommitStash, GitConfigLocation, GitFileChange, GitFileStatus, GitPushBranchMode, GitRepoConfig, GitRepoConfigBranches, GitResetMode, GitSignatureStatus, GitStash, MergeActionOn, RebaseActionOn, SquashMessageFormat, TagType, Writeable } from './types'; import { GitExecutable, UNABLE_TO_FIND_GIT_MSG, UNCOMMITTED, abbrevCommit, constructIncompatibleGitVersionMessage, getPathFromStr, getPathFromUri, isGitAtLeastVersion, openGitTerminal, pathWithTrailingSlash, realpath, resolveSpawnOutput, showErrorMessage } from './utils'; import { Disposable } from './utils/disposable'; import { Event } from './utils/event'; @@ -159,15 +159,15 @@ export class DataSource extends Disposable { * @param stashes An array of all stashes in the repository. * @returns The commits in the repository. */ - public getCommits(repo: string, branches: ReadonlyArray | null, maxCommits: number, showTags: boolean, showRemoteBranches: boolean, includeCommitsMentionedByReflogs: boolean, onlyFollowFirstParent: boolean, commitOrdering: CommitOrdering, remotes: ReadonlyArray, hideRemotes: ReadonlyArray, stashes: ReadonlyArray, pipelineConfigs: PipelineConfig[] | null): Promise { + public getCommits(repo: string, branches: ReadonlyArray | null, maxCommits: number, showTags: boolean, showRemoteBranches: boolean, includeCommitsMentionedByReflogs: boolean, onlyFollowFirstParent: boolean, commitOrdering: CommitOrdering, remotes: ReadonlyArray, hideRemotes: ReadonlyArray, stashes: ReadonlyArray, cidiConfigs: CIDIConfig[] | null): Promise { const config = getConfig(); return Promise.all([ this.getLog(repo, branches, maxCommits + 1, showTags && config.showCommitsOnlyReferencedByTags, showRemoteBranches, includeCommitsMentionedByReflogs, onlyFollowFirstParent, commitOrdering, remotes, hideRemotes, stashes), this.getRefs(repo, showRemoteBranches, config.showRemoteHeads, hideRemotes).then((refData: GitRefData) => refData, (errorMessage: string) => errorMessage), - this.getPipelines(pipelineConfigs).then((refData: GitPipelinesData[] | string | undefined) => refData, (errorMessage: string) => errorMessage) + this.getCIDIs(cidiConfigs).then((refData: GitCIDIData[] | string | undefined) => refData, (errorMessage: string) => errorMessage) ]).then(async (results) => { let commits: GitCommitRecord[] = results[0], refData: GitRefData | string = results[1], i; - let pipelines: GitPipelinesData[] | string | undefined = results[2]; + let cidis: GitCIDIData[] | string | undefined = results[2]; let moreCommitsAvailable = commits.length === maxCommits + 1; if (moreCommitsAvailable) commits.pop(); @@ -200,7 +200,7 @@ export class DataSource extends Disposable { for (i = 0; i < commits.length; i++) { commitLookup[commits[i].hash] = i; - commitNodes.push({ ...commits[i], heads: [], tags: [], remotes: [], stash: null, pipeline: null }); + commitNodes.push({ ...commits[i], heads: [], tags: [], remotes: [], stash: null, cidi: null }); } /* Insert Stashes */ @@ -220,7 +220,7 @@ export class DataSource extends Disposable { for (i = toAdd.length - 1; i >= 0; i--) { let stash = toAdd[i].data; commitNodes.splice(toAdd[i].index, 0, { - pipeline: null, + cidi: null, hash: stash.hash, parents: [stash.baseHash], author: stash.author, @@ -261,13 +261,13 @@ export class DataSource extends Disposable { } } - if (typeof pipelines === 'string' || typeof pipelines === 'undefined') { - pipelines = []; + if (typeof cidis === 'string' || typeof cidis === 'undefined') { + cidis = []; } - /* Annotate Pipelines */ - for (i = 0; i < pipelines.length; i++) { - if (typeof commitLookup[pipelines[i].sha] === 'number') { - commitNodes[commitLookup[pipelines[i].sha]].pipeline = pipelines[i]; + /* Annotate CI/DIs */ + for (i = 0; i < cidis.length; i++) { + if (typeof commitLookup[cidis[i].sha] === 'number') { + commitNodes[commitLookup[cidis[i].sha]].cidi = cidis[i]; } } @@ -1512,33 +1512,33 @@ export class DataSource extends Disposable { } /** - * Get the result in a Pipelines. - * @param pipelineConfigs pipeline configuration. + * Get the result in a CI/DIs. + * @param cidiConfigs CI/DI configuration. * @returns The references data. */ - private async getPipelines(pipelineConfigs: PipelineConfig[] | null) { - if (pipelineConfigs === null) { + private async getCIDIs(cidiConfigs: CIDIConfig[] | null) { + if (cidiConfigs === null) { return ''; } return await Promise.all( - pipelineConfigs.map(async pipelineConfig => { - if (pipelineConfig.provider === PipelineProvider.GitHubV3) { + cidiConfigs.map(async cidiConfig => { + if (cidiConfig.provider === CIDIProvider.GitHubV3) { - const match1 = pipelineConfig.gitUrl.match(/^(https?:\/\/|git@)((?=[^/]+@)[^@]+@|(?![^/]+@))([^/:]+)/); + const match1 = cidiConfig.gitUrl.match(/^(https?:\/\/|git@)((?=[^/]+@)[^@]+@|(?![^/]+@))([^/:]+)/); let hostRootUrl = match1 !== null ? 'https://api.' + match1[3] : ''; - const match2 = pipelineConfig.gitUrl.match(/^(https?:\/\/|git@)[^/:]+[/:]([^/]+)\/([^/]*?)(.git|)$/); + const match2 = cidiConfig.gitUrl.match(/^(https?:\/\/|git@)[^/:]+[/:]([^/]+)\/([^/]*?)(.git|)$/); let sourceOwner = match2 !== null ? match2[2] : ''; let sourceRepo = match2 !== null ? match2[3] : ''; const apiRoot = `${hostRootUrl}`; - const pipelinesRootPath = `/repos/${sourceOwner}/${sourceRepo.replace(/\//g, '%2F')}/actions/runs?per_page=100`; + const cidiRootPath = `/repos/${sourceOwner}/${sourceRepo.replace(/\//g, '%2F')}/actions/runs?per_page=100`; const config: request.RequestPromiseOptions = { method: 'GET', headers: { - 'Authorization': `token ${pipelineConfig.glToken}`, + 'Authorization': `token ${cidiConfig.glToken}`, 'Accept': 'application/vnd.github.v3+json', 'User-Agent': 'vscode-git-graph' } @@ -1585,7 +1585,7 @@ export class DataSource extends Disposable { }); } if (typeof res['workflow_runs'] !== 'undefined' && res['workflow_runs'].length >= 1) { // url found - let ret: GitPipelinesData[] = res['workflow_runs'].map( (elm: { [x: string]: any; }) => { + let ret: GitCIDIData[] = res['workflow_runs'].map( (elm: { [x: string]: any; }) => { return { id: elm['id'], status: elm['conclusion'], @@ -1606,37 +1606,37 @@ export class DataSource extends Disposable { return { x_total_pages: 0, ret: e }; } }; - return request(`${apiRoot}${pipelinesRootPath}`, config).then(async (result1st) => { + return request(`${apiRoot}${cidiRootPath}`, config).then(async (result1st) => { let promises = []; promises.push(result1st.ret); for (let i = 1; i < result1st.x_total_pages; i++) { - promises.push(request(`${apiRoot}${pipelinesRootPath}&page=${i + 1}`, config)); + promises.push(request(`${apiRoot}${cidiRootPath}&page=${i + 1}`, config)); } return await Promise.all(promises); }).then((resultAll) => { - let retAll: GitPipelinesData[] = []; + let retAll: GitCIDIData[] = []; for (let i = 0; i < resultAll.length; i++) { retAll = retAll.concat(resultAll[i]); } return retAll; }); } - if (pipelineConfig.provider === PipelineProvider.GitLabV4) { + if (cidiConfig.provider === CIDIProvider.GitLabV4) { - const match1 = pipelineConfig.gitUrl.match(/^(https?:\/\/|git@)((?=[^/]+@)[^@]+@|(?![^/]+@))([^/:]+)/); + const match1 = cidiConfig.gitUrl.match(/^(https?:\/\/|git@)((?=[^/]+@)[^@]+@|(?![^/]+@))([^/:]+)/); let hostRootUrl = match1 !== null ? 'https://' + match1[3] : ''; - const match2 = pipelineConfig.gitUrl.match(/^(https?:\/\/|git@)[^/:]+[/:]([^/]+)\/([^/]*?)(.git|)$/); + const match2 = cidiConfig.gitUrl.match(/^(https?:\/\/|git@)[^/:]+[/:]([^/]+)\/([^/]*?)(.git|)$/); let sourceOwner = match2 !== null ? match2[2] : ''; let sourceRepo = match2 !== null ? match2[3] : ''; const apiRoot = `${hostRootUrl}/api/v4`; - const pipelinesRootPath = `/projects/${sourceOwner}%2F${sourceRepo.replace(/\//g, '%2F')}/pipelines?per_page=100`; + const cidiRootPath = `/projects/${sourceOwner}%2F${sourceRepo.replace(/\//g, '%2F')}/pipelines?per_page=100`; const config: request.RequestPromiseOptions = { method: 'GET', headers: { - 'PRIVATE-TOKEN': pipelineConfig.glToken, + 'PRIVATE-TOKEN': cidiConfig.glToken, 'User-Agent': 'vscode-git-graph' } }; @@ -1646,7 +1646,7 @@ export class DataSource extends Disposable { if (typeof response.headers['x-page'] === 'string' && typeof response.headers['x-total-pages'] === 'string' && typeof response.headers['x-total'] === 'string') { let res: any = JSON.parse(body); if (parseInt(response.headers['x-total']) !== 0 && res.length && res[0].id) { // url found - let ret: GitPipelinesData[] = res; + let ret: GitCIDIData[] = res; if (parseInt(response.headers['x-page']) === 1) { return { x_total_pages: parseInt(response.headers['x-total-pages']), ret: ret }; } @@ -1658,15 +1658,15 @@ export class DataSource extends Disposable { return { x_total_pages: 0, ret: e }; } }; - return request(`${apiRoot}${pipelinesRootPath}`, config).then(async (result1st) => { + return request(`${apiRoot}${cidiRootPath}`, config).then(async (result1st) => { let promises = []; promises.push(result1st.ret); for (let i = 1; i < result1st.x_total_pages; i++) { - promises.push(request(`${apiRoot}${pipelinesRootPath}&page=${i + 1}`, config)); + promises.push(request(`${apiRoot}${cidiRootPath}&page=${i + 1}`, config)); } return await Promise.all(promises); }).then((resultAll) => { - let retAll: GitPipelinesData[] = []; + let retAll: GitCIDIData[] = []; for (let i = 0; i < resultAll.length; i++) { retAll = retAll.concat(resultAll[i]); } @@ -1675,7 +1675,7 @@ export class DataSource extends Disposable { } }) ).then((resultAll2) => { - let retAll: GitPipelinesData[] = []; + let retAll: GitCIDIData[] = []; resultAll2.forEach(resultList => { resultList?.forEach(result => { retAll = retAll.concat(result); diff --git a/src/extensionState.ts b/src/extensionState.ts index 36f67bb7..3a394467 100644 --- a/src/extensionState.ts +++ b/src/extensionState.ts @@ -32,7 +32,7 @@ export const DEFAULT_REPO_STATE: GitRepoState = { onRepoLoadShowCheckedOutBranch: BooleanOverride.Default, onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, - pipelineConfigs: null, + cidiConfigs: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, diff --git a/src/gitGraphView.ts b/src/gitGraphView.ts index 4b23e525..7c6a2f98 100644 --- a/src/gitGraphView.ts +++ b/src/gitGraphView.ts @@ -404,7 +404,7 @@ export class GitGraphView extends Disposable { command: 'loadCommits', refreshId: msg.refreshId, onlyFollowFirstParent: msg.onlyFollowFirstParent, - ...await this.dataSource.getCommits(msg.repo, msg.branches, msg.maxCommits, msg.showTags, msg.showRemoteBranches, msg.includeCommitsMentionedByReflogs, msg.onlyFollowFirstParent, msg.commitOrdering, msg.remotes, msg.hideRemotes, msg.stashes, msg.pipelineConfigs) + ...await this.dataSource.getCommits(msg.repo, msg.branches, msg.maxCommits, msg.showTags, msg.showRemoteBranches, msg.includeCommitsMentionedByReflogs, msg.onlyFollowFirstParent, msg.commitOrdering, msg.remotes, msg.hideRemotes, msg.stashes, msg.cidiConfigs) }); break; case 'loadConfig': diff --git a/src/types.ts b/src/types.ts index e8ec3914..3311e7ba 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,7 +1,6 @@ /* Git Interfaces / Types */ export interface GitCommit { - readonly pipeline: GitPipelinesData | null; readonly hash: string; readonly parents: ReadonlyArray; readonly author: string; @@ -12,9 +11,10 @@ export interface GitCommit { readonly tags: ReadonlyArray; readonly remotes: ReadonlyArray; readonly stash: GitCommitStash | null; // null => not a stash, otherwise => stash info + readonly cidi: GitCIDIData | null; } -export interface GitPipelinesData { +export interface GitCIDIData { id: string; status: string; ref: string; @@ -201,12 +201,12 @@ export type PullRequestConfig = PullRequestConfigBuiltIn | PullRequestConfigCust -export interface PipelineConfigBase { +export interface CIDIConfigBase { readonly gitUrl: string; readonly glToken: string; } -export const enum PipelineProvider { +export const enum CIDIProvider { Bitbucket, Custom, GitHubV3, @@ -214,20 +214,20 @@ export const enum PipelineProvider { Jenkins } -interface PipelineConfigBuiltIn extends PipelineConfigBase { - readonly provider: PipelineProvider; +interface CIDIConfigBuiltIn extends CIDIConfigBase { + readonly provider: CIDIProvider; readonly custom: null; } -interface PipelineConfigCustom extends PipelineConfigBase { - readonly provider: PipelineProvider.Custom; +interface CIDIConfigCustom extends CIDIConfigBase { + readonly provider: CIDIProvider.Custom; readonly custom: { readonly name: string, readonly templateUrl: string }; } -export type PipelineConfig = PipelineConfigBuiltIn | PipelineConfigCustom; +export type CIDIConfig = CIDIConfigBuiltIn | CIDIConfigCustom; export interface GitRepoState { cdvDivider: number; @@ -244,7 +244,7 @@ export interface GitRepoState { onRepoLoadShowCheckedOutBranch: BooleanOverride; onRepoLoadShowSpecificBranches: string[] | null; pullRequestConfig: PullRequestConfig | null; - pipelineConfigs: PipelineConfig[] | null; + cidiConfigs: CIDIConfig[] | null; showRemoteBranches: boolean; showRemoteBranchesV2: BooleanOverride; showStashes: BooleanOverride; @@ -469,7 +469,7 @@ export interface DefaultColumnVisibility { readonly date: boolean; readonly author: boolean; readonly commit: boolean; - readonly pipeline: boolean; + readonly cidi: boolean; } export interface DialogDefaults { @@ -927,7 +927,7 @@ export interface RequestLoadCommits extends RepoRequest { readonly remotes: ReadonlyArray; readonly hideRemotes: ReadonlyArray; readonly stashes: ReadonlyArray; - readonly pipelineConfigs: PipelineConfig[] | null; + readonly cidiConfigs: CIDIConfig[] | null; } export interface ResponseLoadCommits extends ResponseWithErrorInfo { readonly command: 'loadCommits'; diff --git a/web/main.ts b/web/main.ts index e6fad767..5b4a6ad0 100644 --- a/web/main.ts +++ b/web/main.ts @@ -616,7 +616,7 @@ class GitGraphView { remotes: this.gitRemotes, hideRemotes: repoState.hideRemotes, stashes: this.gitStashes, - pipelineConfigs: repoState.pipelineConfigs + cidiConfigs: repoState.cidiConfigs }); } @@ -821,7 +821,7 @@ class GitGraphView { (colVisibility.date ? 'Date' : '') + (colVisibility.author ? 'Author' : '') + (colVisibility.commit ? 'Commit' : '') + - (colVisibility.pipeline ? 'Pipeline' : '') + + (colVisibility.cidi ? 'CI/DI Status' : '') + ''; for (let i = 0; i < this.commits.length; i++) { @@ -870,7 +870,7 @@ class GitGraphView { (colVisibility.date ? '' + date.formatted + '' : '') + (colVisibility.author ? '' + (this.config.fetchAvatars ? '' + (typeof this.avatars[commit.email] === 'string' ? '' : '') + '' : '') + escapeHtml(commit.author) + '' : '') + (colVisibility.commit ? '' + abbrevCommit(commit.hash) + '' : '') + - (colVisibility.pipeline ? (commit.pipeline ? '' + '' + commit.pipeline.status + '' : '*') : '') + + (colVisibility.cidi ? (commit.cidi ? '' + '' + commit.cidi.status + '' : '*') : '') + ''; } this.tableElem.innerHTML = '' + html + '
'; @@ -929,7 +929,7 @@ class GitGraphView { (colVisibility.date ? '' + date.formatted + '' : '') + (colVisibility.author ? '*' : '') + (colVisibility.commit ? '*' : '') + - (colVisibility.pipeline ? '*' : ''); + (colVisibility.cidi ? '*' : ''); } private renderFetchButton() { @@ -1658,7 +1658,7 @@ class GitGraphView { let cWidths = this.gitRepos[this.currentRepo].columnWidths; if (cWidths === null) { // Initialise auto column layout if it is the first time viewing the repo. let defaults = this.config.defaultColumnVisibility; - columnWidths = [COLUMN_AUTO, COLUMN_AUTO, defaults.date ? COLUMN_AUTO : COLUMN_HIDDEN, defaults.author ? COLUMN_AUTO : COLUMN_HIDDEN, defaults.commit ? COLUMN_AUTO : COLUMN_HIDDEN, defaults.pipeline ? COLUMN_AUTO : COLUMN_HIDDEN]; + columnWidths = [COLUMN_AUTO, COLUMN_AUTO, defaults.date ? COLUMN_AUTO : COLUMN_HIDDEN, defaults.author ? COLUMN_AUTO : COLUMN_HIDDEN, defaults.commit ? COLUMN_AUTO : COLUMN_HIDDEN, defaults.cidi ? COLUMN_AUTO : COLUMN_HIDDEN]; this.saveColumnWidths(columnWidths); } else { columnWidths = [cWidths[0], COLUMN_AUTO, cWidths[1], cWidths[2], cWidths[3], cWidths[4]]; @@ -1778,7 +1778,7 @@ class GitGraphView { onClick: () => toggleColumnState(4, 80) }, { - title: 'Pepeline', + title: 'CI/DI Status', visible: true, checked: columnWidths[5] !== COLUMN_HIDDEN, onClick: () => toggleColumnState(5, 80) @@ -1811,16 +1811,16 @@ class GitGraphView { public getColumnVisibility() { let colWidths = this.gitRepos[this.currentRepo].columnWidths; if (colWidths !== null) { - return { date: colWidths[1] !== COLUMN_HIDDEN, author: colWidths[2] !== COLUMN_HIDDEN, commit: colWidths[3] !== COLUMN_HIDDEN, pipeline: colWidths[4] !== COLUMN_HIDDEN }; + return { date: colWidths[1] !== COLUMN_HIDDEN, author: colWidths[2] !== COLUMN_HIDDEN, commit: colWidths[3] !== COLUMN_HIDDEN, cidi: colWidths[4] !== COLUMN_HIDDEN }; } else { let defaults = this.config.defaultColumnVisibility; - return { date: defaults.date, author: defaults.author, commit: defaults.commit, pipeline: defaults.pipeline }; + return { date: defaults.date, author: defaults.author, commit: defaults.commit, cidi: defaults.cidi }; } } private getNumColumns() { let colVisibility = this.getColumnVisibility(); - return 2 + (colVisibility.date ? 1 : 0) + (colVisibility.author ? 1 : 0) + (colVisibility.commit ? 1 : 0) + (colVisibility.pipeline ? 1 : 0); + return 2 + (colVisibility.date ? 1 : 0) + (colVisibility.author ? 1 : 0) + (colVisibility.commit ? 1 : 0) + (colVisibility.cidi ? 1 : 0); } /** diff --git a/web/settingsWidget.ts b/web/settingsWidget.ts index acc37dbe..ba170e07 100644 --- a/web/settingsWidget.ts +++ b/web/settingsWidget.ts @@ -240,24 +240,24 @@ class SettingsWidget { } if (this.config !== null) { - html += '

Pipeline Status Configuration

'; - const pipelineConfigs = this.repo.pipelineConfigs; - if (pipelineConfigs !== null && pipelineConfigs.length !== 0) { - pipelineConfigs.forEach((pipelineConfig, i) => { + html += '

CI/DI Status Configuration

ProviderURLAction
'; + const cidiConfigs = this.repo.cidiConfigs; + if (cidiConfigs !== null && cidiConfigs.length !== 0) { + cidiConfigs.forEach((cidiConfig, i) => { let providerOptions:any = {}; - providerOptions[(GG.PipelineProvider.GitLabV4).toString()] = 'GitLabV4'; - providerOptions[(GG.PipelineProvider.GitHubV3).toString()] = 'GitHubV3'; - const gitUrl = escapeHtml(pipelineConfig.gitUrl || 'Not Set'); + providerOptions[(GG.CIDIProvider.GitHubV3).toString()] = 'GitHub'; + providerOptions[(GG.CIDIProvider.GitLabV4).toString()] = 'GitLab V4(8.11-)'; + const gitUrl = escapeHtml(cidiConfig.gitUrl || 'Not Set'); html += '' + - '' + + '' + '' + - '' + + '' + ''; }); } else { - html += ''; + html += ''; } - html += '
ProviderURLAction
' + escapeHtml(providerOptions[pipelineConfig.provider]) + '' + escapeHtml(providerOptions[cidiConfig.provider]) + '' + gitUrl + '
' + SVG_ICONS.pencil + '
' + SVG_ICONS.close + '
' + SVG_ICONS.pencil + '
' + SVG_ICONS.close + '
There are no pipelines configured for this repository.
There are no CI/DI configured for this repository.
' + SVG_ICONS.plus + 'Add Pipeline
'; + html += '
' + SVG_ICONS.plus + 'Add CI/DI
'; } html += '

Git Graph Configuration

' + @@ -473,8 +473,8 @@ class SettingsWidget { }); const updateConfigWithFormValues = (values: DialogInputValue[]) => { - let config: GG.PipelineConfig = { - provider: parseInt(values[0]), gitUrl: values[1], + let config: GG.CIDIConfig = { + provider: parseInt(values[0]), gitUrl: values[1], glToken: values[2], custom: null }; @@ -482,71 +482,71 @@ class SettingsWidget { }; const copyConfigs = () => { if (this.repo === null) return []; - let configs: GG.PipelineConfig[]; - if (this.repo.pipelineConfigs === null) { + let configs: GG.CIDIConfig[]; + if (this.repo.cidiConfigs === null) { configs = []; } else { - configs = Object.assign([], this.repo.pipelineConfigs); + configs = Object.assign([], this.repo.cidiConfigs); } return configs; }; - document.getElementById('settingsAddPipeline')!.addEventListener('click', () => { - let defaultProvider = GG.PipelineProvider.GitHubV3.toString(); + document.getElementById('settingsAddCIDI')!.addEventListener('click', () => { + let defaultProvider = GG.CIDIProvider.GitHubV3.toString(); let providerOptions = [ - // { name: 'Bitbucket', value: (GG.PipelineProvider.Bitbucket).toString() }, - { name: 'GitHubV3', value: (GG.PipelineProvider.GitHubV3).toString() }, - { name: 'GitLabV4', value: (GG.PipelineProvider.GitLabV4).toString() } + // { name: 'Bitbucket', value: (GG.CIDIProvider.Bitbucket).toString() }, + { name: 'GitHubV3', value: (GG.CIDIProvider.GitHubV3).toString() }, + { name: 'GitLabV4', value: (GG.CIDIProvider.GitLabV4).toString() } ]; - dialog.showForm('Add a new pipeline to this repository:', [ + dialog.showForm('Add a new cidi to this repository:', [ { type: DialogInputType.Select, name: 'Provider', options: providerOptions, default: defaultProvider, - info: 'In addition to the built-in publicly hosted Pipeline providers.' + info: 'In addition to the built-in publicly hosted CI/DI providers.' }, - { type: DialogInputType.Text, name: 'Git URL', default: '', placeholder: null, info: 'The Pipeline provider\'s Git URL (e.g. https://gitlab.com/OWNER/REPO.git).' }, + { type: DialogInputType.Text, name: 'Git URL', default: '', placeholder: null, info: 'The CI/DI provider\'s Git URL (e.g. https://gitlab.com/OWNER/REPO.git).' }, { type: DialogInputType.PasswordRef, name: 'Access Token', default: '', info: 'The GitLab personal access token or project access token.' } - ], 'Add Pipeline', (values) => { - let configs: GG.PipelineConfig[] = copyConfigs(); - let config: GG.PipelineConfig = updateConfigWithFormValues(values); + ], 'Add CI/DI', (values) => { + let configs: GG.CIDIConfig[] = copyConfigs(); + let config: GG.CIDIConfig = updateConfigWithFormValues(values); configs.push(config); - this.setPipelineConfig(configs); + this.setCIDIConfig(configs); }, { type: TargetType.Repo }); }); - addListenerToClass('editPipeline', 'click', (e) => { - const pipelineConfig = this.getPipelineForBtnEvent(e); - if (pipelineConfig === null) return; + addListenerToClass('editCIDI', 'click', (e) => { + const cidiConfig = this.getCIDIForBtnEvent(e); + if (cidiConfig === null) return; let providerOptions = [ - // { name: 'Bitbucket', value: (GG.PipelineProvider.Bitbucket).toString() }, - { name: 'GitHubV3', value: (GG.PipelineProvider.GitHubV3).toString() }, - { name: 'GitLabV4', value: (GG.PipelineProvider.GitLabV4).toString() } + // { name: 'Bitbucket', value: (GG.CIDIProvider.Bitbucket).toString() }, + { name: 'GitHub', value: (GG.CIDIProvider.GitHubV3).toString() }, + { name: 'GitLab V4(8.11-)', value: (GG.CIDIProvider.GitLabV4).toString() } ]; - dialog.showForm('Edit the Pipeline ' + escapeHtml(pipelineConfig.gitUrl || 'Not Set') + ':', [ + dialog.showForm('Edit the CI/DI ' + escapeHtml(cidiConfig.gitUrl || 'Not Set') + ':', [ { type: DialogInputType.Select, name: 'Provider', - options: providerOptions, default: pipelineConfig.provider.toString(), - info: 'In addition to the built-in publicly hosted Pipeline providers.' + options: providerOptions, default: cidiConfig.provider.toString(), + info: 'In addition to the built-in publicly hosted CI/DI providers.' }, - { type: DialogInputType.Text, name: 'Git URL', default: pipelineConfig.gitUrl || '', placeholder: null, info: 'The Pipeline provider\'s Git URL (e.g. https://gitlab.com/OWNER/REPO.git).' }, - { type: DialogInputType.PasswordRef, name: 'Personal Access Token', default: pipelineConfig.glToken, info: 'The GitLab personal access token.' } + { type: DialogInputType.Text, name: 'Git URL', default: cidiConfig.gitUrl || '', placeholder: null, info: 'The CI/DI provider\'s Git URL (e.g. https://gitlab.com/OWNER/REPO.git).' }, + { type: DialogInputType.PasswordRef, name: 'Personal Access Token', default: cidiConfig.glToken, info: 'The GitLab personal access token.' } ], 'Save Changes', (values) => { - let index = parseInt(((e.target).closest('.pipelineBtns')!).dataset.index!); - let configs: GG.PipelineConfig[] = copyConfigs(); - let config: GG.PipelineConfig = updateConfigWithFormValues(values); + let index = parseInt(((e.target).closest('.cidiBtns')!).dataset.index!); + let configs: GG.CIDIConfig[] = copyConfigs(); + let config: GG.CIDIConfig = updateConfigWithFormValues(values); configs[index] = config; - this.setPipelineConfig(configs); + this.setCIDIConfig(configs); }, { type: TargetType.Repo }); }); - addListenerToClass('deletePipeline', 'click', (e) => { - const pipelineConfig = this.getPipelineForBtnEvent(e); - if (pipelineConfig === null) return; - dialog.showConfirmation('Are you sure you want to delete the Pipeline ' + escapeHtml(pipelineConfig.gitUrl) + '?', 'Yes, delete', () => { - let index = parseInt(((e.target).closest('.pipelineBtns')!).dataset.index!); - let configs: GG.PipelineConfig[] = copyConfigs(); + addListenerToClass('deleteCIDI', 'click', (e) => { + const cidiConfig = this.getCIDIForBtnEvent(e); + if (cidiConfig === null) return; + dialog.showConfirmation('Are you sure you want to delete the CI/DI ' + escapeHtml(cidiConfig.gitUrl) + '?', 'Yes, delete', () => { + let index = parseInt(((e.target).closest('.cidiBtns')!).dataset.index!); + let configs: GG.CIDIConfig[] = copyConfigs(); configs.splice(index, 1); - this.setPipelineConfig(configs); + this.setCIDIConfig(configs); }, { type: TargetType.Repo }); }); @@ -674,9 +674,9 @@ class SettingsWidget { * Save the pull request configuration for this repository. * @param config The pull request configuration to save. */ - private setPipelineConfig(config: GG.PipelineConfig[] | null) { + private setCIDIConfig(config: GG.CIDIConfig[] | null) { if (this.currentRepo === null) return; - this.view.saveRepoStateValue(this.currentRepo, 'pipelineConfigs', config); + this.view.saveRepoStateValue(this.currentRepo, 'cidiConfigs', config); this.render(); } @@ -911,13 +911,13 @@ class SettingsWidget { } /** - * Get the pipeline details corresponding to a mouse event. + * Get the cidi details corresponding to a mouse event. * @param e The mouse event. - * @returns The details of the pipeline. + * @returns The details of the cidi. */ - private getPipelineForBtnEvent(e: Event) { - return this.repo !== null && this.repo.pipelineConfigs !== null - ? this.repo.pipelineConfigs[parseInt(((e.target).closest('.pipelineBtns')!).dataset.index!)] + private getCIDIForBtnEvent(e: Event) { + return this.repo !== null && this.repo.cidiConfigs !== null + ? this.repo.cidiConfigs[parseInt(((e.target).closest('.cidiBtns')!).dataset.index!)] : null; } diff --git a/web/styles/settingsWidget.css b/web/styles/settingsWidget.css index b894e47f..1cb65f49 100644 --- a/web/styles/settingsWidget.css +++ b/web/styles/settingsWidget.css @@ -140,17 +140,17 @@ vertical-align:top; cursor:pointer; } -.settingsSection > table td.btns.remoteBtns div, .settingsSection > table td.btns.pipelineBtns div{ +.settingsSection > table td.btns.remoteBtns div, .settingsSection > table td.btns.cidiBtns div{ vertical-align:middle; } -.settingsSection > table #editRepoName svg, .settingsSection > table #editInitialBranches svg, .settingsSection > table .editRemote svg, .settingsSection > table .editPipeline svg{ +.settingsSection > table #editRepoName svg, .settingsSection > table #editInitialBranches svg, .settingsSection > table .editRemote svg, .settingsSection > table .editCIDI svg{ position:absolute; left:1.5px; top:1.5px; width:14px !important; height:14px !important; } -.settingsSection > table #deleteRepoName svg, .settingsSection > table #clearInitialBranches svg, .settingsSection > table .deleteRemote svg, .settingsSection > table .fetchRemote svg, .settingsSection > table .pruneRemote svg, .settingsSection > table .deletePipeline svg{ +.settingsSection > table #deleteRepoName svg, .settingsSection > table #clearInitialBranches svg, .settingsSection > table .deleteRemote svg, .settingsSection > table .fetchRemote svg, .settingsSection > table .pruneRemote svg, .settingsSection > table .deleteCIDI svg{ position:absolute; left:0.5px; top:0.5px; From b3733680f20210e0dad05889c411a4bd07e57dee Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sun, 7 Mar 2021 16:37:25 +0900 Subject: [PATCH 04/54] #462 Updated frontend behavior of "CI/CD Status" --- web/dialog.ts | 12 ++++++------ web/settingsWidget.ts | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/web/dialog.ts b/web/dialog.ts index 8fe08538..525ebaee 100644 --- a/web/dialog.ts +++ b/web/dialog.ts @@ -11,7 +11,7 @@ const enum DialogType { const enum DialogInputType { Text, TextRef, - PasswordRef, + Password, Select, Radio, Checkbox @@ -32,8 +32,8 @@ interface DialogTextRefInput { readonly info?: string; } -interface DialogPasswordRefInput { - readonly type: DialogInputType.PasswordRef; +interface DialogPasswordInput { + readonly type: DialogInputType.Password; readonly name: string; readonly default: string; readonly info?: string; @@ -79,7 +79,7 @@ interface DialogRadioInputOption { readonly value: string; } -type DialogInput = DialogTextInput | DialogTextRefInput | DialogPasswordRefInput | DialogSelectInput | DialogRadioInput | DialogCheckboxInput; +type DialogInput = DialogTextInput | DialogTextRefInput | DialogPasswordInput | DialogSelectInput | DialogRadioInput | DialogCheckboxInput; type DialogInputValue = string | string[] | boolean; type DialogTarget = { @@ -220,7 +220,7 @@ class Dialog { inputHtml = '
' + (infoColRequired ? '' + infoHtml + '' : ''); } else if (input.type === DialogInputType.Checkbox) { inputHtml = ''; - } else if (input.type === DialogInputType.PasswordRef) { + } else if (input.type === DialogInputType.Password) { inputHtml = '' + (infoColRequired ? '' + infoHtml + '' : ''); } else { inputHtml = '' + (infoColRequired ? '' + infoHtml + '' : ''); @@ -275,7 +275,7 @@ class Dialog { }); // If the dialog contains a TextRef input, attach event listeners for validation - const textRefInput = inputs.findIndex((input) => input.type === DialogInputType.TextRef || input.type === DialogInputType.PasswordRef); + const textRefInput = inputs.findIndex((input) => input.type === DialogInputType.TextRef); if (textRefInput > -1) { let dialogInput = document.getElementById('dialogInput' + textRefInput), dialogAction = document.getElementById('dialogAction')!; if (dialogInput.value === '') this.elem!.classList.add(CLASS_DIALOG_NO_INPUT); diff --git a/web/settingsWidget.ts b/web/settingsWidget.ts index ba170e07..f37d22b2 100644 --- a/web/settingsWidget.ts +++ b/web/settingsWidget.ts @@ -246,7 +246,7 @@ class SettingsWidget { cidiConfigs.forEach((cidiConfig, i) => { let providerOptions:any = {}; providerOptions[(GG.CIDIProvider.GitHubV3).toString()] = 'GitHub'; - providerOptions[(GG.CIDIProvider.GitLabV4).toString()] = 'GitLab V4(8.11-)'; + providerOptions[(GG.CIDIProvider.GitLabV4).toString()] = 'GitLab APIv4(ver8.11-)'; const gitUrl = escapeHtml(cidiConfig.gitUrl || 'Not Set'); html += '' + '' + escapeHtml(providerOptions[cidiConfig.provider]) + '' + @@ -495,8 +495,8 @@ class SettingsWidget { let defaultProvider = GG.CIDIProvider.GitHubV3.toString(); let providerOptions = [ // { name: 'Bitbucket', value: (GG.CIDIProvider.Bitbucket).toString() }, - { name: 'GitHubV3', value: (GG.CIDIProvider.GitHubV3).toString() }, - { name: 'GitLabV4', value: (GG.CIDIProvider.GitLabV4).toString() } + { name: 'GitHub', value: (GG.CIDIProvider.GitHubV3).toString() }, + { name: 'GitLab APIv4(ver8.11-)', value: (GG.CIDIProvider.GitLabV4).toString() } ]; dialog.showForm('Add a new cidi to this repository:', [ { @@ -505,7 +505,7 @@ class SettingsWidget { info: 'In addition to the built-in publicly hosted CI/DI providers.' }, { type: DialogInputType.Text, name: 'Git URL', default: '', placeholder: null, info: 'The CI/DI provider\'s Git URL (e.g. https://gitlab.com/OWNER/REPO.git).' }, - { type: DialogInputType.PasswordRef, name: 'Access Token', default: '', info: 'The GitLab personal access token or project access token.' } + { type: DialogInputType.Password, name: 'Access Token', default: '', info: 'The GitHub/GitLab personal access token or project access token.' } ], 'Add CI/DI', (values) => { let configs: GG.CIDIConfig[] = copyConfigs(); let config: GG.CIDIConfig = updateConfigWithFormValues(values); @@ -520,7 +520,7 @@ class SettingsWidget { let providerOptions = [ // { name: 'Bitbucket', value: (GG.CIDIProvider.Bitbucket).toString() }, { name: 'GitHub', value: (GG.CIDIProvider.GitHubV3).toString() }, - { name: 'GitLab V4(8.11-)', value: (GG.CIDIProvider.GitLabV4).toString() } + { name: 'GitLab APIv4(ver8.11-)', value: (GG.CIDIProvider.GitLabV4).toString() } ]; dialog.showForm('Edit the CI/DI ' + escapeHtml(cidiConfig.gitUrl || 'Not Set') + ':', [ { @@ -529,7 +529,7 @@ class SettingsWidget { info: 'In addition to the built-in publicly hosted CI/DI providers.' }, { type: DialogInputType.Text, name: 'Git URL', default: cidiConfig.gitUrl || '', placeholder: null, info: 'The CI/DI provider\'s Git URL (e.g. https://gitlab.com/OWNER/REPO.git).' }, - { type: DialogInputType.PasswordRef, name: 'Personal Access Token', default: cidiConfig.glToken, info: 'The GitLab personal access token.' } + { type: DialogInputType.Password, name: 'Access Token', default: cidiConfig.glToken, info: 'The GitHub/GitLab personal access token or project access token.' } ], 'Save Changes', (values) => { let index = parseInt(((e.target).closest('.cidiBtns')!).dataset.index!); let configs: GG.CIDIConfig[] = copyConfigs(); From ce8d43ce62659395980527da6d96c13487105fb7 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sun, 7 Mar 2021 16:39:34 +0900 Subject: [PATCH 05/54] #462 Updated test code for CI/DI Status. --- tests/config.test.ts | 34 +++- tests/dataSource.test.ts | 375 ++++++++++++++++++++++++++--------- tests/extensionState.test.ts | 8 + tests/repoManager.test.ts | 4 + 4 files changed, 312 insertions(+), 109 deletions(-) diff --git a/tests/config.test.ts b/tests/config.test.ts index 92de8e20..d674915c 100644 --- a/tests/config.test.ts +++ b/tests/config.test.ts @@ -720,38 +720,50 @@ describe('Config', () => { describe('defaultColumnVisibility', () => { it('Should successfully parse the configuration value (Date column disabled)', () => { // Setup - vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: false, Author: true, Commit: true }); + vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: false, Author: true, Commit: true, CIDI: true }); // Run const value = config.defaultColumnVisibility; // Assert expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); - expect(value).toStrictEqual({ date: false, author: true, commit: true }); + expect(value).toStrictEqual({ date: false, author: true, commit: true, cidi: true }); }); it('Should successfully parse the configuration value (Author column disabled)', () => { // Setup - vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: true, Author: false, Commit: true }); + vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: true, Author: false, Commit: true, CIDI: true }); // Run const value = config.defaultColumnVisibility; // Assert expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); - expect(value).toStrictEqual({ date: true, author: false, commit: true }); + expect(value).toStrictEqual({ date: true, author: false, commit: true, cidi: true }); }); it('Should successfully parse the configuration value (Commit column disabled)', () => { // Setup - vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: true, Author: true, Commit: false }); + vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: true, Author: true, Commit: false, CIDI: true }); // Run const value = config.defaultColumnVisibility; // Assert expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); - expect(value).toStrictEqual({ date: true, author: true, commit: false }); + expect(value).toStrictEqual({ date: true, author: true, commit: false, cidi: true }); + }); + + it('Should successfully parse the configuration value (CI/DI Status column disabled)', () => { + // Setup + vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: true, Author: true, Commit: true, CIDI: false }); + + // Run + const value = config.defaultColumnVisibility; + + // Assert + expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); + expect(value).toStrictEqual({ date: true, author: true, commit: true, cidi: false }); }); it('Should return the default value when the configuration value is invalid (not an object)', () => { @@ -763,7 +775,7 @@ describe('Config', () => { // Assert expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); - expect(value).toStrictEqual({ date: true, author: true, commit: true }); + expect(value).toStrictEqual({ date: true, author: true, commit: true, cidi: true }); }); it('Should return the default value when the configuration value is invalid (NULL)', () => { @@ -775,19 +787,19 @@ describe('Config', () => { // Assert expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); - expect(value).toStrictEqual({ date: true, author: true, commit: true }); + expect(value).toStrictEqual({ date: true, author: true, commit: true, cidi: true }); }); it('Should return the default value when the configuration value is invalid (column value is not a boolean)', () => { // Setup - vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: true, Author: true, Commit: 5 }); + vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: true, Author: true, Commit: 5, CIDI: true }); // Run const value = config.defaultColumnVisibility; // Assert expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); - expect(value).toStrictEqual({ date: true, author: true, commit: true }); + expect(value).toStrictEqual({ date: true, author: true, commit: true, cidi: true }); }); it('Should return the default value when the configuration value is not set', () => { @@ -796,7 +808,7 @@ describe('Config', () => { // Assert expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); - expect(value).toStrictEqual({ date: true, author: true, commit: true }); + expect(value).toStrictEqual({ date: true, author: true, commit: true, cidi: true }); }); }); diff --git a/tests/dataSource.test.ts b/tests/dataSource.test.ts index 10b9e940..412cc7f9 100644 --- a/tests/dataSource.test.ts +++ b/tests/dataSource.test.ts @@ -498,7 +498,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], [], []); // Assert expect(result).toStrictEqual({ @@ -513,7 +513,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -529,7 +530,8 @@ describe('DataSource', () => { { name: 'origin/master', remote: 'origin' }, { name: 'other-remote/master', remote: null } ], - stash: null + stash: null, + cidi: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -541,7 +543,8 @@ describe('DataSource', () => { heads: ['develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -553,7 +556,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -594,7 +598,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', ['master', 'develop'], 300, true, true, false, false, CommitOrdering.AuthorDate, ['origin'], [], []); + const result = await dataSource.getCommits('/path/to/repo', ['master', 'develop'], 300, true, true, false, false, CommitOrdering.AuthorDate, ['origin'], [], [], []); // Assert expect(result).toStrictEqual({ @@ -609,7 +613,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -621,7 +626,8 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null + stash: null, + cidi: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -633,7 +639,8 @@ describe('DataSource', () => { heads: ['develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -645,7 +652,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -680,7 +688,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 2, true, true, false, false, CommitOrdering.Topological, ['origin'], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 2, true, true, false, false, CommitOrdering.Topological, ['origin'], [], [], []); // Assert expect(result).toStrictEqual({ @@ -695,7 +703,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -707,7 +716,8 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -719,7 +729,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -754,7 +765,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], [], []); // Assert expect(result).toStrictEqual({ @@ -769,7 +780,8 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null + stash: null, + cidi: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -781,7 +793,8 @@ describe('DataSource', () => { heads: ['develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -793,7 +806,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null } ], head: '4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e', @@ -828,7 +842,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], [], []); // Assert expect(result).toStrictEqual({ @@ -843,7 +857,8 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null + stash: null, + cidi: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -855,7 +870,8 @@ describe('DataSource', () => { heads: ['develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -867,7 +883,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -906,7 +923,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], [], []); // Assert expect(result).toStrictEqual({ @@ -921,7 +938,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -933,7 +951,8 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null + stash: null, + cidi: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -945,7 +964,8 @@ describe('DataSource', () => { heads: ['develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -957,7 +977,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -997,7 +1018,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, false, true, false, false, CommitOrdering.Date, ['origin'], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, false, true, false, false, CommitOrdering.Date, ['origin'], [], [], []); // Assert expect(result).toStrictEqual({ @@ -1012,7 +1033,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1024,7 +1046,8 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null + stash: null, + cidi: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1036,7 +1059,8 @@ describe('DataSource', () => { heads: ['develop'], tags: [], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1048,7 +1072,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1088,7 +1113,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], [], []); // Assert expect(result).toStrictEqual({ @@ -1103,7 +1128,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1115,7 +1141,8 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null + stash: null, + cidi: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1127,7 +1154,8 @@ describe('DataSource', () => { heads: ['develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1139,7 +1167,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1178,7 +1207,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, false, false, false, CommitOrdering.Date, ['origin'], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, false, false, false, CommitOrdering.Date, ['origin'], [], [], []); // Assert expect(result).toStrictEqual({ @@ -1193,7 +1222,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1205,7 +1235,8 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1217,7 +1248,8 @@ describe('DataSource', () => { heads: ['develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1229,7 +1261,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1270,7 +1303,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, true, false, CommitOrdering.Date, ['origin'], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, true, false, CommitOrdering.Date, ['origin'], [], [], []); // Assert expect(result).toStrictEqual({ @@ -1285,7 +1318,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1297,7 +1331,8 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null + stash: null, + cidi: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1309,7 +1344,8 @@ describe('DataSource', () => { heads: ['develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1321,7 +1357,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1362,7 +1399,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, true, CommitOrdering.Date, ['origin'], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, true, CommitOrdering.Date, ['origin'], [], [], []); // Assert expect(result).toStrictEqual({ @@ -1377,7 +1414,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1389,7 +1427,8 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null + stash: null, + cidi: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1401,7 +1440,8 @@ describe('DataSource', () => { heads: ['develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1413,7 +1453,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1457,7 +1498,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], [], []); // Assert expect(result).toStrictEqual({ @@ -1472,7 +1513,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1487,7 +1529,8 @@ describe('DataSource', () => { { name: 'origin/master', remote: 'origin' }, { name: 'other-remote/master', remote: null } ], - stash: null + stash: null, + cidi: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1499,7 +1542,8 @@ describe('DataSource', () => { heads: ['develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1511,7 +1555,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1553,7 +1598,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin', 'other-remote'], ['other-remote'], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin', 'other-remote'], ['other-remote'], [], []); // Assert expect(result).toStrictEqual({ @@ -1568,7 +1613,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1580,7 +1626,8 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null + stash: null, + cidi: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1592,7 +1639,8 @@ describe('DataSource', () => { heads: ['develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1604,7 +1652,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1655,7 +1704,7 @@ describe('DataSource', () => { date: 1587559258, message: 'WIP' } - ]); + ], []); // Assert expect(result).toStrictEqual({ @@ -1670,7 +1719,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1686,7 +1736,8 @@ describe('DataSource', () => { baseHash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', selector: 'refs/stash@{0}', untrackedFilesHash: '5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f' - } + }, + cidi: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1698,7 +1749,8 @@ describe('DataSource', () => { heads: ['master', 'develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null + stash: null, + cidi: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1710,7 +1762,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null } ], head: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1769,7 +1822,7 @@ describe('DataSource', () => { date: 1587559258, message: 'WIP 2' } - ]); + ], []); // Assert expect(result).toStrictEqual({ @@ -1784,7 +1837,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2', @@ -1800,7 +1854,8 @@ describe('DataSource', () => { baseHash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', selector: 'refs/stash@{0}', untrackedFilesHash: '5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f' - } + }, + cidi: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1812,7 +1867,8 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null + stash: null, + cidi: null }, { hash: 'b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3', @@ -1828,7 +1884,8 @@ describe('DataSource', () => { baseHash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', selector: 'refs/stash@{1}', untrackedFilesHash: '6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a' - } + }, + cidi: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1840,7 +1897,8 @@ describe('DataSource', () => { heads: ['develop'], tags: [], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1852,7 +1910,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1911,7 +1970,7 @@ describe('DataSource', () => { date: 1587559260, message: 'WIP 2' } - ]); + ], []); // Assert expect(result).toStrictEqual({ @@ -1926,7 +1985,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2', @@ -1942,7 +2002,8 @@ describe('DataSource', () => { baseHash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', selector: 'refs/stash@{0}', untrackedFilesHash: '5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f' - } + }, + cidi: null }, { hash: 'b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3', @@ -1958,7 +2019,8 @@ describe('DataSource', () => { baseHash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', selector: 'refs/stash@{1}', untrackedFilesHash: '6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a' - } + }, + cidi: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1970,7 +2032,8 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null + stash: null, + cidi: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1982,7 +2045,8 @@ describe('DataSource', () => { heads: ['develop'], tags: [], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1994,7 +2058,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -2043,7 +2108,7 @@ describe('DataSource', () => { date: 1587559258, message: 'WIP 1' } - ]); + ], []); // Assert expect(result).toStrictEqual({ @@ -2058,7 +2123,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -2070,7 +2136,8 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null + stash: null, + cidi: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -2082,7 +2149,8 @@ describe('DataSource', () => { heads: ['develop'], tags: [], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -2094,7 +2162,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -2131,7 +2200,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], [], []); // Assert expect(result).toStrictEqual({ @@ -2146,7 +2215,8 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null + stash: null, + cidi: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -2158,7 +2228,8 @@ describe('DataSource', () => { heads: ['develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -2170,7 +2241,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -2191,7 +2263,7 @@ describe('DataSource', () => { vscode.mockExtensionSettingReturnValue('repository.showRemoteHeads', true); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], [], []); // Assert expect(result).toStrictEqual({ @@ -2235,7 +2307,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], [], []); // Assert expect(result).toStrictEqual({ @@ -2250,7 +2322,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -2262,7 +2335,8 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }, { name: 'other-remote/master', remote: null }], - stash: null + stash: null, + cidi: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -2274,7 +2348,8 @@ describe('DataSource', () => { heads: ['develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [], - stash: null + stash: null, + cidi: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -2286,7 +2361,8 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null + stash: null, + cidi: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -2315,7 +2391,7 @@ describe('DataSource', () => { vscode.mockExtensionSettingReturnValue('repository.showRemoteHeads', true); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], [], []); // Assert expect(result).toStrictEqual({ @@ -2339,7 +2415,7 @@ describe('DataSource', () => { vscode.mockExtensionSettingReturnValue('repository.showRemoteHeads', true); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], [], []); // Assert expect(result).toStrictEqual({ @@ -2350,6 +2426,109 @@ describe('DataSource', () => { error: 'error message' }); }); + + it('Should return the commits (string)', async () => { + // Setup + mockGitSuccessOnce( + '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2bXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPb2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3cXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPbTest NameXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPbtest@mhutchie.comXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPb1587559258XX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPbCommit Message 3\n' + + '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3cXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPb3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4dXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPbTest NameXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPbtest@mhutchie.comXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPb1587559257XX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPbCommit Message 2\n' + + '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4dXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPbXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPbTest NameXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPbtest@mhutchie.comXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPb1587559256XX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPbCommit Message 1\n' + ); + mockGitSuccessOnce( + '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b HEAD\n' + + '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b refs/heads/master\n' + + '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c refs/heads/develop\n' + + '4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e refs/heads/feature\n' + + '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b refs/remotes/origin/HEAD\n' + + '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b refs/remotes/origin/master\n' + + '4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e refs/remotes/origin/feature\n' + + '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b refs/remotes/other-remote/master\n' + + 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2 refs/tags/tag1\n' + + '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c refs/tags/tag1^{}\n' + + '4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e refs/tags/tag2\n' + ); + mockGitSuccessOnce( + 'M modified.txt\n' + + '?? untracked.txt\n' + ); + vscode.mockExtensionSettingReturnValue('repository.showCommitsOnlyReferencedByTags', true); + vscode.mockExtensionSettingReturnValue('repository.showRemoteHeads', true); + vscode.mockExtensionSettingReturnValue('repository.showUncommittedChanges', true); + vscode.mockExtensionSettingReturnValue('repository.showUntrackedFiles', true); + date.setCurrentTime(1587559259); + + // Run + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], [], null); + + // Assert + expect(result).toStrictEqual({ + commits: [ + { + hash: '*', + parents: ['1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b'], + author: '*', + email: '', + date: 1587559259, + message: 'Uncommitted Changes (2)', + heads: [], + tags: [], + remotes: [], + stash: null, + cidi: null + }, + { + hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', + parents: ['2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c'], + author: 'Test Name', + email: 'test@mhutchie.com', + date: 1587559258, + message: 'Commit Message 3', + heads: ['master'], + tags: [], + remotes: [ + { name: 'origin/HEAD', remote: 'origin' }, + { name: 'origin/master', remote: 'origin' }, + { name: 'other-remote/master', remote: null } + ], + stash: null, + cidi: null + }, + { + hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', + parents: ['3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d'], + author: 'Test Name', + email: 'test@mhutchie.com', + date: 1587559257, + message: 'Commit Message 2', + heads: ['develop'], + tags: [{ name: 'tag1', annotated: true }], + remotes: [], + stash: null, + cidi: null + }, + { + hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', + parents: [], + author: 'Test Name', + email: 'test@mhutchie.com', + date: 1587559256, + message: 'Commit Message 1', + heads: [], + tags: [], + remotes: [], + stash: null, + cidi: null + } + ], + head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', + tags: ['tag1', 'tag2'], + moreCommitsAvailable: false, + error: null + }); + expect(spyOnSpawn).toBeCalledWith('/path/to/git', ['-c', 'log.showSignature=false', 'log', '--max-count=301', '--format=%HXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPb%PXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPb%anXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPb%aeXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPb%atXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPb%s', '--date-order', '--branches', '--tags', '--remotes', 'HEAD', '--'], expect.objectContaining({ cwd: '/path/to/repo' })); + expect(spyOnSpawn).toBeCalledWith('/path/to/git', ['show-ref', '-d', '--head'], expect.objectContaining({ cwd: '/path/to/repo' })); + expect(spyOnSpawn).toBeCalledWith('/path/to/git', ['status', '--untracked-files=all', '--porcelain'], expect.objectContaining({ cwd: '/path/to/repo' })); + }); }); describe('getConfig', () => { diff --git a/tests/extensionState.test.ts b/tests/extensionState.test.ts index 96ad0547..891b1b32 100644 --- a/tests/extensionState.test.ts +++ b/tests/extensionState.test.ts @@ -71,6 +71,7 @@ describe('ExtensionState', () => { onRepoLoadShowCheckedOutBranch: BooleanOverride.Enabled, onRepoLoadShowSpecificBranches: ['master'], pullRequestConfig: null, + cidiConfigs: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Enabled, showStashes: BooleanOverride.Enabled, @@ -118,6 +119,7 @@ describe('ExtensionState', () => { onRepoLoadShowCheckedOutBranch: BooleanOverride.Default, onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, + cidiConfigs: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, @@ -155,6 +157,7 @@ describe('ExtensionState', () => { onRepoLoadShowCheckedOutBranch: BooleanOverride.Default, onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, + cidiConfigs: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, @@ -192,6 +195,7 @@ describe('ExtensionState', () => { onRepoLoadShowCheckedOutBranch: BooleanOverride.Default, onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, + cidiConfigs: null, showRemoteBranches: false, showRemoteBranchesV2: BooleanOverride.Disabled, showStashes: BooleanOverride.Default, @@ -229,6 +233,7 @@ describe('ExtensionState', () => { onRepoLoadShowCheckedOutBranch: BooleanOverride.Default, onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, + cidiConfigs: null, showRemoteBranches: false, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, @@ -266,6 +271,7 @@ describe('ExtensionState', () => { onRepoLoadShowCheckedOutBranch: BooleanOverride.Default, onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, + cidiConfigs: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Enabled, showStashes: BooleanOverride.Default, @@ -306,6 +312,7 @@ describe('ExtensionState', () => { onRepoLoadShowCheckedOutBranch: BooleanOverride.Default, onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, + cidiConfigs: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, @@ -326,6 +333,7 @@ describe('ExtensionState', () => { onRepoLoadShowCheckedOutBranch: BooleanOverride.Default, onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, + cidiConfigs: null, showRemoteBranches: false, showRemoteBranchesV2: BooleanOverride.Disabled, showStashes: BooleanOverride.Default, diff --git a/tests/repoManager.test.ts b/tests/repoManager.test.ts index 32f8a2cd..8a0d3532 100644 --- a/tests/repoManager.test.ts +++ b/tests/repoManager.test.ts @@ -923,6 +923,7 @@ describe('RepoManager', () => { onRepoLoadShowCheckedOutBranch: BooleanOverride.Default, onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, + cidiConfigs: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, @@ -1680,6 +1681,7 @@ describe('RepoManager', () => { onRepoLoadShowCheckedOutBranch: BooleanOverride.Default, onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, + cidiConfigs: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, @@ -2031,6 +2033,7 @@ describe('RepoManager', () => { onRepoLoadShowCheckedOutBranch: BooleanOverride.Default, onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, + cidiConfigs: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, @@ -2106,6 +2109,7 @@ describe('RepoManager', () => { onRepoLoadShowCheckedOutBranch: BooleanOverride.Default, onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, + cidiConfigs: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, From 687c8e87541f8a1fae1ffadf9e4290d7c3b97217 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sun, 7 Mar 2021 21:09:56 +0900 Subject: [PATCH 06/54] #462 Updated library in package.json --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index df527744..96d6556e 100644 --- a/package.json +++ b/package.json @@ -1457,11 +1457,13 @@ }, "dependencies": { "iconv-lite": "0.5.0", - "request-promise": "^4.2.5" + "request": "2.88.2", + "request-promise": "4.2.6" }, "devDependencies": { "@types/jest": "26.0.19", "@types/node": "8.10.62", + "@types/request-promise": "4.1.46", "@types/vscode": "1.38.0", "@typescript-eslint/eslint-plugin": "4.10.0", "@typescript-eslint/parser": "4.10.0", @@ -1469,7 +1471,6 @@ "jest": "26.6.3", "ts-jest": "26.4.4", "typescript": "4.0.2", - "@types/request-promise": "^4.1.46", "uglify-js": "3.10.0" } } From 9c78b778b50d2d7368ad34beb2ba84f3c3271144 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sun, 7 Mar 2021 21:10:28 +0900 Subject: [PATCH 07/54] #462 Updated GitLab Authorization of CI/DI Status --- src/dataSource.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/dataSource.ts b/src/dataSource.ts index 6e7e9838..0f401c7d 100644 --- a/src/dataSource.ts +++ b/src/dataSource.ts @@ -1535,7 +1535,7 @@ export class DataSource extends Disposable { const apiRoot = `${hostRootUrl}`; const cidiRootPath = `/repos/${sourceOwner}/${sourceRepo.replace(/\//g, '%2F')}/actions/runs?per_page=100`; - const config: request.RequestPromiseOptions = { + let config: request.RequestPromiseOptions = { method: 'GET', headers: { 'Authorization': `token ${cidiConfig.glToken}`, @@ -1543,6 +1543,9 @@ export class DataSource extends Disposable { 'User-Agent': 'vscode-git-graph' } }; + if (cidiConfig.glToken === '' && config.headers) { + delete config.headers['Authorization']; + } config.transform = (body, response) => { try { From 64ec2cd7c7ded131ea942952c9cd49cfa0f3211b Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Mon, 8 Mar 2021 09:28:38 +0900 Subject: [PATCH 08/54] #462 Fiexd typo CI/DI to CI/CD --- package.json | 6 +- src/config.ts | 6 +- src/dataSource.ts | 74 ++++++++-------- src/extensionState.ts | 2 +- src/gitGraphView.ts | 2 +- src/types.ts | 24 ++--- tests/config.test.ts | 28 +++--- tests/dataSource.test.ts | 160 +++++++++++++++++----------------- tests/extensionState.test.ts | 16 ++-- tests/repoManager.test.ts | 8 +- web/main.ts | 18 ++-- web/settingsWidget.ts | 114 ++++++++++++------------ web/styles/settingsWidget.css | 6 +- 13 files changed, 232 insertions(+), 232 deletions(-) diff --git a/package.json b/package.json index 96d6556e..bd2dac0c 100644 --- a/package.json +++ b/package.json @@ -478,16 +478,16 @@ "type": "boolean", "title": "Visibility of the Commit column" }, - "CIDI": { + "CICD": { "type": "boolean", - "title": "Visibility of the CI/DI Status column" + "title": "Visibility of the CI/CD Status column" } }, "default": { "Date": true, "Author": true, "Commit": true, - "CIDI": true + "CICD": true }, "description": "An object specifying the default visibility of the Date, Author & Commit columns. Example: {\"Date\": true, \"Author\": true, \"Commit\": true}" }, diff --git a/src/config.ts b/src/config.ts index f63855f2..e44b5a04 100644 --- a/src/config.ts +++ b/src/config.ts @@ -160,10 +160,10 @@ class Config { */ get defaultColumnVisibility(): DefaultColumnVisibility { let obj: any = this.config.get('defaultColumnVisibility', {}); - if (typeof obj === 'object' && obj !== null && typeof obj['Date'] === 'boolean' && typeof obj['Author'] === 'boolean' && typeof obj['Commit'] === 'boolean' && typeof obj['CIDI'] === 'boolean') { - return { author: obj['Author'], commit: obj['Commit'], date: obj['Date'], cidi: obj['CIDI']}; + if (typeof obj === 'object' && obj !== null && typeof obj['Date'] === 'boolean' && typeof obj['Author'] === 'boolean' && typeof obj['Commit'] === 'boolean' && typeof obj['CICD'] === 'boolean') { + return { author: obj['Author'], commit: obj['Commit'], date: obj['Date'], cicd: obj['CICD']}; } else { - return { author: true, commit: true, date: true, cidi: true }; + return { author: true, commit: true, date: true, cicd: true }; } } diff --git a/src/dataSource.ts b/src/dataSource.ts index 0f401c7d..c1122a17 100644 --- a/src/dataSource.ts +++ b/src/dataSource.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode'; import { AskpassEnvironment, AskpassManager } from './askpass/askpassManager'; import { getConfig } from './config'; import { Logger } from './logger'; -import { CIDIConfig, CIDIProvider, CommitOrdering, DateType, DeepWriteable, ErrorInfo, GitCIDIData, GitCommit, GitCommitDetails, GitCommitStash, GitConfigLocation, GitFileChange, GitFileStatus, GitPushBranchMode, GitRepoConfig, GitRepoConfigBranches, GitResetMode, GitSignatureStatus, GitStash, MergeActionOn, RebaseActionOn, SquashMessageFormat, TagType, Writeable } from './types'; +import { CICDConfig, CICDProvider, CommitOrdering, DateType, DeepWriteable, ErrorInfo, GitCICDData, GitCommit, GitCommitDetails, GitCommitStash, GitConfigLocation, GitFileChange, GitFileStatus, GitPushBranchMode, GitRepoConfig, GitRepoConfigBranches, GitResetMode, GitSignatureStatus, GitStash, MergeActionOn, RebaseActionOn, SquashMessageFormat, TagType, Writeable } from './types'; import { GitExecutable, UNABLE_TO_FIND_GIT_MSG, UNCOMMITTED, abbrevCommit, constructIncompatibleGitVersionMessage, getPathFromStr, getPathFromUri, isGitAtLeastVersion, openGitTerminal, pathWithTrailingSlash, realpath, resolveSpawnOutput, showErrorMessage } from './utils'; import { Disposable } from './utils/disposable'; import { Event } from './utils/event'; @@ -159,15 +159,15 @@ export class DataSource extends Disposable { * @param stashes An array of all stashes in the repository. * @returns The commits in the repository. */ - public getCommits(repo: string, branches: ReadonlyArray | null, maxCommits: number, showTags: boolean, showRemoteBranches: boolean, includeCommitsMentionedByReflogs: boolean, onlyFollowFirstParent: boolean, commitOrdering: CommitOrdering, remotes: ReadonlyArray, hideRemotes: ReadonlyArray, stashes: ReadonlyArray, cidiConfigs: CIDIConfig[] | null): Promise { + public getCommits(repo: string, branches: ReadonlyArray | null, maxCommits: number, showTags: boolean, showRemoteBranches: boolean, includeCommitsMentionedByReflogs: boolean, onlyFollowFirstParent: boolean, commitOrdering: CommitOrdering, remotes: ReadonlyArray, hideRemotes: ReadonlyArray, stashes: ReadonlyArray, cicdConfigs: CICDConfig[] | null): Promise { const config = getConfig(); return Promise.all([ this.getLog(repo, branches, maxCommits + 1, showTags && config.showCommitsOnlyReferencedByTags, showRemoteBranches, includeCommitsMentionedByReflogs, onlyFollowFirstParent, commitOrdering, remotes, hideRemotes, stashes), this.getRefs(repo, showRemoteBranches, config.showRemoteHeads, hideRemotes).then((refData: GitRefData) => refData, (errorMessage: string) => errorMessage), - this.getCIDIs(cidiConfigs).then((refData: GitCIDIData[] | string | undefined) => refData, (errorMessage: string) => errorMessage) + this.getCICDs(cicdConfigs).then((refData: GitCICDData[] | string | undefined) => refData, (errorMessage: string) => errorMessage) ]).then(async (results) => { let commits: GitCommitRecord[] = results[0], refData: GitRefData | string = results[1], i; - let cidis: GitCIDIData[] | string | undefined = results[2]; + let cicds: GitCICDData[] | string | undefined = results[2]; let moreCommitsAvailable = commits.length === maxCommits + 1; if (moreCommitsAvailable) commits.pop(); @@ -200,7 +200,7 @@ export class DataSource extends Disposable { for (i = 0; i < commits.length; i++) { commitLookup[commits[i].hash] = i; - commitNodes.push({ ...commits[i], heads: [], tags: [], remotes: [], stash: null, cidi: null }); + commitNodes.push({ ...commits[i], heads: [], tags: [], remotes: [], stash: null, cicd: null }); } /* Insert Stashes */ @@ -220,7 +220,7 @@ export class DataSource extends Disposable { for (i = toAdd.length - 1; i >= 0; i--) { let stash = toAdd[i].data; commitNodes.splice(toAdd[i].index, 0, { - cidi: null, + cicd: null, hash: stash.hash, parents: [stash.baseHash], author: stash.author, @@ -261,13 +261,13 @@ export class DataSource extends Disposable { } } - if (typeof cidis === 'string' || typeof cidis === 'undefined') { - cidis = []; + if (typeof cicds === 'string' || typeof cicds === 'undefined') { + cicds = []; } - /* Annotate CI/DIs */ - for (i = 0; i < cidis.length; i++) { - if (typeof commitLookup[cidis[i].sha] === 'number') { - commitNodes[commitLookup[cidis[i].sha]].cidi = cidis[i]; + /* Annotate CI/CDs */ + for (i = 0; i < cicds.length; i++) { + if (typeof commitLookup[cicds[i].sha] === 'number') { + commitNodes[commitLookup[cicds[i].sha]].cicd = cicds[i]; } } @@ -1512,38 +1512,38 @@ export class DataSource extends Disposable { } /** - * Get the result in a CI/DIs. - * @param cidiConfigs CI/DI configuration. + * Get the result in a CI/CDs. + * @param cicdConfigs CI/CD configuration. * @returns The references data. */ - private async getCIDIs(cidiConfigs: CIDIConfig[] | null) { - if (cidiConfigs === null) { + private async getCICDs(cicdConfigs: CICDConfig[] | null) { + if (cicdConfigs === null) { return ''; } return await Promise.all( - cidiConfigs.map(async cidiConfig => { - if (cidiConfig.provider === CIDIProvider.GitHubV3) { + cicdConfigs.map(async cicdConfig => { + if (cicdConfig.provider === CICDProvider.GitHubV3) { - const match1 = cidiConfig.gitUrl.match(/^(https?:\/\/|git@)((?=[^/]+@)[^@]+@|(?![^/]+@))([^/:]+)/); + const match1 = cicdConfig.gitUrl.match(/^(https?:\/\/|git@)((?=[^/]+@)[^@]+@|(?![^/]+@))([^/:]+)/); let hostRootUrl = match1 !== null ? 'https://api.' + match1[3] : ''; - const match2 = cidiConfig.gitUrl.match(/^(https?:\/\/|git@)[^/:]+[/:]([^/]+)\/([^/]*?)(.git|)$/); + const match2 = cicdConfig.gitUrl.match(/^(https?:\/\/|git@)[^/:]+[/:]([^/]+)\/([^/]*?)(.git|)$/); let sourceOwner = match2 !== null ? match2[2] : ''; let sourceRepo = match2 !== null ? match2[3] : ''; const apiRoot = `${hostRootUrl}`; - const cidiRootPath = `/repos/${sourceOwner}/${sourceRepo.replace(/\//g, '%2F')}/actions/runs?per_page=100`; + const cicdRootPath = `/repos/${sourceOwner}/${sourceRepo.replace(/\//g, '%2F')}/actions/runs?per_page=100`; let config: request.RequestPromiseOptions = { method: 'GET', headers: { - 'Authorization': `token ${cidiConfig.glToken}`, + 'Authorization': `token ${cicdConfig.glToken}`, 'Accept': 'application/vnd.github.v3+json', 'User-Agent': 'vscode-git-graph' } }; - if (cidiConfig.glToken === '' && config.headers) { + if (cicdConfig.glToken === '' && config.headers) { delete config.headers['Authorization']; } @@ -1588,7 +1588,7 @@ export class DataSource extends Disposable { }); } if (typeof res['workflow_runs'] !== 'undefined' && res['workflow_runs'].length >= 1) { // url found - let ret: GitCIDIData[] = res['workflow_runs'].map( (elm: { [x: string]: any; }) => { + let ret: GitCICDData[] = res['workflow_runs'].map( (elm: { [x: string]: any; }) => { return { id: elm['id'], status: elm['conclusion'], @@ -1609,37 +1609,37 @@ export class DataSource extends Disposable { return { x_total_pages: 0, ret: e }; } }; - return request(`${apiRoot}${cidiRootPath}`, config).then(async (result1st) => { + return request(`${apiRoot}${cicdRootPath}`, config).then(async (result1st) => { let promises = []; promises.push(result1st.ret); for (let i = 1; i < result1st.x_total_pages; i++) { - promises.push(request(`${apiRoot}${cidiRootPath}&page=${i + 1}`, config)); + promises.push(request(`${apiRoot}${cicdRootPath}&page=${i + 1}`, config)); } return await Promise.all(promises); }).then((resultAll) => { - let retAll: GitCIDIData[] = []; + let retAll: GitCICDData[] = []; for (let i = 0; i < resultAll.length; i++) { retAll = retAll.concat(resultAll[i]); } return retAll; }); } - if (cidiConfig.provider === CIDIProvider.GitLabV4) { + if (cicdConfig.provider === CICDProvider.GitLabV4) { - const match1 = cidiConfig.gitUrl.match(/^(https?:\/\/|git@)((?=[^/]+@)[^@]+@|(?![^/]+@))([^/:]+)/); + const match1 = cicdConfig.gitUrl.match(/^(https?:\/\/|git@)((?=[^/]+@)[^@]+@|(?![^/]+@))([^/:]+)/); let hostRootUrl = match1 !== null ? 'https://' + match1[3] : ''; - const match2 = cidiConfig.gitUrl.match(/^(https?:\/\/|git@)[^/:]+[/:]([^/]+)\/([^/]*?)(.git|)$/); + const match2 = cicdConfig.gitUrl.match(/^(https?:\/\/|git@)[^/:]+[/:]([^/]+)\/([^/]*?)(.git|)$/); let sourceOwner = match2 !== null ? match2[2] : ''; let sourceRepo = match2 !== null ? match2[3] : ''; const apiRoot = `${hostRootUrl}/api/v4`; - const cidiRootPath = `/projects/${sourceOwner}%2F${sourceRepo.replace(/\//g, '%2F')}/pipelines?per_page=100`; + const cicdRootPath = `/projects/${sourceOwner}%2F${sourceRepo.replace(/\//g, '%2F')}/pipelines?per_page=100`; const config: request.RequestPromiseOptions = { method: 'GET', headers: { - 'PRIVATE-TOKEN': cidiConfig.glToken, + 'PRIVATE-TOKEN': cicdConfig.glToken, 'User-Agent': 'vscode-git-graph' } }; @@ -1649,7 +1649,7 @@ export class DataSource extends Disposable { if (typeof response.headers['x-page'] === 'string' && typeof response.headers['x-total-pages'] === 'string' && typeof response.headers['x-total'] === 'string') { let res: any = JSON.parse(body); if (parseInt(response.headers['x-total']) !== 0 && res.length && res[0].id) { // url found - let ret: GitCIDIData[] = res; + let ret: GitCICDData[] = res; if (parseInt(response.headers['x-page']) === 1) { return { x_total_pages: parseInt(response.headers['x-total-pages']), ret: ret }; } @@ -1661,15 +1661,15 @@ export class DataSource extends Disposable { return { x_total_pages: 0, ret: e }; } }; - return request(`${apiRoot}${cidiRootPath}`, config).then(async (result1st) => { + return request(`${apiRoot}${cicdRootPath}`, config).then(async (result1st) => { let promises = []; promises.push(result1st.ret); for (let i = 1; i < result1st.x_total_pages; i++) { - promises.push(request(`${apiRoot}${cidiRootPath}&page=${i + 1}`, config)); + promises.push(request(`${apiRoot}${cicdRootPath}&page=${i + 1}`, config)); } return await Promise.all(promises); }).then((resultAll) => { - let retAll: GitCIDIData[] = []; + let retAll: GitCICDData[] = []; for (let i = 0; i < resultAll.length; i++) { retAll = retAll.concat(resultAll[i]); } @@ -1678,7 +1678,7 @@ export class DataSource extends Disposable { } }) ).then((resultAll2) => { - let retAll: GitCIDIData[] = []; + let retAll: GitCICDData[] = []; resultAll2.forEach(resultList => { resultList?.forEach(result => { retAll = retAll.concat(result); diff --git a/src/extensionState.ts b/src/extensionState.ts index 3a394467..f156adee 100644 --- a/src/extensionState.ts +++ b/src/extensionState.ts @@ -32,7 +32,7 @@ export const DEFAULT_REPO_STATE: GitRepoState = { onRepoLoadShowCheckedOutBranch: BooleanOverride.Default, onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, - cidiConfigs: null, + cicdConfigs: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, diff --git a/src/gitGraphView.ts b/src/gitGraphView.ts index 7c6a2f98..ffc59bc0 100644 --- a/src/gitGraphView.ts +++ b/src/gitGraphView.ts @@ -404,7 +404,7 @@ export class GitGraphView extends Disposable { command: 'loadCommits', refreshId: msg.refreshId, onlyFollowFirstParent: msg.onlyFollowFirstParent, - ...await this.dataSource.getCommits(msg.repo, msg.branches, msg.maxCommits, msg.showTags, msg.showRemoteBranches, msg.includeCommitsMentionedByReflogs, msg.onlyFollowFirstParent, msg.commitOrdering, msg.remotes, msg.hideRemotes, msg.stashes, msg.cidiConfigs) + ...await this.dataSource.getCommits(msg.repo, msg.branches, msg.maxCommits, msg.showTags, msg.showRemoteBranches, msg.includeCommitsMentionedByReflogs, msg.onlyFollowFirstParent, msg.commitOrdering, msg.remotes, msg.hideRemotes, msg.stashes, msg.cicdConfigs) }); break; case 'loadConfig': diff --git a/src/types.ts b/src/types.ts index 3311e7ba..854c0712 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,10 +11,10 @@ export interface GitCommit { readonly tags: ReadonlyArray; readonly remotes: ReadonlyArray; readonly stash: GitCommitStash | null; // null => not a stash, otherwise => stash info - readonly cidi: GitCIDIData | null; + readonly cicd: GitCICDData | null; } -export interface GitCIDIData { +export interface GitCICDData { id: string; status: string; ref: string; @@ -201,12 +201,12 @@ export type PullRequestConfig = PullRequestConfigBuiltIn | PullRequestConfigCust -export interface CIDIConfigBase { +export interface CICDConfigBase { readonly gitUrl: string; readonly glToken: string; } -export const enum CIDIProvider { +export const enum CICDProvider { Bitbucket, Custom, GitHubV3, @@ -214,20 +214,20 @@ export const enum CIDIProvider { Jenkins } -interface CIDIConfigBuiltIn extends CIDIConfigBase { - readonly provider: CIDIProvider; +interface CICDConfigBuiltIn extends CICDConfigBase { + readonly provider: CICDProvider; readonly custom: null; } -interface CIDIConfigCustom extends CIDIConfigBase { - readonly provider: CIDIProvider.Custom; +interface CICDConfigCustom extends CICDConfigBase { + readonly provider: CICDProvider.Custom; readonly custom: { readonly name: string, readonly templateUrl: string }; } -export type CIDIConfig = CIDIConfigBuiltIn | CIDIConfigCustom; +export type CICDConfig = CICDConfigBuiltIn | CICDConfigCustom; export interface GitRepoState { cdvDivider: number; @@ -244,7 +244,7 @@ export interface GitRepoState { onRepoLoadShowCheckedOutBranch: BooleanOverride; onRepoLoadShowSpecificBranches: string[] | null; pullRequestConfig: PullRequestConfig | null; - cidiConfigs: CIDIConfig[] | null; + cicdConfigs: CICDConfig[] | null; showRemoteBranches: boolean; showRemoteBranchesV2: BooleanOverride; showStashes: BooleanOverride; @@ -469,7 +469,7 @@ export interface DefaultColumnVisibility { readonly date: boolean; readonly author: boolean; readonly commit: boolean; - readonly cidi: boolean; + readonly cicd: boolean; } export interface DialogDefaults { @@ -927,7 +927,7 @@ export interface RequestLoadCommits extends RepoRequest { readonly remotes: ReadonlyArray; readonly hideRemotes: ReadonlyArray; readonly stashes: ReadonlyArray; - readonly cidiConfigs: CIDIConfig[] | null; + readonly cicdConfigs: CICDConfig[] | null; } export interface ResponseLoadCommits extends ResponseWithErrorInfo { readonly command: 'loadCommits'; diff --git a/tests/config.test.ts b/tests/config.test.ts index d674915c..6b4002d6 100644 --- a/tests/config.test.ts +++ b/tests/config.test.ts @@ -720,50 +720,50 @@ describe('Config', () => { describe('defaultColumnVisibility', () => { it('Should successfully parse the configuration value (Date column disabled)', () => { // Setup - vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: false, Author: true, Commit: true, CIDI: true }); + vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: false, Author: true, Commit: true, CICD: true }); // Run const value = config.defaultColumnVisibility; // Assert expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); - expect(value).toStrictEqual({ date: false, author: true, commit: true, cidi: true }); + expect(value).toStrictEqual({ date: false, author: true, commit: true, cicd: true }); }); it('Should successfully parse the configuration value (Author column disabled)', () => { // Setup - vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: true, Author: false, Commit: true, CIDI: true }); + vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: true, Author: false, Commit: true, CICD: true }); // Run const value = config.defaultColumnVisibility; // Assert expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); - expect(value).toStrictEqual({ date: true, author: false, commit: true, cidi: true }); + expect(value).toStrictEqual({ date: true, author: false, commit: true, cicd: true }); }); it('Should successfully parse the configuration value (Commit column disabled)', () => { // Setup - vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: true, Author: true, Commit: false, CIDI: true }); + vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: true, Author: true, Commit: false, CICD: true }); // Run const value = config.defaultColumnVisibility; // Assert expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); - expect(value).toStrictEqual({ date: true, author: true, commit: false, cidi: true }); + expect(value).toStrictEqual({ date: true, author: true, commit: false, cicd: true }); }); - it('Should successfully parse the configuration value (CI/DI Status column disabled)', () => { + it('Should successfully parse the configuration value (CI/CD Status column disabled)', () => { // Setup - vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: true, Author: true, Commit: true, CIDI: false }); + vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: true, Author: true, Commit: true, CICD: false }); // Run const value = config.defaultColumnVisibility; // Assert expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); - expect(value).toStrictEqual({ date: true, author: true, commit: true, cidi: false }); + expect(value).toStrictEqual({ date: true, author: true, commit: true, cicd: false }); }); it('Should return the default value when the configuration value is invalid (not an object)', () => { @@ -775,7 +775,7 @@ describe('Config', () => { // Assert expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); - expect(value).toStrictEqual({ date: true, author: true, commit: true, cidi: true }); + expect(value).toStrictEqual({ date: true, author: true, commit: true, cicd: true }); }); it('Should return the default value when the configuration value is invalid (NULL)', () => { @@ -787,19 +787,19 @@ describe('Config', () => { // Assert expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); - expect(value).toStrictEqual({ date: true, author: true, commit: true, cidi: true }); + expect(value).toStrictEqual({ date: true, author: true, commit: true, cicd: true }); }); it('Should return the default value when the configuration value is invalid (column value is not a boolean)', () => { // Setup - vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: true, Author: true, Commit: 5, CIDI: true }); + vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: true, Author: true, Commit: 5, CICD: true }); // Run const value = config.defaultColumnVisibility; // Assert expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); - expect(value).toStrictEqual({ date: true, author: true, commit: true, cidi: true }); + expect(value).toStrictEqual({ date: true, author: true, commit: true, cicd: true }); }); it('Should return the default value when the configuration value is not set', () => { @@ -808,7 +808,7 @@ describe('Config', () => { // Assert expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); - expect(value).toStrictEqual({ date: true, author: true, commit: true, cidi: true }); + expect(value).toStrictEqual({ date: true, author: true, commit: true, cicd: true }); }); }); diff --git a/tests/dataSource.test.ts b/tests/dataSource.test.ts index 412cc7f9..378fb759 100644 --- a/tests/dataSource.test.ts +++ b/tests/dataSource.test.ts @@ -514,7 +514,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -531,7 +531,7 @@ describe('DataSource', () => { { name: 'other-remote/master', remote: null } ], stash: null, - cidi: null + cicd: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -544,7 +544,7 @@ describe('DataSource', () => { tags: [{ name: 'tag1', annotated: true }], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -557,7 +557,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -614,7 +614,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -627,7 +627,7 @@ describe('DataSource', () => { tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], stash: null, - cidi: null + cicd: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -640,7 +640,7 @@ describe('DataSource', () => { tags: [{ name: 'tag1', annotated: true }], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -653,7 +653,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -704,7 +704,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -717,7 +717,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -730,7 +730,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -781,7 +781,7 @@ describe('DataSource', () => { tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], stash: null, - cidi: null + cicd: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -794,7 +794,7 @@ describe('DataSource', () => { tags: [{ name: 'tag1', annotated: true }], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -807,7 +807,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null } ], head: '4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e', @@ -858,7 +858,7 @@ describe('DataSource', () => { tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], stash: null, - cidi: null + cicd: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -871,7 +871,7 @@ describe('DataSource', () => { tags: [{ name: 'tag1', annotated: true }], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -884,7 +884,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -939,7 +939,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -952,7 +952,7 @@ describe('DataSource', () => { tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], stash: null, - cidi: null + cicd: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -965,7 +965,7 @@ describe('DataSource', () => { tags: [{ name: 'tag1', annotated: true }], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -978,7 +978,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1034,7 +1034,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1047,7 +1047,7 @@ describe('DataSource', () => { tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], stash: null, - cidi: null + cicd: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1060,7 +1060,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1073,7 +1073,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1129,7 +1129,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1142,7 +1142,7 @@ describe('DataSource', () => { tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], stash: null, - cidi: null + cicd: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1155,7 +1155,7 @@ describe('DataSource', () => { tags: [{ name: 'tag1', annotated: true }], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1168,7 +1168,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1223,7 +1223,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1236,7 +1236,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1249,7 +1249,7 @@ describe('DataSource', () => { tags: [{ name: 'tag1', annotated: true }], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1262,7 +1262,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1319,7 +1319,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1332,7 +1332,7 @@ describe('DataSource', () => { tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], stash: null, - cidi: null + cicd: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1345,7 +1345,7 @@ describe('DataSource', () => { tags: [{ name: 'tag1', annotated: true }], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1358,7 +1358,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1415,7 +1415,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1428,7 +1428,7 @@ describe('DataSource', () => { tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], stash: null, - cidi: null + cicd: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1441,7 +1441,7 @@ describe('DataSource', () => { tags: [{ name: 'tag1', annotated: true }], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1454,7 +1454,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1514,7 +1514,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1530,7 +1530,7 @@ describe('DataSource', () => { { name: 'other-remote/master', remote: null } ], stash: null, - cidi: null + cicd: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1543,7 +1543,7 @@ describe('DataSource', () => { tags: [{ name: 'tag1', annotated: true }], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1556,7 +1556,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1614,7 +1614,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1627,7 +1627,7 @@ describe('DataSource', () => { tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], stash: null, - cidi: null + cicd: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1640,7 +1640,7 @@ describe('DataSource', () => { tags: [{ name: 'tag1', annotated: true }], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1653,7 +1653,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1720,7 +1720,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1737,7 +1737,7 @@ describe('DataSource', () => { selector: 'refs/stash@{0}', untrackedFilesHash: '5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f' }, - cidi: null + cicd: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1750,7 +1750,7 @@ describe('DataSource', () => { tags: [{ name: 'tag1', annotated: true }], remotes: [{ name: 'origin/master', remote: 'origin' }], stash: null, - cidi: null + cicd: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1763,7 +1763,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null } ], head: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1838,7 +1838,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2', @@ -1855,7 +1855,7 @@ describe('DataSource', () => { selector: 'refs/stash@{0}', untrackedFilesHash: '5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f' }, - cidi: null + cicd: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1868,7 +1868,7 @@ describe('DataSource', () => { tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], stash: null, - cidi: null + cicd: null }, { hash: 'b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3', @@ -1885,7 +1885,7 @@ describe('DataSource', () => { selector: 'refs/stash@{1}', untrackedFilesHash: '6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a' }, - cidi: null + cicd: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1898,7 +1898,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1911,7 +1911,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1986,7 +1986,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2', @@ -2003,7 +2003,7 @@ describe('DataSource', () => { selector: 'refs/stash@{0}', untrackedFilesHash: '5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f' }, - cidi: null + cicd: null }, { hash: 'b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3', @@ -2020,7 +2020,7 @@ describe('DataSource', () => { selector: 'refs/stash@{1}', untrackedFilesHash: '6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a' }, - cidi: null + cicd: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -2033,7 +2033,7 @@ describe('DataSource', () => { tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], stash: null, - cidi: null + cicd: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -2046,7 +2046,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -2059,7 +2059,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -2124,7 +2124,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -2137,7 +2137,7 @@ describe('DataSource', () => { tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], stash: null, - cidi: null + cicd: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -2150,7 +2150,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -2163,7 +2163,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -2216,7 +2216,7 @@ describe('DataSource', () => { tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], stash: null, - cidi: null + cicd: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -2229,7 +2229,7 @@ describe('DataSource', () => { tags: [{ name: 'tag1', annotated: true }], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -2242,7 +2242,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -2323,7 +2323,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -2336,7 +2336,7 @@ describe('DataSource', () => { tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }, { name: 'other-remote/master', remote: null }], stash: null, - cidi: null + cicd: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -2349,7 +2349,7 @@ describe('DataSource', () => { tags: [{ name: 'tag1', annotated: true }], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -2362,7 +2362,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -2474,7 +2474,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -2491,7 +2491,7 @@ describe('DataSource', () => { { name: 'other-remote/master', remote: null } ], stash: null, - cidi: null + cicd: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -2504,7 +2504,7 @@ describe('DataSource', () => { tags: [{ name: 'tag1', annotated: true }], remotes: [], stash: null, - cidi: null + cicd: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -2517,7 +2517,7 @@ describe('DataSource', () => { tags: [], remotes: [], stash: null, - cidi: null + cicd: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', diff --git a/tests/extensionState.test.ts b/tests/extensionState.test.ts index 891b1b32..b2110869 100644 --- a/tests/extensionState.test.ts +++ b/tests/extensionState.test.ts @@ -71,7 +71,7 @@ describe('ExtensionState', () => { onRepoLoadShowCheckedOutBranch: BooleanOverride.Enabled, onRepoLoadShowSpecificBranches: ['master'], pullRequestConfig: null, - cidiConfigs: null, + cicdConfigs: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Enabled, showStashes: BooleanOverride.Enabled, @@ -119,7 +119,7 @@ describe('ExtensionState', () => { onRepoLoadShowCheckedOutBranch: BooleanOverride.Default, onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, - cidiConfigs: null, + cicdConfigs: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, @@ -157,7 +157,7 @@ describe('ExtensionState', () => { onRepoLoadShowCheckedOutBranch: BooleanOverride.Default, onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, - cidiConfigs: null, + cicdConfigs: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, @@ -195,7 +195,7 @@ describe('ExtensionState', () => { onRepoLoadShowCheckedOutBranch: BooleanOverride.Default, onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, - cidiConfigs: null, + cicdConfigs: null, showRemoteBranches: false, showRemoteBranchesV2: BooleanOverride.Disabled, showStashes: BooleanOverride.Default, @@ -233,7 +233,7 @@ describe('ExtensionState', () => { onRepoLoadShowCheckedOutBranch: BooleanOverride.Default, onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, - cidiConfigs: null, + cicdConfigs: null, showRemoteBranches: false, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, @@ -271,7 +271,7 @@ describe('ExtensionState', () => { onRepoLoadShowCheckedOutBranch: BooleanOverride.Default, onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, - cidiConfigs: null, + cicdConfigs: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Enabled, showStashes: BooleanOverride.Default, @@ -312,7 +312,7 @@ describe('ExtensionState', () => { onRepoLoadShowCheckedOutBranch: BooleanOverride.Default, onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, - cidiConfigs: null, + cicdConfigs: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, @@ -333,7 +333,7 @@ describe('ExtensionState', () => { onRepoLoadShowCheckedOutBranch: BooleanOverride.Default, onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, - cidiConfigs: null, + cicdConfigs: null, showRemoteBranches: false, showRemoteBranchesV2: BooleanOverride.Disabled, showStashes: BooleanOverride.Default, diff --git a/tests/repoManager.test.ts b/tests/repoManager.test.ts index 8a0d3532..1d8f076f 100644 --- a/tests/repoManager.test.ts +++ b/tests/repoManager.test.ts @@ -923,7 +923,7 @@ describe('RepoManager', () => { onRepoLoadShowCheckedOutBranch: BooleanOverride.Default, onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, - cidiConfigs: null, + cicdConfigs: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, @@ -1681,7 +1681,7 @@ describe('RepoManager', () => { onRepoLoadShowCheckedOutBranch: BooleanOverride.Default, onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, - cidiConfigs: null, + cicdConfigs: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, @@ -2033,7 +2033,7 @@ describe('RepoManager', () => { onRepoLoadShowCheckedOutBranch: BooleanOverride.Default, onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, - cidiConfigs: null, + cicdConfigs: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, @@ -2109,7 +2109,7 @@ describe('RepoManager', () => { onRepoLoadShowCheckedOutBranch: BooleanOverride.Default, onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, - cidiConfigs: null, + cicdConfigs: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, diff --git a/web/main.ts b/web/main.ts index 5b4a6ad0..cc8f93c9 100644 --- a/web/main.ts +++ b/web/main.ts @@ -616,7 +616,7 @@ class GitGraphView { remotes: this.gitRemotes, hideRemotes: repoState.hideRemotes, stashes: this.gitStashes, - cidiConfigs: repoState.cidiConfigs + cicdConfigs: repoState.cicdConfigs }); } @@ -821,7 +821,7 @@ class GitGraphView { (colVisibility.date ? 'Date' : '') + (colVisibility.author ? 'Author' : '') + (colVisibility.commit ? 'Commit' : '') + - (colVisibility.cidi ? 'CI/DI Status' : '') + + (colVisibility.cicd ? 'CI/CD Status' : '') + ''; for (let i = 0; i < this.commits.length; i++) { @@ -870,7 +870,7 @@ class GitGraphView { (colVisibility.date ? '' + date.formatted + '' : '') + (colVisibility.author ? '' + (this.config.fetchAvatars ? '' + (typeof this.avatars[commit.email] === 'string' ? '' : '') + '' : '') + escapeHtml(commit.author) + '' : '') + (colVisibility.commit ? '' + abbrevCommit(commit.hash) + '' : '') + - (colVisibility.cidi ? (commit.cidi ? '' + '' + commit.cidi.status + '' : '*') : '') + + (colVisibility.cicd ? (commit.cicd ? '' + '' + commit.cicd.status + '' : '*') : '') + ''; } this.tableElem.innerHTML = '' + html + '
'; @@ -929,7 +929,7 @@ class GitGraphView { (colVisibility.date ? '' + date.formatted + '' : '') + (colVisibility.author ? '*' : '') + (colVisibility.commit ? '*' : '') + - (colVisibility.cidi ? '*' : ''); + (colVisibility.cicd ? '*' : ''); } private renderFetchButton() { @@ -1658,7 +1658,7 @@ class GitGraphView { let cWidths = this.gitRepos[this.currentRepo].columnWidths; if (cWidths === null) { // Initialise auto column layout if it is the first time viewing the repo. let defaults = this.config.defaultColumnVisibility; - columnWidths = [COLUMN_AUTO, COLUMN_AUTO, defaults.date ? COLUMN_AUTO : COLUMN_HIDDEN, defaults.author ? COLUMN_AUTO : COLUMN_HIDDEN, defaults.commit ? COLUMN_AUTO : COLUMN_HIDDEN, defaults.cidi ? COLUMN_AUTO : COLUMN_HIDDEN]; + columnWidths = [COLUMN_AUTO, COLUMN_AUTO, defaults.date ? COLUMN_AUTO : COLUMN_HIDDEN, defaults.author ? COLUMN_AUTO : COLUMN_HIDDEN, defaults.commit ? COLUMN_AUTO : COLUMN_HIDDEN, defaults.cicd ? COLUMN_AUTO : COLUMN_HIDDEN]; this.saveColumnWidths(columnWidths); } else { columnWidths = [cWidths[0], COLUMN_AUTO, cWidths[1], cWidths[2], cWidths[3], cWidths[4]]; @@ -1778,7 +1778,7 @@ class GitGraphView { onClick: () => toggleColumnState(4, 80) }, { - title: 'CI/DI Status', + title: 'CI/CD Status', visible: true, checked: columnWidths[5] !== COLUMN_HIDDEN, onClick: () => toggleColumnState(5, 80) @@ -1811,16 +1811,16 @@ class GitGraphView { public getColumnVisibility() { let colWidths = this.gitRepos[this.currentRepo].columnWidths; if (colWidths !== null) { - return { date: colWidths[1] !== COLUMN_HIDDEN, author: colWidths[2] !== COLUMN_HIDDEN, commit: colWidths[3] !== COLUMN_HIDDEN, cidi: colWidths[4] !== COLUMN_HIDDEN }; + return { date: colWidths[1] !== COLUMN_HIDDEN, author: colWidths[2] !== COLUMN_HIDDEN, commit: colWidths[3] !== COLUMN_HIDDEN, cicd: colWidths[4] !== COLUMN_HIDDEN }; } else { let defaults = this.config.defaultColumnVisibility; - return { date: defaults.date, author: defaults.author, commit: defaults.commit, cidi: defaults.cidi }; + return { date: defaults.date, author: defaults.author, commit: defaults.commit, cicd: defaults.cicd }; } } private getNumColumns() { let colVisibility = this.getColumnVisibility(); - return 2 + (colVisibility.date ? 1 : 0) + (colVisibility.author ? 1 : 0) + (colVisibility.commit ? 1 : 0) + (colVisibility.cidi ? 1 : 0); + return 2 + (colVisibility.date ? 1 : 0) + (colVisibility.author ? 1 : 0) + (colVisibility.commit ? 1 : 0) + (colVisibility.cicd ? 1 : 0); } /** diff --git a/web/settingsWidget.ts b/web/settingsWidget.ts index f37d22b2..0db87ea2 100644 --- a/web/settingsWidget.ts +++ b/web/settingsWidget.ts @@ -240,24 +240,24 @@ class SettingsWidget { } if (this.config !== null) { - html += '

CI/DI Status Configuration

'; - const cidiConfigs = this.repo.cidiConfigs; - if (cidiConfigs !== null && cidiConfigs.length !== 0) { - cidiConfigs.forEach((cidiConfig, i) => { + html += '

CI/CD Status Configuration

ProviderURLAction
'; + const cicdConfigs = this.repo.cicdConfigs; + if (cicdConfigs !== null && cicdConfigs.length !== 0) { + cicdConfigs.forEach((cicdConfig, i) => { let providerOptions:any = {}; - providerOptions[(GG.CIDIProvider.GitHubV3).toString()] = 'GitHub'; - providerOptions[(GG.CIDIProvider.GitLabV4).toString()] = 'GitLab APIv4(ver8.11-)'; - const gitUrl = escapeHtml(cidiConfig.gitUrl || 'Not Set'); + providerOptions[(GG.CICDProvider.GitHubV3).toString()] = 'GitHub'; + providerOptions[(GG.CICDProvider.GitLabV4).toString()] = 'GitLab APIv4(ver8.11-)'; + const gitUrl = escapeHtml(cicdConfig.gitUrl || 'Not Set'); html += '' + - '' + + '' + '' + - '' + + '' + ''; }); } else { - html += ''; + html += ''; } - html += '
ProviderURLAction
' + escapeHtml(providerOptions[cidiConfig.provider]) + '' + escapeHtml(providerOptions[cicdConfig.provider]) + '' + gitUrl + '
' + SVG_ICONS.pencil + '
' + SVG_ICONS.close + '
' + SVG_ICONS.pencil + '
' + SVG_ICONS.close + '
There are no CI/DI configured for this repository.
There are no CI/CD configured for this repository.
' + SVG_ICONS.plus + 'Add CI/DI
'; + html += '
' + SVG_ICONS.plus + 'Add CI/CD
'; } html += '

Git Graph Configuration

' + @@ -473,8 +473,8 @@ class SettingsWidget { }); const updateConfigWithFormValues = (values: DialogInputValue[]) => { - let config: GG.CIDIConfig = { - provider: parseInt(values[0]), gitUrl: values[1], + let config: GG.CICDConfig = { + provider: parseInt(values[0]), gitUrl: values[1], glToken: values[2], custom: null }; @@ -482,71 +482,71 @@ class SettingsWidget { }; const copyConfigs = () => { if (this.repo === null) return []; - let configs: GG.CIDIConfig[]; - if (this.repo.cidiConfigs === null) { + let configs: GG.CICDConfig[]; + if (this.repo.cicdConfigs === null) { configs = []; } else { - configs = Object.assign([], this.repo.cidiConfigs); + configs = Object.assign([], this.repo.cicdConfigs); } return configs; }; - document.getElementById('settingsAddCIDI')!.addEventListener('click', () => { - let defaultProvider = GG.CIDIProvider.GitHubV3.toString(); + document.getElementById('settingsAddCICD')!.addEventListener('click', () => { + let defaultProvider = GG.CICDProvider.GitHubV3.toString(); let providerOptions = [ - // { name: 'Bitbucket', value: (GG.CIDIProvider.Bitbucket).toString() }, - { name: 'GitHub', value: (GG.CIDIProvider.GitHubV3).toString() }, - { name: 'GitLab APIv4(ver8.11-)', value: (GG.CIDIProvider.GitLabV4).toString() } + // { name: 'Bitbucket', value: (GG.CICDProvider.Bitbucket).toString() }, + { name: 'GitHub', value: (GG.CICDProvider.GitHubV3).toString() }, + { name: 'GitLab APIv4(ver8.11-)', value: (GG.CICDProvider.GitLabV4).toString() } ]; - dialog.showForm('Add a new cidi to this repository:', [ + dialog.showForm('Add a new cicd to this repository:', [ { type: DialogInputType.Select, name: 'Provider', options: providerOptions, default: defaultProvider, - info: 'In addition to the built-in publicly hosted CI/DI providers.' + info: 'In addition to the built-in publicly hosted CI/CD providers.' }, - { type: DialogInputType.Text, name: 'Git URL', default: '', placeholder: null, info: 'The CI/DI provider\'s Git URL (e.g. https://gitlab.com/OWNER/REPO.git).' }, + { type: DialogInputType.Text, name: 'Git URL', default: '', placeholder: null, info: 'The CI/CD provider\'s Git URL (e.g. https://gitlab.com/OWNER/REPO.git).' }, { type: DialogInputType.Password, name: 'Access Token', default: '', info: 'The GitHub/GitLab personal access token or project access token.' } - ], 'Add CI/DI', (values) => { - let configs: GG.CIDIConfig[] = copyConfigs(); - let config: GG.CIDIConfig = updateConfigWithFormValues(values); + ], 'Add CI/CD', (values) => { + let configs: GG.CICDConfig[] = copyConfigs(); + let config: GG.CICDConfig = updateConfigWithFormValues(values); configs.push(config); - this.setCIDIConfig(configs); + this.setCICDConfig(configs); }, { type: TargetType.Repo }); }); - addListenerToClass('editCIDI', 'click', (e) => { - const cidiConfig = this.getCIDIForBtnEvent(e); - if (cidiConfig === null) return; + addListenerToClass('editCICD', 'click', (e) => { + const cicdConfig = this.getCICDForBtnEvent(e); + if (cicdConfig === null) return; let providerOptions = [ - // { name: 'Bitbucket', value: (GG.CIDIProvider.Bitbucket).toString() }, - { name: 'GitHub', value: (GG.CIDIProvider.GitHubV3).toString() }, - { name: 'GitLab APIv4(ver8.11-)', value: (GG.CIDIProvider.GitLabV4).toString() } + // { name: 'Bitbucket', value: (GG.CICDProvider.Bitbucket).toString() }, + { name: 'GitHub', value: (GG.CICDProvider.GitHubV3).toString() }, + { name: 'GitLab APIv4(ver8.11-)', value: (GG.CICDProvider.GitLabV4).toString() } ]; - dialog.showForm('Edit the CI/DI ' + escapeHtml(cidiConfig.gitUrl || 'Not Set') + ':', [ + dialog.showForm('Edit the CI/CD ' + escapeHtml(cicdConfig.gitUrl || 'Not Set') + ':', [ { type: DialogInputType.Select, name: 'Provider', - options: providerOptions, default: cidiConfig.provider.toString(), - info: 'In addition to the built-in publicly hosted CI/DI providers.' + options: providerOptions, default: cicdConfig.provider.toString(), + info: 'In addition to the built-in publicly hosted CI/CD providers.' }, - { type: DialogInputType.Text, name: 'Git URL', default: cidiConfig.gitUrl || '', placeholder: null, info: 'The CI/DI provider\'s Git URL (e.g. https://gitlab.com/OWNER/REPO.git).' }, - { type: DialogInputType.Password, name: 'Access Token', default: cidiConfig.glToken, info: 'The GitHub/GitLab personal access token or project access token.' } + { type: DialogInputType.Text, name: 'Git URL', default: cicdConfig.gitUrl || '', placeholder: null, info: 'The CI/CD provider\'s Git URL (e.g. https://gitlab.com/OWNER/REPO.git).' }, + { type: DialogInputType.Password, name: 'Access Token', default: cicdConfig.glToken, info: 'The GitHub/GitLab personal access token or project access token.' } ], 'Save Changes', (values) => { - let index = parseInt(((e.target).closest('.cidiBtns')!).dataset.index!); - let configs: GG.CIDIConfig[] = copyConfigs(); - let config: GG.CIDIConfig = updateConfigWithFormValues(values); + let index = parseInt(((e.target).closest('.cicdBtns')!).dataset.index!); + let configs: GG.CICDConfig[] = copyConfigs(); + let config: GG.CICDConfig = updateConfigWithFormValues(values); configs[index] = config; - this.setCIDIConfig(configs); + this.setCICDConfig(configs); }, { type: TargetType.Repo }); }); - addListenerToClass('deleteCIDI', 'click', (e) => { - const cidiConfig = this.getCIDIForBtnEvent(e); - if (cidiConfig === null) return; - dialog.showConfirmation('Are you sure you want to delete the CI/DI ' + escapeHtml(cidiConfig.gitUrl) + '?', 'Yes, delete', () => { - let index = parseInt(((e.target).closest('.cidiBtns')!).dataset.index!); - let configs: GG.CIDIConfig[] = copyConfigs(); + addListenerToClass('deleteCICD', 'click', (e) => { + const cicdConfig = this.getCICDForBtnEvent(e); + if (cicdConfig === null) return; + dialog.showConfirmation('Are you sure you want to delete the CI/CD ' + escapeHtml(cicdConfig.gitUrl) + '?', 'Yes, delete', () => { + let index = parseInt(((e.target).closest('.cicdBtns')!).dataset.index!); + let configs: GG.CICDConfig[] = copyConfigs(); configs.splice(index, 1); - this.setCIDIConfig(configs); + this.setCICDConfig(configs); }, { type: TargetType.Repo }); }); @@ -674,9 +674,9 @@ class SettingsWidget { * Save the pull request configuration for this repository. * @param config The pull request configuration to save. */ - private setCIDIConfig(config: GG.CIDIConfig[] | null) { + private setCICDConfig(config: GG.CICDConfig[] | null) { if (this.currentRepo === null) return; - this.view.saveRepoStateValue(this.currentRepo, 'cidiConfigs', config); + this.view.saveRepoStateValue(this.currentRepo, 'cicdConfigs', config); this.render(); } @@ -911,13 +911,13 @@ class SettingsWidget { } /** - * Get the cidi details corresponding to a mouse event. + * Get the cicd details corresponding to a mouse event. * @param e The mouse event. - * @returns The details of the cidi. + * @returns The details of the cicd. */ - private getCIDIForBtnEvent(e: Event) { - return this.repo !== null && this.repo.cidiConfigs !== null - ? this.repo.cidiConfigs[parseInt(((e.target).closest('.cidiBtns')!).dataset.index!)] + private getCICDForBtnEvent(e: Event) { + return this.repo !== null && this.repo.cicdConfigs !== null + ? this.repo.cicdConfigs[parseInt(((e.target).closest('.cicdBtns')!).dataset.index!)] : null; } diff --git a/web/styles/settingsWidget.css b/web/styles/settingsWidget.css index 1cb65f49..f713074d 100644 --- a/web/styles/settingsWidget.css +++ b/web/styles/settingsWidget.css @@ -140,17 +140,17 @@ vertical-align:top; cursor:pointer; } -.settingsSection > table td.btns.remoteBtns div, .settingsSection > table td.btns.cidiBtns div{ +.settingsSection > table td.btns.remoteBtns div, .settingsSection > table td.btns.cicdBtns div{ vertical-align:middle; } -.settingsSection > table #editRepoName svg, .settingsSection > table #editInitialBranches svg, .settingsSection > table .editRemote svg, .settingsSection > table .editCIDI svg{ +.settingsSection > table #editRepoName svg, .settingsSection > table #editInitialBranches svg, .settingsSection > table .editRemote svg, .settingsSection > table .editCICD svg{ position:absolute; left:1.5px; top:1.5px; width:14px !important; height:14px !important; } -.settingsSection > table #deleteRepoName svg, .settingsSection > table #clearInitialBranches svg, .settingsSection > table .deleteRemote svg, .settingsSection > table .fetchRemote svg, .settingsSection > table .pruneRemote svg, .settingsSection > table .deleteCIDI svg{ +.settingsSection > table #deleteRepoName svg, .settingsSection > table #clearInitialBranches svg, .settingsSection > table .deleteRemote svg, .settingsSection > table .fetchRemote svg, .settingsSection > table .pruneRemote svg, .settingsSection > table .deleteCICD svg{ position:absolute; left:0.5px; top:0.5px; From d2004abe486f280cdd56966f2f9fe3e1c8be9837 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Thu, 11 Mar 2021 00:08:54 +0900 Subject: [PATCH 09/54] #462 Fiexd typo LF to CRLF --- src/dataSource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dataSource.ts b/src/dataSource.ts index f0266b62..1df43389 100644 --- a/src/dataSource.ts +++ b/src/dataSource.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode'; import { AskpassEnvironment, AskpassManager } from './askpass/askpassManager'; import { getConfig } from './config'; import { Logger } from './logger'; -import { CICDConfig, CICDProvider, CommitOrdering, DateType, DeepWriteable, ErrorInfo, GitCICDData, GitCommit, GitCommitDetails, GitCommitStash, GitConfigLocation, GitFileChange, GitFileStatus, GitPushBranchMode, GitRepoConfig, GitRepoConfigBranches, GitResetMode, GitSignatureStatus, GitStash, MergeActionOn, RebaseActionOn, SquashMessageFormat, TagType, Writeable } from './types'; +import { CICDConfig, CICDProvider, CommitOrdering, DateType, DeepWriteable, ErrorInfo, GitCICDData, GitCommit, GitCommitDetails, GitCommitStash, GitConfigLocation, GitFileChange, GitFileStatus, GitPushBranchMode, GitRepoConfig, GitRepoConfigBranches, GitResetMode, GitSignatureStatus, GitStash, MergeActionOn, RebaseActionOn, SquashMessageFormat, TagType, Writeable } from './types'; import { GitExecutable, UNABLE_TO_FIND_GIT_MSG, UNCOMMITTED, abbrevCommit, constructIncompatibleGitVersionMessage, doesVersionMeetRequirement, getPathFromStr, getPathFromUri, openGitTerminal, pathWithTrailingSlash, realpath, resolveSpawnOutput, showErrorMessage } from './utils'; import { Disposable } from './utils/disposable'; import { Event } from './utils/event'; From 25cc688e350faea298df68467dc28ff0698f28f4 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Thu, 11 Mar 2021 00:24:10 +0900 Subject: [PATCH 10/54] #462 Fiexd typo LF to CRLF --- src/dataSource.ts | 4194 ++++++++++++++++++++++----------------------- 1 file changed, 2097 insertions(+), 2097 deletions(-) diff --git a/src/dataSource.ts b/src/dataSource.ts index 1df43389..2279283c 100644 --- a/src/dataSource.ts +++ b/src/dataSource.ts @@ -1,2097 +1,2097 @@ -import * as cp from 'child_process'; -import * as fs from 'fs'; -import { decode, encodingExists } from 'iconv-lite'; -import * as path from 'path'; -import * as vscode from 'vscode'; -import { AskpassEnvironment, AskpassManager } from './askpass/askpassManager'; -import { getConfig } from './config'; -import { Logger } from './logger'; -import { CICDConfig, CICDProvider, CommitOrdering, DateType, DeepWriteable, ErrorInfo, GitCICDData, GitCommit, GitCommitDetails, GitCommitStash, GitConfigLocation, GitFileChange, GitFileStatus, GitPushBranchMode, GitRepoConfig, GitRepoConfigBranches, GitResetMode, GitSignatureStatus, GitStash, MergeActionOn, RebaseActionOn, SquashMessageFormat, TagType, Writeable } from './types'; -import { GitExecutable, UNABLE_TO_FIND_GIT_MSG, UNCOMMITTED, abbrevCommit, constructIncompatibleGitVersionMessage, doesVersionMeetRequirement, getPathFromStr, getPathFromUri, openGitTerminal, pathWithTrailingSlash, realpath, resolveSpawnOutput, showErrorMessage } from './utils'; -import { Disposable } from './utils/disposable'; -import { Event } from './utils/event'; -import * as request from 'request-promise'; - -const DRIVE_LETTER_PATH_REGEX = /^[a-z]:\//; -const EOL_REGEX = /\r\n|\r|\n/g; -const INVALID_BRANCH_REGEXP = /^\(.* .*\)$/; -const REMOTE_HEAD_BRANCH_REGEXP = /^remotes\/.*\/HEAD$/; -const GIT_LOG_SEPARATOR = 'XX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPb'; - -export const GIT_CONFIG = { - DIFF: { - GUI_TOOL: 'diff.guitool', - TOOL: 'diff.tool' - }, - REMOTE: { - PUSH_DEFAULT: 'remote.pushdefault' - }, - USER: { - EMAIL: 'user.email', - NAME: 'user.name' - } -}; - -/** - * Interfaces Git Graph with the Git executable to provide all Git integrations. - */ -export class DataSource extends Disposable { - private readonly logger: Logger; - private readonly askpassEnv: AskpassEnvironment; - private gitExecutable!: GitExecutable | null; - private gitExecutableSupportsGpgInfo!: boolean; - private gitFormatCommitDetails!: string; - private gitFormatLog!: string; - private gitFormatStash!: string; - - /** - * Creates the Git Graph Data Source. - * @param gitExecutable The Git executable available to Git Graph at startup. - * @param onDidChangeGitExecutable The Event emitting the Git executable for Git Graph to use. - * @param logger The Git Graph Logger instance. - */ - constructor(gitExecutable: GitExecutable | null, onDidChangeConfiguration: Event, onDidChangeGitExecutable: Event, logger: Logger) { - super(); - this.logger = logger; - this.setGitExecutable(gitExecutable); - - const askpassManager = new AskpassManager(); - this.askpassEnv = askpassManager.getEnv(); - - this.registerDisposables( - onDidChangeConfiguration((event) => { - if ( - event.affectsConfiguration('git-graph.date.type') || event.affectsConfiguration('git-graph.dateType') || - event.affectsConfiguration('git-graph.repository.commits.showSignatureStatus') || event.affectsConfiguration('git-graph.showSignatureStatus') || - event.affectsConfiguration('git-graph.repository.useMailmap') || event.affectsConfiguration('git-graph.useMailmap') - ) { - this.generateGitCommandFormats(); - } - }), - onDidChangeGitExecutable((gitExecutable) => { - this.setGitExecutable(gitExecutable); - }), - askpassManager - ); - } - - /** - * Check if the Git executable is unknown. - * @returns TRUE => Git executable is unknown, FALSE => Git executable is known. - */ - public isGitExecutableUnknown() { - return this.gitExecutable === null; - } - - /** - * Set the Git executable used by the DataSource. - * @param gitExecutable The Git executable. - */ - public setGitExecutable(gitExecutable: GitExecutable | null) { - this.gitExecutable = gitExecutable; - this.gitExecutableSupportsGpgInfo = gitExecutable !== null ? doesVersionMeetRequirement(gitExecutable.version, '2.4.0') : false; - this.generateGitCommandFormats(); - } - - /** - * Generate the format strings used by various Git commands. - */ - private generateGitCommandFormats() { - const config = getConfig(); - const dateType = config.dateType === DateType.Author ? '%at' : '%ct'; - const useMailmap = config.useMailmap; - - this.gitFormatCommitDetails = [ - '%H', '%P', // Hash & Parent Information - useMailmap ? '%aN' : '%an', useMailmap ? '%aE' : '%ae', '%at', useMailmap ? '%cN' : '%cn', useMailmap ? '%cE' : '%ce', '%ct', // Author / Commit Information - ...(config.showSignatureStatus && this.gitExecutableSupportsGpgInfo ? ['%G?', '%GS', '%GK'] : ['', '', '']), // GPG Key Information - '%B' // Body - ].join(GIT_LOG_SEPARATOR); - - this.gitFormatLog = [ - '%H', '%P', // Hash & Parent Information - useMailmap ? '%aN' : '%an', useMailmap ? '%aE' : '%ae', dateType, // Author / Commit Information - '%s' // Subject - ].join(GIT_LOG_SEPARATOR); - - this.gitFormatStash = [ - '%H', '%P', '%gD', // Hash, Parent & Selector Information - useMailmap ? '%aN' : '%an', useMailmap ? '%aE' : '%ae', dateType, // Author / Commit Information - '%s' // Subject - ].join(GIT_LOG_SEPARATOR); - } - - - /* Get Data Methods - Core */ - - /** - * Get the high-level information of a repository. - * @param repo The path of the repository. - * @param showRemoteBranches Are remote branches shown. - * @param showStashes Are stashes shown. - * @param hideRemotes An array of hidden remotes. - * @returns The repositories information. - */ - public getRepoInfo(repo: string, showRemoteBranches: boolean, showStashes: boolean, hideRemotes: ReadonlyArray): Promise { - return Promise.all([ - this.getBranches(repo, showRemoteBranches, hideRemotes), - this.getRemotes(repo), - showStashes ? this.getStashes(repo) : Promise.resolve([]) - ]).then((results) => { - return { branches: results[0].branches, head: results[0].head, remotes: results[1], stashes: results[2], error: null }; - }).catch((errorMessage) => { - return { branches: [], head: null, remotes: [], stashes: [], error: errorMessage }; - }); - } - - /** - * Get the commits in a repository. - * @param repo The path of the repository. - * @param branches The list of branch heads to display, or NULL (show all). - * @param maxCommits The maximum number of commits to return. - * @param showTags Are tags are shown. - * @param showRemoteBranches Are remote branches shown. - * @param includeCommitsMentionedByReflogs Should commits mentioned by reflogs being included. - * @param onlyFollowFirstParent Only follow the first parent of commits. - * @param commitOrdering The order for commits to be returned. - * @param remotes An array of known remotes. - * @param hideRemotes An array of hidden remotes. - * @param stashes An array of all stashes in the repository. - * @returns The commits in the repository. - */ - public getCommits(repo: string, branches: ReadonlyArray | null, maxCommits: number, showTags: boolean, showRemoteBranches: boolean, includeCommitsMentionedByReflogs: boolean, onlyFollowFirstParent: boolean, commitOrdering: CommitOrdering, remotes: ReadonlyArray, hideRemotes: ReadonlyArray, stashes: ReadonlyArray, cicdConfigs: CICDConfig[] | null): Promise { - const config = getConfig(); - return Promise.all([ - this.getLog(repo, branches, maxCommits + 1, showTags && config.showCommitsOnlyReferencedByTags, showRemoteBranches, includeCommitsMentionedByReflogs, onlyFollowFirstParent, commitOrdering, remotes, hideRemotes, stashes), - this.getRefs(repo, showRemoteBranches, config.showRemoteHeads, hideRemotes).then((refData: GitRefData) => refData, (errorMessage: string) => errorMessage), - this.getCICDs(cicdConfigs).then((refData: GitCICDData[] | string | undefined) => refData, (errorMessage: string) => errorMessage) - ]).then(async (results) => { - let commits: GitCommitRecord[] = results[0], refData: GitRefData | string = results[1], i; - let cicds: GitCICDData[] | string | undefined = results[2]; - let moreCommitsAvailable = commits.length === maxCommits + 1; - if (moreCommitsAvailable) commits.pop(); - - // It doesn't matter if getRefs() was rejected if no commits exist - if (typeof refData === 'string') { - // getRefs() returned an error message (string) - if (commits.length > 0) { - // Commits exist, throw the error - throw refData; - } else { - // No commits exist, so getRefs() will always return an error. Set refData to the default value - refData = { head: null, heads: [], tags: [], remotes: [] }; - } - } - - if (refData.head !== null && config.showUncommittedChanges) { - for (i = 0; i < commits.length; i++) { - if (refData.head === commits[i].hash) { - const numUncommittedChanges = await this.getUncommittedChanges(repo); - if (numUncommittedChanges > 0) { - commits.unshift({ hash: UNCOMMITTED, parents: [refData.head], author: '*', email: '', date: Math.round((new Date()).getTime() / 1000), message: 'Uncommitted Changes (' + numUncommittedChanges + ')' }); - } - break; - } - } - } - - let commitNodes: DeepWriteable[] = []; - let commitLookup: { [hash: string]: number } = {}; - - for (i = 0; i < commits.length; i++) { - commitLookup[commits[i].hash] = i; - commitNodes.push({ ...commits[i], heads: [], tags: [], remotes: [], stash: null, cicd: null }); - } - - /* Insert Stashes */ - let toAdd: { index: number, data: GitStash }[] = []; - for (i = 0; i < stashes.length; i++) { - if (typeof commitLookup[stashes[i].hash] === 'number') { - commitNodes[commitLookup[stashes[i].hash]].stash = { - selector: stashes[i].selector, - baseHash: stashes[i].baseHash, - untrackedFilesHash: stashes[i].untrackedFilesHash - }; - } else if (typeof commitLookup[stashes[i].baseHash] === 'number') { - toAdd.push({ index: commitLookup[stashes[i].baseHash], data: stashes[i] }); - } - } - toAdd.sort((a, b) => a.index !== b.index ? a.index - b.index : b.data.date - a.data.date); - for (i = toAdd.length - 1; i >= 0; i--) { - let stash = toAdd[i].data; - commitNodes.splice(toAdd[i].index, 0, { - cicd: null, - hash: stash.hash, - parents: [stash.baseHash], - author: stash.author, - email: stash.email, - date: stash.date, - message: stash.message, - heads: [], tags: [], remotes: [], - stash: { - selector: stash.selector, - baseHash: stash.baseHash, - untrackedFilesHash: stash.untrackedFilesHash - } - }); - } - for (i = 0; i < commitNodes.length; i++) { - // Correct commit lookup after stashes have been spliced in - commitLookup[commitNodes[i].hash] = i; - } - - /* Annotate Heads */ - for (i = 0; i < refData.heads.length; i++) { - if (typeof commitLookup[refData.heads[i].hash] === 'number') commitNodes[commitLookup[refData.heads[i].hash]].heads.push(refData.heads[i].name); - } - - /* Annotate Tags */ - if (showTags) { - for (i = 0; i < refData.tags.length; i++) { - if (typeof commitLookup[refData.tags[i].hash] === 'number') commitNodes[commitLookup[refData.tags[i].hash]].tags.push({ name: refData.tags[i].name, annotated: refData.tags[i].annotated }); - } - } - - /* Annotate Remotes */ - for (i = 0; i < refData.remotes.length; i++) { - if (typeof commitLookup[refData.remotes[i].hash] === 'number') { - let name = refData.remotes[i].name; - let remote = remotes.find(remote => name.startsWith(remote + '/')); - commitNodes[commitLookup[refData.remotes[i].hash]].remotes.push({ name: name, remote: remote ? remote : null }); - } - } - - if (typeof cicds === 'string' || typeof cicds === 'undefined') { - cicds = []; - } - /* Annotate CI/CDs */ - for (i = 0; i < cicds.length; i++) { - if (typeof commitLookup[cicds[i].sha] === 'number') { - commitNodes[commitLookup[cicds[i].sha]].cicd = cicds[i]; - } - } - - return { - commits: commitNodes, - head: refData.head, - tags: unique(refData.tags.map((tag) => tag.name)), - moreCommitsAvailable: moreCommitsAvailable, - error: null - }; - }).catch((errorMessage) => { - return { commits: [], head: null, tags: [], moreCommitsAvailable: false, error: errorMessage }; - }); - } - - /** - * Get various Git config variables for a repository that are consumed by the Git Graph View. - * @param repo The path of the repository. - * @param remotes An array of known remotes. - * @returns The config data. - */ - public getConfig(repo: string, remotes: ReadonlyArray): Promise { - return Promise.all([ - this.getConfigList(repo), - this.getConfigList(repo, GitConfigLocation.Local), - this.getConfigList(repo, GitConfigLocation.Global) - ]).then((results) => { - const consolidatedConfigs = results[0], localConfigs = results[1], globalConfigs = results[2]; - - const branches: GitRepoConfigBranches = {}; - Object.keys(localConfigs).forEach((key) => { - if (key.startsWith('branch.')) { - if (key.endsWith('.remote')) { - const branchName = key.substring(7, key.length - 7); - branches[branchName] = { - pushRemote: typeof branches[branchName] !== 'undefined' ? branches[branchName].pushRemote : null, - remote: localConfigs[key] - }; - } else if (key.endsWith('.pushremote')) { - const branchName = key.substring(7, key.length - 11); - branches[branchName] = { - pushRemote: localConfigs[key], - remote: typeof branches[branchName] !== 'undefined' ? branches[branchName].remote : null - }; - } - } - }); - - return { - config: { - branches: branches, - diffTool: getConfigValue(consolidatedConfigs, GIT_CONFIG.DIFF.TOOL), - guiDiffTool: getConfigValue(consolidatedConfigs, GIT_CONFIG.DIFF.GUI_TOOL), - pushDefault: getConfigValue(consolidatedConfigs, GIT_CONFIG.REMOTE.PUSH_DEFAULT), - remotes: remotes.map((remote) => ({ - name: remote, - url: getConfigValue(localConfigs, 'remote.' + remote + '.url'), - pushUrl: getConfigValue(localConfigs, 'remote.' + remote + '.pushurl') - })), - user: { - name: { - local: getConfigValue(localConfigs, GIT_CONFIG.USER.NAME), - global: getConfigValue(globalConfigs, GIT_CONFIG.USER.NAME) - }, - email: { - local: getConfigValue(localConfigs, GIT_CONFIG.USER.EMAIL), - global: getConfigValue(globalConfigs, GIT_CONFIG.USER.EMAIL) - } - } - }, - error: null - }; - }).catch((errorMessage) => { - return { config: null, error: errorMessage }; - }); - } - - - /* Get Data Methods - Commit Details View */ - - /** - * Get the commit details for the Commit Details View. - * @param repo The path of the repository. - * @param commitHash The hash of the commit open in the Commit Details View. - * @param hasParents Does the commit have parents - * @returns The commit details. - */ - public getCommitDetails(repo: string, commitHash: string, hasParents: boolean): Promise { - const fromCommit = commitHash + (hasParents ? '^' : ''); - return Promise.all([ - this.getCommitDetailsBase(repo, commitHash), - this.getDiffNameStatus(repo, fromCommit, commitHash), - this.getDiffNumStat(repo, fromCommit, commitHash) - ]).then((results) => { - results[0].fileChanges = generateFileChanges(results[1], results[2], null); - return { commitDetails: results[0], error: null }; - }).catch((errorMessage) => { - return { commitDetails: null, error: errorMessage }; - }); - } - - /** - * Get the stash details for the Commit Details View. - * @param repo The path of the repository. - * @param commitHash The hash of the stash commit open in the Commit Details View. - * @param stash The stash. - * @returns The stash details. - */ - public getStashDetails(repo: string, commitHash: string, stash: GitCommitStash): Promise { - return Promise.all([ - this.getCommitDetailsBase(repo, commitHash), - this.getDiffNameStatus(repo, stash.baseHash, commitHash), - this.getDiffNumStat(repo, stash.baseHash, commitHash), - stash.untrackedFilesHash !== null ? this.getDiffNameStatus(repo, stash.untrackedFilesHash, stash.untrackedFilesHash) : Promise.resolve([]), - stash.untrackedFilesHash !== null ? this.getDiffNumStat(repo, stash.untrackedFilesHash, stash.untrackedFilesHash) : Promise.resolve([]) - ]).then((results) => { - results[0].fileChanges = generateFileChanges(results[1], results[2], null); - if (stash.untrackedFilesHash !== null) { - generateFileChanges(results[3], results[4], null).forEach((fileChange) => { - if (fileChange.type === GitFileStatus.Added) { - fileChange.type = GitFileStatus.Untracked; - results[0].fileChanges.push(fileChange); - } - }); - } - return { commitDetails: results[0], error: null }; - }).catch((errorMessage) => { - return { commitDetails: null, error: errorMessage }; - }); - } - - /** - * Get the uncommitted details for the Commit Details View. - * @param repo The path of the repository. - * @returns The uncommitted details. - */ - public getUncommittedDetails(repo: string): Promise { - return Promise.all([ - this.getDiffNameStatus(repo, 'HEAD', ''), - this.getDiffNumStat(repo, 'HEAD', ''), - this.getStatus(repo) - ]).then((results) => { - return { - commitDetails: { - hash: UNCOMMITTED, parents: [], - author: '', authorEmail: '', authorDate: 0, - committer: '', committerEmail: '', committerDate: 0, signature: null, - body: '', fileChanges: generateFileChanges(results[0], results[1], results[2]) - }, - error: null - }; - }).catch((errorMessage) => { - return { commitDetails: null, error: errorMessage }; - }); - } - - /** - * Get the comparison details for the Commit Comparison View. - * @param repo The path of the repository. - * @param fromHash The commit hash the comparison is from. - * @param toHash The commit hash the comparison is to. - * @returns The comparison details. - */ - public getCommitComparison(repo: string, fromHash: string, toHash: string): Promise { - return Promise.all([ - this.getDiffNameStatus(repo, fromHash, toHash === UNCOMMITTED ? '' : toHash), - this.getDiffNumStat(repo, fromHash, toHash === UNCOMMITTED ? '' : toHash), - toHash === UNCOMMITTED ? this.getStatus(repo) : Promise.resolve(null) - ]).then((results) => { - return { - fileChanges: generateFileChanges(results[0], results[1], results[2]), - error: null - }; - }).catch((errorMessage) => { - return { fileChanges: [], error: errorMessage }; - }); - } - - /** - * Get the contents of a file at a specific revision. - * @param repo The path of the repository. - * @param commitHash The commit hash specifying the revision of the file. - * @param filePath The path of the file relative to the repositories root. - * @returns The file contents. - */ - public getCommitFile(repo: string, commitHash: string, filePath: string) { - return this._spawnGit(['show', commitHash + ':' + filePath], repo, stdout => { - const encoding = getConfig(repo).fileEncoding; - return decode(stdout, encodingExists(encoding) ? encoding : 'utf8'); - }); - } - - - /* Get Data Methods - General */ - - /** - * Get the subject of a commit. - * @param repo The path of the repository. - * @param commitHash The commit hash. - * @returns The subject string, or NULL if an error occurred. - */ - public getCommitSubject(repo: string, commitHash: string): Promise { - return this.spawnGit(['-c', 'log.showSignature=false', 'log', '--format=%s', '-n', '1', commitHash, '--'], repo, (stdout) => { - return stdout.trim().replace(/\s+/g, ' '); - }).then((subject) => subject, () => null); - } - - /** - * Get the URL of a repositories remote. - * @param repo The path of the repository. - * @param remote The name of the remote. - * @returns The URL, or NULL if an error occurred. - */ - public getRemoteUrl(repo: string, remote: string): Promise { - return this.spawnGit(['config', '--get', 'remote.' + remote + '.url'], repo, (stdout) => { - return stdout.split(EOL_REGEX)[0]; - }).then((url) => url, () => null); - } - - /** - * Get the details of a tag. - * @param repo The path of the repository. - * @param tagName The name of the tag. - * @returns The tag details. - */ - public getTagDetails(repo: string, tagName: string): Promise { - return this.spawnGit(['for-each-ref', 'refs/tags/' + tagName, '--format=' + ['%(objectname)', '%(taggername)', '%(taggeremail)', '%(taggerdate:unix)', '%(contents)'].join(GIT_LOG_SEPARATOR)], repo, (stdout) => { - let data = stdout.split(GIT_LOG_SEPARATOR); - return { - tagHash: data[0], - name: data[1], - email: data[2].substring(data[2].startsWith('<') ? 1 : 0, data[2].length - (data[2].endsWith('>') ? 1 : 0)), - date: parseInt(data[3]), - message: removeTrailingBlankLines(data[4].split(EOL_REGEX)).join('\n'), - error: null - }; - }).then((data) => { - return data; - }).catch((errorMessage) => { - return { tagHash: '', name: '', email: '', date: 0, message: '', error: errorMessage }; - }); - } - - /** - * Get the submodules of a repository. - * @param repo The path of the repository. - * @returns An array of the paths of the submodules. - */ - public getSubmodules(repo: string) { - return new Promise(resolve => { - fs.readFile(path.join(repo, '.gitmodules'), { encoding: 'utf8' }, async (err, data) => { - let submodules: string[] = []; - if (!err) { - let lines = data.split(EOL_REGEX), inSubmoduleSection = false, match; - const section = /^\s*\[.*\]\s*$/, submodule = /^\s*\[submodule "([^"]+)"\]\s*$/, pathProp = /^\s*path\s+=\s+(.*)$/; - - for (let i = 0; i < lines.length; i++) { - if (lines[i].match(section) !== null) { - inSubmoduleSection = lines[i].match(submodule) !== null; - continue; - } - - if (inSubmoduleSection && (match = lines[i].match(pathProp)) !== null) { - let root = await this.repoRoot(getPathFromUri(vscode.Uri.file(path.join(repo, getPathFromStr(match[1]))))); - if (root !== null && !submodules.includes(root)) { - submodules.push(root); - } - } - } - } - resolve(submodules); - }); - }); - } - - - /* Repository Info Methods */ - - /** - * Check if there are any staged changes in the repository. - * @param repo The path of the repository. - * @returns TRUE => Staged Changes, FALSE => No Staged Changes. - */ - private areStagedChanges(repo: string) { - return this.spawnGit(['diff-index', 'HEAD'], repo, (stdout) => stdout !== '').then(changes => changes, () => false); - } - - /** - * Get the root of the repository containing the specified path. - * @param pathOfPotentialRepo The path that is potentially a repository (or is contained within a repository). - * @returns STRING => The root of the repository, NULL => `pathOfPotentialRepo` is not in a repository. - */ - public repoRoot(pathOfPotentialRepo: string) { - return this.spawnGit(['rev-parse', '--show-toplevel'], pathOfPotentialRepo, (stdout) => getPathFromUri(vscode.Uri.file(path.normalize(stdout.trim())))).then(async (pathReturnedByGit) => { - if (process.platform === 'win32') { - // On Windows Mapped Network Drives with Git >= 2.25.0, `git rev-parse --show-toplevel` returns the UNC Path for the Mapped Network Drive, instead of the Drive Letter. - // Attempt to replace the UNC Path with the Drive Letter. - let driveLetterPathMatch: RegExpMatchArray | null; - if ((driveLetterPathMatch = pathOfPotentialRepo.match(DRIVE_LETTER_PATH_REGEX)) && !pathReturnedByGit.match(DRIVE_LETTER_PATH_REGEX)) { - const realPathForDriveLetter = pathWithTrailingSlash(await realpath(driveLetterPathMatch[0], true)); - if (realPathForDriveLetter !== driveLetterPathMatch[0] && pathReturnedByGit.startsWith(realPathForDriveLetter)) { - pathReturnedByGit = driveLetterPathMatch[0] + pathReturnedByGit.substring(realPathForDriveLetter.length); - } - } - } - let path = pathOfPotentialRepo; - let first = path.indexOf('/'); - while (true) { - if (pathReturnedByGit === path || pathReturnedByGit === await realpath(path)) return path; - let next = path.lastIndexOf('/'); - if (first !== next && next > -1) { - path = path.substring(0, next); - } else { - return pathReturnedByGit; - } - } - }).catch(() => null); // null => path is not in a repo - } - - - /* Git Action Methods - Remotes */ - - /** - * Add a new remote to a repository. - * @param repo The path of the repository. - * @param name The name of the remote. - * @param url The URL of the remote. - * @param pushUrl The Push URL of the remote. - * @param fetch Fetch the remote after it is added. - * @returns The ErrorInfo from the executed command. - */ - public async addRemote(repo: string, name: string, url: string, pushUrl: string | null, fetch: boolean) { - let status = await this.runGitCommand(['remote', 'add', name, url], repo); - if (status !== null) return status; - - if (pushUrl !== null) { - status = await this.runGitCommand(['remote', 'set-url', name, '--push', pushUrl], repo); - if (status !== null) return status; - } - - return fetch ? this.fetch(repo, name, false, false) : null; - } - - /** - * Delete an existing remote from a repository. - * @param repo The path of the repository. - * @param name The name of the remote. - * @returns The ErrorInfo from the executed command. - */ - public deleteRemote(repo: string, name: string) { - return this.runGitCommand(['remote', 'remove', name], repo); - } - - /** - * Edit an existing remote of a repository. - * @param repo The path of the repository. - * @param nameOld The old name of the remote. - * @param nameNew The new name of the remote. - * @param urlOld The old URL of the remote. - * @param urlNew The new URL of the remote. - * @param pushUrlOld The old Push URL of the remote. - * @param pushUrlNew The new Push URL of the remote. - * @returns The ErrorInfo from the executed command. - */ - public async editRemote(repo: string, nameOld: string, nameNew: string, urlOld: string | null, urlNew: string | null, pushUrlOld: string | null, pushUrlNew: string | null) { - if (nameOld !== nameNew) { - let status = await this.runGitCommand(['remote', 'rename', nameOld, nameNew], repo); - if (status !== null) return status; - } - - if (urlOld !== urlNew) { - let args = ['remote', 'set-url', nameNew]; - if (urlNew === null) args.push('--delete', urlOld!); - else if (urlOld === null) args.push('--add', urlNew); - else args.push(urlNew, urlOld); - - let status = await this.runGitCommand(args, repo); - if (status !== null) return status; - } - - if (pushUrlOld !== pushUrlNew) { - let args = ['remote', 'set-url', '--push', nameNew]; - if (pushUrlNew === null) args.push('--delete', pushUrlOld!); - else if (pushUrlOld === null) args.push('--add', pushUrlNew); - else args.push(pushUrlNew, pushUrlOld); - - let status = await this.runGitCommand(args, repo); - if (status !== null) return status; - } - - return null; - } - - /** - * Prune an existing remote of a repository. - * @param repo The path of the repository. - * @param name The name of the remote. - * @returns The ErrorInfo from the executed command. - */ - public pruneRemote(repo: string, name: string) { - return this.runGitCommand(['remote', 'prune', name], repo); - } - - - /* Git Action Methods - Tags */ - - /** - * Add a new tag to a commit. - * @param repo The path of the repository. - * @param tagName The name of the tag. - * @param commitHash The hash of the commit the tag should be added to. - * @param type Is the tag annotated or lightweight. - * @param message The message of the tag (if it is an annotated tag). - * @param force Force add the tag, replacing an existing tag with the same name (if it exists). - * @returns The ErrorInfo from the executed command. - */ - public addTag(repo: string, tagName: string, commitHash: string, type: TagType, message: string, force: boolean) { - const args = ['tag']; - if (force) { - args.push('-f'); - } - if (type === TagType.Lightweight) { - args.push(tagName); - } else { - args.push(getConfig().signTags ? '-s' : '-a', tagName, '-m', message); - } - args.push(commitHash); - return this.runGitCommand(args, repo); - } - - /** - * Delete an existing tag from a repository. - * @param repo The path of the repository. - * @param tagName The name of the tag. - * @param deleteOnRemote The name of the remote to delete the tag on, or NULL. - * @returns The ErrorInfo from the executed command. - */ - public async deleteTag(repo: string, tagName: string, deleteOnRemote: string | null) { - if (deleteOnRemote !== null) { - let status = await this.runGitCommand(['push', deleteOnRemote, '--delete', tagName], repo); - if (status !== null) return status; - } - return this.runGitCommand(['tag', '-d', tagName], repo); - } - - - /* Git Action Methods - Remote Sync */ - - /** - * Fetch from the repositories remote(s). - * @param repo The path of the repository. - * @param remote The remote to fetch, or NULL (fetch all remotes). - * @param prune Is pruning enabled. - * @param pruneTags Should tags be pruned. - * @returns The ErrorInfo from the executed command. - */ - public fetch(repo: string, remote: string | null, prune: boolean, pruneTags: boolean) { - let args = ['fetch', remote === null ? '--all' : remote]; - - if (prune) { - args.push('--prune'); - } - if (pruneTags) { - if (!prune) { - return Promise.resolve('In order to Prune Tags, pruning must also be enabled when fetching from ' + (remote !== null ? 'a remote' : 'remote(s)') + '.'); - } else if (this.gitExecutable !== null && !doesVersionMeetRequirement(this.gitExecutable.version, '2.17.0')) { - return Promise.resolve(constructIncompatibleGitVersionMessage(this.gitExecutable, '2.17.0', 'pruning tags when fetching')); - } - args.push('--prune-tags'); - } - - return this.runGitCommand(args, repo); - } - - /** - * Push a branch to a remote. - * @param repo The path of the repository. - * @param branchName The name of the branch to push. - * @param remote The remote to push the branch to. - * @param setUpstream Set the branches upstream. - * @param mode The mode of the push. - * @returns The ErrorInfo from the executed command. - */ - public pushBranch(repo: string, branchName: string, remote: string, setUpstream: boolean, mode: GitPushBranchMode) { - let args = ['push']; - args.push(remote, branchName); - if (setUpstream) args.push('--set-upstream'); - if (mode !== GitPushBranchMode.Normal) args.push('--' + mode); - - return this.runGitCommand(args, repo); - } - - /** - * Push a tag to a remote. - * @param repo The path of the repository. - * @param tagName The name of the tag to push. - * @param remote The remote to push the tag to. - * @returns The ErrorInfo from the executed command. - */ - public pushTag(repo: string, tagName: string, remote: string) { - return this.runGitCommand(['push', remote, tagName], repo); - } - - /** - * Push a branch to multiple remotes. - * @param repo The path of the repository. - * @param branchName The name of the branch to push. - * @param remotes The remotes to push the branch to. - * @param setUpstream Set the branches upstream. - * @param mode The mode of the push. - * @returns The ErrorInfo's from the executed commands. - */ - public async pushBranchToMultipleRemotes(repo: string, branchName: string, remotes: string[], setUpstream: boolean, mode: GitPushBranchMode): Promise { - if (remotes.length === 0) { - return ['No remote(s) were specified to push the branch ' + branchName + ' to.']; - } - - const results: ErrorInfo[] = []; - for (let i = 0; i < remotes.length; i++) { - const result = await this.pushBranch(repo, branchName, remotes[i], setUpstream, mode); - results.push(result); - if (result !== null) break; - } - return results; - } - - /** - * Push a tag to multiple remotes. - * @param repo The path of the repository. - * @param tagName The name of the tag to push. - * @param remote The remotes to push the tag to. - * @returns The ErrorInfo's from the executed commands. - */ - public async pushTagToMultipleRemotes(repo: string, tagName: string, remotes: string[]): Promise { - if (remotes.length === 0) { - return ['No remote(s) were specified to push the tag ' + tagName + ' to.']; - } - - const results: ErrorInfo[] = []; - for (let i = 0; i < remotes.length; i++) { - const result = await this.pushTag(repo, tagName, remotes[i]); - results.push(result); - if (result !== null) break; - } - return results; - } - - - /* Git Action Methods - Branches */ - - /** - * Checkout a branch in a repository. - * @param repo The path of the repository. - * @param branchName The name of the branch to checkout. - * @param remoteBranch The name of the remote branch to check out (if not NULL). - * @returns The ErrorInfo from the executed command. - */ - public checkoutBranch(repo: string, branchName: string, remoteBranch: string | null) { - let args = ['checkout']; - if (remoteBranch === null) args.push(branchName); - else args.push('-b', branchName, remoteBranch); - - return this.runGitCommand(args, repo); - } - - /** - * Create a branch at a commit. - * @param repo The path of the repository. - * @param branchName The name of the branch. - * @param commitHash The hash of the commit the branch should be created at. - * @param checkout Check out the branch after it is created. - * @param force Force create the branch, replacing an existing branch with the same name (if it exists). - * @returns The ErrorInfo's from the executed command(s). - */ - public async createBranch(repo: string, branchName: string, commitHash: string, checkout: boolean, force: boolean) { - const args = []; - if (checkout && !force) { - args.push('checkout', '-b'); - } else { - args.push('branch'); - if (force) { - args.push('-f'); - } - } - args.push(branchName, commitHash); - - const statuses = [await this.runGitCommand(args, repo)]; - if (statuses[0] === null && checkout && force) { - statuses.push(await this.checkoutBranch(repo, branchName, null)); - } - return statuses; - } - - /** - * Delete a branch in a repository. - * @param repo The path of the repository. - * @param branchName The name of the branch. - * @param forceDelete Should the delete be forced. - * @returns The ErrorInfo from the executed command. - */ - public deleteBranch(repo: string, branchName: string, forceDelete: boolean) { - let args = ['branch', '--delete']; - if (forceDelete) args.push('--force'); - args.push(branchName); - - return this.runGitCommand(args, repo); - } - - /** - * Delete a remote branch in a repository. - * @param repo The path of the repository. - * @param branchName The name of the branch. - * @param remote The name of the remote to delete the branch on. - * @returns The ErrorInfo from the executed command. - */ - public async deleteRemoteBranch(repo: string, branchName: string, remote: string) { - let remoteStatus = await this.runGitCommand(['push', remote, '--delete', branchName], repo); - if (remoteStatus !== null && (new RegExp('remote ref does not exist', 'i')).test(remoteStatus)) { - let trackingBranchStatus = await this.runGitCommand(['branch', '-d', '-r', remote + '/' + branchName], repo); - return trackingBranchStatus === null ? null : 'Branch does not exist on the remote, deleting the remote tracking branch ' + remote + '/' + branchName + '.\n' + trackingBranchStatus; - } - return remoteStatus; - } - - /** - * Fetch a remote branch into a local branch. - * @param repo The path of the repository. - * @param remote The name of the remote containing the remote branch. - * @param remoteBranch The name of the remote branch. - * @param localBranch The name of the local branch. - * @param force Force fetch the remote branch. - * @returns The ErrorInfo from the executed command. - */ - public fetchIntoLocalBranch(repo: string, remote: string, remoteBranch: string, localBranch: string, force: boolean) { - const args = ['fetch']; - if (force) { - args.push('-f'); - } - args.push(remote, remoteBranch + ':' + localBranch); - return this.runGitCommand(args, repo); - } - - /** - * Pull a remote branch into the current branch. - * @param repo The path of the repository. - * @param branchName The name of the remote branch. - * @param remote The name of the remote containing the remote branch. - * @param createNewCommit Is `--no-ff` enabled if a merge is required. - * @param squash Is `--squash` enabled if a merge is required. - * @returns The ErrorInfo from the executed command. - */ - public pullBranch(repo: string, branchName: string, remote: string, createNewCommit: boolean, squash: boolean) { - const args = ['pull', remote, branchName], config = getConfig(); - if (squash) { - args.push('--squash'); - } else if (createNewCommit) { - args.push('--no-ff'); - } - if (config.signCommits) { - args.push('-S'); - } - return this.runGitCommand(args, repo).then((pullStatus) => { - return pullStatus === null && squash - ? this.commitSquashIfStagedChangesExist(repo, remote + '/' + branchName, MergeActionOn.Branch, config.squashPullMessageFormat, config.signCommits) - : pullStatus; - }); - } - - /** - * Rename a branch in a repository. - * @param repo The path of the repository. - * @param oldName The old name of the branch. - * @param newName The new name of the branch. - * @returns The ErrorInfo from the executed command. - */ - public renameBranch(repo: string, oldName: string, newName: string) { - return this.runGitCommand(['branch', '-m', oldName, newName], repo); - } - - - /* Git Action Methods - Branches & Commits */ - - /** - * Merge a branch or commit into the current branch. - * @param repo The path of the repository. - * @param obj The object to be merged into the current branch. - * @param actionOn Is the merge on a branch, remote-tracking branch or commit. - * @param createNewCommit Is `--no-ff` enabled. - * @param squash Is `--squash` enabled. - * @param noCommit Is `--no-commit` enabled. - * @returns The ErrorInfo from the executed command. - */ - public merge(repo: string, obj: string, actionOn: MergeActionOn, createNewCommit: boolean, squash: boolean, noCommit: boolean) { - const args = ['merge', obj], config = getConfig(); - if (squash) { - args.push('--squash'); - } else if (createNewCommit) { - args.push('--no-ff'); - } - if (noCommit) { - args.push('--no-commit'); - } - if (config.signCommits) { - args.push('-S'); - } - return this.runGitCommand(args, repo).then((mergeStatus) => { - return mergeStatus === null && squash && !noCommit - ? this.commitSquashIfStagedChangesExist(repo, obj, actionOn, config.squashMergeMessageFormat, config.signCommits) - : mergeStatus; - }); - } - - /** - * Rebase the current branch on a branch or commit. - * @param repo The path of the repository. - * @param obj The object the current branch will be rebased onto. - * @param actionOn Is the rebase on a branch or commit. - * @param ignoreDate Is `--ignore-date` enabled. - * @param interactive Should the rebase be performed interactively. - * @returns The ErrorInfo from the executed command. - */ - public rebase(repo: string, obj: string, actionOn: RebaseActionOn, ignoreDate: boolean, interactive: boolean) { - if (interactive) { - return this.openGitTerminal( - repo, - 'rebase --interactive ' + (getConfig().signCommits ? '-S ' : '') + (actionOn === RebaseActionOn.Branch ? obj.replace(/'/g, '"\'"') : obj), - 'Rebase on "' + (actionOn === RebaseActionOn.Branch ? obj : abbrevCommit(obj)) + '"' - ); - } else { - const args = ['rebase', obj]; - if (ignoreDate) { - args.push('--ignore-date'); - } - if (getConfig().signCommits) { - args.push('-S'); - } - return this.runGitCommand(args, repo); - } - } - - - /* Git Action Methods - Branches & Tags */ - - /** - * Create an archive of a repository at a specific reference, and save to disk. - * @param repo The path of the repository. - * @param ref The reference of the revision to archive. - * @param outputFilePath The file path that the archive should be saved to. - * @param type The type of archive. - * @returns The ErrorInfo from the executed command. - */ - public archive(repo: string, ref: string, outputFilePath: string, type: 'tar' | 'zip') { - return this.runGitCommand(['archive', '--format=' + type, '-o', outputFilePath, ref], repo); - } - - - /* Git Action Methods - Commits */ - - /** - * Checkout a commit in a repository. - * @param repo The path of the repository. - * @param commitHash The hash of the commit to check out. - * @returns The ErrorInfo from the executed command. - */ - public checkoutCommit(repo: string, commitHash: string) { - return this.runGitCommand(['checkout', commitHash], repo); - } - - /** - * Cherrypick a commit in a repository. - * @param repo The path of the repository. - * @param commitHash The hash of the commit to be cherry picked. - * @param parentIndex The parent index if the commit is a merge. - * @param recordOrigin Is `-x` enabled. - * @param noCommit Is `--no-commit` enabled. - * @returns The ErrorInfo from the executed command. - */ - public cherrypickCommit(repo: string, commitHash: string, parentIndex: number, recordOrigin: boolean, noCommit: boolean) { - const args = ['cherry-pick']; - if (noCommit) { - args.push('--no-commit'); - } - if (recordOrigin) { - args.push('-x'); - } - if (getConfig().signCommits) { - args.push('-S'); - } - if (parentIndex > 0) { - args.push('-m', parentIndex.toString()); - } - args.push(commitHash); - return this.runGitCommand(args, repo); - } - - /** - * Drop a commit in a repository. - * @param repo The path of the repository. - * @param commitHash The hash of the commit to drop. - * @returns The ErrorInfo from the executed command. - */ - public dropCommit(repo: string, commitHash: string) { - const args = ['rebase']; - if (getConfig().signCommits) { - args.push('-S'); - } - args.push('--onto', commitHash + '^', commitHash); - return this.runGitCommand(args, repo); - } - - /** - * Reset the current branch to a specified commit. - * @param repo The path of the repository. - * @param commit The hash of the commit that the current branch should be reset to. - * @param resetMode The mode of the reset. - * @returns The ErrorInfo from the executed command. - */ - public resetToCommit(repo: string, commit: string, resetMode: GitResetMode) { - return this.runGitCommand(['reset', '--' + resetMode, commit], repo); - } - - /** - * Revert a commit in a repository. - * @param repo The path of the repository. - * @param commitHash The hash of the commit to revert. - * @param parentIndex The parent index if the commit is a merge. - * @returns The ErrorInfo from the executed command. - */ - public revertCommit(repo: string, commitHash: string, parentIndex: number) { - const args = ['revert', '--no-edit']; - if (getConfig().signCommits) { - args.push('-S'); - } - if (parentIndex > 0) { - args.push('-m', parentIndex.toString()); - } - args.push(commitHash); - return this.runGitCommand(args, repo); - } - - - /* Git Action Methods - Config */ - - /** - * Set a configuration value for a repository. - * @param repo The path of the repository. - * @param key The key to be set. - * @param value The value to be set. - * @param location The location where the configuration value should be set. - * @returns The ErrorInfo from the executed command. - */ - public setConfigValue(repo: string, key: string, value: string, location: GitConfigLocation) { - return this.runGitCommand(['config', '--' + location, key, value], repo); - } - - /** - * Unset a configuration value for a repository. - * @param repo The path of the repository. - * @param key The key to be unset. - * @param location The location where the configuration value should be unset. - * @returns The ErrorInfo from the executed command. - */ - public unsetConfigValue(repo: string, key: string, location: GitConfigLocation) { - return this.runGitCommand(['config', '--' + location, '--unset-all', key], repo); - } - - - /* Git Action Methods - Uncommitted */ - - /** - * Clean the untracked files in a repository. - * @param repo The path of the repository. - * @param directories Is `-d` enabled. - * @returns The ErrorInfo from the executed command. - */ - public cleanUntrackedFiles(repo: string, directories: boolean) { - return this.runGitCommand(['clean', '-f' + (directories ? 'd' : '')], repo); - } - - - /* Git Action Methods - Stash */ - - /** - * Apply a stash in a repository. - * @param repo The path of the repository. - * @param selector The selector of the stash. - * @param reinstateIndex Is `--index` enabled. - * @returns The ErrorInfo from the executed command. - */ - public applyStash(repo: string, selector: string, reinstateIndex: boolean) { - let args = ['stash', 'apply']; - if (reinstateIndex) args.push('--index'); - args.push(selector); - - return this.runGitCommand(args, repo); - } - - /** - * Create a branch from a stash. - * @param repo The path of the repository. - * @param selector The selector of the stash. - * @param branchName The name of the branch to be created. - * @returns The ErrorInfo from the executed command. - */ - public branchFromStash(repo: string, selector: string, branchName: string) { - return this.runGitCommand(['stash', 'branch', branchName, selector], repo); - } - - /** - * Drop a stash in a repository. - * @param repo The path of the repository. - * @param selector The selector of the stash. - * @returns The ErrorInfo from the executed command. - */ - public dropStash(repo: string, selector: string) { - return this.runGitCommand(['stash', 'drop', selector], repo); - } - - /** - * Pop a stash in a repository. - * @param repo The path of the repository. - * @param selector The selector of the stash. - * @param reinstateIndex Is `--index` enabled. - * @returns The ErrorInfo from the executed command. - */ - public popStash(repo: string, selector: string, reinstateIndex: boolean) { - let args = ['stash', 'pop']; - if (reinstateIndex) args.push('--index'); - args.push(selector); - - return this.runGitCommand(args, repo); - } - - /** - * Push the uncommitted changes to a stash. - * @param repo The path of the repository. - * @param message The message of the stash. - * @param includeUntracked Is `--include-untracked` enabled. - * @returns The ErrorInfo from the executed command. - */ - public pushStash(repo: string, message: string, includeUntracked: boolean): Promise { - if (this.gitExecutable === null) { - return Promise.resolve(UNABLE_TO_FIND_GIT_MSG); - } else if (!doesVersionMeetRequirement(this.gitExecutable.version, '2.13.2')) { - return Promise.resolve(constructIncompatibleGitVersionMessage(this.gitExecutable, '2.13.2')); - } - - let args = ['stash', 'push']; - if (includeUntracked) args.push('--include-untracked'); - if (message !== '') args.push('--message', message); - return this.runGitCommand(args, repo); - } - - - /* Public Utils */ - - /** - * Opens an external directory diff for the specified commits. - * @param repo The path of the repository. - * @param fromHash The commit hash the diff is from. - * @param toHash The commit hash the diff is to. - * @param isGui Is the external diff tool GUI based. - * @returns The ErrorInfo from the executed command. - */ - public openExternalDirDiff(repo: string, fromHash: string, toHash: string, isGui: boolean) { - return new Promise((resolve) => { - if (this.gitExecutable === null) { - resolve(UNABLE_TO_FIND_GIT_MSG); - } else { - const args = ['difftool', '--dir-diff']; - if (isGui) { - args.push('-g'); - } - if (fromHash === toHash) { - if (toHash === UNCOMMITTED) { - args.push('HEAD'); - } else { - args.push(toHash + '^..' + toHash); - } - } else { - if (toHash === UNCOMMITTED) { - args.push(fromHash); - } else { - args.push(fromHash + '..' + toHash); - } - } - if (isGui) { - this.logger.log('External diff tool is being opened (' + args[args.length - 1] + ')'); - this.runGitCommand(args, repo).then((errorInfo) => { - this.logger.log('External diff tool has exited (' + args[args.length - 1] + ')'); - if (errorInfo !== null) { - const errorMessage = errorInfo.replace(EOL_REGEX, ' '); - this.logger.logError(errorMessage); - showErrorMessage(errorMessage); - } - }); - } else { - openGitTerminal(repo, this.gitExecutable.path, args.join(' '), 'Open External Directory Diff'); - } - setTimeout(() => resolve(null), 1500); - } - }); - } - - /** - * Open a new terminal, set up the Git executable, and optionally run a command. - * @param repo The path of the repository. - * @param command The command to run. - * @param name The name for the terminal. - * @returns The ErrorInfo from opening the terminal. - */ - public openGitTerminal(repo: string, command: string | null, name: string) { - return new Promise((resolve) => { - if (this.gitExecutable === null) { - resolve(UNABLE_TO_FIND_GIT_MSG); - } else { - openGitTerminal(repo, this.gitExecutable.path, command, name); - setTimeout(() => resolve(null), 1000); - } - }); - } - - - /* Private Data Providers */ - - /** - * Get the branches in a repository. - * @param repo The path of the repository. - * @param showRemoteBranches Are remote branches shown. - * @param hideRemotes An array of hidden remotes. - * @returns The branch data. - */ - private getBranches(repo: string, showRemoteBranches: boolean, hideRemotes: ReadonlyArray) { - let args = ['branch']; - if (showRemoteBranches) args.push('-a'); - args.push('--no-color'); - - const hideRemotePatterns = hideRemotes.map((remote) => 'remotes/' + remote + '/'); - const showRemoteHeads = getConfig().showRemoteHeads; - - return this.spawnGit(args, repo, (stdout) => { - let branchData: GitBranchData = { branches: [], head: null, error: null }; - let lines = stdout.split(EOL_REGEX); - for (let i = 0; i < lines.length - 1; i++) { - let name = lines[i].substring(2).split(' -> ')[0]; - if (INVALID_BRANCH_REGEXP.test(name) || hideRemotePatterns.some((pattern) => name.startsWith(pattern)) || (!showRemoteHeads && REMOTE_HEAD_BRANCH_REGEXP.test(name))) { - continue; - } - - if (lines[i][0] === '*') { - branchData.head = name; - branchData.branches.unshift(name); - } else { - branchData.branches.push(name); - } - } - return branchData; - }); - } - - /** - * Get the base commit details for the Commit Details View. - * @param repo The path of the repository. - * @param commitHash The hash of the commit open in the Commit Details View. - * @returns The base commit details. - */ - private getCommitDetailsBase(repo: string, commitHash: string) { - return this.spawnGit(['-c', 'log.showSignature=false', 'show', '--quiet', commitHash, '--format=' + this.gitFormatCommitDetails], repo, (stdout): DeepWriteable => { - const commitInfo = stdout.split(GIT_LOG_SEPARATOR); - return { - hash: commitInfo[0], - parents: commitInfo[1] !== '' ? commitInfo[1].split(' ') : [], - author: commitInfo[2], - authorEmail: commitInfo[3], - authorDate: parseInt(commitInfo[4]), - committer: commitInfo[5], - committerEmail: commitInfo[6], - committerDate: parseInt(commitInfo[7]), - signature: ['G', 'U', 'X', 'Y', 'R', 'E', 'B'].includes(commitInfo[8]) - ? { - key: commitInfo[10].trim(), - signer: commitInfo[9].trim(), - status: commitInfo[8] - } - : null, - body: removeTrailingBlankLines(commitInfo.slice(11).join(GIT_LOG_SEPARATOR).split(EOL_REGEX)).join('\n'), - fileChanges: [] - }; - }); - } - - /** - * Get the configuration list of a repository. - * @param repo The path of the repository. - * @param location The location of the configuration to be listed. - * @returns A set of key-value pairs of Git configuration records. - */ - private getConfigList(repo: string, location?: GitConfigLocation): Promise { - const args = ['--no-pager', 'config', '--list', '-z', '--includes']; - if (location) { - args.push('--' + location); - } - - return this.spawnGit(args, repo, (stdout) => { - const configs: GitConfigSet = {}, keyValuePairs = stdout.split('\0'); - const numPairs = keyValuePairs.length - 1; - let comps, key; - for (let i = 0; i < numPairs; i++) { - comps = keyValuePairs[i].split(EOL_REGEX); - key = comps.shift()!; - configs[key] = comps.join('\n'); - } - return configs; - }).catch((errorMessage) => { - if (typeof errorMessage === 'string') { - const message = errorMessage.toLowerCase(); - if (message.startsWith('fatal: unable to read config file') && message.endsWith('no such file or directory')) { - // If the Git command failed due to the configuration file not existing, return an empty list instead of throwing the exception - return {}; - } - } else { - errorMessage = 'An unexpected error occurred while spawning the Git child process.'; - } - throw errorMessage; - }); - } - - /** - * Get the diff `--name-status` records. - * @param repo The path of the repository. - * @param fromHash The revision the diff is from. - * @param toHash The revision the diff is to. - * @returns An array of `--name-status` records. - */ - private getDiffNameStatus(repo: string, fromHash: string, toHash: string) { - return this.execDiff(repo, fromHash, toHash, '--name-status').then((output) => { - let records: DiffNameStatusRecord[] = [], i = 0; - while (i < output.length && output[i] !== '') { - let type = output[i][0]; - if (type === GitFileStatus.Added || type === GitFileStatus.Deleted || type === GitFileStatus.Modified) { - // Add, Modify, or Delete - let p = getPathFromStr(output[i + 1]); - records.push({ type: type, oldFilePath: p, newFilePath: p }); - i += 2; - } else if (type === GitFileStatus.Renamed) { - // Rename - records.push({ type: type, oldFilePath: getPathFromStr(output[i + 1]), newFilePath: getPathFromStr(output[i + 2]) }); - i += 3; - } else { - break; - } - } - return records; - }); - } - - /** - * Get the diff `--numstat` records. - * @param repo The path of the repository. - * @param fromHash The revision the diff is from. - * @param toHash The revision the diff is to. - * @returns An array of `--numstat` records. - */ - private getDiffNumStat(repo: string, fromHash: string, toHash: string) { - return this.execDiff(repo, fromHash, toHash, '--numstat').then((output) => { - let records: DiffNumStatRecord[] = [], i = 0; - while (i < output.length && output[i] !== '') { - let fields = output[i].split('\t'); - if (fields.length !== 3) break; - if (fields[2] !== '') { - // Add, Modify, or Delete - records.push({ filePath: getPathFromStr(fields[2]), additions: parseInt(fields[0]), deletions: parseInt(fields[1]) }); - i += 1; - } else { - // Rename - records.push({ filePath: getPathFromStr(output[i + 2]), additions: parseInt(fields[0]), deletions: parseInt(fields[1]) }); - i += 3; - } - } - return records; - }); - } - - /** - * Get the raw commits in a repository. - * @param repo The path of the repository. - * @param branches The list of branch heads to display, or NULL (show all). - * @param num The maximum number of commits to return. - * @param includeTags Include commits only referenced by tags. - * @param includeRemotes Include remote branches. - * @param includeCommitsMentionedByReflogs Include commits mentioned by reflogs. - * @param onlyFollowFirstParent Only follow the first parent of commits. - * @param order The order for commits to be returned. - * @param remotes An array of the known remotes. - * @param hideRemotes An array of hidden remotes. - * @param stashes An array of all stashes in the repository. - * @returns An array of commits. - */ - private getLog(repo: string, branches: ReadonlyArray | null, num: number, includeTags: boolean, includeRemotes: boolean, includeCommitsMentionedByReflogs: boolean, onlyFollowFirstParent: boolean, order: CommitOrdering, remotes: ReadonlyArray, hideRemotes: ReadonlyArray, stashes: ReadonlyArray) { - const args = ['-c', 'log.showSignature=false', 'log', '--max-count=' + num, '--format=' + this.gitFormatLog, '--' + order + '-order']; - if (onlyFollowFirstParent) { - args.push('--first-parent'); - } - if (branches !== null) { - for (let i = 0; i < branches.length; i++) { - args.push(branches[i]); - } - } else { - // Show All - args.push('--branches'); - if (includeTags) args.push('--tags'); - if (includeCommitsMentionedByReflogs) args.push('--reflog'); - if (includeRemotes) { - if (hideRemotes.length === 0) { - args.push('--remotes'); - } else { - remotes.filter((remote) => !hideRemotes.includes(remote)).forEach((remote) => { - args.push('--glob=refs/remotes/' + remote); - }); - } - } - - // Add the unique list of base hashes of stashes, so that commits only referenced by stashes are displayed - const stashBaseHashes = stashes.map((stash) => stash.baseHash); - stashBaseHashes.filter((hash, index) => stashBaseHashes.indexOf(hash) === index).forEach((hash) => args.push(hash)); - - args.push('HEAD'); - } - args.push('--'); - - return this.spawnGit(args, repo, (stdout) => { - let lines = stdout.split(EOL_REGEX); - let commits: GitCommitRecord[] = []; - for (let i = 0; i < lines.length - 1; i++) { - let line = lines[i].split(GIT_LOG_SEPARATOR); - if (line.length !== 6) break; - commits.push({ hash: line[0], parents: line[1] !== '' ? line[1].split(' ') : [], author: line[2], email: line[3], date: parseInt(line[4]), message: line[5] }); - } - return commits; - }); - } - - /** - * Get the result in a CI/CDs. - * @param cicdConfigs CI/CD configuration. - * @returns The references data. - */ - private async getCICDs(cicdConfigs: CICDConfig[] | null) { - if (cicdConfigs === null) { - return ''; - } - - return await Promise.all( - cicdConfigs.map(async cicdConfig => { - if (cicdConfig.provider === CICDProvider.GitHubV3) { - - const match1 = cicdConfig.gitUrl.match(/^(https?:\/\/|git@)((?=[^/]+@)[^@]+@|(?![^/]+@))([^/:]+)/); - let hostRootUrl = match1 !== null ? 'https://api.' + match1[3] : ''; - - const match2 = cicdConfig.gitUrl.match(/^(https?:\/\/|git@)[^/:]+[/:]([^/]+)\/([^/]*?)(.git|)$/); - let sourceOwner = match2 !== null ? match2[2] : ''; - let sourceRepo = match2 !== null ? match2[3] : ''; - - const apiRoot = `${hostRootUrl}`; - const cicdRootPath = `/repos/${sourceOwner}/${sourceRepo.replace(/\//g, '%2F')}/actions/runs?per_page=100`; - - let config: request.RequestPromiseOptions = { - method: 'GET', - headers: { - 'Authorization': `token ${cicdConfig.glToken}`, - 'Accept': 'application/vnd.github.v3+json', - 'User-Agent': 'vscode-git-graph' - } - }; - if (cicdConfig.glToken === '' && config.headers) { - delete config.headers['Authorization']; - } - - config.transform = (body, response) => { - try { - let res: any = JSON.parse(body); - let last = 1; - let next = 2; - if (typeof response.headers['link'] === 'string') { - next = 3; - const DELIM_LINKS = ','; - const DELIM_LINK_PARAM = ';'; - let links = response.headers['link'].split(DELIM_LINKS); - links.forEach(link => { - let segments = link.split(DELIM_LINK_PARAM); - - let linkPart = segments[0].trim(); - if (!linkPart.startsWith('<') || !linkPart.endsWith('>')) { - return true; - } - linkPart = linkPart.substring(1, linkPart.length - 1); - let match3 = linkPart.match(/&page=(\d+).*$/); - let linkPage = match3 !== null ? match3[1] : '0'; - - for (let i = 1; i < segments.length; i++) { - let rel = segments[i].trim().split('='); - if (rel.length < 2) { - continue; - } - - let relValue = rel[1]; - if (relValue.startsWith('"') && relValue.endsWith('"')) { - relValue = relValue.substring(1, relValue.length - 1); - } - - if (relValue === 'last') { - last = parseInt(linkPage); - } else if (relValue === 'next') { - next = parseInt(linkPage); - } - } - }); - } - if (typeof res['workflow_runs'] !== 'undefined' && res['workflow_runs'].length >= 1) { // url found - let ret: GitCICDData[] = res['workflow_runs'].map( (elm: { [x: string]: any; }) => { - return { - id: elm['id'], - status: elm['conclusion'], - ref: elm['name'], - sha: elm['head_sha'], - web_url: elm['html_url'], - created_at: elm['created_at'], - updated_at: elm['updated_at'] - }; - }); - if (next === 2) { - return { x_total_pages: last, ret: ret }; - } - return ret; - } - return { x_total_pages: 0, ret: 'error' }; - } catch (e) { - return { x_total_pages: 0, ret: e }; - } - }; - return request(`${apiRoot}${cicdRootPath}`, config).then(async (result1st) => { - let promises = []; - promises.push(result1st.ret); - for (let i = 1; i < result1st.x_total_pages; i++) { - promises.push(request(`${apiRoot}${cicdRootPath}&page=${i + 1}`, config)); - } - return await Promise.all(promises); - }).then((resultAll) => { - let retAll: GitCICDData[] = []; - for (let i = 0; i < resultAll.length; i++) { - retAll = retAll.concat(resultAll[i]); - } - return retAll; - }); - } - if (cicdConfig.provider === CICDProvider.GitLabV4) { - - const match1 = cicdConfig.gitUrl.match(/^(https?:\/\/|git@)((?=[^/]+@)[^@]+@|(?![^/]+@))([^/:]+)/); - let hostRootUrl = match1 !== null ? 'https://' + match1[3] : ''; - - const match2 = cicdConfig.gitUrl.match(/^(https?:\/\/|git@)[^/:]+[/:]([^/]+)\/([^/]*?)(.git|)$/); - let sourceOwner = match2 !== null ? match2[2] : ''; - let sourceRepo = match2 !== null ? match2[3] : ''; - - const apiRoot = `${hostRootUrl}/api/v4`; - const cicdRootPath = `/projects/${sourceOwner}%2F${sourceRepo.replace(/\//g, '%2F')}/pipelines?per_page=100`; - - const config: request.RequestPromiseOptions = { - method: 'GET', - headers: { - 'PRIVATE-TOKEN': cicdConfig.glToken, - 'User-Agent': 'vscode-git-graph' - } - }; - - config.transform = (body, response) => { - try { - if (typeof response.headers['x-page'] === 'string' && typeof response.headers['x-total-pages'] === 'string' && typeof response.headers['x-total'] === 'string') { - let res: any = JSON.parse(body); - if (parseInt(response.headers['x-total']) !== 0 && res.length && res[0].id) { // url found - let ret: GitCICDData[] = res; - if (parseInt(response.headers['x-page']) === 1) { - return { x_total_pages: parseInt(response.headers['x-total-pages']), ret: ret }; - } - return ret; - } - } - return { x_total_pages: 0, ret: 'error' }; - } catch (e) { - return { x_total_pages: 0, ret: e }; - } - }; - return request(`${apiRoot}${cicdRootPath}`, config).then(async (result1st) => { - let promises = []; - promises.push(result1st.ret); - for (let i = 1; i < result1st.x_total_pages; i++) { - promises.push(request(`${apiRoot}${cicdRootPath}&page=${i + 1}`, config)); - } - return await Promise.all(promises); - }).then((resultAll) => { - let retAll: GitCICDData[] = []; - for (let i = 0; i < resultAll.length; i++) { - retAll = retAll.concat(resultAll[i]); - } - return retAll; - }); - } - }) - ).then((resultAll2) => { - let retAll: GitCICDData[] = []; - resultAll2.forEach(resultList => { - resultList?.forEach(result => { - retAll = retAll.concat(result); - }); - }); - return retAll; - }); - } - /** - * Get the references in a repository. - * @param repo The path of the repository. - * @param showRemoteBranches Are remote branches shown. - * @param showRemoteHeads Are remote heads shown. - * @param hideRemotes An array of hidden remotes. - * @returns The references data. - */ - private getRefs(repo: string, showRemoteBranches: boolean, showRemoteHeads: boolean, hideRemotes: ReadonlyArray) { - let args = ['show-ref']; - if (!showRemoteBranches) args.push('--heads', '--tags'); - args.push('-d', '--head'); - - const hideRemotePatterns = hideRemotes.map((remote) => 'refs/remotes/' + remote + '/'); - - return this.spawnGit(args, repo, (stdout) => { - let refData: GitRefData = { head: null, heads: [], tags: [], remotes: [] }; - let lines = stdout.split(EOL_REGEX); - for (let i = 0; i < lines.length - 1; i++) { - let line = lines[i].split(' '); - if (line.length < 2) continue; - - let hash = line.shift()!; - let ref = line.join(' '); - - if (ref.startsWith('refs/heads/')) { - refData.heads.push({ hash: hash, name: ref.substring(11) }); - } else if (ref.startsWith('refs/tags/')) { - let annotated = ref.endsWith('^{}'); - refData.tags.push({ hash: hash, name: (annotated ? ref.substring(10, ref.length - 3) : ref.substring(10)), annotated: annotated }); - } else if (ref.startsWith('refs/remotes/')) { - if (!hideRemotePatterns.some((pattern) => ref.startsWith(pattern)) && (showRemoteHeads || !ref.endsWith('/HEAD'))) { - refData.remotes.push({ hash: hash, name: ref.substring(13) }); - } - } else if (ref === 'HEAD') { - refData.head = hash; - } - } - return refData; - }); - } - - /** - * Get the stashes in a repository. - * @param repo The path of the repository. - * @returns An array of stashes. - */ - private getStashes(repo: string) { - return this.spawnGit(['reflog', '--format=' + this.gitFormatStash, 'refs/stash', '--'], repo, (stdout) => { - let lines = stdout.split(EOL_REGEX); - let stashes: GitStash[] = []; - for (let i = 0; i < lines.length - 1; i++) { - let line = lines[i].split(GIT_LOG_SEPARATOR); - if (line.length !== 7 || line[1] === '') continue; - let parentHashes = line[1].split(' '); - stashes.push({ - hash: line[0], - baseHash: parentHashes[0], - untrackedFilesHash: parentHashes.length === 3 ? parentHashes[2] : null, - selector: line[2], - author: line[3], - email: line[4], - date: parseInt(line[5]), - message: line[6] - }); - } - return stashes; - }).catch(() => []); - } - - /** - * Get the names of the remotes of a repository. - * @param repo The path of the repository. - * @returns An array of remote names. - */ - private getRemotes(repo: string) { - return this.spawnGit(['remote'], repo, (stdout) => { - let lines = stdout.split(EOL_REGEX); - lines.pop(); - return lines; - }); - } - - /** - * Get the number of uncommitted changes in a repository. - * @param repo The path of the repository. - * @returns The number of uncommitted changes. - */ - private getUncommittedChanges(repo: string) { - return this.spawnGit(['status', '--untracked-files=' + (getConfig().showUntrackedFiles ? 'all' : 'no'), '--porcelain'], repo, (stdout) => { - const numLines = stdout.split(EOL_REGEX).length; - return numLines > 1 ? numLines - 1 : 0; - }); - } - - /** - * Get the untracked and deleted files that are not staged or committed. - * @param repo The path of the repository. - * @returns The untracked and deleted files. - */ - private getStatus(repo: string) { - return this.spawnGit(['status', '-s', '--untracked-files=' + (getConfig().showUntrackedFiles ? 'all' : 'no'), '--porcelain', '-z'], repo, (stdout) => { - let output = stdout.split('\0'), i = 0; - let status: GitStatusFiles = { deleted: [], untracked: [] }; - let path = '', c1 = '', c2 = ''; - while (i < output.length && output[i] !== '') { - if (output[i].length < 4) break; - path = output[i].substring(3); - c1 = output[i].substring(0, 1); - c2 = output[i].substring(1, 2); - if (c1 === 'D' || c2 === 'D') status.deleted.push(path); - else if (c1 === '?' || c2 === '?') status.untracked.push(path); - - if (c1 === 'R' || c2 === 'R' || c1 === 'C' || c2 === 'C') { - // Renames or copies - i += 2; - } else { - i += 1; - } - } - return status; - }); - } - - - /* Private Utils */ - - /** - * Check if there are staged changes that resulted from a squash merge, and if so, commit them. - * @param repo The path of the repository. - * @param obj The object being squash merged into the current branch. - * @param actionOn Is the merge on a branch, remote-tracking branch or commit. - * @param squashMessageFormat The format to be used in the commit message of the squash. - * @returns The ErrorInfo from the executed command. - */ - private commitSquashIfStagedChangesExist(repo: string, obj: string, actionOn: MergeActionOn, squashMessageFormat: SquashMessageFormat, signCommits: boolean): Promise { - return this.areStagedChanges(repo).then((changes) => { - if (changes) { - const args = ['commit']; - if (signCommits) { - args.push('-S'); - } - if (squashMessageFormat === SquashMessageFormat.Default) { - args.push('-m', 'Merge ' + actionOn.toLowerCase() + ' \'' + obj + '\''); - } else { - args.push('--no-edit'); - } - return this.runGitCommand(args, repo); - } else { - return null; - } - }); - } - - /** - * Get the diff between two revisions. - * @param repo The path of the repository. - * @param fromHash The revision the diff is from. - * @param toHash The revision the diff is to. - * @param arg Sets the data reported from the diff. - * @returns The diff output. - */ - private execDiff(repo: string, fromHash: string, toHash: string, arg: '--numstat' | '--name-status') { - let args: string[]; - if (fromHash === toHash) { - args = ['diff-tree', arg, '-r', '--root', '--find-renames', '--diff-filter=AMDR', '-z', fromHash]; - } else { - args = ['diff', arg, '--find-renames', '--diff-filter=AMDR', '-z', fromHash]; - if (toHash !== '') args.push(toHash); - } - - return this.spawnGit(args, repo, (stdout) => { - let lines = stdout.split('\0'); - if (fromHash === toHash) lines.shift(); - return lines; - }); - } - - /** - * Run a Git command (typically for a Git Graph View action). - * @param args The arguments to pass to Git. - * @param repo The repository to run the command in. - * @returns The returned ErrorInfo (suitable for being sent to the Git Graph View). - */ - private runGitCommand(args: string[], repo: string): Promise { - return this._spawnGit(args, repo, () => null).catch((errorMessage: string) => errorMessage); - } - - /** - * Spawn Git, with the return value resolved from `stdout` as a string. - * @param args The arguments to pass to Git. - * @param repo The repository to run the command in. - * @param resolveValue A callback invoked to resolve the data from `stdout`. - */ - private spawnGit(args: string[], repo: string, resolveValue: { (stdout: string): T }) { - return this._spawnGit(args, repo, (stdout) => resolveValue(stdout.toString())); - } - - /** - * Spawn Git, with the return value resolved from `stdout` as a buffer. - * @param args The arguments to pass to Git. - * @param repo The repository to run the command in. - * @param resolveValue A callback invoked to resolve the data from `stdout`. - */ - private _spawnGit(args: string[], repo: string, resolveValue: { (stdout: Buffer): T }) { - return new Promise((resolve, reject) => { - if (this.gitExecutable === null) return reject(UNABLE_TO_FIND_GIT_MSG); - - resolveSpawnOutput(cp.spawn(this.gitExecutable.path, args, { - cwd: repo, - env: Object.assign({}, process.env, this.askpassEnv) - })).then((values) => { - let status = values[0], stdout = values[1]; - if (status.code === 0) { - resolve(resolveValue(stdout)); - } else { - reject(getErrorMessage(status.error, stdout, values[2])); - } - }); - - this.logger.logCmd('git', args); - }); - } -} - - -/** - * Generates the file changes from the diff output and status information. - * @param nameStatusRecords The `--name-status` records. - * @param numStatRecords The `--numstat` records. - * @param status The deleted and untracked files. - * @returns An array of file changes. - */ -function generateFileChanges(nameStatusRecords: DiffNameStatusRecord[], numStatRecords: DiffNumStatRecord[], status: GitStatusFiles | null) { - let fileChanges: Writeable[] = [], fileLookup: { [file: string]: number } = {}, i = 0; - - for (i = 0; i < nameStatusRecords.length; i++) { - fileLookup[nameStatusRecords[i].newFilePath] = fileChanges.length; - fileChanges.push({ oldFilePath: nameStatusRecords[i].oldFilePath, newFilePath: nameStatusRecords[i].newFilePath, type: nameStatusRecords[i].type, additions: null, deletions: null }); - } - - if (status !== null) { - let filePath; - for (i = 0; i < status.deleted.length; i++) { - filePath = getPathFromStr(status.deleted[i]); - if (typeof fileLookup[filePath] === 'number') { - fileChanges[fileLookup[filePath]].type = GitFileStatus.Deleted; - } else { - fileChanges.push({ oldFilePath: filePath, newFilePath: filePath, type: GitFileStatus.Deleted, additions: null, deletions: null }); - } - } - for (i = 0; i < status.untracked.length; i++) { - filePath = getPathFromStr(status.untracked[i]); - fileChanges.push({ oldFilePath: filePath, newFilePath: filePath, type: GitFileStatus.Untracked, additions: null, deletions: null }); - } - } - - for (i = 0; i < numStatRecords.length; i++) { - if (typeof fileLookup[numStatRecords[i].filePath] === 'number') { - fileChanges[fileLookup[numStatRecords[i].filePath]].additions = numStatRecords[i].additions; - fileChanges[fileLookup[numStatRecords[i].filePath]].deletions = numStatRecords[i].deletions; - } - } - - return fileChanges; -} - -/** - * Get the specified config value from a set of key-value config pairs. - * @param configs A set key-value pairs of Git configuration records. - * @param key The key of the desired config. - * @returns The value for `key` if it exists, otherwise NULL. - */ -function getConfigValue(configs: GitConfigSet, key: string) { - return typeof configs[key] !== 'undefined' ? configs[key] : null; -} - -/** - * Produce a suitable error message from a spawned Git command that terminated with an erroneous status code. - * @param error An error generated by JavaScript (optional). - * @param stdoutBuffer A buffer containing the data outputted to `stdout`. - * @param stderr A string containing the data outputted to `stderr`. - * @returns A suitable error message. - */ -function getErrorMessage(error: Error | null, stdoutBuffer: Buffer, stderr: string) { - let stdout = stdoutBuffer.toString(), lines: string[]; - if (stdout !== '' || stderr !== '') { - lines = (stderr + stdout).split(EOL_REGEX); - lines.pop(); - } else if (error) { - lines = error.message.split(EOL_REGEX); - } else { - lines = []; - } - return lines.join('\n'); -} - -/** - * Remove trailing blank lines from an array of lines. - * @param lines The array of lines. - * @returns The same array. - */ -function removeTrailingBlankLines(lines: string[]) { - while (lines.length > 0 && lines[lines.length - 1] === '') { - lines.pop(); - } - return lines; -} - -/** - * Get all the unique strings from an array of strings. - * @param items The array of strings with duplicates. - * @returns An array of unique strings. - */ -function unique(items: ReadonlyArray) { - const uniqueItems: { [item: string]: true } = {}; - items.forEach((item) => uniqueItems[item] = true); - return Object.keys(uniqueItems); -} - - -/* Types */ - -interface DiffNameStatusRecord { - type: GitFileStatus; - oldFilePath: string; - newFilePath: string; -} - -interface DiffNumStatRecord { - filePath: string; - additions: number; - deletions: number; -} - -interface GitBranchData { - branches: string[]; - head: string | null; - error: ErrorInfo; -} - -interface GitCommitRecord { - hash: string; - parents: string[]; - author: string; - email: string; - date: number; - message: string; -} - -interface GitCommitData { - commits: GitCommit[]; - head: string | null; - tags: string[]; - moreCommitsAvailable: boolean; - error: ErrorInfo; -} - -export interface GitCommitDetailsData { - commitDetails: GitCommitDetails | null; - error: ErrorInfo; -} - -interface GitCommitComparisonData { - fileChanges: GitFileChange[]; - error: ErrorInfo; -} - -type GitConfigSet = { [key: string]: string }; - -interface GitRef { - hash: string; - name: string; -} - -interface GitRefTag extends GitRef { - annotated: boolean; -} - -interface GitRefData { - head: string | null; - heads: GitRef[]; - tags: GitRefTag[]; - remotes: GitRef[]; -} - -interface GitRepoInfo extends GitBranchData { - remotes: string[]; - stashes: GitStash[]; -} - -interface GitRepoConfigData { - config: GitRepoConfig | null; - error: ErrorInfo; -} - -interface GitStatusFiles { - deleted: string[]; - untracked: string[]; -} - -interface GitTagDetailsData { - tagHash: string; - name: string; - email: string; - date: number; - message: string; - error: ErrorInfo; -} +import * as cp from 'child_process'; +import * as fs from 'fs'; +import { decode, encodingExists } from 'iconv-lite'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { AskpassEnvironment, AskpassManager } from './askpass/askpassManager'; +import { getConfig } from './config'; +import { Logger } from './logger'; +import { CICDConfig, CICDProvider, CommitOrdering, DateType, DeepWriteable, ErrorInfo, GitCICDData, GitCommit, GitCommitDetails, GitCommitStash, GitConfigLocation, GitFileChange, GitFileStatus, GitPushBranchMode, GitRepoConfig, GitRepoConfigBranches, GitResetMode, GitSignatureStatus, GitStash, MergeActionOn, RebaseActionOn, SquashMessageFormat, TagType, Writeable } from './types'; +import { GitExecutable, UNABLE_TO_FIND_GIT_MSG, UNCOMMITTED, abbrevCommit, constructIncompatibleGitVersionMessage, doesVersionMeetRequirement, getPathFromStr, getPathFromUri, openGitTerminal, pathWithTrailingSlash, realpath, resolveSpawnOutput, showErrorMessage } from './utils'; +import { Disposable } from './utils/disposable'; +import { Event } from './utils/event'; +import * as request from 'request-promise'; + +const DRIVE_LETTER_PATH_REGEX = /^[a-z]:\//; +const EOL_REGEX = /\r\n|\r|\n/g; +const INVALID_BRANCH_REGEXP = /^\(.* .*\)$/; +const REMOTE_HEAD_BRANCH_REGEXP = /^remotes\/.*\/HEAD$/; +const GIT_LOG_SEPARATOR = 'XX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPb'; + +export const GIT_CONFIG = { + DIFF: { + GUI_TOOL: 'diff.guitool', + TOOL: 'diff.tool' + }, + REMOTE: { + PUSH_DEFAULT: 'remote.pushdefault' + }, + USER: { + EMAIL: 'user.email', + NAME: 'user.name' + } +}; + +/** + * Interfaces Git Graph with the Git executable to provide all Git integrations. + */ +export class DataSource extends Disposable { + private readonly logger: Logger; + private readonly askpassEnv: AskpassEnvironment; + private gitExecutable!: GitExecutable | null; + private gitExecutableSupportsGpgInfo!: boolean; + private gitFormatCommitDetails!: string; + private gitFormatLog!: string; + private gitFormatStash!: string; + + /** + * Creates the Git Graph Data Source. + * @param gitExecutable The Git executable available to Git Graph at startup. + * @param onDidChangeGitExecutable The Event emitting the Git executable for Git Graph to use. + * @param logger The Git Graph Logger instance. + */ + constructor(gitExecutable: GitExecutable | null, onDidChangeConfiguration: Event, onDidChangeGitExecutable: Event, logger: Logger) { + super(); + this.logger = logger; + this.setGitExecutable(gitExecutable); + + const askpassManager = new AskpassManager(); + this.askpassEnv = askpassManager.getEnv(); + + this.registerDisposables( + onDidChangeConfiguration((event) => { + if ( + event.affectsConfiguration('git-graph.date.type') || event.affectsConfiguration('git-graph.dateType') || + event.affectsConfiguration('git-graph.repository.commits.showSignatureStatus') || event.affectsConfiguration('git-graph.showSignatureStatus') || + event.affectsConfiguration('git-graph.repository.useMailmap') || event.affectsConfiguration('git-graph.useMailmap') + ) { + this.generateGitCommandFormats(); + } + }), + onDidChangeGitExecutable((gitExecutable) => { + this.setGitExecutable(gitExecutable); + }), + askpassManager + ); + } + + /** + * Check if the Git executable is unknown. + * @returns TRUE => Git executable is unknown, FALSE => Git executable is known. + */ + public isGitExecutableUnknown() { + return this.gitExecutable === null; + } + + /** + * Set the Git executable used by the DataSource. + * @param gitExecutable The Git executable. + */ + public setGitExecutable(gitExecutable: GitExecutable | null) { + this.gitExecutable = gitExecutable; + this.gitExecutableSupportsGpgInfo = gitExecutable !== null ? doesVersionMeetRequirement(gitExecutable.version, '2.4.0') : false; + this.generateGitCommandFormats(); + } + + /** + * Generate the format strings used by various Git commands. + */ + private generateGitCommandFormats() { + const config = getConfig(); + const dateType = config.dateType === DateType.Author ? '%at' : '%ct'; + const useMailmap = config.useMailmap; + + this.gitFormatCommitDetails = [ + '%H', '%P', // Hash & Parent Information + useMailmap ? '%aN' : '%an', useMailmap ? '%aE' : '%ae', '%at', useMailmap ? '%cN' : '%cn', useMailmap ? '%cE' : '%ce', '%ct', // Author / Commit Information + ...(config.showSignatureStatus && this.gitExecutableSupportsGpgInfo ? ['%G?', '%GS', '%GK'] : ['', '', '']), // GPG Key Information + '%B' // Body + ].join(GIT_LOG_SEPARATOR); + + this.gitFormatLog = [ + '%H', '%P', // Hash & Parent Information + useMailmap ? '%aN' : '%an', useMailmap ? '%aE' : '%ae', dateType, // Author / Commit Information + '%s' // Subject + ].join(GIT_LOG_SEPARATOR); + + this.gitFormatStash = [ + '%H', '%P', '%gD', // Hash, Parent & Selector Information + useMailmap ? '%aN' : '%an', useMailmap ? '%aE' : '%ae', dateType, // Author / Commit Information + '%s' // Subject + ].join(GIT_LOG_SEPARATOR); + } + + + /* Get Data Methods - Core */ + + /** + * Get the high-level information of a repository. + * @param repo The path of the repository. + * @param showRemoteBranches Are remote branches shown. + * @param showStashes Are stashes shown. + * @param hideRemotes An array of hidden remotes. + * @returns The repositories information. + */ + public getRepoInfo(repo: string, showRemoteBranches: boolean, showStashes: boolean, hideRemotes: ReadonlyArray): Promise { + return Promise.all([ + this.getBranches(repo, showRemoteBranches, hideRemotes), + this.getRemotes(repo), + showStashes ? this.getStashes(repo) : Promise.resolve([]) + ]).then((results) => { + return { branches: results[0].branches, head: results[0].head, remotes: results[1], stashes: results[2], error: null }; + }).catch((errorMessage) => { + return { branches: [], head: null, remotes: [], stashes: [], error: errorMessage }; + }); + } + + /** + * Get the commits in a repository. + * @param repo The path of the repository. + * @param branches The list of branch heads to display, or NULL (show all). + * @param maxCommits The maximum number of commits to return. + * @param showTags Are tags are shown. + * @param showRemoteBranches Are remote branches shown. + * @param includeCommitsMentionedByReflogs Should commits mentioned by reflogs being included. + * @param onlyFollowFirstParent Only follow the first parent of commits. + * @param commitOrdering The order for commits to be returned. + * @param remotes An array of known remotes. + * @param hideRemotes An array of hidden remotes. + * @param stashes An array of all stashes in the repository. + * @returns The commits in the repository. + */ + public getCommits(repo: string, branches: ReadonlyArray | null, maxCommits: number, showTags: boolean, showRemoteBranches: boolean, includeCommitsMentionedByReflogs: boolean, onlyFollowFirstParent: boolean, commitOrdering: CommitOrdering, remotes: ReadonlyArray, hideRemotes: ReadonlyArray, stashes: ReadonlyArray, cicdConfigs: CICDConfig[] | null): Promise { + const config = getConfig(); + return Promise.all([ + this.getLog(repo, branches, maxCommits + 1, showTags && config.showCommitsOnlyReferencedByTags, showRemoteBranches, includeCommitsMentionedByReflogs, onlyFollowFirstParent, commitOrdering, remotes, hideRemotes, stashes), + this.getRefs(repo, showRemoteBranches, config.showRemoteHeads, hideRemotes).then((refData: GitRefData) => refData, (errorMessage: string) => errorMessage), + this.getCICDs(cicdConfigs).then((refData: GitCICDData[] | string | undefined) => refData, (errorMessage: string) => errorMessage) + ]).then(async (results) => { + let commits: GitCommitRecord[] = results[0], refData: GitRefData | string = results[1], i; + let cicds: GitCICDData[] | string | undefined = results[2]; + let moreCommitsAvailable = commits.length === maxCommits + 1; + if (moreCommitsAvailable) commits.pop(); + + // It doesn't matter if getRefs() was rejected if no commits exist + if (typeof refData === 'string') { + // getRefs() returned an error message (string) + if (commits.length > 0) { + // Commits exist, throw the error + throw refData; + } else { + // No commits exist, so getRefs() will always return an error. Set refData to the default value + refData = { head: null, heads: [], tags: [], remotes: [] }; + } + } + + if (refData.head !== null && config.showUncommittedChanges) { + for (i = 0; i < commits.length; i++) { + if (refData.head === commits[i].hash) { + const numUncommittedChanges = await this.getUncommittedChanges(repo); + if (numUncommittedChanges > 0) { + commits.unshift({ hash: UNCOMMITTED, parents: [refData.head], author: '*', email: '', date: Math.round((new Date()).getTime() / 1000), message: 'Uncommitted Changes (' + numUncommittedChanges + ')' }); + } + break; + } + } + } + + let commitNodes: DeepWriteable[] = []; + let commitLookup: { [hash: string]: number } = {}; + + for (i = 0; i < commits.length; i++) { + commitLookup[commits[i].hash] = i; + commitNodes.push({ ...commits[i], heads: [], tags: [], remotes: [], stash: null, cicd: null }); + } + + /* Insert Stashes */ + let toAdd: { index: number, data: GitStash }[] = []; + for (i = 0; i < stashes.length; i++) { + if (typeof commitLookup[stashes[i].hash] === 'number') { + commitNodes[commitLookup[stashes[i].hash]].stash = { + selector: stashes[i].selector, + baseHash: stashes[i].baseHash, + untrackedFilesHash: stashes[i].untrackedFilesHash + }; + } else if (typeof commitLookup[stashes[i].baseHash] === 'number') { + toAdd.push({ index: commitLookup[stashes[i].baseHash], data: stashes[i] }); + } + } + toAdd.sort((a, b) => a.index !== b.index ? a.index - b.index : b.data.date - a.data.date); + for (i = toAdd.length - 1; i >= 0; i--) { + let stash = toAdd[i].data; + commitNodes.splice(toAdd[i].index, 0, { + cicd: null, + hash: stash.hash, + parents: [stash.baseHash], + author: stash.author, + email: stash.email, + date: stash.date, + message: stash.message, + heads: [], tags: [], remotes: [], + stash: { + selector: stash.selector, + baseHash: stash.baseHash, + untrackedFilesHash: stash.untrackedFilesHash + } + }); + } + for (i = 0; i < commitNodes.length; i++) { + // Correct commit lookup after stashes have been spliced in + commitLookup[commitNodes[i].hash] = i; + } + + /* Annotate Heads */ + for (i = 0; i < refData.heads.length; i++) { + if (typeof commitLookup[refData.heads[i].hash] === 'number') commitNodes[commitLookup[refData.heads[i].hash]].heads.push(refData.heads[i].name); + } + + /* Annotate Tags */ + if (showTags) { + for (i = 0; i < refData.tags.length; i++) { + if (typeof commitLookup[refData.tags[i].hash] === 'number') commitNodes[commitLookup[refData.tags[i].hash]].tags.push({ name: refData.tags[i].name, annotated: refData.tags[i].annotated }); + } + } + + /* Annotate Remotes */ + for (i = 0; i < refData.remotes.length; i++) { + if (typeof commitLookup[refData.remotes[i].hash] === 'number') { + let name = refData.remotes[i].name; + let remote = remotes.find(remote => name.startsWith(remote + '/')); + commitNodes[commitLookup[refData.remotes[i].hash]].remotes.push({ name: name, remote: remote ? remote : null }); + } + } + + if (typeof cicds === 'string' || typeof cicds === 'undefined') { + cicds = []; + } + /* Annotate CI/CDs */ + for (i = 0; i < cicds.length; i++) { + if (typeof commitLookup[cicds[i].sha] === 'number') { + commitNodes[commitLookup[cicds[i].sha]].cicd = cicds[i]; + } + } + + return { + commits: commitNodes, + head: refData.head, + tags: unique(refData.tags.map((tag) => tag.name)), + moreCommitsAvailable: moreCommitsAvailable, + error: null + }; + }).catch((errorMessage) => { + return { commits: [], head: null, tags: [], moreCommitsAvailable: false, error: errorMessage }; + }); + } + + /** + * Get various Git config variables for a repository that are consumed by the Git Graph View. + * @param repo The path of the repository. + * @param remotes An array of known remotes. + * @returns The config data. + */ + public getConfig(repo: string, remotes: ReadonlyArray): Promise { + return Promise.all([ + this.getConfigList(repo), + this.getConfigList(repo, GitConfigLocation.Local), + this.getConfigList(repo, GitConfigLocation.Global) + ]).then((results) => { + const consolidatedConfigs = results[0], localConfigs = results[1], globalConfigs = results[2]; + + const branches: GitRepoConfigBranches = {}; + Object.keys(localConfigs).forEach((key) => { + if (key.startsWith('branch.')) { + if (key.endsWith('.remote')) { + const branchName = key.substring(7, key.length - 7); + branches[branchName] = { + pushRemote: typeof branches[branchName] !== 'undefined' ? branches[branchName].pushRemote : null, + remote: localConfigs[key] + }; + } else if (key.endsWith('.pushremote')) { + const branchName = key.substring(7, key.length - 11); + branches[branchName] = { + pushRemote: localConfigs[key], + remote: typeof branches[branchName] !== 'undefined' ? branches[branchName].remote : null + }; + } + } + }); + + return { + config: { + branches: branches, + diffTool: getConfigValue(consolidatedConfigs, GIT_CONFIG.DIFF.TOOL), + guiDiffTool: getConfigValue(consolidatedConfigs, GIT_CONFIG.DIFF.GUI_TOOL), + pushDefault: getConfigValue(consolidatedConfigs, GIT_CONFIG.REMOTE.PUSH_DEFAULT), + remotes: remotes.map((remote) => ({ + name: remote, + url: getConfigValue(localConfigs, 'remote.' + remote + '.url'), + pushUrl: getConfigValue(localConfigs, 'remote.' + remote + '.pushurl') + })), + user: { + name: { + local: getConfigValue(localConfigs, GIT_CONFIG.USER.NAME), + global: getConfigValue(globalConfigs, GIT_CONFIG.USER.NAME) + }, + email: { + local: getConfigValue(localConfigs, GIT_CONFIG.USER.EMAIL), + global: getConfigValue(globalConfigs, GIT_CONFIG.USER.EMAIL) + } + } + }, + error: null + }; + }).catch((errorMessage) => { + return { config: null, error: errorMessage }; + }); + } + + + /* Get Data Methods - Commit Details View */ + + /** + * Get the commit details for the Commit Details View. + * @param repo The path of the repository. + * @param commitHash The hash of the commit open in the Commit Details View. + * @param hasParents Does the commit have parents + * @returns The commit details. + */ + public getCommitDetails(repo: string, commitHash: string, hasParents: boolean): Promise { + const fromCommit = commitHash + (hasParents ? '^' : ''); + return Promise.all([ + this.getCommitDetailsBase(repo, commitHash), + this.getDiffNameStatus(repo, fromCommit, commitHash), + this.getDiffNumStat(repo, fromCommit, commitHash) + ]).then((results) => { + results[0].fileChanges = generateFileChanges(results[1], results[2], null); + return { commitDetails: results[0], error: null }; + }).catch((errorMessage) => { + return { commitDetails: null, error: errorMessage }; + }); + } + + /** + * Get the stash details for the Commit Details View. + * @param repo The path of the repository. + * @param commitHash The hash of the stash commit open in the Commit Details View. + * @param stash The stash. + * @returns The stash details. + */ + public getStashDetails(repo: string, commitHash: string, stash: GitCommitStash): Promise { + return Promise.all([ + this.getCommitDetailsBase(repo, commitHash), + this.getDiffNameStatus(repo, stash.baseHash, commitHash), + this.getDiffNumStat(repo, stash.baseHash, commitHash), + stash.untrackedFilesHash !== null ? this.getDiffNameStatus(repo, stash.untrackedFilesHash, stash.untrackedFilesHash) : Promise.resolve([]), + stash.untrackedFilesHash !== null ? this.getDiffNumStat(repo, stash.untrackedFilesHash, stash.untrackedFilesHash) : Promise.resolve([]) + ]).then((results) => { + results[0].fileChanges = generateFileChanges(results[1], results[2], null); + if (stash.untrackedFilesHash !== null) { + generateFileChanges(results[3], results[4], null).forEach((fileChange) => { + if (fileChange.type === GitFileStatus.Added) { + fileChange.type = GitFileStatus.Untracked; + results[0].fileChanges.push(fileChange); + } + }); + } + return { commitDetails: results[0], error: null }; + }).catch((errorMessage) => { + return { commitDetails: null, error: errorMessage }; + }); + } + + /** + * Get the uncommitted details for the Commit Details View. + * @param repo The path of the repository. + * @returns The uncommitted details. + */ + public getUncommittedDetails(repo: string): Promise { + return Promise.all([ + this.getDiffNameStatus(repo, 'HEAD', ''), + this.getDiffNumStat(repo, 'HEAD', ''), + this.getStatus(repo) + ]).then((results) => { + return { + commitDetails: { + hash: UNCOMMITTED, parents: [], + author: '', authorEmail: '', authorDate: 0, + committer: '', committerEmail: '', committerDate: 0, signature: null, + body: '', fileChanges: generateFileChanges(results[0], results[1], results[2]) + }, + error: null + }; + }).catch((errorMessage) => { + return { commitDetails: null, error: errorMessage }; + }); + } + + /** + * Get the comparison details for the Commit Comparison View. + * @param repo The path of the repository. + * @param fromHash The commit hash the comparison is from. + * @param toHash The commit hash the comparison is to. + * @returns The comparison details. + */ + public getCommitComparison(repo: string, fromHash: string, toHash: string): Promise { + return Promise.all([ + this.getDiffNameStatus(repo, fromHash, toHash === UNCOMMITTED ? '' : toHash), + this.getDiffNumStat(repo, fromHash, toHash === UNCOMMITTED ? '' : toHash), + toHash === UNCOMMITTED ? this.getStatus(repo) : Promise.resolve(null) + ]).then((results) => { + return { + fileChanges: generateFileChanges(results[0], results[1], results[2]), + error: null + }; + }).catch((errorMessage) => { + return { fileChanges: [], error: errorMessage }; + }); + } + + /** + * Get the contents of a file at a specific revision. + * @param repo The path of the repository. + * @param commitHash The commit hash specifying the revision of the file. + * @param filePath The path of the file relative to the repositories root. + * @returns The file contents. + */ + public getCommitFile(repo: string, commitHash: string, filePath: string) { + return this._spawnGit(['show', commitHash + ':' + filePath], repo, stdout => { + const encoding = getConfig(repo).fileEncoding; + return decode(stdout, encodingExists(encoding) ? encoding : 'utf8'); + }); + } + + + /* Get Data Methods - General */ + + /** + * Get the subject of a commit. + * @param repo The path of the repository. + * @param commitHash The commit hash. + * @returns The subject string, or NULL if an error occurred. + */ + public getCommitSubject(repo: string, commitHash: string): Promise { + return this.spawnGit(['-c', 'log.showSignature=false', 'log', '--format=%s', '-n', '1', commitHash, '--'], repo, (stdout) => { + return stdout.trim().replace(/\s+/g, ' '); + }).then((subject) => subject, () => null); + } + + /** + * Get the URL of a repositories remote. + * @param repo The path of the repository. + * @param remote The name of the remote. + * @returns The URL, or NULL if an error occurred. + */ + public getRemoteUrl(repo: string, remote: string): Promise { + return this.spawnGit(['config', '--get', 'remote.' + remote + '.url'], repo, (stdout) => { + return stdout.split(EOL_REGEX)[0]; + }).then((url) => url, () => null); + } + + /** + * Get the details of a tag. + * @param repo The path of the repository. + * @param tagName The name of the tag. + * @returns The tag details. + */ + public getTagDetails(repo: string, tagName: string): Promise { + return this.spawnGit(['for-each-ref', 'refs/tags/' + tagName, '--format=' + ['%(objectname)', '%(taggername)', '%(taggeremail)', '%(taggerdate:unix)', '%(contents)'].join(GIT_LOG_SEPARATOR)], repo, (stdout) => { + let data = stdout.split(GIT_LOG_SEPARATOR); + return { + tagHash: data[0], + name: data[1], + email: data[2].substring(data[2].startsWith('<') ? 1 : 0, data[2].length - (data[2].endsWith('>') ? 1 : 0)), + date: parseInt(data[3]), + message: removeTrailingBlankLines(data[4].split(EOL_REGEX)).join('\n'), + error: null + }; + }).then((data) => { + return data; + }).catch((errorMessage) => { + return { tagHash: '', name: '', email: '', date: 0, message: '', error: errorMessage }; + }); + } + + /** + * Get the submodules of a repository. + * @param repo The path of the repository. + * @returns An array of the paths of the submodules. + */ + public getSubmodules(repo: string) { + return new Promise(resolve => { + fs.readFile(path.join(repo, '.gitmodules'), { encoding: 'utf8' }, async (err, data) => { + let submodules: string[] = []; + if (!err) { + let lines = data.split(EOL_REGEX), inSubmoduleSection = false, match; + const section = /^\s*\[.*\]\s*$/, submodule = /^\s*\[submodule "([^"]+)"\]\s*$/, pathProp = /^\s*path\s+=\s+(.*)$/; + + for (let i = 0; i < lines.length; i++) { + if (lines[i].match(section) !== null) { + inSubmoduleSection = lines[i].match(submodule) !== null; + continue; + } + + if (inSubmoduleSection && (match = lines[i].match(pathProp)) !== null) { + let root = await this.repoRoot(getPathFromUri(vscode.Uri.file(path.join(repo, getPathFromStr(match[1]))))); + if (root !== null && !submodules.includes(root)) { + submodules.push(root); + } + } + } + } + resolve(submodules); + }); + }); + } + + + /* Repository Info Methods */ + + /** + * Check if there are any staged changes in the repository. + * @param repo The path of the repository. + * @returns TRUE => Staged Changes, FALSE => No Staged Changes. + */ + private areStagedChanges(repo: string) { + return this.spawnGit(['diff-index', 'HEAD'], repo, (stdout) => stdout !== '').then(changes => changes, () => false); + } + + /** + * Get the root of the repository containing the specified path. + * @param pathOfPotentialRepo The path that is potentially a repository (or is contained within a repository). + * @returns STRING => The root of the repository, NULL => `pathOfPotentialRepo` is not in a repository. + */ + public repoRoot(pathOfPotentialRepo: string) { + return this.spawnGit(['rev-parse', '--show-toplevel'], pathOfPotentialRepo, (stdout) => getPathFromUri(vscode.Uri.file(path.normalize(stdout.trim())))).then(async (pathReturnedByGit) => { + if (process.platform === 'win32') { + // On Windows Mapped Network Drives with Git >= 2.25.0, `git rev-parse --show-toplevel` returns the UNC Path for the Mapped Network Drive, instead of the Drive Letter. + // Attempt to replace the UNC Path with the Drive Letter. + let driveLetterPathMatch: RegExpMatchArray | null; + if ((driveLetterPathMatch = pathOfPotentialRepo.match(DRIVE_LETTER_PATH_REGEX)) && !pathReturnedByGit.match(DRIVE_LETTER_PATH_REGEX)) { + const realPathForDriveLetter = pathWithTrailingSlash(await realpath(driveLetterPathMatch[0], true)); + if (realPathForDriveLetter !== driveLetterPathMatch[0] && pathReturnedByGit.startsWith(realPathForDriveLetter)) { + pathReturnedByGit = driveLetterPathMatch[0] + pathReturnedByGit.substring(realPathForDriveLetter.length); + } + } + } + let path = pathOfPotentialRepo; + let first = path.indexOf('/'); + while (true) { + if (pathReturnedByGit === path || pathReturnedByGit === await realpath(path)) return path; + let next = path.lastIndexOf('/'); + if (first !== next && next > -1) { + path = path.substring(0, next); + } else { + return pathReturnedByGit; + } + } + }).catch(() => null); // null => path is not in a repo + } + + + /* Git Action Methods - Remotes */ + + /** + * Add a new remote to a repository. + * @param repo The path of the repository. + * @param name The name of the remote. + * @param url The URL of the remote. + * @param pushUrl The Push URL of the remote. + * @param fetch Fetch the remote after it is added. + * @returns The ErrorInfo from the executed command. + */ + public async addRemote(repo: string, name: string, url: string, pushUrl: string | null, fetch: boolean) { + let status = await this.runGitCommand(['remote', 'add', name, url], repo); + if (status !== null) return status; + + if (pushUrl !== null) { + status = await this.runGitCommand(['remote', 'set-url', name, '--push', pushUrl], repo); + if (status !== null) return status; + } + + return fetch ? this.fetch(repo, name, false, false) : null; + } + + /** + * Delete an existing remote from a repository. + * @param repo The path of the repository. + * @param name The name of the remote. + * @returns The ErrorInfo from the executed command. + */ + public deleteRemote(repo: string, name: string) { + return this.runGitCommand(['remote', 'remove', name], repo); + } + + /** + * Edit an existing remote of a repository. + * @param repo The path of the repository. + * @param nameOld The old name of the remote. + * @param nameNew The new name of the remote. + * @param urlOld The old URL of the remote. + * @param urlNew The new URL of the remote. + * @param pushUrlOld The old Push URL of the remote. + * @param pushUrlNew The new Push URL of the remote. + * @returns The ErrorInfo from the executed command. + */ + public async editRemote(repo: string, nameOld: string, nameNew: string, urlOld: string | null, urlNew: string | null, pushUrlOld: string | null, pushUrlNew: string | null) { + if (nameOld !== nameNew) { + let status = await this.runGitCommand(['remote', 'rename', nameOld, nameNew], repo); + if (status !== null) return status; + } + + if (urlOld !== urlNew) { + let args = ['remote', 'set-url', nameNew]; + if (urlNew === null) args.push('--delete', urlOld!); + else if (urlOld === null) args.push('--add', urlNew); + else args.push(urlNew, urlOld); + + let status = await this.runGitCommand(args, repo); + if (status !== null) return status; + } + + if (pushUrlOld !== pushUrlNew) { + let args = ['remote', 'set-url', '--push', nameNew]; + if (pushUrlNew === null) args.push('--delete', pushUrlOld!); + else if (pushUrlOld === null) args.push('--add', pushUrlNew); + else args.push(pushUrlNew, pushUrlOld); + + let status = await this.runGitCommand(args, repo); + if (status !== null) return status; + } + + return null; + } + + /** + * Prune an existing remote of a repository. + * @param repo The path of the repository. + * @param name The name of the remote. + * @returns The ErrorInfo from the executed command. + */ + public pruneRemote(repo: string, name: string) { + return this.runGitCommand(['remote', 'prune', name], repo); + } + + + /* Git Action Methods - Tags */ + + /** + * Add a new tag to a commit. + * @param repo The path of the repository. + * @param tagName The name of the tag. + * @param commitHash The hash of the commit the tag should be added to. + * @param type Is the tag annotated or lightweight. + * @param message The message of the tag (if it is an annotated tag). + * @param force Force add the tag, replacing an existing tag with the same name (if it exists). + * @returns The ErrorInfo from the executed command. + */ + public addTag(repo: string, tagName: string, commitHash: string, type: TagType, message: string, force: boolean) { + const args = ['tag']; + if (force) { + args.push('-f'); + } + if (type === TagType.Lightweight) { + args.push(tagName); + } else { + args.push(getConfig().signTags ? '-s' : '-a', tagName, '-m', message); + } + args.push(commitHash); + return this.runGitCommand(args, repo); + } + + /** + * Delete an existing tag from a repository. + * @param repo The path of the repository. + * @param tagName The name of the tag. + * @param deleteOnRemote The name of the remote to delete the tag on, or NULL. + * @returns The ErrorInfo from the executed command. + */ + public async deleteTag(repo: string, tagName: string, deleteOnRemote: string | null) { + if (deleteOnRemote !== null) { + let status = await this.runGitCommand(['push', deleteOnRemote, '--delete', tagName], repo); + if (status !== null) return status; + } + return this.runGitCommand(['tag', '-d', tagName], repo); + } + + + /* Git Action Methods - Remote Sync */ + + /** + * Fetch from the repositories remote(s). + * @param repo The path of the repository. + * @param remote The remote to fetch, or NULL (fetch all remotes). + * @param prune Is pruning enabled. + * @param pruneTags Should tags be pruned. + * @returns The ErrorInfo from the executed command. + */ + public fetch(repo: string, remote: string | null, prune: boolean, pruneTags: boolean) { + let args = ['fetch', remote === null ? '--all' : remote]; + + if (prune) { + args.push('--prune'); + } + if (pruneTags) { + if (!prune) { + return Promise.resolve('In order to Prune Tags, pruning must also be enabled when fetching from ' + (remote !== null ? 'a remote' : 'remote(s)') + '.'); + } else if (this.gitExecutable !== null && !doesVersionMeetRequirement(this.gitExecutable.version, '2.17.0')) { + return Promise.resolve(constructIncompatibleGitVersionMessage(this.gitExecutable, '2.17.0', 'pruning tags when fetching')); + } + args.push('--prune-tags'); + } + + return this.runGitCommand(args, repo); + } + + /** + * Push a branch to a remote. + * @param repo The path of the repository. + * @param branchName The name of the branch to push. + * @param remote The remote to push the branch to. + * @param setUpstream Set the branches upstream. + * @param mode The mode of the push. + * @returns The ErrorInfo from the executed command. + */ + public pushBranch(repo: string, branchName: string, remote: string, setUpstream: boolean, mode: GitPushBranchMode) { + let args = ['push']; + args.push(remote, branchName); + if (setUpstream) args.push('--set-upstream'); + if (mode !== GitPushBranchMode.Normal) args.push('--' + mode); + + return this.runGitCommand(args, repo); + } + + /** + * Push a tag to a remote. + * @param repo The path of the repository. + * @param tagName The name of the tag to push. + * @param remote The remote to push the tag to. + * @returns The ErrorInfo from the executed command. + */ + public pushTag(repo: string, tagName: string, remote: string) { + return this.runGitCommand(['push', remote, tagName], repo); + } + + /** + * Push a branch to multiple remotes. + * @param repo The path of the repository. + * @param branchName The name of the branch to push. + * @param remotes The remotes to push the branch to. + * @param setUpstream Set the branches upstream. + * @param mode The mode of the push. + * @returns The ErrorInfo's from the executed commands. + */ + public async pushBranchToMultipleRemotes(repo: string, branchName: string, remotes: string[], setUpstream: boolean, mode: GitPushBranchMode): Promise { + if (remotes.length === 0) { + return ['No remote(s) were specified to push the branch ' + branchName + ' to.']; + } + + const results: ErrorInfo[] = []; + for (let i = 0; i < remotes.length; i++) { + const result = await this.pushBranch(repo, branchName, remotes[i], setUpstream, mode); + results.push(result); + if (result !== null) break; + } + return results; + } + + /** + * Push a tag to multiple remotes. + * @param repo The path of the repository. + * @param tagName The name of the tag to push. + * @param remote The remotes to push the tag to. + * @returns The ErrorInfo's from the executed commands. + */ + public async pushTagToMultipleRemotes(repo: string, tagName: string, remotes: string[]): Promise { + if (remotes.length === 0) { + return ['No remote(s) were specified to push the tag ' + tagName + ' to.']; + } + + const results: ErrorInfo[] = []; + for (let i = 0; i < remotes.length; i++) { + const result = await this.pushTag(repo, tagName, remotes[i]); + results.push(result); + if (result !== null) break; + } + return results; + } + + + /* Git Action Methods - Branches */ + + /** + * Checkout a branch in a repository. + * @param repo The path of the repository. + * @param branchName The name of the branch to checkout. + * @param remoteBranch The name of the remote branch to check out (if not NULL). + * @returns The ErrorInfo from the executed command. + */ + public checkoutBranch(repo: string, branchName: string, remoteBranch: string | null) { + let args = ['checkout']; + if (remoteBranch === null) args.push(branchName); + else args.push('-b', branchName, remoteBranch); + + return this.runGitCommand(args, repo); + } + + /** + * Create a branch at a commit. + * @param repo The path of the repository. + * @param branchName The name of the branch. + * @param commitHash The hash of the commit the branch should be created at. + * @param checkout Check out the branch after it is created. + * @param force Force create the branch, replacing an existing branch with the same name (if it exists). + * @returns The ErrorInfo's from the executed command(s). + */ + public async createBranch(repo: string, branchName: string, commitHash: string, checkout: boolean, force: boolean) { + const args = []; + if (checkout && !force) { + args.push('checkout', '-b'); + } else { + args.push('branch'); + if (force) { + args.push('-f'); + } + } + args.push(branchName, commitHash); + + const statuses = [await this.runGitCommand(args, repo)]; + if (statuses[0] === null && checkout && force) { + statuses.push(await this.checkoutBranch(repo, branchName, null)); + } + return statuses; + } + + /** + * Delete a branch in a repository. + * @param repo The path of the repository. + * @param branchName The name of the branch. + * @param forceDelete Should the delete be forced. + * @returns The ErrorInfo from the executed command. + */ + public deleteBranch(repo: string, branchName: string, forceDelete: boolean) { + let args = ['branch', '--delete']; + if (forceDelete) args.push('--force'); + args.push(branchName); + + return this.runGitCommand(args, repo); + } + + /** + * Delete a remote branch in a repository. + * @param repo The path of the repository. + * @param branchName The name of the branch. + * @param remote The name of the remote to delete the branch on. + * @returns The ErrorInfo from the executed command. + */ + public async deleteRemoteBranch(repo: string, branchName: string, remote: string) { + let remoteStatus = await this.runGitCommand(['push', remote, '--delete', branchName], repo); + if (remoteStatus !== null && (new RegExp('remote ref does not exist', 'i')).test(remoteStatus)) { + let trackingBranchStatus = await this.runGitCommand(['branch', '-d', '-r', remote + '/' + branchName], repo); + return trackingBranchStatus === null ? null : 'Branch does not exist on the remote, deleting the remote tracking branch ' + remote + '/' + branchName + '.\n' + trackingBranchStatus; + } + return remoteStatus; + } + + /** + * Fetch a remote branch into a local branch. + * @param repo The path of the repository. + * @param remote The name of the remote containing the remote branch. + * @param remoteBranch The name of the remote branch. + * @param localBranch The name of the local branch. + * @param force Force fetch the remote branch. + * @returns The ErrorInfo from the executed command. + */ + public fetchIntoLocalBranch(repo: string, remote: string, remoteBranch: string, localBranch: string, force: boolean) { + const args = ['fetch']; + if (force) { + args.push('-f'); + } + args.push(remote, remoteBranch + ':' + localBranch); + return this.runGitCommand(args, repo); + } + + /** + * Pull a remote branch into the current branch. + * @param repo The path of the repository. + * @param branchName The name of the remote branch. + * @param remote The name of the remote containing the remote branch. + * @param createNewCommit Is `--no-ff` enabled if a merge is required. + * @param squash Is `--squash` enabled if a merge is required. + * @returns The ErrorInfo from the executed command. + */ + public pullBranch(repo: string, branchName: string, remote: string, createNewCommit: boolean, squash: boolean) { + const args = ['pull', remote, branchName], config = getConfig(); + if (squash) { + args.push('--squash'); + } else if (createNewCommit) { + args.push('--no-ff'); + } + if (config.signCommits) { + args.push('-S'); + } + return this.runGitCommand(args, repo).then((pullStatus) => { + return pullStatus === null && squash + ? this.commitSquashIfStagedChangesExist(repo, remote + '/' + branchName, MergeActionOn.Branch, config.squashPullMessageFormat, config.signCommits) + : pullStatus; + }); + } + + /** + * Rename a branch in a repository. + * @param repo The path of the repository. + * @param oldName The old name of the branch. + * @param newName The new name of the branch. + * @returns The ErrorInfo from the executed command. + */ + public renameBranch(repo: string, oldName: string, newName: string) { + return this.runGitCommand(['branch', '-m', oldName, newName], repo); + } + + + /* Git Action Methods - Branches & Commits */ + + /** + * Merge a branch or commit into the current branch. + * @param repo The path of the repository. + * @param obj The object to be merged into the current branch. + * @param actionOn Is the merge on a branch, remote-tracking branch or commit. + * @param createNewCommit Is `--no-ff` enabled. + * @param squash Is `--squash` enabled. + * @param noCommit Is `--no-commit` enabled. + * @returns The ErrorInfo from the executed command. + */ + public merge(repo: string, obj: string, actionOn: MergeActionOn, createNewCommit: boolean, squash: boolean, noCommit: boolean) { + const args = ['merge', obj], config = getConfig(); + if (squash) { + args.push('--squash'); + } else if (createNewCommit) { + args.push('--no-ff'); + } + if (noCommit) { + args.push('--no-commit'); + } + if (config.signCommits) { + args.push('-S'); + } + return this.runGitCommand(args, repo).then((mergeStatus) => { + return mergeStatus === null && squash && !noCommit + ? this.commitSquashIfStagedChangesExist(repo, obj, actionOn, config.squashMergeMessageFormat, config.signCommits) + : mergeStatus; + }); + } + + /** + * Rebase the current branch on a branch or commit. + * @param repo The path of the repository. + * @param obj The object the current branch will be rebased onto. + * @param actionOn Is the rebase on a branch or commit. + * @param ignoreDate Is `--ignore-date` enabled. + * @param interactive Should the rebase be performed interactively. + * @returns The ErrorInfo from the executed command. + */ + public rebase(repo: string, obj: string, actionOn: RebaseActionOn, ignoreDate: boolean, interactive: boolean) { + if (interactive) { + return this.openGitTerminal( + repo, + 'rebase --interactive ' + (getConfig().signCommits ? '-S ' : '') + (actionOn === RebaseActionOn.Branch ? obj.replace(/'/g, '"\'"') : obj), + 'Rebase on "' + (actionOn === RebaseActionOn.Branch ? obj : abbrevCommit(obj)) + '"' + ); + } else { + const args = ['rebase', obj]; + if (ignoreDate) { + args.push('--ignore-date'); + } + if (getConfig().signCommits) { + args.push('-S'); + } + return this.runGitCommand(args, repo); + } + } + + + /* Git Action Methods - Branches & Tags */ + + /** + * Create an archive of a repository at a specific reference, and save to disk. + * @param repo The path of the repository. + * @param ref The reference of the revision to archive. + * @param outputFilePath The file path that the archive should be saved to. + * @param type The type of archive. + * @returns The ErrorInfo from the executed command. + */ + public archive(repo: string, ref: string, outputFilePath: string, type: 'tar' | 'zip') { + return this.runGitCommand(['archive', '--format=' + type, '-o', outputFilePath, ref], repo); + } + + + /* Git Action Methods - Commits */ + + /** + * Checkout a commit in a repository. + * @param repo The path of the repository. + * @param commitHash The hash of the commit to check out. + * @returns The ErrorInfo from the executed command. + */ + public checkoutCommit(repo: string, commitHash: string) { + return this.runGitCommand(['checkout', commitHash], repo); + } + + /** + * Cherrypick a commit in a repository. + * @param repo The path of the repository. + * @param commitHash The hash of the commit to be cherry picked. + * @param parentIndex The parent index if the commit is a merge. + * @param recordOrigin Is `-x` enabled. + * @param noCommit Is `--no-commit` enabled. + * @returns The ErrorInfo from the executed command. + */ + public cherrypickCommit(repo: string, commitHash: string, parentIndex: number, recordOrigin: boolean, noCommit: boolean) { + const args = ['cherry-pick']; + if (noCommit) { + args.push('--no-commit'); + } + if (recordOrigin) { + args.push('-x'); + } + if (getConfig().signCommits) { + args.push('-S'); + } + if (parentIndex > 0) { + args.push('-m', parentIndex.toString()); + } + args.push(commitHash); + return this.runGitCommand(args, repo); + } + + /** + * Drop a commit in a repository. + * @param repo The path of the repository. + * @param commitHash The hash of the commit to drop. + * @returns The ErrorInfo from the executed command. + */ + public dropCommit(repo: string, commitHash: string) { + const args = ['rebase']; + if (getConfig().signCommits) { + args.push('-S'); + } + args.push('--onto', commitHash + '^', commitHash); + return this.runGitCommand(args, repo); + } + + /** + * Reset the current branch to a specified commit. + * @param repo The path of the repository. + * @param commit The hash of the commit that the current branch should be reset to. + * @param resetMode The mode of the reset. + * @returns The ErrorInfo from the executed command. + */ + public resetToCommit(repo: string, commit: string, resetMode: GitResetMode) { + return this.runGitCommand(['reset', '--' + resetMode, commit], repo); + } + + /** + * Revert a commit in a repository. + * @param repo The path of the repository. + * @param commitHash The hash of the commit to revert. + * @param parentIndex The parent index if the commit is a merge. + * @returns The ErrorInfo from the executed command. + */ + public revertCommit(repo: string, commitHash: string, parentIndex: number) { + const args = ['revert', '--no-edit']; + if (getConfig().signCommits) { + args.push('-S'); + } + if (parentIndex > 0) { + args.push('-m', parentIndex.toString()); + } + args.push(commitHash); + return this.runGitCommand(args, repo); + } + + + /* Git Action Methods - Config */ + + /** + * Set a configuration value for a repository. + * @param repo The path of the repository. + * @param key The key to be set. + * @param value The value to be set. + * @param location The location where the configuration value should be set. + * @returns The ErrorInfo from the executed command. + */ + public setConfigValue(repo: string, key: string, value: string, location: GitConfigLocation) { + return this.runGitCommand(['config', '--' + location, key, value], repo); + } + + /** + * Unset a configuration value for a repository. + * @param repo The path of the repository. + * @param key The key to be unset. + * @param location The location where the configuration value should be unset. + * @returns The ErrorInfo from the executed command. + */ + public unsetConfigValue(repo: string, key: string, location: GitConfigLocation) { + return this.runGitCommand(['config', '--' + location, '--unset-all', key], repo); + } + + + /* Git Action Methods - Uncommitted */ + + /** + * Clean the untracked files in a repository. + * @param repo The path of the repository. + * @param directories Is `-d` enabled. + * @returns The ErrorInfo from the executed command. + */ + public cleanUntrackedFiles(repo: string, directories: boolean) { + return this.runGitCommand(['clean', '-f' + (directories ? 'd' : '')], repo); + } + + + /* Git Action Methods - Stash */ + + /** + * Apply a stash in a repository. + * @param repo The path of the repository. + * @param selector The selector of the stash. + * @param reinstateIndex Is `--index` enabled. + * @returns The ErrorInfo from the executed command. + */ + public applyStash(repo: string, selector: string, reinstateIndex: boolean) { + let args = ['stash', 'apply']; + if (reinstateIndex) args.push('--index'); + args.push(selector); + + return this.runGitCommand(args, repo); + } + + /** + * Create a branch from a stash. + * @param repo The path of the repository. + * @param selector The selector of the stash. + * @param branchName The name of the branch to be created. + * @returns The ErrorInfo from the executed command. + */ + public branchFromStash(repo: string, selector: string, branchName: string) { + return this.runGitCommand(['stash', 'branch', branchName, selector], repo); + } + + /** + * Drop a stash in a repository. + * @param repo The path of the repository. + * @param selector The selector of the stash. + * @returns The ErrorInfo from the executed command. + */ + public dropStash(repo: string, selector: string) { + return this.runGitCommand(['stash', 'drop', selector], repo); + } + + /** + * Pop a stash in a repository. + * @param repo The path of the repository. + * @param selector The selector of the stash. + * @param reinstateIndex Is `--index` enabled. + * @returns The ErrorInfo from the executed command. + */ + public popStash(repo: string, selector: string, reinstateIndex: boolean) { + let args = ['stash', 'pop']; + if (reinstateIndex) args.push('--index'); + args.push(selector); + + return this.runGitCommand(args, repo); + } + + /** + * Push the uncommitted changes to a stash. + * @param repo The path of the repository. + * @param message The message of the stash. + * @param includeUntracked Is `--include-untracked` enabled. + * @returns The ErrorInfo from the executed command. + */ + public pushStash(repo: string, message: string, includeUntracked: boolean): Promise { + if (this.gitExecutable === null) { + return Promise.resolve(UNABLE_TO_FIND_GIT_MSG); + } else if (!doesVersionMeetRequirement(this.gitExecutable.version, '2.13.2')) { + return Promise.resolve(constructIncompatibleGitVersionMessage(this.gitExecutable, '2.13.2')); + } + + let args = ['stash', 'push']; + if (includeUntracked) args.push('--include-untracked'); + if (message !== '') args.push('--message', message); + return this.runGitCommand(args, repo); + } + + + /* Public Utils */ + + /** + * Opens an external directory diff for the specified commits. + * @param repo The path of the repository. + * @param fromHash The commit hash the diff is from. + * @param toHash The commit hash the diff is to. + * @param isGui Is the external diff tool GUI based. + * @returns The ErrorInfo from the executed command. + */ + public openExternalDirDiff(repo: string, fromHash: string, toHash: string, isGui: boolean) { + return new Promise((resolve) => { + if (this.gitExecutable === null) { + resolve(UNABLE_TO_FIND_GIT_MSG); + } else { + const args = ['difftool', '--dir-diff']; + if (isGui) { + args.push('-g'); + } + if (fromHash === toHash) { + if (toHash === UNCOMMITTED) { + args.push('HEAD'); + } else { + args.push(toHash + '^..' + toHash); + } + } else { + if (toHash === UNCOMMITTED) { + args.push(fromHash); + } else { + args.push(fromHash + '..' + toHash); + } + } + if (isGui) { + this.logger.log('External diff tool is being opened (' + args[args.length - 1] + ')'); + this.runGitCommand(args, repo).then((errorInfo) => { + this.logger.log('External diff tool has exited (' + args[args.length - 1] + ')'); + if (errorInfo !== null) { + const errorMessage = errorInfo.replace(EOL_REGEX, ' '); + this.logger.logError(errorMessage); + showErrorMessage(errorMessage); + } + }); + } else { + openGitTerminal(repo, this.gitExecutable.path, args.join(' '), 'Open External Directory Diff'); + } + setTimeout(() => resolve(null), 1500); + } + }); + } + + /** + * Open a new terminal, set up the Git executable, and optionally run a command. + * @param repo The path of the repository. + * @param command The command to run. + * @param name The name for the terminal. + * @returns The ErrorInfo from opening the terminal. + */ + public openGitTerminal(repo: string, command: string | null, name: string) { + return new Promise((resolve) => { + if (this.gitExecutable === null) { + resolve(UNABLE_TO_FIND_GIT_MSG); + } else { + openGitTerminal(repo, this.gitExecutable.path, command, name); + setTimeout(() => resolve(null), 1000); + } + }); + } + + + /* Private Data Providers */ + + /** + * Get the branches in a repository. + * @param repo The path of the repository. + * @param showRemoteBranches Are remote branches shown. + * @param hideRemotes An array of hidden remotes. + * @returns The branch data. + */ + private getBranches(repo: string, showRemoteBranches: boolean, hideRemotes: ReadonlyArray) { + let args = ['branch']; + if (showRemoteBranches) args.push('-a'); + args.push('--no-color'); + + const hideRemotePatterns = hideRemotes.map((remote) => 'remotes/' + remote + '/'); + const showRemoteHeads = getConfig().showRemoteHeads; + + return this.spawnGit(args, repo, (stdout) => { + let branchData: GitBranchData = { branches: [], head: null, error: null }; + let lines = stdout.split(EOL_REGEX); + for (let i = 0; i < lines.length - 1; i++) { + let name = lines[i].substring(2).split(' -> ')[0]; + if (INVALID_BRANCH_REGEXP.test(name) || hideRemotePatterns.some((pattern) => name.startsWith(pattern)) || (!showRemoteHeads && REMOTE_HEAD_BRANCH_REGEXP.test(name))) { + continue; + } + + if (lines[i][0] === '*') { + branchData.head = name; + branchData.branches.unshift(name); + } else { + branchData.branches.push(name); + } + } + return branchData; + }); + } + + /** + * Get the base commit details for the Commit Details View. + * @param repo The path of the repository. + * @param commitHash The hash of the commit open in the Commit Details View. + * @returns The base commit details. + */ + private getCommitDetailsBase(repo: string, commitHash: string) { + return this.spawnGit(['-c', 'log.showSignature=false', 'show', '--quiet', commitHash, '--format=' + this.gitFormatCommitDetails], repo, (stdout): DeepWriteable => { + const commitInfo = stdout.split(GIT_LOG_SEPARATOR); + return { + hash: commitInfo[0], + parents: commitInfo[1] !== '' ? commitInfo[1].split(' ') : [], + author: commitInfo[2], + authorEmail: commitInfo[3], + authorDate: parseInt(commitInfo[4]), + committer: commitInfo[5], + committerEmail: commitInfo[6], + committerDate: parseInt(commitInfo[7]), + signature: ['G', 'U', 'X', 'Y', 'R', 'E', 'B'].includes(commitInfo[8]) + ? { + key: commitInfo[10].trim(), + signer: commitInfo[9].trim(), + status: commitInfo[8] + } + : null, + body: removeTrailingBlankLines(commitInfo.slice(11).join(GIT_LOG_SEPARATOR).split(EOL_REGEX)).join('\n'), + fileChanges: [] + }; + }); + } + + /** + * Get the configuration list of a repository. + * @param repo The path of the repository. + * @param location The location of the configuration to be listed. + * @returns A set of key-value pairs of Git configuration records. + */ + private getConfigList(repo: string, location?: GitConfigLocation): Promise { + const args = ['--no-pager', 'config', '--list', '-z', '--includes']; + if (location) { + args.push('--' + location); + } + + return this.spawnGit(args, repo, (stdout) => { + const configs: GitConfigSet = {}, keyValuePairs = stdout.split('\0'); + const numPairs = keyValuePairs.length - 1; + let comps, key; + for (let i = 0; i < numPairs; i++) { + comps = keyValuePairs[i].split(EOL_REGEX); + key = comps.shift()!; + configs[key] = comps.join('\n'); + } + return configs; + }).catch((errorMessage) => { + if (typeof errorMessage === 'string') { + const message = errorMessage.toLowerCase(); + if (message.startsWith('fatal: unable to read config file') && message.endsWith('no such file or directory')) { + // If the Git command failed due to the configuration file not existing, return an empty list instead of throwing the exception + return {}; + } + } else { + errorMessage = 'An unexpected error occurred while spawning the Git child process.'; + } + throw errorMessage; + }); + } + + /** + * Get the diff `--name-status` records. + * @param repo The path of the repository. + * @param fromHash The revision the diff is from. + * @param toHash The revision the diff is to. + * @returns An array of `--name-status` records. + */ + private getDiffNameStatus(repo: string, fromHash: string, toHash: string) { + return this.execDiff(repo, fromHash, toHash, '--name-status').then((output) => { + let records: DiffNameStatusRecord[] = [], i = 0; + while (i < output.length && output[i] !== '') { + let type = output[i][0]; + if (type === GitFileStatus.Added || type === GitFileStatus.Deleted || type === GitFileStatus.Modified) { + // Add, Modify, or Delete + let p = getPathFromStr(output[i + 1]); + records.push({ type: type, oldFilePath: p, newFilePath: p }); + i += 2; + } else if (type === GitFileStatus.Renamed) { + // Rename + records.push({ type: type, oldFilePath: getPathFromStr(output[i + 1]), newFilePath: getPathFromStr(output[i + 2]) }); + i += 3; + } else { + break; + } + } + return records; + }); + } + + /** + * Get the diff `--numstat` records. + * @param repo The path of the repository. + * @param fromHash The revision the diff is from. + * @param toHash The revision the diff is to. + * @returns An array of `--numstat` records. + */ + private getDiffNumStat(repo: string, fromHash: string, toHash: string) { + return this.execDiff(repo, fromHash, toHash, '--numstat').then((output) => { + let records: DiffNumStatRecord[] = [], i = 0; + while (i < output.length && output[i] !== '') { + let fields = output[i].split('\t'); + if (fields.length !== 3) break; + if (fields[2] !== '') { + // Add, Modify, or Delete + records.push({ filePath: getPathFromStr(fields[2]), additions: parseInt(fields[0]), deletions: parseInt(fields[1]) }); + i += 1; + } else { + // Rename + records.push({ filePath: getPathFromStr(output[i + 2]), additions: parseInt(fields[0]), deletions: parseInt(fields[1]) }); + i += 3; + } + } + return records; + }); + } + + /** + * Get the raw commits in a repository. + * @param repo The path of the repository. + * @param branches The list of branch heads to display, or NULL (show all). + * @param num The maximum number of commits to return. + * @param includeTags Include commits only referenced by tags. + * @param includeRemotes Include remote branches. + * @param includeCommitsMentionedByReflogs Include commits mentioned by reflogs. + * @param onlyFollowFirstParent Only follow the first parent of commits. + * @param order The order for commits to be returned. + * @param remotes An array of the known remotes. + * @param hideRemotes An array of hidden remotes. + * @param stashes An array of all stashes in the repository. + * @returns An array of commits. + */ + private getLog(repo: string, branches: ReadonlyArray | null, num: number, includeTags: boolean, includeRemotes: boolean, includeCommitsMentionedByReflogs: boolean, onlyFollowFirstParent: boolean, order: CommitOrdering, remotes: ReadonlyArray, hideRemotes: ReadonlyArray, stashes: ReadonlyArray) { + const args = ['-c', 'log.showSignature=false', 'log', '--max-count=' + num, '--format=' + this.gitFormatLog, '--' + order + '-order']; + if (onlyFollowFirstParent) { + args.push('--first-parent'); + } + if (branches !== null) { + for (let i = 0; i < branches.length; i++) { + args.push(branches[i]); + } + } else { + // Show All + args.push('--branches'); + if (includeTags) args.push('--tags'); + if (includeCommitsMentionedByReflogs) args.push('--reflog'); + if (includeRemotes) { + if (hideRemotes.length === 0) { + args.push('--remotes'); + } else { + remotes.filter((remote) => !hideRemotes.includes(remote)).forEach((remote) => { + args.push('--glob=refs/remotes/' + remote); + }); + } + } + + // Add the unique list of base hashes of stashes, so that commits only referenced by stashes are displayed + const stashBaseHashes = stashes.map((stash) => stash.baseHash); + stashBaseHashes.filter((hash, index) => stashBaseHashes.indexOf(hash) === index).forEach((hash) => args.push(hash)); + + args.push('HEAD'); + } + args.push('--'); + + return this.spawnGit(args, repo, (stdout) => { + let lines = stdout.split(EOL_REGEX); + let commits: GitCommitRecord[] = []; + for (let i = 0; i < lines.length - 1; i++) { + let line = lines[i].split(GIT_LOG_SEPARATOR); + if (line.length !== 6) break; + commits.push({ hash: line[0], parents: line[1] !== '' ? line[1].split(' ') : [], author: line[2], email: line[3], date: parseInt(line[4]), message: line[5] }); + } + return commits; + }); + } + + /** + * Get the result in a CI/CDs. + * @param cicdConfigs CI/CD configuration. + * @returns The references data. + */ + private async getCICDs(cicdConfigs: CICDConfig[] | null) { + if (cicdConfigs === null) { + return ''; + } + + return await Promise.all( + cicdConfigs.map(async cicdConfig => { + if (cicdConfig.provider === CICDProvider.GitHubV3) { + + const match1 = cicdConfig.gitUrl.match(/^(https?:\/\/|git@)((?=[^/]+@)[^@]+@|(?![^/]+@))([^/:]+)/); + let hostRootUrl = match1 !== null ? 'https://api.' + match1[3] : ''; + + const match2 = cicdConfig.gitUrl.match(/^(https?:\/\/|git@)[^/:]+[/:]([^/]+)\/([^/]*?)(.git|)$/); + let sourceOwner = match2 !== null ? match2[2] : ''; + let sourceRepo = match2 !== null ? match2[3] : ''; + + const apiRoot = `${hostRootUrl}`; + const cicdRootPath = `/repos/${sourceOwner}/${sourceRepo.replace(/\//g, '%2F')}/actions/runs?per_page=100`; + + let config: request.RequestPromiseOptions = { + method: 'GET', + headers: { + 'Authorization': `token ${cicdConfig.glToken}`, + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'vscode-git-graph' + } + }; + if (cicdConfig.glToken === '' && config.headers) { + delete config.headers['Authorization']; + } + + config.transform = (body, response) => { + try { + let res: any = JSON.parse(body); + let last = 1; + let next = 2; + if (typeof response.headers['link'] === 'string') { + next = 3; + const DELIM_LINKS = ','; + const DELIM_LINK_PARAM = ';'; + let links = response.headers['link'].split(DELIM_LINKS); + links.forEach(link => { + let segments = link.split(DELIM_LINK_PARAM); + + let linkPart = segments[0].trim(); + if (!linkPart.startsWith('<') || !linkPart.endsWith('>')) { + return true; + } + linkPart = linkPart.substring(1, linkPart.length - 1); + let match3 = linkPart.match(/&page=(\d+).*$/); + let linkPage = match3 !== null ? match3[1] : '0'; + + for (let i = 1; i < segments.length; i++) { + let rel = segments[i].trim().split('='); + if (rel.length < 2) { + continue; + } + + let relValue = rel[1]; + if (relValue.startsWith('"') && relValue.endsWith('"')) { + relValue = relValue.substring(1, relValue.length - 1); + } + + if (relValue === 'last') { + last = parseInt(linkPage); + } else if (relValue === 'next') { + next = parseInt(linkPage); + } + } + }); + } + if (typeof res['workflow_runs'] !== 'undefined' && res['workflow_runs'].length >= 1) { // url found + let ret: GitCICDData[] = res['workflow_runs'].map( (elm: { [x: string]: any; }) => { + return { + id: elm['id'], + status: elm['conclusion'], + ref: elm['name'], + sha: elm['head_sha'], + web_url: elm['html_url'], + created_at: elm['created_at'], + updated_at: elm['updated_at'] + }; + }); + if (next === 2) { + return { x_total_pages: last, ret: ret }; + } + return ret; + } + return { x_total_pages: 0, ret: 'error' }; + } catch (e) { + return { x_total_pages: 0, ret: e }; + } + }; + return request(`${apiRoot}${cicdRootPath}`, config).then(async (result1st) => { + let promises = []; + promises.push(result1st.ret); + for (let i = 1; i < result1st.x_total_pages; i++) { + promises.push(request(`${apiRoot}${cicdRootPath}&page=${i + 1}`, config)); + } + return await Promise.all(promises); + }).then((resultAll) => { + let retAll: GitCICDData[] = []; + for (let i = 0; i < resultAll.length; i++) { + retAll = retAll.concat(resultAll[i]); + } + return retAll; + }); + } + if (cicdConfig.provider === CICDProvider.GitLabV4) { + + const match1 = cicdConfig.gitUrl.match(/^(https?:\/\/|git@)((?=[^/]+@)[^@]+@|(?![^/]+@))([^/:]+)/); + let hostRootUrl = match1 !== null ? 'https://' + match1[3] : ''; + + const match2 = cicdConfig.gitUrl.match(/^(https?:\/\/|git@)[^/:]+[/:]([^/]+)\/([^/]*?)(.git|)$/); + let sourceOwner = match2 !== null ? match2[2] : ''; + let sourceRepo = match2 !== null ? match2[3] : ''; + + const apiRoot = `${hostRootUrl}/api/v4`; + const cicdRootPath = `/projects/${sourceOwner}%2F${sourceRepo.replace(/\//g, '%2F')}/pipelines?per_page=100`; + + const config: request.RequestPromiseOptions = { + method: 'GET', + headers: { + 'PRIVATE-TOKEN': cicdConfig.glToken, + 'User-Agent': 'vscode-git-graph' + } + }; + + config.transform = (body, response) => { + try { + if (typeof response.headers['x-page'] === 'string' && typeof response.headers['x-total-pages'] === 'string' && typeof response.headers['x-total'] === 'string') { + let res: any = JSON.parse(body); + if (parseInt(response.headers['x-total']) !== 0 && res.length && res[0].id) { // url found + let ret: GitCICDData[] = res; + if (parseInt(response.headers['x-page']) === 1) { + return { x_total_pages: parseInt(response.headers['x-total-pages']), ret: ret }; + } + return ret; + } + } + return { x_total_pages: 0, ret: 'error' }; + } catch (e) { + return { x_total_pages: 0, ret: e }; + } + }; + return request(`${apiRoot}${cicdRootPath}`, config).then(async (result1st) => { + let promises = []; + promises.push(result1st.ret); + for (let i = 1; i < result1st.x_total_pages; i++) { + promises.push(request(`${apiRoot}${cicdRootPath}&page=${i + 1}`, config)); + } + return await Promise.all(promises); + }).then((resultAll) => { + let retAll: GitCICDData[] = []; + for (let i = 0; i < resultAll.length; i++) { + retAll = retAll.concat(resultAll[i]); + } + return retAll; + }); + } + }) + ).then((resultAll2) => { + let retAll: GitCICDData[] = []; + resultAll2.forEach(resultList => { + resultList?.forEach(result => { + retAll = retAll.concat(result); + }); + }); + return retAll; + }); + } + /** + * Get the references in a repository. + * @param repo The path of the repository. + * @param showRemoteBranches Are remote branches shown. + * @param showRemoteHeads Are remote heads shown. + * @param hideRemotes An array of hidden remotes. + * @returns The references data. + */ + private getRefs(repo: string, showRemoteBranches: boolean, showRemoteHeads: boolean, hideRemotes: ReadonlyArray) { + let args = ['show-ref']; + if (!showRemoteBranches) args.push('--heads', '--tags'); + args.push('-d', '--head'); + + const hideRemotePatterns = hideRemotes.map((remote) => 'refs/remotes/' + remote + '/'); + + return this.spawnGit(args, repo, (stdout) => { + let refData: GitRefData = { head: null, heads: [], tags: [], remotes: [] }; + let lines = stdout.split(EOL_REGEX); + for (let i = 0; i < lines.length - 1; i++) { + let line = lines[i].split(' '); + if (line.length < 2) continue; + + let hash = line.shift()!; + let ref = line.join(' '); + + if (ref.startsWith('refs/heads/')) { + refData.heads.push({ hash: hash, name: ref.substring(11) }); + } else if (ref.startsWith('refs/tags/')) { + let annotated = ref.endsWith('^{}'); + refData.tags.push({ hash: hash, name: (annotated ? ref.substring(10, ref.length - 3) : ref.substring(10)), annotated: annotated }); + } else if (ref.startsWith('refs/remotes/')) { + if (!hideRemotePatterns.some((pattern) => ref.startsWith(pattern)) && (showRemoteHeads || !ref.endsWith('/HEAD'))) { + refData.remotes.push({ hash: hash, name: ref.substring(13) }); + } + } else if (ref === 'HEAD') { + refData.head = hash; + } + } + return refData; + }); + } + + /** + * Get the stashes in a repository. + * @param repo The path of the repository. + * @returns An array of stashes. + */ + private getStashes(repo: string) { + return this.spawnGit(['reflog', '--format=' + this.gitFormatStash, 'refs/stash', '--'], repo, (stdout) => { + let lines = stdout.split(EOL_REGEX); + let stashes: GitStash[] = []; + for (let i = 0; i < lines.length - 1; i++) { + let line = lines[i].split(GIT_LOG_SEPARATOR); + if (line.length !== 7 || line[1] === '') continue; + let parentHashes = line[1].split(' '); + stashes.push({ + hash: line[0], + baseHash: parentHashes[0], + untrackedFilesHash: parentHashes.length === 3 ? parentHashes[2] : null, + selector: line[2], + author: line[3], + email: line[4], + date: parseInt(line[5]), + message: line[6] + }); + } + return stashes; + }).catch(() => []); + } + + /** + * Get the names of the remotes of a repository. + * @param repo The path of the repository. + * @returns An array of remote names. + */ + private getRemotes(repo: string) { + return this.spawnGit(['remote'], repo, (stdout) => { + let lines = stdout.split(EOL_REGEX); + lines.pop(); + return lines; + }); + } + + /** + * Get the number of uncommitted changes in a repository. + * @param repo The path of the repository. + * @returns The number of uncommitted changes. + */ + private getUncommittedChanges(repo: string) { + return this.spawnGit(['status', '--untracked-files=' + (getConfig().showUntrackedFiles ? 'all' : 'no'), '--porcelain'], repo, (stdout) => { + const numLines = stdout.split(EOL_REGEX).length; + return numLines > 1 ? numLines - 1 : 0; + }); + } + + /** + * Get the untracked and deleted files that are not staged or committed. + * @param repo The path of the repository. + * @returns The untracked and deleted files. + */ + private getStatus(repo: string) { + return this.spawnGit(['status', '-s', '--untracked-files=' + (getConfig().showUntrackedFiles ? 'all' : 'no'), '--porcelain', '-z'], repo, (stdout) => { + let output = stdout.split('\0'), i = 0; + let status: GitStatusFiles = { deleted: [], untracked: [] }; + let path = '', c1 = '', c2 = ''; + while (i < output.length && output[i] !== '') { + if (output[i].length < 4) break; + path = output[i].substring(3); + c1 = output[i].substring(0, 1); + c2 = output[i].substring(1, 2); + if (c1 === 'D' || c2 === 'D') status.deleted.push(path); + else if (c1 === '?' || c2 === '?') status.untracked.push(path); + + if (c1 === 'R' || c2 === 'R' || c1 === 'C' || c2 === 'C') { + // Renames or copies + i += 2; + } else { + i += 1; + } + } + return status; + }); + } + + + /* Private Utils */ + + /** + * Check if there are staged changes that resulted from a squash merge, and if so, commit them. + * @param repo The path of the repository. + * @param obj The object being squash merged into the current branch. + * @param actionOn Is the merge on a branch, remote-tracking branch or commit. + * @param squashMessageFormat The format to be used in the commit message of the squash. + * @returns The ErrorInfo from the executed command. + */ + private commitSquashIfStagedChangesExist(repo: string, obj: string, actionOn: MergeActionOn, squashMessageFormat: SquashMessageFormat, signCommits: boolean): Promise { + return this.areStagedChanges(repo).then((changes) => { + if (changes) { + const args = ['commit']; + if (signCommits) { + args.push('-S'); + } + if (squashMessageFormat === SquashMessageFormat.Default) { + args.push('-m', 'Merge ' + actionOn.toLowerCase() + ' \'' + obj + '\''); + } else { + args.push('--no-edit'); + } + return this.runGitCommand(args, repo); + } else { + return null; + } + }); + } + + /** + * Get the diff between two revisions. + * @param repo The path of the repository. + * @param fromHash The revision the diff is from. + * @param toHash The revision the diff is to. + * @param arg Sets the data reported from the diff. + * @returns The diff output. + */ + private execDiff(repo: string, fromHash: string, toHash: string, arg: '--numstat' | '--name-status') { + let args: string[]; + if (fromHash === toHash) { + args = ['diff-tree', arg, '-r', '--root', '--find-renames', '--diff-filter=AMDR', '-z', fromHash]; + } else { + args = ['diff', arg, '--find-renames', '--diff-filter=AMDR', '-z', fromHash]; + if (toHash !== '') args.push(toHash); + } + + return this.spawnGit(args, repo, (stdout) => { + let lines = stdout.split('\0'); + if (fromHash === toHash) lines.shift(); + return lines; + }); + } + + /** + * Run a Git command (typically for a Git Graph View action). + * @param args The arguments to pass to Git. + * @param repo The repository to run the command in. + * @returns The returned ErrorInfo (suitable for being sent to the Git Graph View). + */ + private runGitCommand(args: string[], repo: string): Promise { + return this._spawnGit(args, repo, () => null).catch((errorMessage: string) => errorMessage); + } + + /** + * Spawn Git, with the return value resolved from `stdout` as a string. + * @param args The arguments to pass to Git. + * @param repo The repository to run the command in. + * @param resolveValue A callback invoked to resolve the data from `stdout`. + */ + private spawnGit(args: string[], repo: string, resolveValue: { (stdout: string): T }) { + return this._spawnGit(args, repo, (stdout) => resolveValue(stdout.toString())); + } + + /** + * Spawn Git, with the return value resolved from `stdout` as a buffer. + * @param args The arguments to pass to Git. + * @param repo The repository to run the command in. + * @param resolveValue A callback invoked to resolve the data from `stdout`. + */ + private _spawnGit(args: string[], repo: string, resolveValue: { (stdout: Buffer): T }) { + return new Promise((resolve, reject) => { + if (this.gitExecutable === null) return reject(UNABLE_TO_FIND_GIT_MSG); + + resolveSpawnOutput(cp.spawn(this.gitExecutable.path, args, { + cwd: repo, + env: Object.assign({}, process.env, this.askpassEnv) + })).then((values) => { + let status = values[0], stdout = values[1]; + if (status.code === 0) { + resolve(resolveValue(stdout)); + } else { + reject(getErrorMessage(status.error, stdout, values[2])); + } + }); + + this.logger.logCmd('git', args); + }); + } +} + + +/** + * Generates the file changes from the diff output and status information. + * @param nameStatusRecords The `--name-status` records. + * @param numStatRecords The `--numstat` records. + * @param status The deleted and untracked files. + * @returns An array of file changes. + */ +function generateFileChanges(nameStatusRecords: DiffNameStatusRecord[], numStatRecords: DiffNumStatRecord[], status: GitStatusFiles | null) { + let fileChanges: Writeable[] = [], fileLookup: { [file: string]: number } = {}, i = 0; + + for (i = 0; i < nameStatusRecords.length; i++) { + fileLookup[nameStatusRecords[i].newFilePath] = fileChanges.length; + fileChanges.push({ oldFilePath: nameStatusRecords[i].oldFilePath, newFilePath: nameStatusRecords[i].newFilePath, type: nameStatusRecords[i].type, additions: null, deletions: null }); + } + + if (status !== null) { + let filePath; + for (i = 0; i < status.deleted.length; i++) { + filePath = getPathFromStr(status.deleted[i]); + if (typeof fileLookup[filePath] === 'number') { + fileChanges[fileLookup[filePath]].type = GitFileStatus.Deleted; + } else { + fileChanges.push({ oldFilePath: filePath, newFilePath: filePath, type: GitFileStatus.Deleted, additions: null, deletions: null }); + } + } + for (i = 0; i < status.untracked.length; i++) { + filePath = getPathFromStr(status.untracked[i]); + fileChanges.push({ oldFilePath: filePath, newFilePath: filePath, type: GitFileStatus.Untracked, additions: null, deletions: null }); + } + } + + for (i = 0; i < numStatRecords.length; i++) { + if (typeof fileLookup[numStatRecords[i].filePath] === 'number') { + fileChanges[fileLookup[numStatRecords[i].filePath]].additions = numStatRecords[i].additions; + fileChanges[fileLookup[numStatRecords[i].filePath]].deletions = numStatRecords[i].deletions; + } + } + + return fileChanges; +} + +/** + * Get the specified config value from a set of key-value config pairs. + * @param configs A set key-value pairs of Git configuration records. + * @param key The key of the desired config. + * @returns The value for `key` if it exists, otherwise NULL. + */ +function getConfigValue(configs: GitConfigSet, key: string) { + return typeof configs[key] !== 'undefined' ? configs[key] : null; +} + +/** + * Produce a suitable error message from a spawned Git command that terminated with an erroneous status code. + * @param error An error generated by JavaScript (optional). + * @param stdoutBuffer A buffer containing the data outputted to `stdout`. + * @param stderr A string containing the data outputted to `stderr`. + * @returns A suitable error message. + */ +function getErrorMessage(error: Error | null, stdoutBuffer: Buffer, stderr: string) { + let stdout = stdoutBuffer.toString(), lines: string[]; + if (stdout !== '' || stderr !== '') { + lines = (stderr + stdout).split(EOL_REGEX); + lines.pop(); + } else if (error) { + lines = error.message.split(EOL_REGEX); + } else { + lines = []; + } + return lines.join('\n'); +} + +/** + * Remove trailing blank lines from an array of lines. + * @param lines The array of lines. + * @returns The same array. + */ +function removeTrailingBlankLines(lines: string[]) { + while (lines.length > 0 && lines[lines.length - 1] === '') { + lines.pop(); + } + return lines; +} + +/** + * Get all the unique strings from an array of strings. + * @param items The array of strings with duplicates. + * @returns An array of unique strings. + */ +function unique(items: ReadonlyArray) { + const uniqueItems: { [item: string]: true } = {}; + items.forEach((item) => uniqueItems[item] = true); + return Object.keys(uniqueItems); +} + + +/* Types */ + +interface DiffNameStatusRecord { + type: GitFileStatus; + oldFilePath: string; + newFilePath: string; +} + +interface DiffNumStatRecord { + filePath: string; + additions: number; + deletions: number; +} + +interface GitBranchData { + branches: string[]; + head: string | null; + error: ErrorInfo; +} + +interface GitCommitRecord { + hash: string; + parents: string[]; + author: string; + email: string; + date: number; + message: string; +} + +interface GitCommitData { + commits: GitCommit[]; + head: string | null; + tags: string[]; + moreCommitsAvailable: boolean; + error: ErrorInfo; +} + +export interface GitCommitDetailsData { + commitDetails: GitCommitDetails | null; + error: ErrorInfo; +} + +interface GitCommitComparisonData { + fileChanges: GitFileChange[]; + error: ErrorInfo; +} + +type GitConfigSet = { [key: string]: string }; + +interface GitRef { + hash: string; + name: string; +} + +interface GitRefTag extends GitRef { + annotated: boolean; +} + +interface GitRefData { + head: string | null; + heads: GitRef[]; + tags: GitRefTag[]; + remotes: GitRef[]; +} + +interface GitRepoInfo extends GitBranchData { + remotes: string[]; + stashes: GitStash[]; +} + +interface GitRepoConfigData { + config: GitRepoConfig | null; + error: ErrorInfo; +} + +interface GitStatusFiles { + deleted: string[]; + untracked: string[]; +} + +interface GitTagDetailsData { + tagHash: string; + name: string; + email: string; + date: number; + message: string; + error: ErrorInfo; +} From 7a39b30c447f3b4abecea2cea9d252323e4a5fa2 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Fri, 12 Mar 2021 02:05:50 +0900 Subject: [PATCH 11/54] #462 Added cicdManager and changed relation files --- package.json | 3009 +++++++++++++++++++++-------------------- src/cicdManager.ts | 589 ++++++++ src/commands.ts | 24 +- src/config.ts | 7 + src/extension.ts | 5 +- src/extensionState.ts | 47 + src/gitGraphView.ts | 24 +- src/types.ts | 14 + 8 files changed, 2213 insertions(+), 1506 deletions(-) create mode 100644 src/cicdManager.ts diff --git a/package.json b/package.json index d820d090..ca74f692 100644 --- a/package.json +++ b/package.json @@ -1,1496 +1,1513 @@ -{ - "name": "git-graph", - "displayName": "Git Graph", - "version": "1.29.0", - "publisher": "mhutchie", - "author": { - "name": "Michael Hutchison", - "email": "mhutchie@16right.com" - }, - "description": "View a Git Graph of your repository, and perform Git actions from the graph.", - "keywords": [ - "git", - "graph", - "visualise", - "diff", - "action" - ], - "categories": [ - "Other" - ], - "homepage": "https://github.com/mhutchie/vscode-git-graph", - "repository": { - "type": "git", - "url": "https://github.com/mhutchie/vscode-git-graph.git" - }, - "bugs": { - "url": "https://github.com/mhutchie/vscode-git-graph/issues" - }, - "qna": "https://github.com/mhutchie/vscode-git-graph/wiki/Support-Resources", - "license": "SEE LICENSE IN 'LICENSE'", - "icon": "resources/icon.png", - "engines": { - "vscode": "^1.38.0" - }, - "extensionKind": [ - "workspace" - ], - "activationEvents": [ - "*" - ], - "main": "./out/extension.js", - "contributes": { - "commands": [ - { - "category": "Git Graph", - "command": "git-graph.view", - "title": "View Git Graph (git log)", - "icon": { - "light": "resources/cmd-icon-light.svg", - "dark": "resources/cmd-icon-dark.svg" - } - }, - { - "category": "Git Graph", - "command": "git-graph.addGitRepository", - "title": "Add Git Repository..." - }, - { - "category": "Git Graph", - "command": "git-graph.clearAvatarCache", - "title": "Clear Avatar Cache" - }, - { - "category": "Git Graph", - "command": "git-graph.endAllWorkspaceCodeReviews", - "title": "End All Code Reviews in Workspace" - }, - { - "category": "Git Graph", - "command": "git-graph.endSpecificWorkspaceCodeReview", - "title": "End a specific Code Review in Workspace..." - }, - { - "category": "Git Graph", - "command": "git-graph.fetch", - "title": "Fetch from Remote(s)" - }, - { - "category": "Git Graph", - "command": "git-graph.removeGitRepository", - "title": "Remove Git Repository..." - }, - { - "category": "Git Graph", - "command": "git-graph.resumeWorkspaceCodeReview", - "title": "Resume a specific Code Review in Workspace..." - }, - { - "category": "Git Graph", - "command": "git-graph.version", - "title": "Get Version Information" - }, - { - "category": "Git Graph", - "command": "git-graph.openFile", - "title": "Open File", - "icon": "$(go-to-file)", - "enablement": "isInDiffEditor && resourceScheme == git-graph && git-graph:codiconsSupported" - } - ], - "configuration": { - "type": "object", - "title": "Git Graph", - "properties": { - "git-graph.commitDetailsView.autoCenter": { - "type": "boolean", - "default": true, - "description": "Automatically center the Commit Details View when it is opened." - }, - "git-graph.commitDetailsView.fileView.fileTree.compactFolders": { - "type": "boolean", - "default": true, - "description": "Render the File Tree in the Commit Details View in a compacted form, such that folders with a single child folder are compressed into a single combined folder element." - }, - "git-graph.commitDetailsView.fileView.type": { - "type": "string", - "enum": [ - "File Tree", - "File List" - ], - "enumDescriptions": [ - "Display files in a tree structure.", - "Display files in a list (useful for repositories with deep folder structures)." - ], - "default": "File Tree", - "description": "Sets the default type of File View used in the Commit Details View. This can be overridden per repository using the controls on the right side of the Commit Details View." - }, - "git-graph.commitDetailsView.location": { - "type": "string", - "enum": [ - "Inline", - "Docked to Bottom" - ], - "enumDescriptions": [ - "Show the Commit Details View inline with the graph & commits.", - "Show the Commit Details View docked to the bottom of the Git Graph View." - ], - "default": "Inline", - "description": "Specifies where the Commit Details View is rendered in the Git Graph View." - }, - "git-graph.contextMenuActionsVisibility": { - "type": "object", - "default": {}, - "properties": { - "branch": { - "type": "object", - "properties": { - "checkout": { - "type": "boolean", - "title": "Checkout Branch" - }, - "rename": { - "type": "boolean", - "title": "Rename Branch..." - }, - "delete": { - "type": "boolean", - "title": "Delete Branch..." - }, - "merge": { - "type": "boolean", - "title": "Merge into current branch..." - }, - "rebase": { - "type": "boolean", - "title": "Rebase current branch on Branch..." - }, - "push": { - "type": "boolean", - "title": "Push Branch..." - }, - "createPullRequest": { - "type": "boolean", - "title": "Create Pull Request..." - }, - "createArchive": { - "type": "boolean", - "title": "Create Archive" - }, - "selectInBranchesDropdown": { - "type": "boolean", - "title": "Select in Branches Dropdown" - }, - "unselectInBranchesDropdown": { - "type": "boolean", - "title": "Unselect in Branches Dropdown" - }, - "copyName": { - "type": "boolean", - "title": "Copy Branch Name to Clipboard" - } - } - }, - "commit": { - "type": "object", - "properties": { - "addTag": { - "type": "boolean", - "title": "Add Tag..." - }, - "createBranch": { - "type": "boolean", - "title": "Create Branch..." - }, - "checkout": { - "type": "boolean", - "title": "Checkout..." - }, - "cherrypick": { - "type": "boolean", - "title": "Cherry Pick..." - }, - "revert": { - "type": "boolean", - "title": "Revert..." - }, - "drop": { - "type": "boolean", - "title": "Drop..." - }, - "merge": { - "type": "boolean", - "title": "Merge into current branch..." - }, - "rebase": { - "type": "boolean", - "title": "Rebase current branch on this Commit..." - }, - "reset": { - "type": "boolean", - "title": "Reset current branch to this Commit..." - }, - "copyHash": { - "type": "boolean", - "title": "Copy Commit Hash to Clipboard" - }, - "copySubject": { - "type": "boolean", - "title": "Copy Commit Subject to Clipboard" - } - } - }, - "remoteBranch": { - "type": "object", - "properties": { - "checkout": { - "type": "boolean", - "title": "Checkout Branch..." - }, - "delete": { - "type": "boolean", - "title": "Delete Remote Branch..." - }, - "fetch": { - "type": "boolean", - "title": "Fetch into local branch..." - }, - "merge": { - "type": "boolean", - "title": "Merge into current branch..." - }, - "pull": { - "type": "boolean", - "title": "Pull into current branch..." - }, - "createPullRequest": { - "type": "boolean", - "title": "Create Pull Request" - }, - "createArchive": { - "type": "boolean", - "title": "Create Archive" - }, - "selectInBranchesDropdown": { - "type": "boolean", - "title": "Select in Branches Dropdown" - }, - "unselectInBranchesDropdown": { - "type": "boolean", - "title": "Unselect in Branches Dropdown" - }, - "copyName": { - "type": "boolean", - "title": "Copy Branch Name to Clipboard" - } - } - }, - "stash": { - "type": "object", - "properties": { - "apply": { - "type": "boolean", - "title": "Apply Stash..." - }, - "createBranch": { - "type": "boolean", - "title": "Create Branch from Stash..." - }, - "pop": { - "type": "boolean", - "title": "Pop Stash..." - }, - "drop": { - "type": "boolean", - "title": "Drop Stash..." - }, - "copyName": { - "type": "boolean", - "title": "Copy Stash Name to Clipboard" - }, - "copyHash": { - "type": "boolean", - "title": "Copy Stash Hash to Clipboard" - } - } - }, - "tag": { - "type": "object", - "properties": { - "viewDetails": { - "type": "boolean", - "title": "View Details" - }, - "delete": { - "type": "boolean", - "title": "Delete Tag..." - }, - "push": { - "type": "boolean", - "title": "Push Tag..." - }, - "createArchive": { - "type": "boolean", - "title": "Create Archive" - }, - "copyName": { - "type": "boolean", - "title": "Copy Tag Name to Clipboard" - } - } - }, - "uncommittedChanges": { - "type": "object", - "properties": { - "stash": { - "type": "boolean", - "title": "Stash uncommitted changes..." - }, - "reset": { - "type": "boolean", - "title": "Reset uncommitted changes..." - }, - "clean": { - "type": "boolean", - "title": "Clean untracked files..." - }, - "openSourceControlView": { - "type": "boolean", - "title": "Open Source Control View" - } - } - } - }, - "markdownDescription": "Customise which context menu actions are visible. For example, if you want to hide the rebase action from the branch context menu, a suitable value for this setting is `{ \"branch\": { \"rebase\": false } }`. For more information of how to configure this setting, view the documentation [here](https://github.com/mhutchie/vscode-git-graph/wiki/Extension-Settings#context-menu-actions-visibility)." - }, - "git-graph.customBranchGlobPatterns": { - "type": "array", - "items": { - "type": "object", - "title": "Branch Glob Pattern", - "required": [ - "name", - "glob" - ], - "properties": { - "name": { - "type": "string", - "title": "Name of pattern", - "description": "Name used to reference the pattern in the 'Branches' dropdown" - }, - "glob": { - "type": "string", - "title": "Glob pattern", - "description": "The Glob Pattern , as used in 'git log --glob='. For example: heads/feature/*" - } - } - }, - "default": [], - "description": "An array of Custom Branch Glob Patterns to be shown in the 'Branches' dropdown. Example: [{\"name\": \"Feature Requests\", \"glob\": \"heads/feature/*\"}]" - }, - "git-graph.customEmojiShortcodeMappings": { - "type": "array", - "items": { - "type": "object", - "title": "Custom Emoji Shortcode Mapping", - "required": [ - "shortcode", - "emoji" - ], - "properties": { - "shortcode": { - "type": "string", - "title": "Emoji Shortcode", - "description": "Emoji Shortcode (e.g. \":sparkles:\")" - }, - "emoji": { - "type": "string", - "title": "Emoji", - "description": "Emoji (e.g. \"✨\")" - } - } - }, - "default": [], - "description": "An array of custom Emoji Shortcode mappings. Example: [{\"shortcode\": \":sparkles:\", \"emoji\":\"✨\"}]" - }, - "git-graph.customPullRequestProviders": { - "type": "array", - "items": { - "type": "object", - "title": "Pull Request Provider", - "required": [ - "name", - "templateUrl" - ], - "properties": { - "name": { - "type": "string", - "title": "Name of the Provider", - "description": "A unique, identifying, display name for the provider." - }, - "templateUrl": { - "type": "string", - "title": "Template URL", - "markdownDescription": "A template URL that can be used to create a Pull Request, after the $1 - $8 variables have been substituted to construct the final URL. For information on how to configure this setting, see the documentation [here](https://github.com/mhutchie/vscode-git-graph/wiki/Configuring-a-custom-Pull-Request-Provider)." - } - } - }, - "default": [], - "markdownDescription": "An array of custom Pull Request providers that can be used in the \"Pull Request Creation\" Integration. For information on how to configure this setting, see the documentation [here](https://github.com/mhutchie/vscode-git-graph/wiki/Configuring-a-custom-Pull-Request-Provider)." - }, - "git-graph.date.format": { - "type": "string", - "enum": [ - "Date & Time", - "Date Only", - "ISO Date & Time", - "ISO Date Only", - "Relative" - ], - "enumDescriptions": [ - "Show the date and time (e.g. \"24 Mar 2019 21:34\")", - "Show the date only (e.g. \"24 Mar 2019\")", - "Show the ISO date and time (e.g. \"2019-03-24 21:34\")", - "Show the ISO date only (e.g. \"2019-03-24\")", - "Show relative times (e.g. \"5 minutes ago\")" - ], - "default": "Date & Time", - "description": "Specifies the date format to be used in the \"Date\" column on the Git Graph View." - }, - "git-graph.date.type": { - "type": "string", - "enum": [ - "Author Date", - "Commit Date" - ], - "enumDescriptions": [ - "Use the author date of a commit.", - "Use the committer date of a commit." - ], - "default": "Author Date", - "description": "Specifies the date type to be displayed in the \"Date\" column on the Git Graph View." - }, - "git-graph.defaultColumnVisibility": { - "type": "object", - "properties": { - "Date": { - "type": "boolean", - "title": "Visibility of the Date column" - }, - "Author": { - "type": "boolean", - "title": "Visibility of the Author column" - }, - "Commit": { - "type": "boolean", - "title": "Visibility of the Commit column" - }, - "CICD": { - "type": "boolean", - "title": "Visibility of the CI/CD Status column" - } - }, - "default": { - "Date": true, - "Author": true, - "Commit": true, - "CICD": true - }, - "description": "An object specifying the default visibility of the Date, Author & Commit columns. Example: {\"Date\": true, \"Author\": true, \"Commit\": true}" - }, - "git-graph.dialog.addTag.pushToRemote": { - "type": "boolean", - "default": false, - "description": "Default state of the field indicating whether the tag should be pushed to a remote once it is added." - }, - "git-graph.dialog.addTag.type": { - "type": "string", - "enum": [ - "Annotated", - "Lightweight" - ], - "default": "Annotated", - "description": "Default type of the tag being added." - }, - "git-graph.dialog.applyStash.reinstateIndex": { - "type": "boolean", - "default": false, - "description": "Default state of the \"Reinstate Index\" checkbox." - }, - "git-graph.dialog.cherryPick.noCommit": { - "type": "boolean", - "default": false, - "description": "Default state of the \"No Commit\" checkbox." - }, - "git-graph.dialog.cherryPick.recordOrigin": { - "type": "boolean", - "default": false, - "description": "Default state of the \"Record Origin\" checkbox." - }, - "git-graph.dialog.createBranch.checkOut": { - "type": "boolean", - "default": false, - "description": "Default state of the \"Check out\" checkbox." - }, - "git-graph.dialog.deleteBranch.forceDelete": { - "type": "boolean", - "default": false, - "description": "Default state of the \"Force Delete\" checkbox." - }, - "git-graph.dialog.fetchIntoLocalBranch.forceFetch": { - "type": "boolean", - "default": false, - "description": "Default state of the \"Force Fetch\" checkbox." - }, - "git-graph.dialog.fetchRemote.prune": { - "type": "boolean", - "default": false, - "description": "Default state of the \"Prune\" checkbox." - }, - "git-graph.dialog.fetchRemote.pruneTags": { - "type": "boolean", - "default": false, - "description": "Default state of the \"Prune Tags\" checkbox." - }, - "git-graph.dialog.general.referenceInputSpaceSubstitution": { - "type": "string", - "enum": [ - "None", - "Hyphen", - "Underscore" - ], - "enumDescriptions": [ - "Don't replace spaces.", - "Replace space characters with hyphens, for example: \"new branch\" -> \"new-branch\".", - "Replace space characters with underscores, for example: \"new branch\" -> \"new_branch\"." - ], - "default": "None", - "description": "Specifies a substitution that is automatically performed when space characters are entered or pasted into reference inputs on dialogs (e.g. Create Branch, Add Tag, etc.)." - }, - "git-graph.dialog.merge.noCommit": { - "type": "boolean", - "default": false, - "description": "Default state of the \"No Commit\" checkbox." - }, - "git-graph.dialog.merge.noFastForward": { - "type": "boolean", - "default": true, - "description": "Default state of the \"Create a new commit even if fast-forward is possible\" checkbox." - }, - "git-graph.dialog.merge.squashCommits": { - "type": "boolean", - "default": false, - "description": "Default state of the \"Squash Commits\" checkbox." - }, - "git-graph.dialog.merge.squashMessageFormat": { - "type": "string", - "enum": [ - "Default", - "Git SQUASH_MSG" - ], - "enumDescriptions": [ - "Use the squash message generated by Git Graph.", - "Use the detailed squash message generated by Git (stored in .git/SQUASH_MSG)." - ], - "default": "Default", - "description": "Specifies the message format used for the squashed commit (when the \"Squash Commits\" option is selected)." - }, - "git-graph.dialog.popStash.reinstateIndex": { - "type": "boolean", - "default": false, - "description": "Default state of the \"Reinstate Index\" checkbox." - }, - "git-graph.dialog.pullBranch.noFastForward": { - "type": "boolean", - "default": false, - "description": "Default state of the \"Create a new commit even if fast-forward is possible\" checkbox." - }, - "git-graph.dialog.pullBranch.squashCommits": { - "type": "boolean", - "default": false, - "description": "Default state of the \"Squash Commits\" checkbox." - }, - "git-graph.dialog.pullBranch.squashMessageFormat": { - "type": "string", - "enum": [ - "Default", - "Git SQUASH_MSG" - ], - "enumDescriptions": [ - "Use the squash message generated by Git Graph.", - "Use the detailed squash message generated by Git (stored in .git/SQUASH_MSG)." - ], - "default": "Default", - "description": "Specifies the message format used for the squashed commit (when the \"Squash Commits\" option is selected)." - }, - "git-graph.dialog.rebase.ignoreDate": { - "type": "boolean", - "default": true, - "description": "Default state of the \"Ignore Date (non-interactive rebase only)\" checkbox." - }, - "git-graph.dialog.rebase.launchInteractiveRebase": { - "type": "boolean", - "default": false, - "description": "Default state of the \"Launch Interactive Rebase in new Terminal\" checkbox." - }, - "git-graph.dialog.resetCurrentBranchToCommit.mode": { - "type": "string", - "enum": [ - "Soft", - "Mixed", - "Hard" - ], - "enumDescriptions": [ - "Soft - Keep all changes, but reset head", - "Mixed - Keep working tree, but reset index", - "Hard - Discard all changes" - ], - "default": "Mixed", - "description": "Default mode to be used for the reset action." - }, - "git-graph.dialog.resetUncommittedChanges.mode": { - "type": "string", - "enum": [ - "Mixed", - "Hard" - ], - "enumDescriptions": [ - "Mixed - Keep working tree, but reset index", - "Hard - Discard all changes" - ], - "default": "Mixed", - "description": "Default mode to be used for the reset action." - }, - "git-graph.dialog.stashUncommittedChanges.includeUntracked": { - "type": "boolean", - "default": true, - "description": "Default state of the \"Include Untracked\" checkbox." - }, - "git-graph.enhancedAccessibility": { - "type": "boolean", - "default": false, - "description": "Visual file change A|M|D|R|U indicators in the Commit Details View for users with colour blindness. In the future, this setting will enable any additional accessibility related features of Git Graph that aren't enabled by default." - }, - "git-graph.fileEncoding": { - "type": "string", - "default": "utf8", - "markdownDescription": "The character set encoding used when retrieving a specific version of repository files (e.g. in the Diff View). A list of all supported encodings can be found [here](https://github.com/ashtuchkin/iconv-lite/wiki/Supported-Encodings).", - "scope": "resource" - }, - "git-graph.graph.colours": { - "type": "array", - "items": { - "type": "string", - "description": "Colour (HEX or RGB)", - "pattern": "^\\s*(#[0-9a-fA-F]{6}|#[0-9a-fA-F]{8}|rgb[a]?\\s*\\(\\d{1,3},\\s*\\d{1,3},\\s*\\d{1,3}\\))\\s*$" - }, - "default": [ - "#0085d9", - "#d9008f", - "#00d90a", - "#d98500", - "#a300d9", - "#ff0000", - "#00d9cc", - "#e138e8", - "#85d900", - "#dc5b23", - "#6f24d6", - "#ffcc00" - ], - "description": "Specifies the colours used on the graph." - }, - "git-graph.graph.style": { - "type": "string", - "enum": [ - "rounded", - "angular" - ], - "enumDescriptions": [ - "Use smooth curves when transitioning between branches on the graph.", - "Use angular lines when transitioning between branches on the graph." - ], - "default": "rounded", - "description": "Specifies the style of the graph." - }, - "git-graph.graph.uncommittedChanges": { - "type": "string", - "enum": [ - "Open Circle at the Uncommitted Changes", - "Open Circle at the Checked Out Commit" - ], - "enumDescriptions": [ - "Display the Uncommitted Changes as a grey open circle, connected to the commit referenced by HEAD with a solid grey line. The current file system's state is therefore always displayed as an open circle.", - "Display the Uncommitted Changes as a grey closed circle, connected to the commit referenced by HEAD with a dotted grey line. The commit referenced by HEAD is therefore always displayed as an open circle." - ], - "default": "Open Circle at the Uncommitted Changes", - "description": "Specifies how the Uncommitted Changes are displayed on the graph." - }, - "git-graph.integratedTerminalShell": { - "type": "string", - "default": "", - "description": "Specifies the path and filename of the Shell executable to be used by the Visual Studio Code Integrated Terminal, when it is opened by Git Graph. For example, to use Git Bash on Windows this setting would commonly be set to \"C:\\Program Files\\Git\\bin\\bash.exe\". If this setting is left blank, the default Shell is used.", - "scope": "machine" - }, - "git-graph.keyboardShortcut.find": { - "type": "string", - "enum": [ - "CTRL/CMD + A", - "CTRL/CMD + B", - "CTRL/CMD + C", - "CTRL/CMD + D", - "CTRL/CMD + E", - "CTRL/CMD + F", - "CTRL/CMD + G", - "CTRL/CMD + H", - "CTRL/CMD + I", - "CTRL/CMD + J", - "CTRL/CMD + K", - "CTRL/CMD + L", - "CTRL/CMD + M", - "CTRL/CMD + N", - "CTRL/CMD + O", - "CTRL/CMD + P", - "CTRL/CMD + Q", - "CTRL/CMD + R", - "CTRL/CMD + S", - "CTRL/CMD + T", - "CTRL/CMD + U", - "CTRL/CMD + V", - "CTRL/CMD + W", - "CTRL/CMD + X", - "CTRL/CMD + Y", - "CTRL/CMD + Z" - ], - "default": "CTRL/CMD + F", - "description": "The keybinding for the keyboard shortcut that opens the Find Widget in the Git Graph View." - }, - "git-graph.keyboardShortcut.refresh": { - "type": "string", - "enum": [ - "CTRL/CMD + A", - "CTRL/CMD + B", - "CTRL/CMD + C", - "CTRL/CMD + D", - "CTRL/CMD + E", - "CTRL/CMD + F", - "CTRL/CMD + G", - "CTRL/CMD + H", - "CTRL/CMD + I", - "CTRL/CMD + J", - "CTRL/CMD + K", - "CTRL/CMD + L", - "CTRL/CMD + M", - "CTRL/CMD + N", - "CTRL/CMD + O", - "CTRL/CMD + P", - "CTRL/CMD + Q", - "CTRL/CMD + R", - "CTRL/CMD + S", - "CTRL/CMD + T", - "CTRL/CMD + U", - "CTRL/CMD + V", - "CTRL/CMD + W", - "CTRL/CMD + X", - "CTRL/CMD + Y", - "CTRL/CMD + Z" - ], - "default": "CTRL/CMD + R", - "description": "The keybinding for the keyboard shortcut that refreshes the Git Graph View." - }, - "git-graph.keyboardShortcut.scrollToHead": { - "type": "string", - "enum": [ - "CTRL/CMD + A", - "CTRL/CMD + B", - "CTRL/CMD + C", - "CTRL/CMD + D", - "CTRL/CMD + E", - "CTRL/CMD + F", - "CTRL/CMD + G", - "CTRL/CMD + H", - "CTRL/CMD + I", - "CTRL/CMD + J", - "CTRL/CMD + K", - "CTRL/CMD + L", - "CTRL/CMD + M", - "CTRL/CMD + N", - "CTRL/CMD + O", - "CTRL/CMD + P", - "CTRL/CMD + Q", - "CTRL/CMD + R", - "CTRL/CMD + S", - "CTRL/CMD + T", - "CTRL/CMD + U", - "CTRL/CMD + V", - "CTRL/CMD + W", - "CTRL/CMD + X", - "CTRL/CMD + Y", - "CTRL/CMD + Z" - ], - "default": "CTRL/CMD + H", - "description": "The keybinding for the keyboard shortcut that scrolls the Git Graph View to be centered on the commit referenced by HEAD." - }, - "git-graph.keyboardShortcut.scrollToStash": { - "type": "string", - "enum": [ - "CTRL/CMD + A", - "CTRL/CMD + B", - "CTRL/CMD + C", - "CTRL/CMD + D", - "CTRL/CMD + E", - "CTRL/CMD + F", - "CTRL/CMD + G", - "CTRL/CMD + H", - "CTRL/CMD + I", - "CTRL/CMD + J", - "CTRL/CMD + K", - "CTRL/CMD + L", - "CTRL/CMD + M", - "CTRL/CMD + N", - "CTRL/CMD + O", - "CTRL/CMD + P", - "CTRL/CMD + Q", - "CTRL/CMD + R", - "CTRL/CMD + S", - "CTRL/CMD + T", - "CTRL/CMD + U", - "CTRL/CMD + V", - "CTRL/CMD + W", - "CTRL/CMD + X", - "CTRL/CMD + Y", - "CTRL/CMD + Z" - ], - "default": "CTRL/CMD + S", - "description": "The keybinding for the keyboard shortcut that scrolls the Git Graph View to the first (or next) stash in the loaded commits. The Shift Key Modifier can be applied to this keybinding to scroll the Git Graph View to the last (or previous) stash in the loaded commits." - }, - "git-graph.markdown": { - "type": "boolean", - "default": true, - "description": "Parse and render a frequently used subset of inline Markdown formatting rules in commit messages and tag details (bold, italics, bold & italics, and inline code blocks)." - }, - "git-graph.maxDepthOfRepoSearch": { - "type": "number", - "default": 0, - "description": "Specifies the maximum depth of subfolders to search when discovering repositories in the workspace. Note: Sub-repos are not automatically detected when searching subfolders, however they can be manually added by running the command \"Git Graph: Add Git Repository\" in the Command Palette." - }, - "git-graph.openNewTabEditorGroup": { - "type": "string", - "enum": [ - "Active", - "Beside", - "One", - "Two", - "Three", - "Four", - "Five", - "Six", - "Seven", - "Eight", - "Nine" - ], - "enumDescriptions": [ - "Open the new tab in the Active Editor Group.", - "Open the new tab beside the Active Editor Group.", - "Open the new tab in the First Editor Group.", - "Open the new tab in the Second Editor Group.", - "Open the new tab in the Third Editor Group.", - "Open the new tab in the Fourth Editor Group.", - "Open the new tab in the Fifth Editor Group.", - "Open the new tab in the Sixth Editor Group.", - "Open the new tab in the Seventh Editor Group.", - "Open the new tab in the Eighth Editor Group.", - "Open the new tab in the Ninth Editor Group." - ], - "default": "Active", - "description": "Specifies the Editor Group where Git Graph should open new tabs, when performing the following actions from the Git Graph View: Viewing the Visual Studio Code Diff View, Opening a File, Viewing a File at a Specific Revision." - }, - "git-graph.openToTheRepoOfTheActiveTextEditorDocument": { - "type": "boolean", - "default": false, - "description": "Open the Git Graph View to the repository containing the active Text Editor document." - }, - "git-graph.referenceLabels.alignment": { - "type": "string", - "enum": [ - "Normal", - "Branches (on the left) & Tags (on the right)", - "Branches (aligned to the graph) & Tags (on the right)" - ], - "enumDescriptions": [ - "Show branch & tag labels on the left of the commit message in the 'Description' column.", - "Show branch labels on the left of the commit message in the 'Description' column, and tag labels on the right.", - "Show branch labels aligned to the graph in the 'Graph' column, and tag labels on the right in the 'Description' column." - ], - "default": "Normal", - "description": "Specifies how branch and tag reference labels are aligned for each commit." - }, - "git-graph.referenceLabels.combineLocalAndRemoteBranchLabels": { - "type": "boolean", - "default": true, - "description": "Combine local and remote branch labels if they refer to the same branch, and are on the same commit." - }, - "git-graph.repository.commits.fetchAvatars": { - "type": "boolean", - "default": false, - "description": "Fetch avatars of commit authors and committers. By enabling this setting, you consent to commit author and committer email addresses being sent GitHub, GitLab or Gravatar, depending on the repositories remote origin." - }, - "git-graph.repository.commits.initialLoad": { - "type": "number", - "default": 300, - "description": "Specifies the number of commits to initially load." - }, - "git-graph.repository.commits.loadMore": { - "type": "number", - "default": 100, - "description": "Specifies the number of additional commits to load when the \"Load More Commits\" button is pressed, or more commits are automatically loaded." - }, - "git-graph.repository.commits.loadMoreAutomatically": { - "type": "boolean", - "default": true, - "description": "When the view has been scrolled to the bottom, automatically load more commits if they exist (instead of having to press the \"Load More Commits\" button)." - }, - "git-graph.repository.commits.mute.commitsThatAreNotAncestorsOfHead": { - "type": "boolean", - "default": false, - "description": "Display commits that aren't ancestors of the checked-out branch / commit with a muted text color. Muting will only occur if the commit referenced by HEAD is within the loaded commits on the Git Graph View." - }, - "git-graph.repository.commits.mute.mergeCommits": { - "type": "boolean", - "default": true, - "description": "Display merge commits with a muted text color." - }, - "git-graph.repository.commits.order": { - "type": "string", - "enum": [ - "date", - "author-date", - "topo" - ], - "enumDescriptions": [ - "Show commits in the commit timestamp order.", - "Show commits in the author timestamp order.", - "Avoid showing commits on multiple lines of history intermixed." - ], - "default": "date", - "markdownDescription": "Specifies the order of commits on the Git Graph View. See [git log](https://git-scm.com/docs/git-log#_commit_ordering) for more information on each order option. This can be overridden per repository via the Git Graph View's Column Header Context Menu." - }, - "git-graph.repository.commits.showSignatureStatus": { - "type": "boolean", - "default": false, - "description": "Show the commit's signature status to the right of the Committer in the Commit Details View (only for signed commits). Hovering over the signature icon displays a tooltip with the signature details. Requires Git (>= 2.4.0) & GPG (or equivalent) to be installed on the same machine that is running Visual Studio Code." - }, - "git-graph.repository.fetchAndPrune": { - "type": "boolean", - "default": false, - "description": "Before fetching from remote(s) using the Fetch button on the Git Graph View Control Bar, remove any remote-tracking references that no longer exist on the remote(s)." - }, - "git-graph.repository.fetchAndPruneTags": { - "type": "boolean", - "default": false, - "description": "Before fetching from remote(s) using the Fetch button on the Git Graph View Control Bar, remove any local tags that no longer exist on the remote(s). Requires Git >= 2.17.0, and the \"Repository: Fetch And Prune\" setting to be enabled. Caution: If you work in repositories that have multiple remotes, it is not recommended to use this setting (instead you can prune tags for a specific remote via \"Fetch Remote\" Dialog from the Repository Settings Widget on the Git Graph View)." - }, - "git-graph.repository.includeCommitsMentionedByReflogs": { - "type": "boolean", - "default": false, - "description": "Include commits only mentioned by reflogs in the Git Graph View (only applies when showing all branches). This can be overridden per repository in the Git Graph View's Repository Settings Widget." - }, - "git-graph.repository.onLoad.scrollToHead": { - "type": "boolean", - "default": false, - "description": "Automatically scroll the Git Graph View to be centered on the commit referenced by HEAD. This will only occur if the commit referenced by HEAD is within the loaded commits on the Git Graph View." - }, - "git-graph.repository.onLoad.showCheckedOutBranch": { - "type": "boolean", - "default": false, - "description": "Show the checked out branch when a repository is loaded in the Git Graph View. This setting can be used in conjunction with \"Repository > On Load: Show Specific Branches\". Default: false (show all branches)" - }, - "git-graph.repository.onLoad.showSpecificBranches": { - "type": "array", - "items": { - "type": "string", - "description": "A local branch name (e.g. \"master\"), a remote-tracking branch name prefixed with \"remotes/\" (e.g. \"remotes/origin/master\"), or a glob pattern defined in git-graph.customBranchGlobPatterns prefixed with \"--glob=\" (e.g. \"--glob=heads/feature/*\")." - }, - "default": [], - "markdownDescription": "Show specific branches when a repository is loaded in the Git Graph View. Branches can be specified as follows: A local branch name (e.g. `master`), a remote-tracking branch name prefixed with \"remotes/\" (e.g. `remotes/origin/master`), or a glob pattern defined in `git-graph.customBranchGlobPatterns` prefixed with \"--glob=\" (e.g. `--glob=heads/feature/*`). This setting can be used in conjunction with \"Repository > On Load: Show Checked Out Branch\". Default: [] (show all branches)" - }, - "git-graph.repository.onlyFollowFirstParent": { - "type": "boolean", - "default": false, - "markdownDescription": "Only follow the first parent of commits when discovering the commits to load in the Git Graph View. See [--first-parent](https://git-scm.com/docs/git-log#Documentation/git-log.txt---first-parent) to find out more about this setting. This can be overridden per repository in the Git Graph View's Repository Settings Widget." - }, - "git-graph.repository.showCommitsOnlyReferencedByTags": { - "type": "boolean", - "default": true, - "description": "Show Commits that are only referenced by tags in Git Graph." - }, - "git-graph.repository.showRemoteBranches": { - "type": "boolean", - "default": true, - "description": "Show Remote Branches in Git Graph by default. This can be overridden per repository from the Git Graph View's Control Bar." - }, - "git-graph.repository.showRemoteHeads": { - "type": "boolean", - "default": true, - "description": "Show Remote HEAD Symbolic References in Git Graph (e.g. \"origin/HEAD\")." - }, - "git-graph.repository.showStashes": { - "type": "boolean", - "default": true, - "description": "Show Stashes in Git Graph by default. This can be overridden per repository in the Git Graph View's Repository Settings Widget." - }, - "git-graph.repository.showTags": { - "type": "boolean", - "default": true, - "description": "Show Tags in Git Graph by default. This can be overridden per repository in the Git Graph View's Repository Settings Widget." - }, - "git-graph.repository.showUncommittedChanges": { - "type": "boolean", - "default": true, - "description": "Show uncommitted changes. If you work on large repositories, disabling this setting can reduce the load time of the Git Graph View." - }, - "git-graph.repository.showUntrackedFiles": { - "type": "boolean", - "default": true, - "description": "Show untracked files when viewing the uncommitted changes. If you work on large repositories, disabling this setting can reduce the load time of the Git Graph View." - }, - "git-graph.repository.sign.commits": { - "type": "boolean", - "default": false, - "description": "Enables commit signing with GPG or X.509." - }, - "git-graph.repository.sign.tags": { - "type": "boolean", - "default": false, - "description": "Enables tag signing with GPG or X.509." - }, - "git-graph.repository.useMailmap": { - "type": "boolean", - "default": false, - "markdownDescription": "Respect [.mailmap](https://git-scm.com/docs/git-check-mailmap#_mapping_authors) files when displaying author & committer names and email addresses." - }, - "git-graph.repositoryDropdownOrder": { - "type": "string", - "enum": [ - "Full Path", - "Name" - ], - "enumDescriptions": [ - "Sort repositories alphabetically by the full path of the repository.", - "Sort repositories alphabetically by the name of the repository." - ], - "default": "Full Path", - "description": "Specifies the order that repositories are sorted in the repository dropdown on the Git Graph View (only visible when more than one repository exists in the current Visual Studio Code Workspace)." - }, - "git-graph.retainContextWhenHidden": { - "type": "boolean", - "default": true, - "description": "Specifies if the Git Graph View's Visual Studio Code context is kept when the panel is no longer visible (e.g. moved to background tab). Enabling this setting will make Git Graph load significantly faster when switching back to the Git Graph tab, however has a higher memory overhead." - }, - "git-graph.showStatusBarItem": { - "type": "boolean", - "default": true, - "description": "Show a Status Bar Item that opens the Git Graph View when clicked." - }, - "git-graph.sourceCodeProviderIntegrationLocation": { - "type": "string", - "enum": [ - "Inline", - "More Actions" - ], - "enumDescriptions": [ - "Show the 'View Git Graph' action on the title of SCM Providers", - "Show the 'View Git Graph' action in the 'More Actions...' menu on the title of SCM Providers" - ], - "default": "Inline", - "description": "Specifies where the \"View Git Graph\" action appears on the title of SCM Providers." - }, - "git-graph.tabIconColourTheme": { - "type": "string", - "enum": [ - "colour", - "grey" - ], - "enumDescriptions": [ - "Show a colour icon which suits most Visual Studio Code colour themes", - "Show a grey icon which suits Visual Studio Code colour themes that are predominantly grayscale" - ], - "default": "colour", - "description": "Specifies the colour theme of the icon displayed on the Git Graph tab." - }, - "git-graph.autoCenterCommitDetailsView": { - "type": "boolean", - "default": true, - "description": "Automatically center the commit details view when it is opened.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.commitDetailsView.autoCenter", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.commitDetailsView.autoCenter#`" - }, - "git-graph.combineLocalAndRemoteBranchLabels": { - "type": "boolean", - "default": true, - "description": "Combine local and remote branch labels if they refer to the same branch, and are on the same commit.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.referenceLabels.combineLocalAndRemoteBranchLabels", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.referenceLabels.combineLocalAndRemoteBranchLabels#`" - }, - "git-graph.commitDetailsViewFileTreeCompactFolders": { - "type": "boolean", - "default": true, - "description": "Render the File Tree in the Commit Details / Comparison View in a compacted form, such that folders with a single child folder are compressed into a single combined folder element.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.commitDetailsView.fileView.fileTree.compactFolders", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.commitDetailsView.fileView.fileTree.compactFolders#`" - }, - "git-graph.commitDetailsViewLocation": { - "type": "string", - "enum": [ - "Inline", - "Docked to Bottom" - ], - "enumDescriptions": [ - "Show the Commit Details View inline with the graph", - "Show the Commit Details View docked to the bottom of the Git Graph view" - ], - "default": "Inline", - "description": "Specifies where the Commit Details View is rendered in the Git Graph view.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.commitDetailsView.location", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.commitDetailsView.location#`" - }, - "git-graph.commitOrdering": { - "type": "string", - "enum": [ - "date", - "author-date", - "topo" - ], - "enumDescriptions": [ - "Show commits in the commit timestamp order.", - "Show commits in the author timestamp order.", - "Avoid showing commits on multiple lines of history intermixed." - ], - "default": "date", - "markdownDescription": "Specifies the order of commits on the Git Graph view. See [git log](https://git-scm.com/docs/git-log#_commit_ordering) for more information on each order option.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.order", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.order#`" - }, - "git-graph.dateFormat": { - "type": "string", - "enum": [ - "Date & Time", - "Date Only", - "ISO Date & Time", - "ISO Date Only", - "Relative" - ], - "enumDescriptions": [ - "Show the date and time, for example \"24 Mar 2019 21:34\"", - "Show the date only, for example \"24 Mar 2019\"", - "Show the ISO date and time, for example \"2019-03-24 21:34\"", - "Show the ISO date only, for example \"2019-03-24\"", - "Show relative times, for example \"5 minutes ago\"" - ], - "default": "Date & Time", - "description": "Specifies the date format to be used in the \"Date\" column on the Git Graph View.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.date.format", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.date.format#`" - }, - "git-graph.dateType": { - "type": "string", - "enum": [ - "Author Date", - "Commit Date" - ], - "enumDescriptions": [ - "Use the author date of a commit", - "Use the committer date of a commit" - ], - "default": "Author Date", - "description": "Specifies the date type to be displayed in the \"Date\" column on the Git Graph View.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.date.type", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.date.type#`" - }, - "git-graph.defaultFileViewType": { - "type": "string", - "enum": [ - "File Tree", - "File List" - ], - "enumDescriptions": [ - "Display files in a tree structure", - "Display files in a list (useful for repositories with deep folder structures)" - ], - "default": "File Tree", - "description": "Sets the default type of File View used in the Commit Details / Comparison Views. This can be overridden per repository using the controls on the right side of the Commit Details / Comparison Views.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.commitDetailsView.fileView.type", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.commitDetailsView.fileView.type#`" - }, - "git-graph.fetchAndPrune": { - "type": "boolean", - "default": false, - "description": "Before fetching from remote(s) using the Fetch button on the Git Graph View Control Bar, remove any remote-tracking references that no longer exist on the remote(s).", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.fetchAndPrune", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.fetchAndPrune#`" - }, - "git-graph.fetchAvatars": { - "type": "boolean", - "default": false, - "description": "Fetch avatars of commit authors and committers. By enabling this setting, you consent to commit author and committer email addresses being sent GitHub, GitLab or Gravatar, depending on the repositories remote origin.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.fetchAvatars", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.fetchAvatars#`" - }, - "git-graph.graphColours": { - "type": "array", - "items": { - "type": "string", - "description": "Colour (HEX or RGB)", - "pattern": "^\\s*(#[0-9a-fA-F]{6}|#[0-9a-fA-F]{8}|rgb[a]?\\s*\\(\\d{1,3},\\s*\\d{1,3},\\s*\\d{1,3}\\))\\s*$" - }, - "default": [ - "#0085d9", - "#d9008f", - "#00d90a", - "#d98500", - "#a300d9", - "#ff0000", - "#00d9cc", - "#e138e8", - "#85d900", - "#dc5b23", - "#6f24d6", - "#ffcc00" - ], - "description": "Specifies the colours used on the graph.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.graph.colours", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.graph.colours#`" - }, - "git-graph.graphStyle": { - "type": "string", - "enum": [ - "rounded", - "angular" - ], - "enumDescriptions": [ - "Use smooth curves when transitioning between branches on the graph", - "Use angular lines when transitioning between branches on the graph" - ], - "default": "rounded", - "description": "Specifies the style of the graph.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.graph.style", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.graph.style#`" - }, - "git-graph.includeCommitsMentionedByReflogs": { - "type": "boolean", - "default": false, - "description": "Include commits only mentioned by reflogs in the Git Graph View (only applies when showing all branches). This can be overridden per repository in the Git Graph View's Repository Settings Widget.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.includeCommitsMentionedByReflogs", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.includeCommitsMentionedByReflogs#`" - }, - "git-graph.initialLoadCommits": { - "type": "number", - "default": 300, - "description": "Specifies the number of commits to initially load.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.initialLoad", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.initialLoad#`" - }, - "git-graph.loadMoreCommits": { - "type": "number", - "default": 100, - "description": "Specifies the number of additional commits to load when the \"Load More Commits\" button is pressed, or more commits are automatically loaded.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.loadMore", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.loadMore#`" - }, - "git-graph.loadMoreCommitsAutomatically": { - "type": "boolean", - "default": true, - "description": "When the view has been scrolled to the bottom, automatically load more commits if they exist (instead of having to press the \"Load More Commits\" button).", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.loadMoreAutomatically", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.loadMoreAutomatically#`" - }, - "git-graph.muteCommitsThatAreNotAncestorsOfHead": { - "type": "boolean", - "default": false, - "description": "Display commits that aren't ancestors of the checked-out branch / commit with a muted text color. Muting will only occur if the commit referenced by HEAD is within the loaded commits on the Git Graph View.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.mute.commitsThatAreNotAncestorsOfHead", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.mute.commitsThatAreNotAncestorsOfHead#`" - }, - "git-graph.muteMergeCommits": { - "type": "boolean", - "default": true, - "description": "Display merge commits with a muted text color.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.mute.mergeCommits", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.mute.mergeCommits#`" - }, - "git-graph.onlyFollowFirstParent": { - "type": "boolean", - "default": false, - "markdownDescription": "Only follow the first parent of commits when discovering the commits to load in the Git Graph View. See [--first-parent](https://git-scm.com/docs/git-log#Documentation/git-log.txt---first-parent) to find out more about this setting.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.onlyFollowFirstParent", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.onlyFollowFirstParent#`" - }, - "git-graph.openDiffTabLocation": { - "type": "string", - "enum": [ - "Active", - "Beside", - "One", - "Two", - "Three", - "Four", - "Five", - "Six", - "Seven", - "Eight", - "Nine" - ], - "enumDescriptions": [ - "Open the Visual Studio Code Diff View in the Active Editor Group.", - "Open the Visual Studio Code Diff View beside the Active Editor Group.", - "Open the Visual Studio Code Diff View in the First Editor Group.", - "Open the Visual Studio Code Diff View in the Second Editor Group.", - "Open the Visual Studio Code Diff View in the Third Editor Group.", - "Open the Visual Studio Code Diff View in the Fourth Editor Group.", - "Open the Visual Studio Code Diff View in the Fifth Editor Group.", - "Open the Visual Studio Code Diff View in the Sixth Editor Group.", - "Open the Visual Studio Code Diff View in the Seventh Editor Group.", - "Open the Visual Studio Code Diff View in the Eighth Editor Group.", - "Open the Visual Studio Code Diff View in the Ninth Editor Group." - ], - "default": "Active", - "description": "Specifies which Editor Group the Visual Studio Code Diff View is opened in.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.openNewTabEditorGroup", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.openNewTabEditorGroup#`" - }, - "git-graph.openRepoToHead": { - "type": "boolean", - "default": false, - "description": "When opening or switching repositories in the Git Graph View, automatically scroll the view to be centered on the commit referenced by HEAD. This will only occur if the commit referenced by HEAD is within the loaded commits on the Git Graph View.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.onLoad.scrollToHead", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.onLoad.scrollToHead#`" - }, - "git-graph.referenceLabelAlignment": { - "type": "string", - "enum": [ - "Normal", - "Branches (on the left) & Tags (on the right)", - "Branches (aligned to the graph) & Tags (on the right)" - ], - "enumDescriptions": [ - "Show branch & tag labels on the left of the commit message in the 'Description' column.", - "Show branch labels on the left of the commit message in the 'Description' column, and tag labels on the right.", - "Show branch labels aligned to the graph in the 'Graph' column, and tag labels on the right in the 'Description' column." - ], - "default": "Normal", - "description": "Specifies how branch and tag reference labels are aligned for each commit.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.referenceLabels.alignment", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.referenceLabels.alignment#`" - }, - "git-graph.showCommitsOnlyReferencedByTags": { - "type": "boolean", - "default": true, - "description": "Show commits that are only referenced by tags in Git Graph.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.showCommitsOnlyReferencedByTags", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.showCommitsOnlyReferencedByTags#`" - }, - "git-graph.showCurrentBranchByDefault": { - "type": "boolean", - "default": false, - "description": "Show the current branch by default when Git Graph is opened. Default: false (show all branches)", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.onLoad.showCheckedOutBranch", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.onLoad.showCheckedOutBranch#`" - }, - "git-graph.showSignatureStatus": { - "type": "boolean", - "default": false, - "description": "Show the commit's signature status to the right of the Committer in the Commit Details View (only for signed commits). Hovering over the signature icon displays a tooltip with the signature details. Requires Git (>= 2.4.0) & GPG (or equivalent) to be installed on the same machine that is running Visual Studio Code.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.showSignatureStatus", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.showSignatureStatus#`" - }, - "git-graph.showTags": { - "type": "boolean", - "default": true, - "description": "Show Tags in Git Graph by default. This can be overridden per repository in the Git Graph View's Repository Settings Widget.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.showTags", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.showTags#`" - }, - "git-graph.showUncommittedChanges": { - "type": "boolean", - "default": true, - "description": "Show uncommitted changes. If you work on large repositories, disabling this setting can reduce the load time of the Git Graph View.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.showUncommittedChanges", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.showUncommittedChanges#`" - }, - "git-graph.showUntrackedFiles": { - "type": "boolean", - "default": true, - "description": "Show untracked files when viewing the uncommitted changes. If you work on large repositories, disabling this setting can reduce the load time of the Git Graph View.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.showUntrackedFiles", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.showUntrackedFiles#`" - }, - "git-graph.useMailmap": { - "type": "boolean", - "default": false, - "markdownDescription": "Respect [.mailmap](https://git-scm.com/docs/git-check-mailmap#_mapping_authors) files when displaying author & committer names and email addresses.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.useMailmap", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.useMailmap#`" - } - } - }, - "menus": { - "commandPalette": [ - { - "command": "git-graph.openFile", - "when": "isInDiffEditor && resourceScheme == git-graph && git-graph:codiconsSupported" - } - ], - "editor/title": [ - { - "command": "git-graph.openFile", - "group": "navigation", - "when": "isInDiffEditor && resourceScheme == git-graph && git-graph:codiconsSupported" - } - ], - "scm/title": [ - { - "when": "scmProvider == git && config.git-graph.sourceCodeProviderIntegrationLocation == 'Inline'", - "command": "git-graph.view", - "group": "navigation" - }, - { - "when": "scmProvider == git && config.git-graph.sourceCodeProviderIntegrationLocation == 'More Actions'", - "command": "git-graph.view", - "group": "inline" - } - ] - } - }, - "scripts": { - "vscode:prepublish": "npm run compile", - "vscode:uninstall": "node ./out/life-cycle/uninstall.js", - "clean": "node ./.vscode/clean.js", - "compile": "npm run lint && npm run clean && npm run compile-src && npm run compile-web", - "compile-src": "tsc -p ./src && node ./.vscode/package-src.js", - "compile-web": "tsc -p ./web && node ./.vscode/package-web.js", - "compile-web-debug": "tsc -p ./web && node ./.vscode/package-web.js debug", - "lint": "eslint -c .eslintrc.json --ext .ts ./src ./tests ./web", - "package": "npm run clean && vsce package", - "package-and-install": "npm run package && node ./.vscode/install-package.js", - "test": "jest --verbose", - "test-and-report-coverage": "jest --verbose --coverage" - }, - "dependencies": { - "iconv-lite": "0.5.0", - "request": "2.88.2", - "request-promise": "4.2.6" - }, - "devDependencies": { - "@types/jest": "26.0.19", - "@types/node": "8.10.62", - "@types/request-promise": "4.1.46", - "@types/vscode": "1.38.0", - "@typescript-eslint/eslint-plugin": "4.10.0", - "@typescript-eslint/parser": "4.10.0", - "eslint": "7.15.0", - "jest": "26.6.3", - "ts-jest": "26.4.4", - "typescript": "4.0.2", - "uglify-js": "3.10.0" - } -} +{ + "name": "git-graph", + "displayName": "Git Graph", + "version": "1.29.0", + "publisher": "mhutchie", + "author": { + "name": "Michael Hutchison", + "email": "mhutchie@16right.com" + }, + "description": "View a Git Graph of your repository, and perform Git actions from the graph.", + "keywords": [ + "git", + "graph", + "visualise", + "diff", + "action" + ], + "categories": [ + "Other" + ], + "homepage": "https://github.com/mhutchie/vscode-git-graph", + "repository": { + "type": "git", + "url": "https://github.com/mhutchie/vscode-git-graph.git" + }, + "bugs": { + "url": "https://github.com/mhutchie/vscode-git-graph/issues" + }, + "qna": "https://github.com/mhutchie/vscode-git-graph/wiki/Support-Resources", + "license": "SEE LICENSE IN 'LICENSE'", + "icon": "resources/icon.png", + "engines": { + "vscode": "^1.38.0" + }, + "extensionKind": [ + "workspace" + ], + "activationEvents": [ + "*" + ], + "main": "./out/extension.js", + "contributes": { + "commands": [ + { + "category": "Git Graph", + "command": "git-graph.view", + "title": "View Git Graph (git log)", + "icon": { + "light": "resources/cmd-icon-light.svg", + "dark": "resources/cmd-icon-dark.svg" + } + }, + { + "category": "Git Graph", + "command": "git-graph.addGitRepository", + "title": "Add Git Repository..." + }, + { + "category": "Git Graph", + "command": "git-graph.clearAvatarCache", + "title": "Clear Avatar Cache" + }, + { + "category": "Git Graph", + "command": "git-graph.clearCICDCache", + "title": "Clear CICD Cache" + }, + { + "category": "Git Graph", + "command": "git-graph.endAllWorkspaceCodeReviews", + "title": "End All Code Reviews in Workspace" + }, + { + "category": "Git Graph", + "command": "git-graph.endSpecificWorkspaceCodeReview", + "title": "End a specific Code Review in Workspace..." + }, + { + "category": "Git Graph", + "command": "git-graph.fetch", + "title": "Fetch from Remote(s)" + }, + { + "category": "Git Graph", + "command": "git-graph.removeGitRepository", + "title": "Remove Git Repository..." + }, + { + "category": "Git Graph", + "command": "git-graph.resumeWorkspaceCodeReview", + "title": "Resume a specific Code Review in Workspace..." + }, + { + "category": "Git Graph", + "command": "git-graph.version", + "title": "Get Version Information" + }, + { + "category": "Git Graph", + "command": "git-graph.openFile", + "title": "Open File", + "icon": "$(go-to-file)", + "enablement": "isInDiffEditor && resourceScheme == git-graph && git-graph:codiconsSupported" + } + ], + "configuration": { + "type": "object", + "title": "Git Graph", + "properties": { + "git-graph.commitDetailsView.autoCenter": { + "type": "boolean", + "default": true, + "description": "Automatically center the Commit Details View when it is opened." + }, + "git-graph.commitDetailsView.fileView.fileTree.compactFolders": { + "type": "boolean", + "default": true, + "description": "Render the File Tree in the Commit Details View in a compacted form, such that folders with a single child folder are compressed into a single combined folder element." + }, + "git-graph.commitDetailsView.fileView.type": { + "type": "string", + "enum": [ + "File Tree", + "File List" + ], + "enumDescriptions": [ + "Display files in a tree structure.", + "Display files in a list (useful for repositories with deep folder structures)." + ], + "default": "File Tree", + "description": "Sets the default type of File View used in the Commit Details View. This can be overridden per repository using the controls on the right side of the Commit Details View." + }, + "git-graph.commitDetailsView.location": { + "type": "string", + "enum": [ + "Inline", + "Docked to Bottom" + ], + "enumDescriptions": [ + "Show the Commit Details View inline with the graph & commits.", + "Show the Commit Details View docked to the bottom of the Git Graph View." + ], + "default": "Inline", + "description": "Specifies where the Commit Details View is rendered in the Git Graph View." + }, + "git-graph.contextMenuActionsVisibility": { + "type": "object", + "default": {}, + "properties": { + "branch": { + "type": "object", + "properties": { + "checkout": { + "type": "boolean", + "title": "Checkout Branch" + }, + "rename": { + "type": "boolean", + "title": "Rename Branch..." + }, + "delete": { + "type": "boolean", + "title": "Delete Branch..." + }, + "merge": { + "type": "boolean", + "title": "Merge into current branch..." + }, + "rebase": { + "type": "boolean", + "title": "Rebase current branch on Branch..." + }, + "push": { + "type": "boolean", + "title": "Push Branch..." + }, + "createPullRequest": { + "type": "boolean", + "title": "Create Pull Request..." + }, + "createArchive": { + "type": "boolean", + "title": "Create Archive" + }, + "selectInBranchesDropdown": { + "type": "boolean", + "title": "Select in Branches Dropdown" + }, + "unselectInBranchesDropdown": { + "type": "boolean", + "title": "Unselect in Branches Dropdown" + }, + "copyName": { + "type": "boolean", + "title": "Copy Branch Name to Clipboard" + } + } + }, + "commit": { + "type": "object", + "properties": { + "addTag": { + "type": "boolean", + "title": "Add Tag..." + }, + "createBranch": { + "type": "boolean", + "title": "Create Branch..." + }, + "checkout": { + "type": "boolean", + "title": "Checkout..." + }, + "cherrypick": { + "type": "boolean", + "title": "Cherry Pick..." + }, + "revert": { + "type": "boolean", + "title": "Revert..." + }, + "drop": { + "type": "boolean", + "title": "Drop..." + }, + "merge": { + "type": "boolean", + "title": "Merge into current branch..." + }, + "rebase": { + "type": "boolean", + "title": "Rebase current branch on this Commit..." + }, + "reset": { + "type": "boolean", + "title": "Reset current branch to this Commit..." + }, + "copyHash": { + "type": "boolean", + "title": "Copy Commit Hash to Clipboard" + }, + "copySubject": { + "type": "boolean", + "title": "Copy Commit Subject to Clipboard" + } + } + }, + "remoteBranch": { + "type": "object", + "properties": { + "checkout": { + "type": "boolean", + "title": "Checkout Branch..." + }, + "delete": { + "type": "boolean", + "title": "Delete Remote Branch..." + }, + "fetch": { + "type": "boolean", + "title": "Fetch into local branch..." + }, + "merge": { + "type": "boolean", + "title": "Merge into current branch..." + }, + "pull": { + "type": "boolean", + "title": "Pull into current branch..." + }, + "createPullRequest": { + "type": "boolean", + "title": "Create Pull Request" + }, + "createArchive": { + "type": "boolean", + "title": "Create Archive" + }, + "selectInBranchesDropdown": { + "type": "boolean", + "title": "Select in Branches Dropdown" + }, + "unselectInBranchesDropdown": { + "type": "boolean", + "title": "Unselect in Branches Dropdown" + }, + "copyName": { + "type": "boolean", + "title": "Copy Branch Name to Clipboard" + } + } + }, + "stash": { + "type": "object", + "properties": { + "apply": { + "type": "boolean", + "title": "Apply Stash..." + }, + "createBranch": { + "type": "boolean", + "title": "Create Branch from Stash..." + }, + "pop": { + "type": "boolean", + "title": "Pop Stash..." + }, + "drop": { + "type": "boolean", + "title": "Drop Stash..." + }, + "copyName": { + "type": "boolean", + "title": "Copy Stash Name to Clipboard" + }, + "copyHash": { + "type": "boolean", + "title": "Copy Stash Hash to Clipboard" + } + } + }, + "tag": { + "type": "object", + "properties": { + "viewDetails": { + "type": "boolean", + "title": "View Details" + }, + "delete": { + "type": "boolean", + "title": "Delete Tag..." + }, + "push": { + "type": "boolean", + "title": "Push Tag..." + }, + "createArchive": { + "type": "boolean", + "title": "Create Archive" + }, + "copyName": { + "type": "boolean", + "title": "Copy Tag Name to Clipboard" + } + } + }, + "uncommittedChanges": { + "type": "object", + "properties": { + "stash": { + "type": "boolean", + "title": "Stash uncommitted changes..." + }, + "reset": { + "type": "boolean", + "title": "Reset uncommitted changes..." + }, + "clean": { + "type": "boolean", + "title": "Clean untracked files..." + }, + "openSourceControlView": { + "type": "boolean", + "title": "Open Source Control View" + } + } + } + }, + "markdownDescription": "Customise which context menu actions are visible. For example, if you want to hide the rebase action from the branch context menu, a suitable value for this setting is `{ \"branch\": { \"rebase\": false } }`. For more information of how to configure this setting, view the documentation [here](https://github.com/mhutchie/vscode-git-graph/wiki/Extension-Settings#context-menu-actions-visibility)." + }, + "git-graph.customBranchGlobPatterns": { + "type": "array", + "items": { + "type": "object", + "title": "Branch Glob Pattern", + "required": [ + "name", + "glob" + ], + "properties": { + "name": { + "type": "string", + "title": "Name of pattern", + "description": "Name used to reference the pattern in the 'Branches' dropdown" + }, + "glob": { + "type": "string", + "title": "Glob pattern", + "description": "The Glob Pattern , as used in 'git log --glob='. For example: heads/feature/*" + } + } + }, + "default": [], + "description": "An array of Custom Branch Glob Patterns to be shown in the 'Branches' dropdown. Example: [{\"name\": \"Feature Requests\", \"glob\": \"heads/feature/*\"}]" + }, + "git-graph.customEmojiShortcodeMappings": { + "type": "array", + "items": { + "type": "object", + "title": "Custom Emoji Shortcode Mapping", + "required": [ + "shortcode", + "emoji" + ], + "properties": { + "shortcode": { + "type": "string", + "title": "Emoji Shortcode", + "description": "Emoji Shortcode (e.g. \":sparkles:\")" + }, + "emoji": { + "type": "string", + "title": "Emoji", + "description": "Emoji (e.g. \"✨\")" + } + } + }, + "default": [], + "description": "An array of custom Emoji Shortcode mappings. Example: [{\"shortcode\": \":sparkles:\", \"emoji\":\"✨\"}]" + }, + "git-graph.customPullRequestProviders": { + "type": "array", + "items": { + "type": "object", + "title": "Pull Request Provider", + "required": [ + "name", + "templateUrl" + ], + "properties": { + "name": { + "type": "string", + "title": "Name of the Provider", + "description": "A unique, identifying, display name for the provider." + }, + "templateUrl": { + "type": "string", + "title": "Template URL", + "markdownDescription": "A template URL that can be used to create a Pull Request, after the $1 - $8 variables have been substituted to construct the final URL. For information on how to configure this setting, see the documentation [here](https://github.com/mhutchie/vscode-git-graph/wiki/Configuring-a-custom-Pull-Request-Provider)." + } + } + }, + "default": [], + "markdownDescription": "An array of custom Pull Request providers that can be used in the \"Pull Request Creation\" Integration. For information on how to configure this setting, see the documentation [here](https://github.com/mhutchie/vscode-git-graph/wiki/Configuring-a-custom-Pull-Request-Provider)." + }, + "git-graph.date.format": { + "type": "string", + "enum": [ + "Date & Time", + "Date Only", + "ISO Date & Time", + "ISO Date Only", + "Relative" + ], + "enumDescriptions": [ + "Show the date and time (e.g. \"24 Mar 2019 21:34\")", + "Show the date only (e.g. \"24 Mar 2019\")", + "Show the ISO date and time (e.g. \"2019-03-24 21:34\")", + "Show the ISO date only (e.g. \"2019-03-24\")", + "Show relative times (e.g. \"5 minutes ago\")" + ], + "default": "Date & Time", + "description": "Specifies the date format to be used in the \"Date\" column on the Git Graph View." + }, + "git-graph.date.type": { + "type": "string", + "enum": [ + "Author Date", + "Commit Date" + ], + "enumDescriptions": [ + "Use the author date of a commit.", + "Use the committer date of a commit." + ], + "default": "Author Date", + "description": "Specifies the date type to be displayed in the \"Date\" column on the Git Graph View." + }, + "git-graph.defaultColumnVisibility": { + "type": "object", + "properties": { + "Date": { + "type": "boolean", + "title": "Visibility of the Date column" + }, + "Author": { + "type": "boolean", + "title": "Visibility of the Author column" + }, + "Commit": { + "type": "boolean", + "title": "Visibility of the Commit column" + }, + "CICD": { + "type": "boolean", + "title": "Visibility of the CI/CD Status column" + } + }, + "default": { + "Date": true, + "Author": true, + "Commit": true, + "CICD": true + }, + "description": "An object specifying the default visibility of the Date, Author & Commit columns. Example: {\"Date\": true, \"Author\": true, \"Commit\": true}" + }, + "git-graph.dialog.addTag.pushToRemote": { + "type": "boolean", + "default": false, + "description": "Default state of the field indicating whether the tag should be pushed to a remote once it is added." + }, + "git-graph.dialog.addTag.type": { + "type": "string", + "enum": [ + "Annotated", + "Lightweight" + ], + "default": "Annotated", + "description": "Default type of the tag being added." + }, + "git-graph.dialog.applyStash.reinstateIndex": { + "type": "boolean", + "default": false, + "description": "Default state of the \"Reinstate Index\" checkbox." + }, + "git-graph.dialog.cherryPick.noCommit": { + "type": "boolean", + "default": false, + "description": "Default state of the \"No Commit\" checkbox." + }, + "git-graph.dialog.cherryPick.recordOrigin": { + "type": "boolean", + "default": false, + "description": "Default state of the \"Record Origin\" checkbox." + }, + "git-graph.dialog.createBranch.checkOut": { + "type": "boolean", + "default": false, + "description": "Default state of the \"Check out\" checkbox." + }, + "git-graph.dialog.deleteBranch.forceDelete": { + "type": "boolean", + "default": false, + "description": "Default state of the \"Force Delete\" checkbox." + }, + "git-graph.dialog.fetchIntoLocalBranch.forceFetch": { + "type": "boolean", + "default": false, + "description": "Default state of the \"Force Fetch\" checkbox." + }, + "git-graph.dialog.fetchRemote.prune": { + "type": "boolean", + "default": false, + "description": "Default state of the \"Prune\" checkbox." + }, + "git-graph.dialog.fetchRemote.pruneTags": { + "type": "boolean", + "default": false, + "description": "Default state of the \"Prune Tags\" checkbox." + }, + "git-graph.dialog.general.referenceInputSpaceSubstitution": { + "type": "string", + "enum": [ + "None", + "Hyphen", + "Underscore" + ], + "enumDescriptions": [ + "Don't replace spaces.", + "Replace space characters with hyphens, for example: \"new branch\" -> \"new-branch\".", + "Replace space characters with underscores, for example: \"new branch\" -> \"new_branch\"." + ], + "default": "None", + "description": "Specifies a substitution that is automatically performed when space characters are entered or pasted into reference inputs on dialogs (e.g. Create Branch, Add Tag, etc.)." + }, + "git-graph.dialog.merge.noCommit": { + "type": "boolean", + "default": false, + "description": "Default state of the \"No Commit\" checkbox." + }, + "git-graph.dialog.merge.noFastForward": { + "type": "boolean", + "default": true, + "description": "Default state of the \"Create a new commit even if fast-forward is possible\" checkbox." + }, + "git-graph.dialog.merge.squashCommits": { + "type": "boolean", + "default": false, + "description": "Default state of the \"Squash Commits\" checkbox." + }, + "git-graph.dialog.merge.squashMessageFormat": { + "type": "string", + "enum": [ + "Default", + "Git SQUASH_MSG" + ], + "enumDescriptions": [ + "Use the squash message generated by Git Graph.", + "Use the detailed squash message generated by Git (stored in .git/SQUASH_MSG)." + ], + "default": "Default", + "description": "Specifies the message format used for the squashed commit (when the \"Squash Commits\" option is selected)." + }, + "git-graph.dialog.popStash.reinstateIndex": { + "type": "boolean", + "default": false, + "description": "Default state of the \"Reinstate Index\" checkbox." + }, + "git-graph.dialog.pullBranch.noFastForward": { + "type": "boolean", + "default": false, + "description": "Default state of the \"Create a new commit even if fast-forward is possible\" checkbox." + }, + "git-graph.dialog.pullBranch.squashCommits": { + "type": "boolean", + "default": false, + "description": "Default state of the \"Squash Commits\" checkbox." + }, + "git-graph.dialog.pullBranch.squashMessageFormat": { + "type": "string", + "enum": [ + "Default", + "Git SQUASH_MSG" + ], + "enumDescriptions": [ + "Use the squash message generated by Git Graph.", + "Use the detailed squash message generated by Git (stored in .git/SQUASH_MSG)." + ], + "default": "Default", + "description": "Specifies the message format used for the squashed commit (when the \"Squash Commits\" option is selected)." + }, + "git-graph.dialog.rebase.ignoreDate": { + "type": "boolean", + "default": true, + "description": "Default state of the \"Ignore Date (non-interactive rebase only)\" checkbox." + }, + "git-graph.dialog.rebase.launchInteractiveRebase": { + "type": "boolean", + "default": false, + "description": "Default state of the \"Launch Interactive Rebase in new Terminal\" checkbox." + }, + "git-graph.dialog.resetCurrentBranchToCommit.mode": { + "type": "string", + "enum": [ + "Soft", + "Mixed", + "Hard" + ], + "enumDescriptions": [ + "Soft - Keep all changes, but reset head", + "Mixed - Keep working tree, but reset index", + "Hard - Discard all changes" + ], + "default": "Mixed", + "description": "Default mode to be used for the reset action." + }, + "git-graph.dialog.resetUncommittedChanges.mode": { + "type": "string", + "enum": [ + "Mixed", + "Hard" + ], + "enumDescriptions": [ + "Mixed - Keep working tree, but reset index", + "Hard - Discard all changes" + ], + "default": "Mixed", + "description": "Default mode to be used for the reset action." + }, + "git-graph.dialog.stashUncommittedChanges.includeUntracked": { + "type": "boolean", + "default": true, + "description": "Default state of the \"Include Untracked\" checkbox." + }, + "git-graph.enhancedAccessibility": { + "type": "boolean", + "default": false, + "description": "Visual file change A|M|D|R|U indicators in the Commit Details View for users with colour blindness. In the future, this setting will enable any additional accessibility related features of Git Graph that aren't enabled by default." + }, + "git-graph.fileEncoding": { + "type": "string", + "default": "utf8", + "markdownDescription": "The character set encoding used when retrieving a specific version of repository files (e.g. in the Diff View). A list of all supported encodings can be found [here](https://github.com/ashtuchkin/iconv-lite/wiki/Supported-Encodings).", + "scope": "resource" + }, + "git-graph.graph.colours": { + "type": "array", + "items": { + "type": "string", + "description": "Colour (HEX or RGB)", + "pattern": "^\\s*(#[0-9a-fA-F]{6}|#[0-9a-fA-F]{8}|rgb[a]?\\s*\\(\\d{1,3},\\s*\\d{1,3},\\s*\\d{1,3}\\))\\s*$" + }, + "default": [ + "#0085d9", + "#d9008f", + "#00d90a", + "#d98500", + "#a300d9", + "#ff0000", + "#00d9cc", + "#e138e8", + "#85d900", + "#dc5b23", + "#6f24d6", + "#ffcc00" + ], + "description": "Specifies the colours used on the graph." + }, + "git-graph.graph.style": { + "type": "string", + "enum": [ + "rounded", + "angular" + ], + "enumDescriptions": [ + "Use smooth curves when transitioning between branches on the graph.", + "Use angular lines when transitioning between branches on the graph." + ], + "default": "rounded", + "description": "Specifies the style of the graph." + }, + "git-graph.graph.uncommittedChanges": { + "type": "string", + "enum": [ + "Open Circle at the Uncommitted Changes", + "Open Circle at the Checked Out Commit" + ], + "enumDescriptions": [ + "Display the Uncommitted Changes as a grey open circle, connected to the commit referenced by HEAD with a solid grey line. The current file system's state is therefore always displayed as an open circle.", + "Display the Uncommitted Changes as a grey closed circle, connected to the commit referenced by HEAD with a dotted grey line. The commit referenced by HEAD is therefore always displayed as an open circle." + ], + "default": "Open Circle at the Uncommitted Changes", + "description": "Specifies how the Uncommitted Changes are displayed on the graph." + }, + "git-graph.integratedTerminalShell": { + "type": "string", + "default": "", + "description": "Specifies the path and filename of the Shell executable to be used by the Visual Studio Code Integrated Terminal, when it is opened by Git Graph. For example, to use Git Bash on Windows this setting would commonly be set to \"C:\\Program Files\\Git\\bin\\bash.exe\". If this setting is left blank, the default Shell is used.", + "scope": "machine" + }, + "git-graph.keyboardShortcut.find": { + "type": "string", + "enum": [ + "CTRL/CMD + A", + "CTRL/CMD + B", + "CTRL/CMD + C", + "CTRL/CMD + D", + "CTRL/CMD + E", + "CTRL/CMD + F", + "CTRL/CMD + G", + "CTRL/CMD + H", + "CTRL/CMD + I", + "CTRL/CMD + J", + "CTRL/CMD + K", + "CTRL/CMD + L", + "CTRL/CMD + M", + "CTRL/CMD + N", + "CTRL/CMD + O", + "CTRL/CMD + P", + "CTRL/CMD + Q", + "CTRL/CMD + R", + "CTRL/CMD + S", + "CTRL/CMD + T", + "CTRL/CMD + U", + "CTRL/CMD + V", + "CTRL/CMD + W", + "CTRL/CMD + X", + "CTRL/CMD + Y", + "CTRL/CMD + Z" + ], + "default": "CTRL/CMD + F", + "description": "The keybinding for the keyboard shortcut that opens the Find Widget in the Git Graph View." + }, + "git-graph.keyboardShortcut.refresh": { + "type": "string", + "enum": [ + "CTRL/CMD + A", + "CTRL/CMD + B", + "CTRL/CMD + C", + "CTRL/CMD + D", + "CTRL/CMD + E", + "CTRL/CMD + F", + "CTRL/CMD + G", + "CTRL/CMD + H", + "CTRL/CMD + I", + "CTRL/CMD + J", + "CTRL/CMD + K", + "CTRL/CMD + L", + "CTRL/CMD + M", + "CTRL/CMD + N", + "CTRL/CMD + O", + "CTRL/CMD + P", + "CTRL/CMD + Q", + "CTRL/CMD + R", + "CTRL/CMD + S", + "CTRL/CMD + T", + "CTRL/CMD + U", + "CTRL/CMD + V", + "CTRL/CMD + W", + "CTRL/CMD + X", + "CTRL/CMD + Y", + "CTRL/CMD + Z" + ], + "default": "CTRL/CMD + R", + "description": "The keybinding for the keyboard shortcut that refreshes the Git Graph View." + }, + "git-graph.keyboardShortcut.scrollToHead": { + "type": "string", + "enum": [ + "CTRL/CMD + A", + "CTRL/CMD + B", + "CTRL/CMD + C", + "CTRL/CMD + D", + "CTRL/CMD + E", + "CTRL/CMD + F", + "CTRL/CMD + G", + "CTRL/CMD + H", + "CTRL/CMD + I", + "CTRL/CMD + J", + "CTRL/CMD + K", + "CTRL/CMD + L", + "CTRL/CMD + M", + "CTRL/CMD + N", + "CTRL/CMD + O", + "CTRL/CMD + P", + "CTRL/CMD + Q", + "CTRL/CMD + R", + "CTRL/CMD + S", + "CTRL/CMD + T", + "CTRL/CMD + U", + "CTRL/CMD + V", + "CTRL/CMD + W", + "CTRL/CMD + X", + "CTRL/CMD + Y", + "CTRL/CMD + Z" + ], + "default": "CTRL/CMD + H", + "description": "The keybinding for the keyboard shortcut that scrolls the Git Graph View to be centered on the commit referenced by HEAD." + }, + "git-graph.keyboardShortcut.scrollToStash": { + "type": "string", + "enum": [ + "CTRL/CMD + A", + "CTRL/CMD + B", + "CTRL/CMD + C", + "CTRL/CMD + D", + "CTRL/CMD + E", + "CTRL/CMD + F", + "CTRL/CMD + G", + "CTRL/CMD + H", + "CTRL/CMD + I", + "CTRL/CMD + J", + "CTRL/CMD + K", + "CTRL/CMD + L", + "CTRL/CMD + M", + "CTRL/CMD + N", + "CTRL/CMD + O", + "CTRL/CMD + P", + "CTRL/CMD + Q", + "CTRL/CMD + R", + "CTRL/CMD + S", + "CTRL/CMD + T", + "CTRL/CMD + U", + "CTRL/CMD + V", + "CTRL/CMD + W", + "CTRL/CMD + X", + "CTRL/CMD + Y", + "CTRL/CMD + Z" + ], + "default": "CTRL/CMD + S", + "description": "The keybinding for the keyboard shortcut that scrolls the Git Graph View to the first (or next) stash in the loaded commits. The Shift Key Modifier can be applied to this keybinding to scroll the Git Graph View to the last (or previous) stash in the loaded commits." + }, + "git-graph.markdown": { + "type": "boolean", + "default": true, + "description": "Parse and render a frequently used subset of inline Markdown formatting rules in commit messages and tag details (bold, italics, bold & italics, and inline code blocks)." + }, + "git-graph.maxDepthOfRepoSearch": { + "type": "number", + "default": 0, + "description": "Specifies the maximum depth of subfolders to search when discovering repositories in the workspace. Note: Sub-repos are not automatically detected when searching subfolders, however they can be manually added by running the command \"Git Graph: Add Git Repository\" in the Command Palette." + }, + "git-graph.openNewTabEditorGroup": { + "type": "string", + "enum": [ + "Active", + "Beside", + "One", + "Two", + "Three", + "Four", + "Five", + "Six", + "Seven", + "Eight", + "Nine" + ], + "enumDescriptions": [ + "Open the new tab in the Active Editor Group.", + "Open the new tab beside the Active Editor Group.", + "Open the new tab in the First Editor Group.", + "Open the new tab in the Second Editor Group.", + "Open the new tab in the Third Editor Group.", + "Open the new tab in the Fourth Editor Group.", + "Open the new tab in the Fifth Editor Group.", + "Open the new tab in the Sixth Editor Group.", + "Open the new tab in the Seventh Editor Group.", + "Open the new tab in the Eighth Editor Group.", + "Open the new tab in the Ninth Editor Group." + ], + "default": "Active", + "description": "Specifies the Editor Group where Git Graph should open new tabs, when performing the following actions from the Git Graph View: Viewing the Visual Studio Code Diff View, Opening a File, Viewing a File at a Specific Revision." + }, + "git-graph.openToTheRepoOfTheActiveTextEditorDocument": { + "type": "boolean", + "default": false, + "description": "Open the Git Graph View to the repository containing the active Text Editor document." + }, + "git-graph.referenceLabels.alignment": { + "type": "string", + "enum": [ + "Normal", + "Branches (on the left) & Tags (on the right)", + "Branches (aligned to the graph) & Tags (on the right)" + ], + "enumDescriptions": [ + "Show branch & tag labels on the left of the commit message in the 'Description' column.", + "Show branch labels on the left of the commit message in the 'Description' column, and tag labels on the right.", + "Show branch labels aligned to the graph in the 'Graph' column, and tag labels on the right in the 'Description' column." + ], + "default": "Normal", + "description": "Specifies how branch and tag reference labels are aligned for each commit." + }, + "git-graph.referenceLabels.combineLocalAndRemoteBranchLabels": { + "type": "boolean", + "default": true, + "description": "Combine local and remote branch labels if they refer to the same branch, and are on the same commit." + }, + "git-graph.repository.commits.fetchAvatars": { + "type": "boolean", + "default": false, + "description": "Fetch avatars of commit authors and committers. By enabling this setting, you consent to commit author and committer email addresses being sent GitHub, GitLab or Gravatar, depending on the repositories remote origin." + }, + "git-graph.repository.commits.fetchCICDs": { + "type": "boolean", + "default": false, + "description": "Fetch cicds of commit authors and committers. By enabling this setting, you consent to commit author and committer email addresses being sent GitHub, GitLab or Gravatar, depending on the repositories remote origin." + }, + "git-graph.repository.commits.initialLoad": { + "type": "number", + "default": 300, + "description": "Specifies the number of commits to initially load." + }, + "git-graph.repository.commits.loadMore": { + "type": "number", + "default": 100, + "description": "Specifies the number of additional commits to load when the \"Load More Commits\" button is pressed, or more commits are automatically loaded." + }, + "git-graph.repository.commits.loadMoreAutomatically": { + "type": "boolean", + "default": true, + "description": "When the view has been scrolled to the bottom, automatically load more commits if they exist (instead of having to press the \"Load More Commits\" button)." + }, + "git-graph.repository.commits.mute.commitsThatAreNotAncestorsOfHead": { + "type": "boolean", + "default": false, + "description": "Display commits that aren't ancestors of the checked-out branch / commit with a muted text color. Muting will only occur if the commit referenced by HEAD is within the loaded commits on the Git Graph View." + }, + "git-graph.repository.commits.mute.mergeCommits": { + "type": "boolean", + "default": true, + "description": "Display merge commits with a muted text color." + }, + "git-graph.repository.commits.order": { + "type": "string", + "enum": [ + "date", + "author-date", + "topo" + ], + "enumDescriptions": [ + "Show commits in the commit timestamp order.", + "Show commits in the author timestamp order.", + "Avoid showing commits on multiple lines of history intermixed." + ], + "default": "date", + "markdownDescription": "Specifies the order of commits on the Git Graph View. See [git log](https://git-scm.com/docs/git-log#_commit_ordering) for more information on each order option. This can be overridden per repository via the Git Graph View's Column Header Context Menu." + }, + "git-graph.repository.commits.showSignatureStatus": { + "type": "boolean", + "default": false, + "description": "Show the commit's signature status to the right of the Committer in the Commit Details View (only for signed commits). Hovering over the signature icon displays a tooltip with the signature details. Requires Git (>= 2.4.0) & GPG (or equivalent) to be installed on the same machine that is running Visual Studio Code." + }, + "git-graph.repository.fetchAndPrune": { + "type": "boolean", + "default": false, + "description": "Before fetching from remote(s) using the Fetch button on the Git Graph View Control Bar, remove any remote-tracking references that no longer exist on the remote(s)." + }, + "git-graph.repository.fetchAndPruneTags": { + "type": "boolean", + "default": false, + "description": "Before fetching from remote(s) using the Fetch button on the Git Graph View Control Bar, remove any local tags that no longer exist on the remote(s). Requires Git >= 2.17.0, and the \"Repository: Fetch And Prune\" setting to be enabled. Caution: If you work in repositories that have multiple remotes, it is not recommended to use this setting (instead you can prune tags for a specific remote via \"Fetch Remote\" Dialog from the Repository Settings Widget on the Git Graph View)." + }, + "git-graph.repository.includeCommitsMentionedByReflogs": { + "type": "boolean", + "default": false, + "description": "Include commits only mentioned by reflogs in the Git Graph View (only applies when showing all branches). This can be overridden per repository in the Git Graph View's Repository Settings Widget." + }, + "git-graph.repository.onLoad.scrollToHead": { + "type": "boolean", + "default": false, + "description": "Automatically scroll the Git Graph View to be centered on the commit referenced by HEAD. This will only occur if the commit referenced by HEAD is within the loaded commits on the Git Graph View." + }, + "git-graph.repository.onLoad.showCheckedOutBranch": { + "type": "boolean", + "default": false, + "description": "Show the checked out branch when a repository is loaded in the Git Graph View. This setting can be used in conjunction with \"Repository > On Load: Show Specific Branches\". Default: false (show all branches)" + }, + "git-graph.repository.onLoad.showSpecificBranches": { + "type": "array", + "items": { + "type": "string", + "description": "A local branch name (e.g. \"master\"), a remote-tracking branch name prefixed with \"remotes/\" (e.g. \"remotes/origin/master\"), or a glob pattern defined in git-graph.customBranchGlobPatterns prefixed with \"--glob=\" (e.g. \"--glob=heads/feature/*\")." + }, + "default": [], + "markdownDescription": "Show specific branches when a repository is loaded in the Git Graph View. Branches can be specified as follows: A local branch name (e.g. `master`), a remote-tracking branch name prefixed with \"remotes/\" (e.g. `remotes/origin/master`), or a glob pattern defined in `git-graph.customBranchGlobPatterns` prefixed with \"--glob=\" (e.g. `--glob=heads/feature/*`). This setting can be used in conjunction with \"Repository > On Load: Show Checked Out Branch\". Default: [] (show all branches)" + }, + "git-graph.repository.onlyFollowFirstParent": { + "type": "boolean", + "default": false, + "markdownDescription": "Only follow the first parent of commits when discovering the commits to load in the Git Graph View. See [--first-parent](https://git-scm.com/docs/git-log#Documentation/git-log.txt---first-parent) to find out more about this setting. This can be overridden per repository in the Git Graph View's Repository Settings Widget." + }, + "git-graph.repository.showCommitsOnlyReferencedByTags": { + "type": "boolean", + "default": true, + "description": "Show Commits that are only referenced by tags in Git Graph." + }, + "git-graph.repository.showRemoteBranches": { + "type": "boolean", + "default": true, + "description": "Show Remote Branches in Git Graph by default. This can be overridden per repository from the Git Graph View's Control Bar." + }, + "git-graph.repository.showRemoteHeads": { + "type": "boolean", + "default": true, + "description": "Show Remote HEAD Symbolic References in Git Graph (e.g. \"origin/HEAD\")." + }, + "git-graph.repository.showStashes": { + "type": "boolean", + "default": true, + "description": "Show Stashes in Git Graph by default. This can be overridden per repository in the Git Graph View's Repository Settings Widget." + }, + "git-graph.repository.showTags": { + "type": "boolean", + "default": true, + "description": "Show Tags in Git Graph by default. This can be overridden per repository in the Git Graph View's Repository Settings Widget." + }, + "git-graph.repository.showUncommittedChanges": { + "type": "boolean", + "default": true, + "description": "Show uncommitted changes. If you work on large repositories, disabling this setting can reduce the load time of the Git Graph View." + }, + "git-graph.repository.showUntrackedFiles": { + "type": "boolean", + "default": true, + "description": "Show untracked files when viewing the uncommitted changes. If you work on large repositories, disabling this setting can reduce the load time of the Git Graph View." + }, + "git-graph.repository.sign.commits": { + "type": "boolean", + "default": false, + "description": "Enables commit signing with GPG or X.509." + }, + "git-graph.repository.sign.tags": { + "type": "boolean", + "default": false, + "description": "Enables tag signing with GPG or X.509." + }, + "git-graph.repository.useMailmap": { + "type": "boolean", + "default": false, + "markdownDescription": "Respect [.mailmap](https://git-scm.com/docs/git-check-mailmap#_mapping_authors) files when displaying author & committer names and email addresses." + }, + "git-graph.repositoryDropdownOrder": { + "type": "string", + "enum": [ + "Full Path", + "Name" + ], + "enumDescriptions": [ + "Sort repositories alphabetically by the full path of the repository.", + "Sort repositories alphabetically by the name of the repository." + ], + "default": "Full Path", + "description": "Specifies the order that repositories are sorted in the repository dropdown on the Git Graph View (only visible when more than one repository exists in the current Visual Studio Code Workspace)." + }, + "git-graph.retainContextWhenHidden": { + "type": "boolean", + "default": true, + "description": "Specifies if the Git Graph View's Visual Studio Code context is kept when the panel is no longer visible (e.g. moved to background tab). Enabling this setting will make Git Graph load significantly faster when switching back to the Git Graph tab, however has a higher memory overhead." + }, + "git-graph.showStatusBarItem": { + "type": "boolean", + "default": true, + "description": "Show a Status Bar Item that opens the Git Graph View when clicked." + }, + "git-graph.sourceCodeProviderIntegrationLocation": { + "type": "string", + "enum": [ + "Inline", + "More Actions" + ], + "enumDescriptions": [ + "Show the 'View Git Graph' action on the title of SCM Providers", + "Show the 'View Git Graph' action in the 'More Actions...' menu on the title of SCM Providers" + ], + "default": "Inline", + "description": "Specifies where the \"View Git Graph\" action appears on the title of SCM Providers." + }, + "git-graph.tabIconColourTheme": { + "type": "string", + "enum": [ + "colour", + "grey" + ], + "enumDescriptions": [ + "Show a colour icon which suits most Visual Studio Code colour themes", + "Show a grey icon which suits Visual Studio Code colour themes that are predominantly grayscale" + ], + "default": "colour", + "description": "Specifies the colour theme of the icon displayed on the Git Graph tab." + }, + "git-graph.autoCenterCommitDetailsView": { + "type": "boolean", + "default": true, + "description": "Automatically center the commit details view when it is opened.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.commitDetailsView.autoCenter", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.commitDetailsView.autoCenter#`" + }, + "git-graph.combineLocalAndRemoteBranchLabels": { + "type": "boolean", + "default": true, + "description": "Combine local and remote branch labels if they refer to the same branch, and are on the same commit.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.referenceLabels.combineLocalAndRemoteBranchLabels", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.referenceLabels.combineLocalAndRemoteBranchLabels#`" + }, + "git-graph.commitDetailsViewFileTreeCompactFolders": { + "type": "boolean", + "default": true, + "description": "Render the File Tree in the Commit Details / Comparison View in a compacted form, such that folders with a single child folder are compressed into a single combined folder element.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.commitDetailsView.fileView.fileTree.compactFolders", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.commitDetailsView.fileView.fileTree.compactFolders#`" + }, + "git-graph.commitDetailsViewLocation": { + "type": "string", + "enum": [ + "Inline", + "Docked to Bottom" + ], + "enumDescriptions": [ + "Show the Commit Details View inline with the graph", + "Show the Commit Details View docked to the bottom of the Git Graph view" + ], + "default": "Inline", + "description": "Specifies where the Commit Details View is rendered in the Git Graph view.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.commitDetailsView.location", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.commitDetailsView.location#`" + }, + "git-graph.commitOrdering": { + "type": "string", + "enum": [ + "date", + "author-date", + "topo" + ], + "enumDescriptions": [ + "Show commits in the commit timestamp order.", + "Show commits in the author timestamp order.", + "Avoid showing commits on multiple lines of history intermixed." + ], + "default": "date", + "markdownDescription": "Specifies the order of commits on the Git Graph view. See [git log](https://git-scm.com/docs/git-log#_commit_ordering) for more information on each order option.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.order", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.order#`" + }, + "git-graph.dateFormat": { + "type": "string", + "enum": [ + "Date & Time", + "Date Only", + "ISO Date & Time", + "ISO Date Only", + "Relative" + ], + "enumDescriptions": [ + "Show the date and time, for example \"24 Mar 2019 21:34\"", + "Show the date only, for example \"24 Mar 2019\"", + "Show the ISO date and time, for example \"2019-03-24 21:34\"", + "Show the ISO date only, for example \"2019-03-24\"", + "Show relative times, for example \"5 minutes ago\"" + ], + "default": "Date & Time", + "description": "Specifies the date format to be used in the \"Date\" column on the Git Graph View.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.date.format", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.date.format#`" + }, + "git-graph.dateType": { + "type": "string", + "enum": [ + "Author Date", + "Commit Date" + ], + "enumDescriptions": [ + "Use the author date of a commit", + "Use the committer date of a commit" + ], + "default": "Author Date", + "description": "Specifies the date type to be displayed in the \"Date\" column on the Git Graph View.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.date.type", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.date.type#`" + }, + "git-graph.defaultFileViewType": { + "type": "string", + "enum": [ + "File Tree", + "File List" + ], + "enumDescriptions": [ + "Display files in a tree structure", + "Display files in a list (useful for repositories with deep folder structures)" + ], + "default": "File Tree", + "description": "Sets the default type of File View used in the Commit Details / Comparison Views. This can be overridden per repository using the controls on the right side of the Commit Details / Comparison Views.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.commitDetailsView.fileView.type", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.commitDetailsView.fileView.type#`" + }, + "git-graph.fetchAndPrune": { + "type": "boolean", + "default": false, + "description": "Before fetching from remote(s) using the Fetch button on the Git Graph View Control Bar, remove any remote-tracking references that no longer exist on the remote(s).", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.fetchAndPrune", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.fetchAndPrune#`" + }, + "git-graph.fetchAvatars": { + "type": "boolean", + "default": false, + "description": "Fetch avatars of commit authors and committers. By enabling this setting, you consent to commit author and committer email addresses being sent GitHub, GitLab or Gravatar, depending on the repositories remote origin.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.fetchAvatars", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.fetchAvatars#`" + }, + "git-graph.fetchCICDs": { + "type": "boolean", + "default": false, + "description": "Fetch cicds of commit authors and committers. By enabling this setting, you consent to commit author and committer email addresses being sent GitHub, GitLab or Gravatar, depending on the repositories remote origin.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.fetchCICDs", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.fetchCICDs#`" + }, + "git-graph.graphColours": { + "type": "array", + "items": { + "type": "string", + "description": "Colour (HEX or RGB)", + "pattern": "^\\s*(#[0-9a-fA-F]{6}|#[0-9a-fA-F]{8}|rgb[a]?\\s*\\(\\d{1,3},\\s*\\d{1,3},\\s*\\d{1,3}\\))\\s*$" + }, + "default": [ + "#0085d9", + "#d9008f", + "#00d90a", + "#d98500", + "#a300d9", + "#ff0000", + "#00d9cc", + "#e138e8", + "#85d900", + "#dc5b23", + "#6f24d6", + "#ffcc00" + ], + "description": "Specifies the colours used on the graph.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.graph.colours", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.graph.colours#`" + }, + "git-graph.graphStyle": { + "type": "string", + "enum": [ + "rounded", + "angular" + ], + "enumDescriptions": [ + "Use smooth curves when transitioning between branches on the graph", + "Use angular lines when transitioning between branches on the graph" + ], + "default": "rounded", + "description": "Specifies the style of the graph.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.graph.style", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.graph.style#`" + }, + "git-graph.includeCommitsMentionedByReflogs": { + "type": "boolean", + "default": false, + "description": "Include commits only mentioned by reflogs in the Git Graph View (only applies when showing all branches). This can be overridden per repository in the Git Graph View's Repository Settings Widget.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.includeCommitsMentionedByReflogs", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.includeCommitsMentionedByReflogs#`" + }, + "git-graph.initialLoadCommits": { + "type": "number", + "default": 300, + "description": "Specifies the number of commits to initially load.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.initialLoad", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.initialLoad#`" + }, + "git-graph.loadMoreCommits": { + "type": "number", + "default": 100, + "description": "Specifies the number of additional commits to load when the \"Load More Commits\" button is pressed, or more commits are automatically loaded.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.loadMore", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.loadMore#`" + }, + "git-graph.loadMoreCommitsAutomatically": { + "type": "boolean", + "default": true, + "description": "When the view has been scrolled to the bottom, automatically load more commits if they exist (instead of having to press the \"Load More Commits\" button).", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.loadMoreAutomatically", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.loadMoreAutomatically#`" + }, + "git-graph.muteCommitsThatAreNotAncestorsOfHead": { + "type": "boolean", + "default": false, + "description": "Display commits that aren't ancestors of the checked-out branch / commit with a muted text color. Muting will only occur if the commit referenced by HEAD is within the loaded commits on the Git Graph View.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.mute.commitsThatAreNotAncestorsOfHead", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.mute.commitsThatAreNotAncestorsOfHead#`" + }, + "git-graph.muteMergeCommits": { + "type": "boolean", + "default": true, + "description": "Display merge commits with a muted text color.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.mute.mergeCommits", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.mute.mergeCommits#`" + }, + "git-graph.onlyFollowFirstParent": { + "type": "boolean", + "default": false, + "markdownDescription": "Only follow the first parent of commits when discovering the commits to load in the Git Graph View. See [--first-parent](https://git-scm.com/docs/git-log#Documentation/git-log.txt---first-parent) to find out more about this setting.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.onlyFollowFirstParent", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.onlyFollowFirstParent#`" + }, + "git-graph.openDiffTabLocation": { + "type": "string", + "enum": [ + "Active", + "Beside", + "One", + "Two", + "Three", + "Four", + "Five", + "Six", + "Seven", + "Eight", + "Nine" + ], + "enumDescriptions": [ + "Open the Visual Studio Code Diff View in the Active Editor Group.", + "Open the Visual Studio Code Diff View beside the Active Editor Group.", + "Open the Visual Studio Code Diff View in the First Editor Group.", + "Open the Visual Studio Code Diff View in the Second Editor Group.", + "Open the Visual Studio Code Diff View in the Third Editor Group.", + "Open the Visual Studio Code Diff View in the Fourth Editor Group.", + "Open the Visual Studio Code Diff View in the Fifth Editor Group.", + "Open the Visual Studio Code Diff View in the Sixth Editor Group.", + "Open the Visual Studio Code Diff View in the Seventh Editor Group.", + "Open the Visual Studio Code Diff View in the Eighth Editor Group.", + "Open the Visual Studio Code Diff View in the Ninth Editor Group." + ], + "default": "Active", + "description": "Specifies which Editor Group the Visual Studio Code Diff View is opened in.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.openNewTabEditorGroup", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.openNewTabEditorGroup#`" + }, + "git-graph.openRepoToHead": { + "type": "boolean", + "default": false, + "description": "When opening or switching repositories in the Git Graph View, automatically scroll the view to be centered on the commit referenced by HEAD. This will only occur if the commit referenced by HEAD is within the loaded commits on the Git Graph View.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.onLoad.scrollToHead", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.onLoad.scrollToHead#`" + }, + "git-graph.referenceLabelAlignment": { + "type": "string", + "enum": [ + "Normal", + "Branches (on the left) & Tags (on the right)", + "Branches (aligned to the graph) & Tags (on the right)" + ], + "enumDescriptions": [ + "Show branch & tag labels on the left of the commit message in the 'Description' column.", + "Show branch labels on the left of the commit message in the 'Description' column, and tag labels on the right.", + "Show branch labels aligned to the graph in the 'Graph' column, and tag labels on the right in the 'Description' column." + ], + "default": "Normal", + "description": "Specifies how branch and tag reference labels are aligned for each commit.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.referenceLabels.alignment", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.referenceLabels.alignment#`" + }, + "git-graph.showCommitsOnlyReferencedByTags": { + "type": "boolean", + "default": true, + "description": "Show commits that are only referenced by tags in Git Graph.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.showCommitsOnlyReferencedByTags", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.showCommitsOnlyReferencedByTags#`" + }, + "git-graph.showCurrentBranchByDefault": { + "type": "boolean", + "default": false, + "description": "Show the current branch by default when Git Graph is opened. Default: false (show all branches)", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.onLoad.showCheckedOutBranch", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.onLoad.showCheckedOutBranch#`" + }, + "git-graph.showSignatureStatus": { + "type": "boolean", + "default": false, + "description": "Show the commit's signature status to the right of the Committer in the Commit Details View (only for signed commits). Hovering over the signature icon displays a tooltip with the signature details. Requires Git (>= 2.4.0) & GPG (or equivalent) to be installed on the same machine that is running Visual Studio Code.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.showSignatureStatus", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.showSignatureStatus#`" + }, + "git-graph.showTags": { + "type": "boolean", + "default": true, + "description": "Show Tags in Git Graph by default. This can be overridden per repository in the Git Graph View's Repository Settings Widget.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.showTags", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.showTags#`" + }, + "git-graph.showUncommittedChanges": { + "type": "boolean", + "default": true, + "description": "Show uncommitted changes. If you work on large repositories, disabling this setting can reduce the load time of the Git Graph View.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.showUncommittedChanges", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.showUncommittedChanges#`" + }, + "git-graph.showUntrackedFiles": { + "type": "boolean", + "default": true, + "description": "Show untracked files when viewing the uncommitted changes. If you work on large repositories, disabling this setting can reduce the load time of the Git Graph View.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.showUntrackedFiles", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.showUntrackedFiles#`" + }, + "git-graph.useMailmap": { + "type": "boolean", + "default": false, + "markdownDescription": "Respect [.mailmap](https://git-scm.com/docs/git-check-mailmap#_mapping_authors) files when displaying author & committer names and email addresses.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.useMailmap", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.useMailmap#`" + } + } + }, + "menus": { + "commandPalette": [ + { + "command": "git-graph.openFile", + "when": "isInDiffEditor && resourceScheme == git-graph && git-graph:codiconsSupported" + } + ], + "editor/title": [ + { + "command": "git-graph.openFile", + "group": "navigation", + "when": "isInDiffEditor && resourceScheme == git-graph && git-graph:codiconsSupported" + } + ], + "scm/title": [ + { + "when": "scmProvider == git && config.git-graph.sourceCodeProviderIntegrationLocation == 'Inline'", + "command": "git-graph.view", + "group": "navigation" + }, + { + "when": "scmProvider == git && config.git-graph.sourceCodeProviderIntegrationLocation == 'More Actions'", + "command": "git-graph.view", + "group": "inline" + } + ] + } + }, + "scripts": { + "vscode:prepublish": "npm run compile", + "vscode:uninstall": "node ./out/life-cycle/uninstall.js", + "clean": "node ./.vscode/clean.js", + "compile": "npm run lint && npm run clean && npm run compile-src && npm run compile-web", + "compile-src": "tsc -p ./src && node ./.vscode/package-src.js", + "compile-web": "tsc -p ./web && node ./.vscode/package-web.js", + "compile-web-debug": "tsc -p ./web && node ./.vscode/package-web.js debug", + "lint": "eslint -c .eslintrc.json --ext .ts ./src ./tests ./web", + "package": "npm run clean && vsce package", + "package-and-install": "npm run package && node ./.vscode/install-package.js", + "test": "jest --verbose", + "test-and-report-coverage": "jest --verbose --coverage" + }, + "dependencies": { + "iconv-lite": "0.5.0", + "request": "2.88.2", + "request-promise": "4.2.6" + }, + "devDependencies": { + "@types/jest": "26.0.19", + "@types/node": "8.10.62", + "@types/request-promise": "4.1.46", + "@types/vscode": "1.38.0", + "@typescript-eslint/eslint-plugin": "4.10.0", + "@typescript-eslint/parser": "4.10.0", + "eslint": "7.15.0", + "jest": "26.6.3", + "ts-jest": "26.4.4", + "typescript": "4.0.2", + "uglify-js": "3.10.0" + } +} diff --git a/src/cicdManager.ts b/src/cicdManager.ts new file mode 100644 index 00000000..93405f1d --- /dev/null +++ b/src/cicdManager.ts @@ -0,0 +1,589 @@ +import * as crypto from 'crypto'; +import * as https from 'https'; +import * as url from 'url'; +import { DataSource } from './dataSource'; +import { ExtensionState } from './extensionState'; +import { Logger } from './logger'; +import { Disposable, toDisposable } from './utils/disposable'; +import { EventEmitter } from './utils/event'; + +/** + * Manages fetching and caching CICDs. + */ +export class CicdManager extends Disposable { + private readonly dataSource: DataSource; + private readonly extensionState: ExtensionState; + private readonly logger: Logger; + private readonly cicdEventEmitter: EventEmitter; + + private cicds: CICDCache; + private queue: CicdRequestQueue; + private remoteSourceCache: { [repo: string]: RemoteSource } = {}; + private interval: NodeJS.Timer | null = null; + + private githubTimeout: number = 0; + private gitLabTimeout: number = 0; + + /** + * Creates the Git Graph CICD Manager. + * @param dataSource The Git Graph DataSource instance. + * @param extensionState The Git Graph ExtensionState instance. + * @param logger The Git Graph Logger instance. + */ + constructor(dataSource: DataSource, extensionState: ExtensionState, logger: Logger) { + super(); + this.dataSource = dataSource; + this.extensionState = extensionState; + this.logger = logger; + this.cicdEventEmitter = new EventEmitter(); + this.cicds = this.extensionState.getCICDCache(); + this.queue = new CicdRequestQueue(() => { + if (this.interval !== null) return; + this.interval = setInterval(() => { + // Fetch cicds every 10 seconds + this.fetchCICDsInterval(); + }, 10000); + this.fetchCICDsInterval(); + }); + + this.registerDisposables( + // Stop fetching cicds when disposed + toDisposable(() => { + this.stopInterval(); + }), + + // Dispose the cicd event emitter + this.cicdEventEmitter + ); + } + + /** + * Stops the interval used to fetch cicds. + */ + private stopInterval() { + if (this.interval !== null) { + clearInterval(this.interval); + this.interval = null; + this.remoteSourceCache = {}; + } + } + + /** + * Fetch an cicd, either from the cache if it already exists, or queue it to be fetched. + * @param email The email address identifying the cicd. + * @param repo The repository that the cicd is used in. + * @param remote The remote that the cicd can be fetched from. + * @param commits The commits that reference the cicd. + */ + public fetchCICDImage(email: string, repo: string, remote: string | null, commits: string[]) { + if (typeof this.cicds[email] !== 'undefined') { + // CICD exists in the cache + let t = (new Date()).getTime(); + if (this.cicds[email].timestamp < t - 1209600000 || (this.cicds[email].identicon && this.cicds[email].timestamp < t - 345600000)) { + // Refresh cicd after 14 days, or if an cicd couldn't previously be found after 4 days + this.queue.add(email, repo, remote, commits, false); + } + + this.emitCICD(email).catch(() => { + // CICD couldn't be found, request it again + this.removeCICDFromCache(email); + this.queue.add(email, repo, remote, commits, true); + }); + } else { + // CICD not in the cache, request it + this.queue.add(email, repo, remote, commits, true); + } + } + + /** + * Get the image data of an cicd. + * @param email The email address identifying the cicd. + * @returns A base64 encoded data URI if the cicd exists, otherwise NULL. + */ + public getCICDImage(email: string) { + return new Promise((resolve) => { + if (typeof this.cicds[email] !== 'undefined' && this.cicds[email].image !== null) { + // fs.readFile(this.cicdStorageFolder + '/' + this.cicds[email].image, (err, data) => { + // resolve(err ? null : 'data:image/' + this.cicds[email].image.split('.')[1] + ';base64,' + data.toString('base64')); + // }); + } else { + resolve(null); + } + }); + } + + /** + * Get the Event that can be used to subscribe to receive requested cicds. + * @returns The Event. + */ + get onCICD() { + return this.cicdEventEmitter.subscribe; + } + + /** + * Remove an cicd from the cache. + * @param email The email address identifying the cicd. + */ + private removeCICDFromCache(email: string) { + delete this.cicds[email]; + this.extensionState.removeCICDFromCache(email); + } + + /** + * Remove all cicds from the cache. + */ + public clearCache() { + this.cicds = {}; + this.extensionState.clearCICDCache(); + } + + /** + * Triggered by an interval to fetch cicds from Github, GitLab and Grcicd. + */ + private async fetchCICDsInterval() { + if (this.queue.hasItems()) { + let cicdRequest = this.queue.takeItem(); + if (cicdRequest === null) return; // No cicd can be checked at the current time + + let remoteSource = await this.getRemoteSource(cicdRequest); // Fetch the remote source of the cicd + switch (remoteSource.type) { + case 'github': + this.fetchFromGithub(cicdRequest, remoteSource.owner, remoteSource.repo); + break; + case 'gitlab': + this.fetchFromGitLab(cicdRequest); + break; + default: + this.fetchFromGrcicd(cicdRequest); + } + } else { + // Stop the interval if there are no items remaining in the queue + this.stopInterval(); + } + } + + /** + * Get the remote source of an cicd request. + * @param cicdRequest The cicd request. + * @returns The remote source. + */ + private async getRemoteSource(cicdRequest: CICDRequestItem) { + if (typeof this.remoteSourceCache[cicdRequest.repo] === 'object') { + // If the repo exists in the cache of remote sources + return this.remoteSourceCache[cicdRequest.repo]; + } else { + // Fetch the remote repo source + let remoteSource: RemoteSource = { type: 'grcicd' }; + if (cicdRequest.remote !== null) { + let remoteUrl = await this.dataSource.getRemoteUrl(cicdRequest.repo, cicdRequest.remote); + if (remoteUrl !== null) { + // Depending on the domain of the remote repo source, determine the type of source it is + let match; + if ((match = remoteUrl.match(/^(https:\/\/github\.com\/|git@github\.com:)([^\/]+)\/(.*)\.git$/)) !== null) { + remoteSource = { type: 'github', owner: match[2], repo: match[3] }; + } else if (remoteUrl.startsWith('https://gitlab.com/') || remoteUrl.startsWith('git@gitlab.com:')) { + remoteSource = { type: 'gitlab' }; + } + } + } + this.remoteSourceCache[cicdRequest.repo] = remoteSource; // Add the remote source to the cache for future use + return remoteSource; + } + } + + /** + * Fetch an cicd from Github. + * @param cicdRequest The cicd request to fetch. + * @param owner The owner of the repository. + * @param repo The repository that the cicd is used in. + */ + private fetchFromGithub(cicdRequest: CICDRequestItem, owner: string, repo: string) { + let t = (new Date()).getTime(); + if (t < this.githubTimeout) { + // Defer request until after timeout + this.queue.addItem(cicdRequest, this.githubTimeout, false); + this.fetchCICDsInterval(); + return; + } + + this.logger.log('Requesting CICD for ' + maskEmail(cicdRequest.email) + ' from GitHub'); + + const commitIndex = cicdRequest.commits.length < 5 + ? cicdRequest.commits.length - 1 - cicdRequest.attempts + : Math.round((4 - cicdRequest.attempts) * 0.25 * (cicdRequest.commits.length - 1)); + + let triggeredOnError = false; + const onError = () => { + if (!triggeredOnError) { + // If an error occurs, try again after 5 minutes + triggeredOnError = true; + this.githubTimeout = t + 300000; + this.queue.addItem(cicdRequest, this.githubTimeout, false); + } + }; + + https.get({ + hostname: 'api.github.com', path: '/repos/' + owner + '/' + repo + '/commits/' + cicdRequest.commits[commitIndex], + headers: { 'User-Agent': 'vscode-git-graph' }, + agent: false, timeout: 15000 + }, (res) => { + let respBody = ''; + res.on('data', (chunk: Buffer) => { respBody += chunk; }); + res.on('end', async () => { + if (res.headers['x-ratelimit-remaining'] === '0') { + // If the GitHub Api rate limit was reached, store the github timeout to prevent subsequent requests + this.githubTimeout = parseInt(res.headers['x-ratelimit-reset']) * 1000; + this.logger.log('GitHub API Rate Limit Reached - Paused fetching from GitHub until the Rate Limit is reset'); + } + + if (res.statusCode === 200) { // Success + let commit: any = JSON.parse(respBody); + if (commit.author && commit.author.cicd_url) { // CICD url found + let img = await this.downloadCICDImage(cicdRequest.email, commit.author.cicd_url + '&size=162'); + if (img !== null) { + this.saveCICD(cicdRequest.email, img, false); + } else { + this.logger.log('Failed to download cicd from GitHub for ' + maskEmail(cicdRequest.email)); + } + return; + } + } else if (res.statusCode === 403) { + // Rate limit reached, try again after timeout + this.queue.addItem(cicdRequest, this.githubTimeout, false); + return; + } else if (res.statusCode === 422 && cicdRequest.commits.length > cicdRequest.attempts + 1 && cicdRequest.attempts < 4) { + // Commit not found on remote, try again with the next commit if less than 5 attempts have been made + this.queue.addItem(cicdRequest, 0, true); + return; + } else if (res.statusCode! >= 500) { + // If server error, try again after 10 minutes + this.githubTimeout = t + 600000; + this.queue.addItem(cicdRequest, this.githubTimeout, false); + return; + } + this.fetchFromGrcicd(cicdRequest); // Fallback to Grcicd + }); + res.on('error', onError); + }).on('error', onError); + } + + /** + * Fetch an cicd from GitLab. + * @param cicdRequest The cicd request to fetch. + */ + private fetchFromGitLab(cicdRequest: CICDRequestItem) { + let t = (new Date()).getTime(); + if (t < this.gitLabTimeout) { + // Defer request until after timeout + this.queue.addItem(cicdRequest, this.gitLabTimeout, false); + this.fetchCICDsInterval(); + return; + } + + this.logger.log('Requesting CICD for ' + maskEmail(cicdRequest.email) + ' from GitLab'); + + let triggeredOnError = false; + const onError = () => { + if (!triggeredOnError) { + // If an error occurs, try again after 5 minutes + triggeredOnError = true; + this.gitLabTimeout = t + 300000; + this.queue.addItem(cicdRequest, this.gitLabTimeout, false); + } + }; + + https.get({ + hostname: 'gitlab.com', path: '/api/v4/users?search=' + cicdRequest.email, + headers: { 'User-Agent': 'vscode-git-graph', 'Private-Token': 'w87U_3gAxWWaPtFgCcus' }, // Token only has read access + agent: false, timeout: 15000 + }, (res) => { + let respBody = ''; + res.on('data', (chunk: Buffer) => { respBody += chunk; }); + res.on('end', async () => { + if (res.headers['ratelimit-remaining'] === '0') { + // If the GitLab Api rate limit was reached, store the gitlab timeout to prevent subsequent requests + this.gitLabTimeout = parseInt(res.headers['ratelimit-reset']) * 1000; + this.logger.log('GitLab API Rate Limit Reached - Paused fetching from GitLab until the Rate Limit is reset'); + } + + if (res.statusCode === 200) { // Success + let users: any = JSON.parse(respBody); + if (users.length > 0 && users[0].cicd_url) { // CICD url found + let img = await this.downloadCICDImage(cicdRequest.email, users[0].cicd_url); + if (img !== null) { + this.saveCICD(cicdRequest.email, img, false); + } else { + this.logger.log('Failed to download cicd from GitLab for ' + maskEmail(cicdRequest.email)); + } + return; + } + } else if (res.statusCode === 429) { + // Rate limit reached, try again after timeout + this.queue.addItem(cicdRequest, this.gitLabTimeout, false); + return; + } else if (res.statusCode! >= 500) { + // If server error, try again after 10 minutes + this.gitLabTimeout = t + 600000; + this.queue.addItem(cicdRequest, this.gitLabTimeout, false); + return; + } + this.fetchFromGrcicd(cicdRequest); // Fallback to Grcicd + }); + res.on('error', onError); + }).on('error', onError); + } + + /** + * Fetch an cicd from Grcicd. + * @param cicdRequest The cicd request to fetch. + */ + private async fetchFromGrcicd(cicdRequest: CICDRequestItem) { + this.logger.log('Requesting CICD for ' + maskEmail(cicdRequest.email) + ' from Grcicd'); + const hash: string = crypto.createHash('md5').update(cicdRequest.email.trim().toLowerCase()).digest('hex'); + + let img = await this.downloadCICDImage(cicdRequest.email, 'https://secure.grcicd.com/cicd/' + hash + '?s=162&d=404'), identicon = false; + if (img === null) { + img = await this.downloadCICDImage(cicdRequest.email, 'https://secure.grcicd.com/cicd/' + hash + '?s=162&d=identicon'); + identicon = true; + } + + if (img !== null) { + this.saveCICD(cicdRequest.email, img, identicon); + } else { + this.logger.log('No CICD could be found for ' + maskEmail(cicdRequest.email)); + } + } + + /** + * Download and save an cicd image. + * @param _email The email address identifying the cicd. + * @param imageUrl The URL the cicd can be downloaded from. + * @returns A promise that resolves to the image name of the cicd on disk, or NULL if downloading failed. + */ + private downloadCICDImage(_email: string, imageUrl: string) { + return (new Promise((resolve) => { + // const hash = crypto.createHash('md5').update(email).digest('hex'); + const imgUrl = url.parse(imageUrl); + + let completed = false; + const complete = (fileName: string | null = null) => { + if (!completed) { + completed = true; + resolve(fileName); + } + }; + + https.get({ + hostname: imgUrl.hostname, path: imgUrl.path, + headers: { 'User-Agent': 'vscode-git-graph' }, + agent: false, timeout: 15000 + }, (res) => { + let imageBufferArray: Buffer[] = []; + res.on('data', (chunk: Buffer) => { imageBufferArray.push(chunk); }); + res.on('end', () => { + if (res.statusCode === 200) { // If success response, save the image to the cicd folder + // let format = res.headers['content-type']!.split('/')[1]; + // fs.writeFile(this.cicdStorageFolder + '/' + hash + '.' + format, Buffer.concat(imageBufferArray), err => { + // complete(err ? null : hash + '.' + format); + // }); + } else { + complete(); + } + }); + res.on('error', complete); + }).on('error', complete); + })).catch(() => null); + } + + /** + * Emit an CICDEvent to any listeners. + * @param email The email address identifying the cicd. + * @returns A promise indicating if the event was emitted successfully. + */ + private emitCICD(email: string) { + return new Promise((resolve, reject) => { + if (this.cicdEventEmitter.hasSubscribers()) { + this.getCICDImage(email).then((image) => { + if (image === null) { + reject(); + } else { + this.cicdEventEmitter.emit({ + email: email, + image: image + }); + resolve(true); + } + }); + } else { + resolve(false); + } + }); + } + + /** + * Save an cicd in the cache. + * @param email The email address identifying the cicd. + * @param image The image name of the cicd on disk. + * @param identicon Whether this cicd is an identicon. + */ + private saveCICD(email: string, image: string, identicon: boolean) { + if (typeof this.cicds[email] !== 'undefined') { + if (!identicon || this.cicds[email].identicon) { + this.cicds[email].image = image; + this.cicds[email].identicon = identicon; + } + this.cicds[email].timestamp = (new Date()).getTime(); + } else { + this.cicds[email] = { image: image, timestamp: (new Date()).getTime(), identicon: identicon }; + } + this.extensionState.saveCICD(email, this.cicds[email]); + this.logger.log('Saved CICD for ' + maskEmail(email)); + this.emitCICD(email).then( + (sent) => this.logger.log(sent + ? 'Sent CICD for ' + maskEmail(email) + ' to the Git Graph View' + : 'CICD for ' + maskEmail(email) + ' is ready to be used the next time the Git Graph View is opened' + ), + () => this.logger.log('Failed to Send CICD for ' + maskEmail(email) + ' to the Git Graph View') + ); + } +} + +/** + * Represents a queue of cicd requests, ordered by their `checkAfter` value. + */ +class CicdRequestQueue { + private queue: CICDRequestItem[] = []; + private itemsAvailableCallback: () => void; + + /** + * Create an CICD Request Queue. + * @param itemsAvailableCallback A callback that is invoked when the queue transitions from having no items, to having at least one item. + */ + constructor(itemsAvailableCallback: () => void) { + this.itemsAvailableCallback = itemsAvailableCallback; + } + + /** + * Create and add a new cicd request to the queue. + * @param email The email address identifying the cicd. + * @param repo The repository that the cicd is used in. + * @param remote The remote that the cicd can be fetched from. + * @param commits The commits that reference the cicd. + * @param immediate Whether the cicd should be fetched immediately. + */ + public add(email: string, repo: string, remote: string | null, commits: string[], immediate: boolean) { + const existingRequest = this.queue.find((request) => request.email === email && request.repo === repo); + if (existingRequest) { + commits.forEach((commit) => { + if (!existingRequest.commits.includes(commit)) { + existingRequest.commits.push(commit); + } + }); + } else { + this.insertItem({ + email: email, + repo: repo, + remote: remote, + commits: commits, + checkAfter: immediate || this.queue.length === 0 + ? 0 + : this.queue[this.queue.length - 1].checkAfter + 1, + attempts: 0 + }); + } + } + + /** + * Add an existing cicd request item back onto the queue. + * @param item The cicd request item. + * @param checkAfter The earliest time the cicd should be requested. + * @param failedAttempt Did the fetch attempt fail. + */ + public addItem(item: CICDRequestItem, checkAfter: number, failedAttempt: boolean) { + item.checkAfter = checkAfter; + if (failedAttempt) item.attempts++; + this.insertItem(item); + } + + /** + * Check if there are items in the queue. + * @returns TRUE => Items in the queue, FALSE => Queue is empty. + */ + public hasItems() { + return this.queue.length > 0; + } + + /** + * Take the next item from the queue if an item is available. + * @returns An cicd request item, or NULL if no item is available. + */ + public takeItem() { + if (this.queue.length > 0 && this.queue[0].checkAfter < (new Date()).getTime()) return this.queue.shift()!; + return null; + } + + /** + * Insert an cicd request item into the queue. + * @param item The cicd request item. + */ + private insertItem(item: CICDRequestItem) { + var l = 0, r = this.queue.length - 1, c, prevLength = this.queue.length; + while (l <= r) { + c = l + r >> 1; + if (this.queue[c].checkAfter <= item.checkAfter) { + l = c + 1; + } else { + r = c - 1; + } + } + this.queue.splice(l, 0, item); + if (prevLength === 0) this.itemsAvailableCallback(); + } +} + +/** + * Mask an email address for logging. + * @param email The string containing the email address. + * @returns The masked email address. + */ +function maskEmail(email: string) { + return email.substring(0, email.indexOf('@')) + '@*****'; +} + +export interface CICD { + image: string; + timestamp: number; + identicon: boolean; +} + +export type CICDCache = { [email: string]: CICD }; + +interface CICDRequestItem { + email: string; + repo: string; + remote: string | null; + commits: string[]; + checkAfter: number; + attempts: number; +} + +interface GitHubRemoteSource { + readonly type: 'github'; + readonly owner: string; + readonly repo: string; +} + +export interface CICDEvent { + email: string; + image: string; +} + +interface GitLabRemoteSource { + readonly type: 'gitlab'; +} + +interface GrcicdRemoteSource { + readonly type: 'grcicd'; +} + +type RemoteSource = GitHubRemoteSource | GitLabRemoteSource | GrcicdRemoteSource; diff --git a/src/commands.ts b/src/commands.ts index bc77347c..23529a2e 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -1,6 +1,7 @@ import * as os from 'os'; import * as vscode from 'vscode'; import { AvatarManager } from './avatarManager'; +import { CicdManager } from './cicdManager'; import { getConfig } from './config'; import { DataSource } from './dataSource'; import { DiffDocProvider, decodeDiffDocUri } from './diffDocProvider'; @@ -18,6 +19,7 @@ import { Event } from './utils/event'; export class CommandManager extends Disposable { private readonly context: vscode.ExtensionContext; private readonly avatarManager: AvatarManager; + private readonly cicdManager: CicdManager; private readonly dataSource: DataSource; private readonly extensionState: ExtensionState; private readonly logger: Logger; @@ -28,6 +30,7 @@ export class CommandManager extends Disposable { * Creates the Git Graph Command Manager. * @param extensionPath The absolute file path of the directory containing the extension. * @param avatarManger The Git Graph AvatarManager instance. + * @param cicdManager The Git Graph CicdManager instance. * @param dataSource The Git Graph DataSource instance. * @param extensionState The Git Graph ExtensionState instance. * @param repoManager The Git Graph RepoManager instance. @@ -35,10 +38,11 @@ export class CommandManager extends Disposable { * @param onDidChangeGitExecutable The Event emitting the Git executable for Git Graph to use. * @param logger The Git Graph Logger instance. */ - constructor(context: vscode.ExtensionContext, avatarManger: AvatarManager, dataSource: DataSource, extensionState: ExtensionState, repoManager: RepoManager, gitExecutable: GitExecutable | null, onDidChangeGitExecutable: Event, logger: Logger) { + constructor(context: vscode.ExtensionContext, avatarManger: AvatarManager, cicdManager: CicdManager, dataSource: DataSource, extensionState: ExtensionState, repoManager: RepoManager, gitExecutable: GitExecutable | null, onDidChangeGitExecutable: Event, logger: Logger) { super(); this.context = context; this.avatarManager = avatarManger; + this.cicdManager = cicdManager; this.dataSource = dataSource; this.extensionState = extensionState; this.logger = logger; @@ -50,6 +54,7 @@ export class CommandManager extends Disposable { this.registerCommand('git-graph.addGitRepository', () => this.addGitRepository()); this.registerCommand('git-graph.removeGitRepository', () => this.removeGitRepository()); this.registerCommand('git-graph.clearAvatarCache', () => this.clearAvatarCache()); + this.registerCommand('git-graph.clearCICDCache', () => this.clearCICDCache()); this.registerCommand('git-graph.fetch', () => this.fetch()); this.registerCommand('git-graph.endAllWorkspaceCodeReviews', () => this.endAllWorkspaceCodeReviews()); this.registerCommand('git-graph.endSpecificWorkspaceCodeReview', () => this.endSpecificWorkspaceCodeReview()); @@ -117,7 +122,7 @@ export class CommandManager extends Disposable { loadRepo = this.repoManager.getRepoContainingFile(getPathFromUri(vscode.window.activeTextEditor.document.uri)); } - GitGraphView.createOrShow(this.context.extensionPath, this.dataSource, this.extensionState, this.avatarManager, this.repoManager, this.logger, loadRepo !== null ? { repo: loadRepo } : null); + GitGraphView.createOrShow(this.context.extensionPath, this.dataSource, this.extensionState, this.avatarManager, this.cicdManager, this.repoManager, this.logger, loadRepo !== null ? { repo: loadRepo } : null); } /** @@ -183,6 +188,13 @@ export class CommandManager extends Disposable { this.avatarManager.clearCache(); } + /** + * The method run when the `git-graph.clearCICDCache` command is invoked. + */ + private clearCICDCache() { + this.cicdManager.clearCache(); + } + /** * The method run when the `git-graph.fetch` command is invoked. */ @@ -210,7 +222,7 @@ export class CommandManager extends Disposable { canPickMany: false }).then((item) => { if (item && item.description) { - GitGraphView.createOrShow(this.context.extensionPath, this.dataSource, this.extensionState, this.avatarManager, this.repoManager, this.logger, { + GitGraphView.createOrShow(this.context.extensionPath, this.dataSource, this.extensionState, this.avatarManager, this.cicdManager, this.repoManager, this.logger, { repo: item.description, runCommandOnLoad: 'fetch' }); @@ -219,12 +231,12 @@ export class CommandManager extends Disposable { showErrorMessage('An unexpected error occurred while running the command "Fetch from Remote(s)".'); }); } else if (repoPaths.length === 1) { - GitGraphView.createOrShow(this.context.extensionPath, this.dataSource, this.extensionState, this.avatarManager, this.repoManager, this.logger, { + GitGraphView.createOrShow(this.context.extensionPath, this.dataSource, this.extensionState, this.avatarManager, this.cicdManager, this.repoManager, this.logger, { repo: repoPaths[0], runCommandOnLoad: 'fetch' }); } else { - GitGraphView.createOrShow(this.context.extensionPath, this.dataSource, this.extensionState, this.avatarManager, this.repoManager, this.logger, null); + GitGraphView.createOrShow(this.context.extensionPath, this.dataSource, this.extensionState, this.avatarManager, this.cicdManager, this.repoManager, this.logger, null); } } @@ -280,7 +292,7 @@ export class CommandManager extends Disposable { }).then((item) => { if (item) { const commitHashes = item.codeReviewId.split('-'); - GitGraphView.createOrShow(this.context.extensionPath, this.dataSource, this.extensionState, this.avatarManager, this.repoManager, this.logger, { + GitGraphView.createOrShow(this.context.extensionPath, this.dataSource, this.extensionState, this.avatarManager, this.cicdManager, this.repoManager, this.logger, { repo: item.codeReviewRepo, commitDetails: { commitHash: commitHashes[commitHashes.length > 1 ? 1 : 0], diff --git a/src/config.ts b/src/config.ts index e44b5a04..f2ba9c00 100644 --- a/src/config.ts +++ b/src/config.ts @@ -356,6 +356,13 @@ class Config { return !!this.getRenamedExtensionSetting('repository.commits.fetchAvatars', 'fetchAvatars', false); } + /** + * Get the value of the `git-graph.repository.commits.fetchCICDs` Extension Setting. + */ + get fetchCICDs() { + return !!this.getRenamedExtensionSetting('repository.commits.fetchCICDs', 'fetchCICDs', false); + } + /** * Get the value of the `git-graph.repository.commits.initialLoad` Extension Setting. */ diff --git a/src/extension.ts b/src/extension.ts index 946b87cd..8e3780f2 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,5 +1,6 @@ import * as vscode from 'vscode'; import { AvatarManager } from './avatarManager'; +import { CicdManager } from './cicdManager'; import { CommandManager } from './commands'; import { getConfig } from './config'; import { DataSource } from './dataSource'; @@ -41,9 +42,10 @@ export async function activate(context: vscode.ExtensionContext) { const dataSource = new DataSource(gitExecutable, onDidChangeConfiguration, onDidChangeGitExecutable, logger); const avatarManager = new AvatarManager(dataSource, extensionState, logger); + const cicdManager = new CicdManager(dataSource, extensionState, logger); const repoManager = new RepoManager(dataSource, extensionState, onDidChangeConfiguration, logger); const statusBarItem = new StatusBarItem(repoManager.getNumRepos(), repoManager.onDidChangeRepos, onDidChangeConfiguration, logger); - const commandManager = new CommandManager(context, avatarManager, dataSource, extensionState, repoManager, gitExecutable, onDidChangeGitExecutable, logger); + const commandManager = new CommandManager(context, avatarManager, cicdManager, dataSource, extensionState, repoManager, gitExecutable, onDidChangeGitExecutable, logger); const diffDocProvider = new DiffDocProvider(dataSource); context.subscriptions.push( @@ -73,6 +75,7 @@ export async function activate(context: vscode.ExtensionContext) { statusBarItem, repoManager, avatarManager, + cicdManager, dataSource, configurationEmitter, extensionState, diff --git a/src/extensionState.ts b/src/extensionState.ts index f156adee..ed2a8b3f 100644 --- a/src/extensionState.ts +++ b/src/extensionState.ts @@ -1,6 +1,7 @@ import * as fs from 'fs'; import * as vscode from 'vscode'; import { Avatar, AvatarCache } from './avatarManager'; +import { CICD, CICDCache } from './cicdManager'; import { getConfig } from './config'; import { BooleanOverride, CodeReview, ErrorInfo, FileViewType, GitGraphViewGlobalState, GitGraphViewWorkspaceState, GitRepoSet, GitRepoState, RepoCommitOrdering } from './types'; import { GitExecutable, getPathFromStr } from './utils'; @@ -9,6 +10,7 @@ import { Event } from './utils/event'; const AVATAR_STORAGE_FOLDER = '/avatars'; const AVATAR_CACHE = 'avatarCache'; +const CICD_CACHE = 'cicdCache'; const CODE_REVIEWS = 'codeReviews'; const GLOBAL_VIEW_STATE = 'globalViewState'; const IGNORED_REPOS = 'ignoredRepos'; @@ -309,6 +311,51 @@ export class ExtensionState extends Disposable { } + /* CICDs */ + + /** + * Gets the cache of cicds known to Git Graph. + * @returns The cicd cache. + */ + public getCICDCache() { + return this.workspaceState.get(CICD_CACHE, {}); + } + + /** + * Add a new cicd to the cache of cicds known to Git Graph. + * @param email The email address that the cicd is for. + * @param cicd The details of the cicd. + */ + public saveCICD(email: string, cicd: CICD) { + let cicds = this.getCICDCache(); + cicds[email] = cicd; + this.updateWorkspaceState(CICD_CACHE, cicds); + } + + /** + * Removes an cicd from the cache of cicds known to Git Graph. + * @param email The email address of the cicd to remove. + */ + public removeCICDFromCache(email: string) { + let cicds = this.getCICDCache(); + delete cicds[email]; + this.updateWorkspaceState(CICD_CACHE, cicds); + } + + /** + * Clear all cicds from the cache of cicds known to Git Graph. + */ + public clearCICDCache() { + this.updateWorkspaceState(CICD_CACHE, {}); + // fs.readdir(this.globalStoragePath + CICD_STORAGE_FOLDER, (err, files) => { + // if (err) return; + // for (let i = 0; i < files.length; i++) { + // fs.unlink(this.globalStoragePath + CICD_STORAGE_FOLDER + '/' + files[i], () => { }); + // } + // }); + } + + /* Code Review */ // Note: id => the commit arguments to 'git diff' (either or -) diff --git a/src/gitGraphView.ts b/src/gitGraphView.ts index f2626c3c..446c25a7 100644 --- a/src/gitGraphView.ts +++ b/src/gitGraphView.ts @@ -1,6 +1,7 @@ import * as path from 'path'; import * as vscode from 'vscode'; import { AvatarManager } from './avatarManager'; +import { CicdManager } from './cicdManager'; import { getConfig } from './config'; import { DataSource, GIT_CONFIG, GitCommitDetailsData } from './dataSource'; import { ExtensionState } from './extensionState'; @@ -20,6 +21,7 @@ export class GitGraphView extends Disposable { private readonly panel: vscode.WebviewPanel; private readonly extensionPath: string; private readonly avatarManager: AvatarManager; + private readonly cicdManager: CicdManager; private readonly dataSource: DataSource; private readonly extensionState: ExtensionState; private readonly repoFileWatcher: RepoFileWatcher; @@ -39,11 +41,12 @@ export class GitGraphView extends Disposable { * @param dataSource The Git Graph DataSource instance. * @param extensionState The Git Graph ExtensionState instance. * @param avatarManger The Git Graph AvatarManager instance. + * @param cicdManager The Git Graph CicdManager instance. * @param repoManager The Git Graph RepoManager instance. * @param logger The Git Graph Logger instance. * @param loadViewTo What to load the view to. */ - public static createOrShow(extensionPath: string, dataSource: DataSource, extensionState: ExtensionState, avatarManager: AvatarManager, repoManager: RepoManager, logger: Logger, loadViewTo: LoadGitGraphViewTo) { + public static createOrShow(extensionPath: string, dataSource: DataSource, extensionState: ExtensionState, avatarManager: AvatarManager, cicdManager: CicdManager, repoManager: RepoManager, logger: Logger, loadViewTo: LoadGitGraphViewTo) { const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined; if (GitGraphView.currentPanel) { @@ -60,7 +63,7 @@ export class GitGraphView extends Disposable { GitGraphView.currentPanel.panel.reveal(column); } else { // If Git Graph panel doesn't already exist - GitGraphView.currentPanel = new GitGraphView(extensionPath, dataSource, extensionState, avatarManager, repoManager, logger, loadViewTo, column); + GitGraphView.currentPanel = new GitGraphView(extensionPath, dataSource, extensionState, avatarManager, cicdManager, repoManager, logger, loadViewTo, column); } } @@ -70,15 +73,17 @@ export class GitGraphView extends Disposable { * @param dataSource The Git Graph DataSource instance. * @param extensionState The Git Graph ExtensionState instance. * @param avatarManger The Git Graph AvatarManager instance. + * @param cicdManager The Git Graph CicdManager instance. * @param repoManager The Git Graph RepoManager instance. * @param logger The Git Graph Logger instance. * @param loadViewTo What to load the view to. * @param column The column the view should be loaded in. */ - private constructor(extensionPath: string, dataSource: DataSource, extensionState: ExtensionState, avatarManager: AvatarManager, repoManager: RepoManager, logger: Logger, loadViewTo: LoadGitGraphViewTo, column: vscode.ViewColumn | undefined) { + private constructor(extensionPath: string, dataSource: DataSource, extensionState: ExtensionState, avatarManager: AvatarManager, cicdManager: CicdManager, repoManager: RepoManager, logger: Logger, loadViewTo: LoadGitGraphViewTo, column: vscode.ViewColumn | undefined) { super(); this.extensionPath = extensionPath; this.avatarManager = avatarManager; + this.cicdManager = cicdManager; this.dataSource = dataSource; this.extensionState = extensionState; this.repoManager = repoManager; @@ -143,6 +148,15 @@ export class GitGraphView extends Disposable { }); }), + // Subscribe to events triggered when an cicd is available + cicdManager.onCICD((event) => { + this.sendMessage({ + command: 'fetchCICD', + email: event.email, + image: event.image + }); + }), + // Respond to messages sent from the Webview this.panel.webview.onDidReceiveMessage((msg) => this.respondToMessage(msg)), @@ -383,6 +397,9 @@ export class GitGraphView extends Disposable { case 'fetchAvatar': this.avatarManager.fetchAvatarImage(msg.email, msg.repo, msg.remote, msg.commits); break; + case 'fetchCICD': + this.cicdManager.fetchCICDImage(msg.email, msg.repo, msg.remote, msg.commits); + break; case 'fetchIntoLocalBranch': this.sendMessage({ command: 'fetchIntoLocalBranch', @@ -641,6 +658,7 @@ export class GitGraphView extends Disposable { fetchAndPrune: config.fetchAndPrune, fetchAndPruneTags: config.fetchAndPruneTags, fetchAvatars: config.fetchAvatars && this.extensionState.isAvatarStorageAvailable(), + fetchCICDs: config.fetchCICDs, graph: config.graph, includeCommitsMentionedByReflogs: config.includeCommitsMentionedByReflogs, initialLoadCommits: config.initialLoadCommits, diff --git a/src/types.ts b/src/types.ts index f6de04fd..46f42214 100644 --- a/src/types.ts +++ b/src/types.ts @@ -277,6 +277,7 @@ export interface GitGraphViewConfig { readonly fetchAndPrune: boolean; readonly fetchAndPruneTags: boolean; readonly fetchAvatars: boolean; + readonly fetchCICDs: boolean; readonly graph: GraphConfig; readonly includeCommitsMentionedByReflogs: boolean; readonly initialLoadCommits: number; @@ -902,6 +903,17 @@ export interface ResponseFetchAvatar extends BaseMessage { readonly email: string; readonly image: string; } +export interface RequestFetchCICD extends RepoRequest { + readonly command: 'fetchCICD'; + readonly remote: string | null; + readonly email: string; + readonly commits: string[]; +} +export interface ResponseFetchCICD extends BaseMessage { + readonly command: 'fetchCICD'; + readonly email: string; + readonly image: string; +} export interface RequestFetchIntoLocalBranch extends RepoRequest { readonly command: 'fetchIntoLocalBranch'; @@ -1271,6 +1283,7 @@ export type RequestMessage = | RequestExportRepoConfig | RequestFetch | RequestFetchAvatar + | RequestFetchCICD | RequestFetchIntoLocalBranch | RequestLoadCommits | RequestLoadConfig @@ -1332,6 +1345,7 @@ export type ResponseMessage = | ResponseExportRepoConfig | ResponseFetch | ResponseFetchAvatar + | ResponseFetchCICD | ResponseFetchIntoLocalBranch | ResponseLoadCommits | ResponseLoadConfig From ca4153e5d6d93208674315a144455b5be98d6e46 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Mon, 15 Mar 2021 00:27:40 +0900 Subject: [PATCH 12/54] #462 Updated cicdManager and reset DataSource. --- package.json | 15 +- src/cicdManager.ts | 483 ++++++++++++++++++--------------------- src/config.ts | 4 +- src/dataSource.ts | 198 +--------------- src/extension.ts | 2 +- src/extensionState.ts | 23 +- src/gitGraphView.ts | 7 +- src/types.ts | 30 +-- tests/commands.test.ts | 38 +-- tests/dataSource.test.ts | 375 ++++++++---------------------- web/main.ts | 34 ++- web/styles/main.css | 17 ++ 12 files changed, 428 insertions(+), 798 deletions(-) diff --git a/package.json b/package.json index ca74f692..2342af98 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ { "category": "Git Graph", "command": "git-graph.clearCICDCache", - "title": "Clear CICD Cache" + "title": "Clear CI/CD Status Cache" }, { "category": "Git Graph", @@ -942,8 +942,8 @@ }, "git-graph.repository.commits.fetchCICDs": { "type": "boolean", - "default": false, - "description": "Fetch cicds of commit authors and committers. By enabling this setting, you consent to commit author and committer email addresses being sent GitHub, GitLab or Gravatar, depending on the repositories remote origin." + "default": true, + "description": "Fetch CI/CD status of commit. By enabling this setting, you consent to Access Token being sent GitHub or GitLab, depending on the repositories and user configuration." }, "git-graph.repository.commits.initialLoad": { "type": "number", @@ -1248,8 +1248,8 @@ }, "git-graph.fetchCICDs": { "type": "boolean", - "default": false, - "description": "Fetch cicds of commit authors and committers. By enabling this setting, you consent to commit author and committer email addresses being sent GitHub, GitLab or Gravatar, depending on the repositories remote origin.", + "default": true, + "description": "Fetch CI/CD status of commit. By enabling this setting, you consent to Access Token being sent GitHub or GitLab, depending on the repositories and user configuration.", "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.fetchCICDs", "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.fetchCICDs#`" }, @@ -1493,14 +1493,11 @@ "test-and-report-coverage": "jest --verbose --coverage" }, "dependencies": { - "iconv-lite": "0.5.0", - "request": "2.88.2", - "request-promise": "4.2.6" + "iconv-lite": "0.5.0" }, "devDependencies": { "@types/jest": "26.0.19", "@types/node": "8.10.62", - "@types/request-promise": "4.1.46", "@types/vscode": "1.38.0", "@typescript-eslint/eslint-plugin": "4.10.0", "@typescript-eslint/parser": "4.10.0", diff --git a/src/cicdManager.ts b/src/cicdManager.ts index 93405f1d..6a8c8093 100644 --- a/src/cicdManager.ts +++ b/src/cicdManager.ts @@ -1,9 +1,9 @@ -import * as crypto from 'crypto'; +// import * as crypto from 'crypto'; import * as https from 'https'; -import * as url from 'url'; -import { DataSource } from './dataSource'; +// import * as url from 'url'; import { ExtensionState } from './extensionState'; import { Logger } from './logger'; +import { CICDConfig, CICDData, CICDProvider } from './types'; import { Disposable, toDisposable } from './utils/disposable'; import { EventEmitter } from './utils/event'; @@ -11,28 +11,25 @@ import { EventEmitter } from './utils/event'; * Manages fetching and caching CICDs. */ export class CicdManager extends Disposable { - private readonly dataSource: DataSource; private readonly extensionState: ExtensionState; private readonly logger: Logger; private readonly cicdEventEmitter: EventEmitter; private cicds: CICDCache; private queue: CicdRequestQueue; - private remoteSourceCache: { [repo: string]: RemoteSource } = {}; private interval: NodeJS.Timer | null = null; private githubTimeout: number = 0; private gitLabTimeout: number = 0; + private initialState: boolean = true; /** * Creates the Git Graph CICD Manager. - * @param dataSource The Git Graph DataSource instance. * @param extensionState The Git Graph ExtensionState instance. * @param logger The Git Graph Logger instance. */ - constructor(dataSource: DataSource, extensionState: ExtensionState, logger: Logger) { + constructor(extensionState: ExtensionState, logger: Logger) { super(); - this.dataSource = dataSource; this.extensionState = extensionState; this.logger = logger; this.cicdEventEmitter = new EventEmitter(); @@ -64,48 +61,48 @@ export class CicdManager extends Disposable { if (this.interval !== null) { clearInterval(this.interval); this.interval = null; - this.remoteSourceCache = {}; } } /** * Fetch an cicd, either from the cache if it already exists, or queue it to be fetched. - * @param email The email address identifying the cicd. - * @param repo The repository that the cicd is used in. - * @param remote The remote that the cicd can be fetched from. - * @param commits The commits that reference the cicd. + * @param hash The hash identifying the cicd. */ - public fetchCICDImage(email: string, repo: string, remote: string | null, commits: string[]) { - if (typeof this.cicds[email] !== 'undefined') { + public fetchCICDStatus(hash: string, cicdConfigs: CICDConfig[]) { + if (typeof this.cicds[hash] !== 'undefined') { // CICD exists in the cache - let t = (new Date()).getTime(); - if (this.cicds[email].timestamp < t - 1209600000 || (this.cicds[email].identicon && this.cicds[email].timestamp < t - 345600000)) { - // Refresh cicd after 14 days, or if an cicd couldn't previously be found after 4 days - this.queue.add(email, repo, remote, commits, false); - } - - this.emitCICD(email).catch(() => { + this.emitCICD(this.cicds[hash]).catch(() => { // CICD couldn't be found, request it again - this.removeCICDFromCache(email); - this.queue.add(email, repo, remote, commits, true); + this.removeCICDFromCache(hash); + cicdConfigs.forEach(cicdConfig => { + this.queue.add(cicdConfig, -1, true); + }); }); } else { // CICD not in the cache, request it - this.queue.add(email, repo, remote, commits, true); + if (this.initialState === true) { + this.initialState = false; + cicdConfigs.forEach(cicdConfig => { + this.queue.add(cicdConfig, -1, true); + }); + // Reset initial state for 10 seconds + setTimeout(() => { + this.logger.log('Reset initial timer of CICD'); + this.initialState = true; + }, 10000); + } } } /** - * Get the image data of an cicd. - * @param email The email address identifying the cicd. - * @returns A base64 encoded data URI if the cicd exists, otherwise NULL. + * Get the data of an cicd. + * @param hash The hash identifying the cicd. + * @returns A JSON encoded data of an cicd if the cicd exists, otherwise NULL. */ - public getCICDImage(email: string) { + public getCICDImage(hash: string) { return new Promise((resolve) => { - if (typeof this.cicds[email] !== 'undefined' && this.cicds[email].image !== null) { - // fs.readFile(this.cicdStorageFolder + '/' + this.cicds[email].image, (err, data) => { - // resolve(err ? null : 'data:image/' + this.cicds[email].image.split('.')[1] + ';base64,' + data.toString('base64')); - // }); + if (typeof this.cicds[hash] !== 'undefined' && this.cicds[hash] !== null) { + resolve(JSON.stringify(this.cicds[hash])); } else { resolve(null); } @@ -122,11 +119,11 @@ export class CicdManager extends Disposable { /** * Remove an cicd from the cache. - * @param email The email address identifying the cicd. + * @param hash The hash identifying the cicd. */ - private removeCICDFromCache(email: string) { - delete this.cicds[email]; - this.extensionState.removeCICDFromCache(email); + private removeCICDFromCache(hash: string) { + delete this.cicds[hash]; + this.extensionState.removeCICDFromCache(hash); } /** @@ -138,23 +135,23 @@ export class CicdManager extends Disposable { } /** - * Triggered by an interval to fetch cicds from Github, GitLab and Grcicd. + * Triggered by an interval to fetch cicds from GitHub and GitLab. */ private async fetchCICDsInterval() { if (this.queue.hasItems()) { + let cicdRequest = this.queue.takeItem(); if (cicdRequest === null) return; // No cicd can be checked at the current time - let remoteSource = await this.getRemoteSource(cicdRequest); // Fetch the remote source of the cicd - switch (remoteSource.type) { - case 'github': - this.fetchFromGithub(cicdRequest, remoteSource.owner, remoteSource.repo); + switch (cicdRequest.cicdConfig.provider) { + case CICDProvider.GitHubV3: + this.fetchFromGitHub(cicdRequest); break; - case 'gitlab': + case CICDProvider.GitLabV4: this.fetchFromGitLab(cicdRequest); break; default: - this.fetchFromGrcicd(cicdRequest); + break; } } else { // Stop the interval if there are no items remaining in the queue @@ -163,57 +160,44 @@ export class CicdManager extends Disposable { } /** - * Get the remote source of an cicd request. - * @param cicdRequest The cicd request. - * @returns The remote source. - */ - private async getRemoteSource(cicdRequest: CICDRequestItem) { - if (typeof this.remoteSourceCache[cicdRequest.repo] === 'object') { - // If the repo exists in the cache of remote sources - return this.remoteSourceCache[cicdRequest.repo]; - } else { - // Fetch the remote repo source - let remoteSource: RemoteSource = { type: 'grcicd' }; - if (cicdRequest.remote !== null) { - let remoteUrl = await this.dataSource.getRemoteUrl(cicdRequest.repo, cicdRequest.remote); - if (remoteUrl !== null) { - // Depending on the domain of the remote repo source, determine the type of source it is - let match; - if ((match = remoteUrl.match(/^(https:\/\/github\.com\/|git@github\.com:)([^\/]+)\/(.*)\.git$/)) !== null) { - remoteSource = { type: 'github', owner: match[2], repo: match[3] }; - } else if (remoteUrl.startsWith('https://gitlab.com/') || remoteUrl.startsWith('git@gitlab.com:')) { - remoteSource = { type: 'gitlab' }; - } - } - } - this.remoteSourceCache[cicdRequest.repo] = remoteSource; // Add the remote source to the cache for future use - return remoteSource; - } - } - - /** - * Fetch an cicd from Github. + * Fetch an cicd from GitHub. * @param cicdRequest The cicd request to fetch. - * @param owner The owner of the repository. - * @param repo The repository that the cicd is used in. */ - private fetchFromGithub(cicdRequest: CICDRequestItem, owner: string, repo: string) { + private fetchFromGitHub(cicdRequest: CICDRequestItem) { let t = (new Date()).getTime(); - if (t < this.githubTimeout) { + if (cicdRequest.checkAfter !== 0 && t < this.githubTimeout) { // Defer request until after timeout this.queue.addItem(cicdRequest, this.githubTimeout, false); this.fetchCICDsInterval(); return; } - this.logger.log('Requesting CICD for ' + maskEmail(cicdRequest.email) + ' from GitHub'); + let cicdConfig = cicdRequest.cicdConfig; + this.logger.log('Requesting CICD for ' + cicdConfig.gitUrl + ' page=' + cicdRequest.page + ' from GitHub'); - const commitIndex = cicdRequest.commits.length < 5 - ? cicdRequest.commits.length - 1 - cicdRequest.attempts - : Math.round((4 - cicdRequest.attempts) * 0.25 * (cicdRequest.commits.length - 1)); + const match1 = cicdConfig.gitUrl.match(/^(https?:\/\/|git@)((?=[^/]+@)[^@]+@|(?![^/]+@))([^/:]+)/); + let hostRootUrl = match1 !== null ? 'api.' + match1[3] : ''; + + const match2 = cicdConfig.gitUrl.match(/^(https?:\/\/|git@)[^/:]+[/:]([^/]+)\/([^/]*?)(.git|)$/); + let sourceOwner = match2 !== null ? match2[2] : ''; + let sourceRepo = match2 !== null ? match2[3] : ''; + + let cicdRootPath = `/repos/${sourceOwner}/${sourceRepo.replace(/\//g, '%2F')}/actions/runs?per_page=100`; + if (cicdRequest.page > 1) { + cicdRootPath = `${cicdRootPath}&page=${cicdRequest.page}`; + } + + let headers: any = { + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'vscode-git-graph' + }; + if (cicdConfig.glToken !== '') { + headers['Authorization'] = `token ${cicdConfig.glToken}`; + } let triggeredOnError = false; - const onError = () => { + const onError = (err: Error) => { + this.logger.log('GitHub API HTTPS Error - ' + err.message); if (!triggeredOnError) { // If an error occurs, try again after 5 minutes triggeredOnError = true; @@ -223,8 +207,8 @@ export class CicdManager extends Disposable { }; https.get({ - hostname: 'api.github.com', path: '/repos/' + owner + '/' + repo + '/commits/' + cicdRequest.commits[commitIndex], - headers: { 'User-Agent': 'vscode-git-graph' }, + hostname: hostRootUrl, path: cicdRootPath, + headers: headers, agent: false, timeout: 15000 }, (res) => { let respBody = ''; @@ -237,21 +221,77 @@ export class CicdManager extends Disposable { } if (res.statusCode === 200) { // Success - let commit: any = JSON.parse(respBody); - if (commit.author && commit.author.cicd_url) { // CICD url found - let img = await this.downloadCICDImage(cicdRequest.email, commit.author.cicd_url + '&size=162'); - if (img !== null) { - this.saveCICD(cicdRequest.email, img, false); - } else { - this.logger.log('Failed to download cicd from GitHub for ' + maskEmail(cicdRequest.email)); + try { + let respJson: any = JSON.parse(respBody); + if (typeof respJson['workflow_runs'] !== 'undefined' && respJson['workflow_runs'].length >= 1) { // url found + let ret: CICDData[] = respJson['workflow_runs'].map((elm: { [x: string]: any; }) => { + return { + id: elm['id'], + status: elm['conclusion'], + ref: elm['name'], + sha: elm['head_sha'], + web_url: elm['html_url'], + created_at: elm['created_at'], + updated_at: elm['updated_at'] + }; + }); + ret.forEach(element => { + this.saveCICD(element); + }); + + if (cicdRequest.page === -1) { + let last = 1; + if (typeof res.headers['link'] === 'string') { + const DELIM_LINKS = ','; + const DELIM_LINK_PARAM = ';'; + let links = res.headers['link'].split(DELIM_LINKS); + links.forEach(link => { + let segments = link.split(DELIM_LINK_PARAM); + + let linkPart = segments[0].trim(); + if (!linkPart.startsWith('<') || !linkPart.endsWith('>')) { + return true; + } + linkPart = linkPart.substring(1, linkPart.length - 1); + let match3 = linkPart.match(/&page=(\d+).*$/); + let linkPage = match3 !== null ? match3[1] : '0'; + + for (let i = 1; i < segments.length; i++) { + let rel = segments[i].trim().split('='); + if (rel.length < 2) { + continue; + } + + let relValue = rel[1]; + if (relValue.startsWith('"') && relValue.endsWith('"')) { + relValue = relValue.substring(1, relValue.length - 1); + } + + if (relValue === 'last') { + last = parseInt(linkPage); + } + } + }); + } + + for (var i = 1; i < last; i++) { + // let cicdRequestNew = Object.assign({}, cicdRequest);; + // cicdRequestNew.page = i + 1; + // this.queue.addItem(cicdRequestNew, 0, false); + this.queue.add(cicdRequest.cicdConfig, i + 1, true); + } + } + return; } - return; + } catch (e) { + this.logger.log('GitHub API Error - (' + res.statusCode + ')API Result error.'); } + return; } else if (res.statusCode === 403) { // Rate limit reached, try again after timeout this.queue.addItem(cicdRequest, this.githubTimeout, false); return; - } else if (res.statusCode === 422 && cicdRequest.commits.length > cicdRequest.attempts + 1 && cicdRequest.attempts < 4) { + } else if (res.statusCode === 422 && cicdRequest.attempts < 4) { // Commit not found on remote, try again with the next commit if less than 5 attempts have been made this.queue.addItem(cicdRequest, 0, true); return; @@ -260,8 +300,15 @@ export class CicdManager extends Disposable { this.githubTimeout = t + 600000; this.queue.addItem(cicdRequest, this.githubTimeout, false); return; + } else { + // API Error + try { + let respJson: any = JSON.parse(respBody); + this.logger.log('GitHub API Error - (' + res.statusCode + ')' + respJson.message); + } catch (e) { + this.logger.log('GitHub API Error - (' + res.statusCode + ')' + res.statusMessage); + } } - this.fetchFromGrcicd(cicdRequest); // Fallback to Grcicd }); res.on('error', onError); }).on('error', onError); @@ -273,17 +320,35 @@ export class CicdManager extends Disposable { */ private fetchFromGitLab(cicdRequest: CICDRequestItem) { let t = (new Date()).getTime(); - if (t < this.gitLabTimeout) { + if (cicdRequest.checkAfter !== 0 && t < this.gitLabTimeout) { // Defer request until after timeout this.queue.addItem(cicdRequest, this.gitLabTimeout, false); this.fetchCICDsInterval(); return; } - this.logger.log('Requesting CICD for ' + maskEmail(cicdRequest.email) + ' from GitLab'); + let cicdConfig = cicdRequest.cicdConfig; + this.logger.log('Requesting CICD for ' + cicdConfig.gitUrl + ' page=' + cicdRequest.page + ' from GitLab'); + + const match1 = cicdConfig.gitUrl.match(/^(https?:\/\/|git@)((?=[^/]+@)[^@]+@|(?![^/]+@))([^/:]+)/); + let hostRootUrl = match1 !== null ? '' + match1[3] : ''; + + const match2 = cicdConfig.gitUrl.match(/^(https?:\/\/|git@)[^/:]+[/:]([^/]+)\/([^/]*?)(.git|)$/); + let sourceOwner = match2 !== null ? match2[2] : ''; + let sourceRepo = match2 !== null ? match2[3] : ''; + + const cicdRootPath = `/api/v4/projects/${sourceOwner}%2F${sourceRepo.replace(/\//g, '%2F')}/pipelines?per_page=100`; + + let headers: any = { + 'User-Agent': 'vscode-git-graph' + }; + if (cicdConfig.glToken !== '') { + headers['PRIVATE-TOKEN'] = cicdConfig.glToken; + } let triggeredOnError = false; - const onError = () => { + const onError = (err: Error) => { + this.logger.log('GitLab API HTTPS Error - ' + err.message); if (!triggeredOnError) { // If an error occurs, try again after 5 minutes triggeredOnError = true; @@ -293,8 +358,8 @@ export class CicdManager extends Disposable { }; https.get({ - hostname: 'gitlab.com', path: '/api/v4/users?search=' + cicdRequest.email, - headers: { 'User-Agent': 'vscode-git-graph', 'Private-Token': 'w87U_3gAxWWaPtFgCcus' }, // Token only has read access + hostname: hostRootUrl, path: cicdRootPath, + headers: headers, agent: false, timeout: 15000 }, (res) => { let respBody = ''; @@ -307,15 +372,29 @@ export class CicdManager extends Disposable { } if (res.statusCode === 200) { // Success - let users: any = JSON.parse(respBody); - if (users.length > 0 && users[0].cicd_url) { // CICD url found - let img = await this.downloadCICDImage(cicdRequest.email, users[0].cicd_url); - if (img !== null) { - this.saveCICD(cicdRequest.email, img, false); - } else { - this.logger.log('Failed to download cicd from GitLab for ' + maskEmail(cicdRequest.email)); + try { + if (typeof res.headers['x-page'] === 'string' && typeof res.headers['x-total-pages'] === 'string' && typeof res.headers['x-total'] === 'string') { + let respJson: any = JSON.parse(respBody); + if (parseInt(res.headers['x-total']) !== 0 && respJson.length && respJson[0].id) { // url found + let ret: CICDData[] = respJson; + ret.forEach(element => { + this.saveCICD(element); + }); + + let last = parseInt(res.headers['x-total-pages']); + if (cicdRequest.page === -1) { + for (var i = 1; i < last; i++) { + // let cicdRequestNew = Object.assign({}, cicdRequest);; + // cicdRequestNew.page = i + 1; + // this.queue.addItem(cicdRequestNew, 0, false); + this.queue.add(cicdRequest.cicdConfig, i + 1, true); + } + } + return; + } } - return; + } catch (e) { + this.logger.log('GitLab API Error - (' + res.statusCode + ')API Result error.'); } } else if (res.statusCode === 429) { // Rate limit reached, try again after timeout @@ -326,94 +405,32 @@ export class CicdManager extends Disposable { this.gitLabTimeout = t + 600000; this.queue.addItem(cicdRequest, this.gitLabTimeout, false); return; + } else { + // API Error + try { + let respJson: any = JSON.parse(respBody); + this.logger.log('GitLab API Error - (' + res.statusCode + ')' + respJson.message); + } catch (e) { + this.logger.log('GitLab API Error - (' + res.statusCode + ')' + res.statusMessage); + } } - this.fetchFromGrcicd(cicdRequest); // Fallback to Grcicd }); res.on('error', onError); }).on('error', onError); } - /** - * Fetch an cicd from Grcicd. - * @param cicdRequest The cicd request to fetch. - */ - private async fetchFromGrcicd(cicdRequest: CICDRequestItem) { - this.logger.log('Requesting CICD for ' + maskEmail(cicdRequest.email) + ' from Grcicd'); - const hash: string = crypto.createHash('md5').update(cicdRequest.email.trim().toLowerCase()).digest('hex'); - - let img = await this.downloadCICDImage(cicdRequest.email, 'https://secure.grcicd.com/cicd/' + hash + '?s=162&d=404'), identicon = false; - if (img === null) { - img = await this.downloadCICDImage(cicdRequest.email, 'https://secure.grcicd.com/cicd/' + hash + '?s=162&d=identicon'); - identicon = true; - } - - if (img !== null) { - this.saveCICD(cicdRequest.email, img, identicon); - } else { - this.logger.log('No CICD could be found for ' + maskEmail(cicdRequest.email)); - } - } - - /** - * Download and save an cicd image. - * @param _email The email address identifying the cicd. - * @param imageUrl The URL the cicd can be downloaded from. - * @returns A promise that resolves to the image name of the cicd on disk, or NULL if downloading failed. - */ - private downloadCICDImage(_email: string, imageUrl: string) { - return (new Promise((resolve) => { - // const hash = crypto.createHash('md5').update(email).digest('hex'); - const imgUrl = url.parse(imageUrl); - - let completed = false; - const complete = (fileName: string | null = null) => { - if (!completed) { - completed = true; - resolve(fileName); - } - }; - - https.get({ - hostname: imgUrl.hostname, path: imgUrl.path, - headers: { 'User-Agent': 'vscode-git-graph' }, - agent: false, timeout: 15000 - }, (res) => { - let imageBufferArray: Buffer[] = []; - res.on('data', (chunk: Buffer) => { imageBufferArray.push(chunk); }); - res.on('end', () => { - if (res.statusCode === 200) { // If success response, save the image to the cicd folder - // let format = res.headers['content-type']!.split('/')[1]; - // fs.writeFile(this.cicdStorageFolder + '/' + hash + '.' + format, Buffer.concat(imageBufferArray), err => { - // complete(err ? null : hash + '.' + format); - // }); - } else { - complete(); - } - }); - res.on('error', complete); - }).on('error', complete); - })).catch(() => null); - } - /** * Emit an CICDEvent to any listeners. - * @param email The email address identifying the cicd. + * @param cicdData The CICDData. * @returns A promise indicating if the event was emitted successfully. */ - private emitCICD(email: string) { - return new Promise((resolve, reject) => { + private emitCICD(cicdData: CICDData) { + return new Promise((resolve, _reject) => { if (this.cicdEventEmitter.hasSubscribers()) { - this.getCICDImage(email).then((image) => { - if (image === null) { - reject(); - } else { - this.cicdEventEmitter.emit({ - email: email, - image: image - }); - resolve(true); - } + this.cicdEventEmitter.emit({ + cicdData: cicdData }); + resolve(true); } else { resolve(false); } @@ -422,28 +439,19 @@ export class CicdManager extends Disposable { /** * Save an cicd in the cache. - * @param email The email address identifying the cicd. - * @param image The image name of the cicd on disk. - * @param identicon Whether this cicd is an identicon. + * @param cicdData The CICDData. */ - private saveCICD(email: string, image: string, identicon: boolean) { - if (typeof this.cicds[email] !== 'undefined') { - if (!identicon || this.cicds[email].identicon) { - this.cicds[email].image = image; - this.cicds[email].identicon = identicon; - } - this.cicds[email].timestamp = (new Date()).getTime(); - } else { - this.cicds[email] = { image: image, timestamp: (new Date()).getTime(), identicon: identicon }; - } - this.extensionState.saveCICD(email, this.cicds[email]); - this.logger.log('Saved CICD for ' + maskEmail(email)); - this.emitCICD(email).then( - (sent) => this.logger.log(sent - ? 'Sent CICD for ' + maskEmail(email) + ' to the Git Graph View' - : 'CICD for ' + maskEmail(email) + ' is ready to be used the next time the Git Graph View is opened' - ), - () => this.logger.log('Failed to Send CICD for ' + maskEmail(email) + ' to the Git Graph View') + private saveCICD(cicdData: CICDData) { + this.cicds[cicdData.sha] = cicdData; + this.extensionState.saveCICD(this.cicds[cicdData.sha]); + // this.logger.log('Saved CICD for ' + cicdData.sha); + this.emitCICD(this.cicds[cicdData.sha]).then( + // (sent) => this.logger.log(sent + // ? 'Sent CICD for ' + cicdData.sha + ' to the Git Graph View' + // : 'CICD for ' + cicdData.sha + ' is ready to be used the next time the Git Graph View is opened' + // ), + () => { }, + () => this.logger.log('Failed to Send CICD for ' + cicdData.sha + ' to the Git Graph View') ); } } @@ -465,26 +473,17 @@ class CicdRequestQueue { /** * Create and add a new cicd request to the queue. - * @param email The email address identifying the cicd. - * @param repo The repository that the cicd is used in. - * @param remote The remote that the cicd can be fetched from. - * @param commits The commits that reference the cicd. - * @param immediate Whether the cicd should be fetched immediately. + * @param cicdConfig The CICDConfig. + * @param page The page of cicd request. + * @param immediate Whether the avatar should be fetched immediately. */ - public add(email: string, repo: string, remote: string | null, commits: string[], immediate: boolean) { - const existingRequest = this.queue.find((request) => request.email === email && request.repo === repo); + public add(cicdConfig: CICDConfig, page: number, immediate: boolean) { + const existingRequest = this.queue.find((request) => request.cicdConfig.gitUrl === cicdConfig.gitUrl && request.page === page); if (existingRequest) { - commits.forEach((commit) => { - if (!existingRequest.commits.includes(commit)) { - existingRequest.commits.push(commit); - } - }); } else { this.insertItem({ - email: email, - repo: repo, - remote: remote, - commits: commits, + cicdConfig: cicdConfig, + page: page, checkAfter: immediate || this.queue.length === 0 ? 0 : this.queue[this.queue.length - 1].checkAfter + 1, @@ -541,49 +540,19 @@ class CicdRequestQueue { } } -/** - * Mask an email address for logging. - * @param email The string containing the email address. - * @returns The masked email address. - */ -function maskEmail(email: string) { - return email.substring(0, email.indexOf('@')) + '@*****'; -} -export interface CICD { - image: string; - timestamp: number; - identicon: boolean; -} +export type CICDCache = { [hash: string]: CICDData }; -export type CICDCache = { [email: string]: CICD }; +// Request item to CicdRequestQueue interface CICDRequestItem { - email: string; - repo: string; - remote: string | null; - commits: string[]; + cicdConfig: CICDConfig; + page: number; checkAfter: number; attempts: number; } -interface GitHubRemoteSource { - readonly type: 'github'; - readonly owner: string; - readonly repo: string; -} - +// Event to GitGraphView export interface CICDEvent { - email: string; - image: string; -} - -interface GitLabRemoteSource { - readonly type: 'gitlab'; -} - -interface GrcicdRemoteSource { - readonly type: 'grcicd'; + cicdData: CICDData; } - -type RemoteSource = GitHubRemoteSource | GitLabRemoteSource | GrcicdRemoteSource; diff --git a/src/config.ts b/src/config.ts index f2ba9c00..f6c34d8c 100644 --- a/src/config.ts +++ b/src/config.ts @@ -161,7 +161,7 @@ class Config { get defaultColumnVisibility(): DefaultColumnVisibility { let obj: any = this.config.get('defaultColumnVisibility', {}); if (typeof obj === 'object' && obj !== null && typeof obj['Date'] === 'boolean' && typeof obj['Author'] === 'boolean' && typeof obj['Commit'] === 'boolean' && typeof obj['CICD'] === 'boolean') { - return { author: obj['Author'], commit: obj['Commit'], date: obj['Date'], cicd: obj['CICD']}; + return { author: obj['Author'], commit: obj['Commit'], date: obj['Date'], cicd: obj['CICD'] }; } else { return { author: true, commit: true, date: true, cicd: true }; } @@ -360,7 +360,7 @@ class Config { * Get the value of the `git-graph.repository.commits.fetchCICDs` Extension Setting. */ get fetchCICDs() { - return !!this.getRenamedExtensionSetting('repository.commits.fetchCICDs', 'fetchCICDs', false); + return !!this.getRenamedExtensionSetting('repository.commits.fetchCICDs', 'fetchCICDs', true); } /** diff --git a/src/dataSource.ts b/src/dataSource.ts index 2279283c..d1a0be67 100644 --- a/src/dataSource.ts +++ b/src/dataSource.ts @@ -6,11 +6,10 @@ import * as vscode from 'vscode'; import { AskpassEnvironment, AskpassManager } from './askpass/askpassManager'; import { getConfig } from './config'; import { Logger } from './logger'; -import { CICDConfig, CICDProvider, CommitOrdering, DateType, DeepWriteable, ErrorInfo, GitCICDData, GitCommit, GitCommitDetails, GitCommitStash, GitConfigLocation, GitFileChange, GitFileStatus, GitPushBranchMode, GitRepoConfig, GitRepoConfigBranches, GitResetMode, GitSignatureStatus, GitStash, MergeActionOn, RebaseActionOn, SquashMessageFormat, TagType, Writeable } from './types'; +import { CommitOrdering, DateType, DeepWriteable, ErrorInfo, GitCommit, GitCommitDetails, GitCommitStash, GitConfigLocation, GitFileChange, GitFileStatus, GitPushBranchMode, GitRepoConfig, GitRepoConfigBranches, GitResetMode, GitSignatureStatus, GitStash, MergeActionOn, RebaseActionOn, SquashMessageFormat, TagType, Writeable } from './types'; import { GitExecutable, UNABLE_TO_FIND_GIT_MSG, UNCOMMITTED, abbrevCommit, constructIncompatibleGitVersionMessage, doesVersionMeetRequirement, getPathFromStr, getPathFromUri, openGitTerminal, pathWithTrailingSlash, realpath, resolveSpawnOutput, showErrorMessage } from './utils'; import { Disposable } from './utils/disposable'; import { Event } from './utils/event'; -import * as request from 'request-promise'; const DRIVE_LETTER_PATH_REGEX = /^[a-z]:\//; const EOL_REGEX = /\r\n|\r|\n/g; @@ -159,15 +158,13 @@ export class DataSource extends Disposable { * @param stashes An array of all stashes in the repository. * @returns The commits in the repository. */ - public getCommits(repo: string, branches: ReadonlyArray | null, maxCommits: number, showTags: boolean, showRemoteBranches: boolean, includeCommitsMentionedByReflogs: boolean, onlyFollowFirstParent: boolean, commitOrdering: CommitOrdering, remotes: ReadonlyArray, hideRemotes: ReadonlyArray, stashes: ReadonlyArray, cicdConfigs: CICDConfig[] | null): Promise { + public getCommits(repo: string, branches: ReadonlyArray | null, maxCommits: number, showTags: boolean, showRemoteBranches: boolean, includeCommitsMentionedByReflogs: boolean, onlyFollowFirstParent: boolean, commitOrdering: CommitOrdering, remotes: ReadonlyArray, hideRemotes: ReadonlyArray, stashes: ReadonlyArray): Promise { const config = getConfig(); return Promise.all([ this.getLog(repo, branches, maxCommits + 1, showTags && config.showCommitsOnlyReferencedByTags, showRemoteBranches, includeCommitsMentionedByReflogs, onlyFollowFirstParent, commitOrdering, remotes, hideRemotes, stashes), - this.getRefs(repo, showRemoteBranches, config.showRemoteHeads, hideRemotes).then((refData: GitRefData) => refData, (errorMessage: string) => errorMessage), - this.getCICDs(cicdConfigs).then((refData: GitCICDData[] | string | undefined) => refData, (errorMessage: string) => errorMessage) + this.getRefs(repo, showRemoteBranches, config.showRemoteHeads, hideRemotes).then((refData: GitRefData) => refData, (errorMessage: string) => errorMessage) ]).then(async (results) => { let commits: GitCommitRecord[] = results[0], refData: GitRefData | string = results[1], i; - let cicds: GitCICDData[] | string | undefined = results[2]; let moreCommitsAvailable = commits.length === maxCommits + 1; if (moreCommitsAvailable) commits.pop(); @@ -200,7 +197,7 @@ export class DataSource extends Disposable { for (i = 0; i < commits.length; i++) { commitLookup[commits[i].hash] = i; - commitNodes.push({ ...commits[i], heads: [], tags: [], remotes: [], stash: null, cicd: null }); + commitNodes.push({ ...commits[i], heads: [], tags: [], remotes: [], stash: null }); } /* Insert Stashes */ @@ -220,7 +217,6 @@ export class DataSource extends Disposable { for (i = toAdd.length - 1; i >= 0; i--) { let stash = toAdd[i].data; commitNodes.splice(toAdd[i].index, 0, { - cicd: null, hash: stash.hash, parents: [stash.baseHash], author: stash.author, @@ -261,16 +257,6 @@ export class DataSource extends Disposable { } } - if (typeof cicds === 'string' || typeof cicds === 'undefined') { - cicds = []; - } - /* Annotate CI/CDs */ - for (i = 0; i < cicds.length; i++) { - if (typeof commitLookup[cicds[i].sha] === 'number') { - commitNodes[commitLookup[cicds[i].sha]].cicd = cicds[i]; - } - } - return { commits: commitNodes, head: refData.head, @@ -1510,182 +1496,6 @@ export class DataSource extends Disposable { }); } - /** - * Get the result in a CI/CDs. - * @param cicdConfigs CI/CD configuration. - * @returns The references data. - */ - private async getCICDs(cicdConfigs: CICDConfig[] | null) { - if (cicdConfigs === null) { - return ''; - } - - return await Promise.all( - cicdConfigs.map(async cicdConfig => { - if (cicdConfig.provider === CICDProvider.GitHubV3) { - - const match1 = cicdConfig.gitUrl.match(/^(https?:\/\/|git@)((?=[^/]+@)[^@]+@|(?![^/]+@))([^/:]+)/); - let hostRootUrl = match1 !== null ? 'https://api.' + match1[3] : ''; - - const match2 = cicdConfig.gitUrl.match(/^(https?:\/\/|git@)[^/:]+[/:]([^/]+)\/([^/]*?)(.git|)$/); - let sourceOwner = match2 !== null ? match2[2] : ''; - let sourceRepo = match2 !== null ? match2[3] : ''; - - const apiRoot = `${hostRootUrl}`; - const cicdRootPath = `/repos/${sourceOwner}/${sourceRepo.replace(/\//g, '%2F')}/actions/runs?per_page=100`; - - let config: request.RequestPromiseOptions = { - method: 'GET', - headers: { - 'Authorization': `token ${cicdConfig.glToken}`, - 'Accept': 'application/vnd.github.v3+json', - 'User-Agent': 'vscode-git-graph' - } - }; - if (cicdConfig.glToken === '' && config.headers) { - delete config.headers['Authorization']; - } - - config.transform = (body, response) => { - try { - let res: any = JSON.parse(body); - let last = 1; - let next = 2; - if (typeof response.headers['link'] === 'string') { - next = 3; - const DELIM_LINKS = ','; - const DELIM_LINK_PARAM = ';'; - let links = response.headers['link'].split(DELIM_LINKS); - links.forEach(link => { - let segments = link.split(DELIM_LINK_PARAM); - - let linkPart = segments[0].trim(); - if (!linkPart.startsWith('<') || !linkPart.endsWith('>')) { - return true; - } - linkPart = linkPart.substring(1, linkPart.length - 1); - let match3 = linkPart.match(/&page=(\d+).*$/); - let linkPage = match3 !== null ? match3[1] : '0'; - - for (let i = 1; i < segments.length; i++) { - let rel = segments[i].trim().split('='); - if (rel.length < 2) { - continue; - } - - let relValue = rel[1]; - if (relValue.startsWith('"') && relValue.endsWith('"')) { - relValue = relValue.substring(1, relValue.length - 1); - } - - if (relValue === 'last') { - last = parseInt(linkPage); - } else if (relValue === 'next') { - next = parseInt(linkPage); - } - } - }); - } - if (typeof res['workflow_runs'] !== 'undefined' && res['workflow_runs'].length >= 1) { // url found - let ret: GitCICDData[] = res['workflow_runs'].map( (elm: { [x: string]: any; }) => { - return { - id: elm['id'], - status: elm['conclusion'], - ref: elm['name'], - sha: elm['head_sha'], - web_url: elm['html_url'], - created_at: elm['created_at'], - updated_at: elm['updated_at'] - }; - }); - if (next === 2) { - return { x_total_pages: last, ret: ret }; - } - return ret; - } - return { x_total_pages: 0, ret: 'error' }; - } catch (e) { - return { x_total_pages: 0, ret: e }; - } - }; - return request(`${apiRoot}${cicdRootPath}`, config).then(async (result1st) => { - let promises = []; - promises.push(result1st.ret); - for (let i = 1; i < result1st.x_total_pages; i++) { - promises.push(request(`${apiRoot}${cicdRootPath}&page=${i + 1}`, config)); - } - return await Promise.all(promises); - }).then((resultAll) => { - let retAll: GitCICDData[] = []; - for (let i = 0; i < resultAll.length; i++) { - retAll = retAll.concat(resultAll[i]); - } - return retAll; - }); - } - if (cicdConfig.provider === CICDProvider.GitLabV4) { - - const match1 = cicdConfig.gitUrl.match(/^(https?:\/\/|git@)((?=[^/]+@)[^@]+@|(?![^/]+@))([^/:]+)/); - let hostRootUrl = match1 !== null ? 'https://' + match1[3] : ''; - - const match2 = cicdConfig.gitUrl.match(/^(https?:\/\/|git@)[^/:]+[/:]([^/]+)\/([^/]*?)(.git|)$/); - let sourceOwner = match2 !== null ? match2[2] : ''; - let sourceRepo = match2 !== null ? match2[3] : ''; - - const apiRoot = `${hostRootUrl}/api/v4`; - const cicdRootPath = `/projects/${sourceOwner}%2F${sourceRepo.replace(/\//g, '%2F')}/pipelines?per_page=100`; - - const config: request.RequestPromiseOptions = { - method: 'GET', - headers: { - 'PRIVATE-TOKEN': cicdConfig.glToken, - 'User-Agent': 'vscode-git-graph' - } - }; - - config.transform = (body, response) => { - try { - if (typeof response.headers['x-page'] === 'string' && typeof response.headers['x-total-pages'] === 'string' && typeof response.headers['x-total'] === 'string') { - let res: any = JSON.parse(body); - if (parseInt(response.headers['x-total']) !== 0 && res.length && res[0].id) { // url found - let ret: GitCICDData[] = res; - if (parseInt(response.headers['x-page']) === 1) { - return { x_total_pages: parseInt(response.headers['x-total-pages']), ret: ret }; - } - return ret; - } - } - return { x_total_pages: 0, ret: 'error' }; - } catch (e) { - return { x_total_pages: 0, ret: e }; - } - }; - return request(`${apiRoot}${cicdRootPath}`, config).then(async (result1st) => { - let promises = []; - promises.push(result1st.ret); - for (let i = 1; i < result1st.x_total_pages; i++) { - promises.push(request(`${apiRoot}${cicdRootPath}&page=${i + 1}`, config)); - } - return await Promise.all(promises); - }).then((resultAll) => { - let retAll: GitCICDData[] = []; - for (let i = 0; i < resultAll.length; i++) { - retAll = retAll.concat(resultAll[i]); - } - return retAll; - }); - } - }) - ).then((resultAll2) => { - let retAll: GitCICDData[] = []; - resultAll2.forEach(resultList => { - resultList?.forEach(result => { - retAll = retAll.concat(result); - }); - }); - return retAll; - }); - } /** * Get the references in a repository. * @param repo The path of the repository. diff --git a/src/extension.ts b/src/extension.ts index 8e3780f2..be581cbb 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -42,7 +42,7 @@ export async function activate(context: vscode.ExtensionContext) { const dataSource = new DataSource(gitExecutable, onDidChangeConfiguration, onDidChangeGitExecutable, logger); const avatarManager = new AvatarManager(dataSource, extensionState, logger); - const cicdManager = new CicdManager(dataSource, extensionState, logger); + const cicdManager = new CicdManager(extensionState, logger); const repoManager = new RepoManager(dataSource, extensionState, onDidChangeConfiguration, logger); const statusBarItem = new StatusBarItem(repoManager.getNumRepos(), repoManager.onDidChangeRepos, onDidChangeConfiguration, logger); const commandManager = new CommandManager(context, avatarManager, cicdManager, dataSource, extensionState, repoManager, gitExecutable, onDidChangeGitExecutable, logger); diff --git a/src/extensionState.ts b/src/extensionState.ts index ed2a8b3f..3efbd6a9 100644 --- a/src/extensionState.ts +++ b/src/extensionState.ts @@ -1,9 +1,9 @@ import * as fs from 'fs'; import * as vscode from 'vscode'; import { Avatar, AvatarCache } from './avatarManager'; -import { CICD, CICDCache } from './cicdManager'; +import { CICDCache } from './cicdManager'; import { getConfig } from './config'; -import { BooleanOverride, CodeReview, ErrorInfo, FileViewType, GitGraphViewGlobalState, GitGraphViewWorkspaceState, GitRepoSet, GitRepoState, RepoCommitOrdering } from './types'; +import { BooleanOverride, CICDData, CodeReview, ErrorInfo, FileViewType, GitGraphViewGlobalState, GitGraphViewWorkspaceState, GitRepoSet, GitRepoState, RepoCommitOrdering } from './types'; import { GitExecutable, getPathFromStr } from './utils'; import { Disposable } from './utils/disposable'; import { Event } from './utils/event'; @@ -323,22 +323,21 @@ export class ExtensionState extends Disposable { /** * Add a new cicd to the cache of cicds known to Git Graph. - * @param email The email address that the cicd is for. - * @param cicd The details of the cicd. + * @param cicdData The CICDData. */ - public saveCICD(email: string, cicd: CICD) { + public saveCICD(cicdData: CICDData) { let cicds = this.getCICDCache(); - cicds[email] = cicd; + cicds[cicdData.sha] = cicdData; this.updateWorkspaceState(CICD_CACHE, cicds); } /** * Removes an cicd from the cache of cicds known to Git Graph. - * @param email The email address of the cicd to remove. + * @param hash The hash of the cicd to remove. */ - public removeCICDFromCache(email: string) { + public removeCICDFromCache(hash: string) { let cicds = this.getCICDCache(); - delete cicds[email]; + delete cicds[hash]; this.updateWorkspaceState(CICD_CACHE, cicds); } @@ -347,12 +346,6 @@ export class ExtensionState extends Disposable { */ public clearCICDCache() { this.updateWorkspaceState(CICD_CACHE, {}); - // fs.readdir(this.globalStoragePath + CICD_STORAGE_FOLDER, (err, files) => { - // if (err) return; - // for (let i = 0; i < files.length; i++) { - // fs.unlink(this.globalStoragePath + CICD_STORAGE_FOLDER + '/' + files[i], () => { }); - // } - // }); } diff --git a/src/gitGraphView.ts b/src/gitGraphView.ts index 446c25a7..45f353bb 100644 --- a/src/gitGraphView.ts +++ b/src/gitGraphView.ts @@ -152,8 +152,7 @@ export class GitGraphView extends Disposable { cicdManager.onCICD((event) => { this.sendMessage({ command: 'fetchCICD', - email: event.email, - image: event.image + cicdData: event.cicdData }); }), @@ -398,7 +397,7 @@ export class GitGraphView extends Disposable { this.avatarManager.fetchAvatarImage(msg.email, msg.repo, msg.remote, msg.commits); break; case 'fetchCICD': - this.cicdManager.fetchCICDImage(msg.email, msg.repo, msg.remote, msg.commits); + this.cicdManager.fetchCICDStatus(msg.sha, msg.cicdConfigs); break; case 'fetchIntoLocalBranch': this.sendMessage({ @@ -421,7 +420,7 @@ export class GitGraphView extends Disposable { command: 'loadCommits', refreshId: msg.refreshId, onlyFollowFirstParent: msg.onlyFollowFirstParent, - ...await this.dataSource.getCommits(msg.repo, msg.branches, msg.maxCommits, msg.showTags, msg.showRemoteBranches, msg.includeCommitsMentionedByReflogs, msg.onlyFollowFirstParent, msg.commitOrdering, msg.remotes, msg.hideRemotes, msg.stashes, msg.cicdConfigs) + ...await this.dataSource.getCommits(msg.repo, msg.branches, msg.maxCommits, msg.showTags, msg.showRemoteBranches, msg.includeCommitsMentionedByReflogs, msg.onlyFollowFirstParent, msg.commitOrdering, msg.remotes, msg.hideRemotes, msg.stashes) }); break; case 'loadConfig': diff --git a/src/types.ts b/src/types.ts index 46f42214..cfffef68 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,17 +11,6 @@ export interface GitCommit { readonly tags: ReadonlyArray; readonly remotes: ReadonlyArray; readonly stash: GitCommitStash | null; // null => not a stash, otherwise => stash info - readonly cicd: GitCICDData | null; -} - -export interface GitCICDData { - id: string; - status: string; - ref: string; - sha: string; - web_url: string; - created_at: string; - updated_at: string; } export interface GitCommitTag { @@ -199,7 +188,15 @@ interface PullRequestConfigCustom extends PullRequestConfigBase { export type PullRequestConfig = PullRequestConfigBuiltIn | PullRequestConfigCustom; - +export interface CICDData { + id: string; + status: string; + ref: string; + sha: string; + web_url: string; + created_at: string; + updated_at: string; +} export interface CICDConfigBase { readonly gitUrl: string; @@ -905,14 +902,12 @@ export interface ResponseFetchAvatar extends BaseMessage { } export interface RequestFetchCICD extends RepoRequest { readonly command: 'fetchCICD'; - readonly remote: string | null; - readonly email: string; - readonly commits: string[]; + readonly sha: string; + readonly cicdConfigs: CICDConfig[]; } export interface ResponseFetchCICD extends BaseMessage { readonly command: 'fetchCICD'; - readonly email: string; - readonly image: string; + readonly cicdData: CICDData; } export interface RequestFetchIntoLocalBranch extends RepoRequest { @@ -939,7 +934,6 @@ export interface RequestLoadCommits extends RepoRequest { readonly remotes: ReadonlyArray; readonly hideRemotes: ReadonlyArray; readonly stashes: ReadonlyArray; - readonly cicdConfigs: CICDConfig[] | null; } export interface ResponseLoadCommits extends ResponseWithErrorInfo { readonly command: 'loadCommits'; diff --git a/tests/commands.test.ts b/tests/commands.test.ts index 3ebbf415..48604325 100644 --- a/tests/commands.test.ts +++ b/tests/commands.test.ts @@ -23,6 +23,7 @@ import { RepoManager } from '../src/repoManager'; import { GitFileStatus } from '../src/types'; import * as utils from '../src/utils'; import { EventEmitter } from '../src/utils/event'; +import { CicdManager } from '../src/cicdManager'; let onDidChangeConfiguration: EventEmitter; let onDidChangeGitExecutable: EventEmitter; @@ -30,6 +31,7 @@ let logger: Logger; let dataSource: DataSource; let extensionState: ExtensionState; let avatarManager: AvatarManager; +let cicdManager: CicdManager; let repoManager: RepoManager; let spyOnGitGraphViewCreateOrShow: jest.SpyInstance, spyOnGetRepos: jest.SpyInstance, spyOnGetKnownRepo: jest.SpyInstance, spyOnRegisterRepo: jest.SpyInstance, spyOnGetCodeReviews: jest.SpyInstance, spyOnEndCodeReview: jest.SpyInstance, spyOnGetCommitSubject: jest.SpyInstance; @@ -40,6 +42,7 @@ beforeAll(() => { dataSource = new DataSource(null, onDidChangeConfiguration.subscribe, onDidChangeGitExecutable.subscribe, logger); extensionState = new ExtensionState(vscode.mocks.extensionContext, onDidChangeGitExecutable.subscribe); avatarManager = new AvatarManager(dataSource, extensionState, logger); + cicdManager = new CicdManager(extensionState, logger); repoManager = new RepoManager(dataSource, extensionState, onDidChangeConfiguration.subscribe, logger); spyOnGitGraphViewCreateOrShow = jest.spyOn(GitGraphView, 'createOrShow'); spyOnGetRepos = jest.spyOn(repoManager, 'getRepos'); @@ -53,6 +56,7 @@ beforeAll(() => { afterAll(() => { repoManager.dispose(); avatarManager.dispose(); + cicdManager.dispose(); extensionState.dispose(); dataSource.dispose(); logger.dispose(); @@ -63,7 +67,7 @@ afterAll(() => { describe('CommandManager', () => { let commandManager: CommandManager; beforeEach(() => { - commandManager = new CommandManager(vscode.mocks.extensionContext, avatarManager, dataSource, extensionState, repoManager, { path: '/path/to/git', version: '2.25.0' }, onDidChangeGitExecutable.subscribe, logger); + commandManager = new CommandManager(vscode.mocks.extensionContext, avatarManager, cicdManager, dataSource, extensionState, repoManager, { path: '/path/to/git', version: '2.25.0' }, onDidChangeGitExecutable.subscribe, logger); }); afterEach(() => { commandManager.dispose(); @@ -71,7 +75,7 @@ describe('CommandManager', () => { it('Should construct a CommandManager, and be disposed', () => { // Assert - expect(commandManager['disposables']).toHaveLength(11); + expect(commandManager['disposables']).toHaveLength(12); expect(commandManager['gitExecutable']).toStrictEqual({ path: '/path/to/git', version: '2.25.0' @@ -107,7 +111,7 @@ describe('CommandManager', () => { vscode.commands.executeCommand.mockResolvedValueOnce(null); // Run - commandManager = new CommandManager(vscode.mocks.extensionContext, avatarManager, dataSource, extensionState, repoManager, { path: '/path/to/git', version: '2.25.0' }, onDidChangeGitExecutable.subscribe, logger); + commandManager = new CommandManager(vscode.mocks.extensionContext, avatarManager, cicdManager, dataSource, extensionState, repoManager, { path: '/path/to/git', version: '2.25.0' }, onDidChangeGitExecutable.subscribe, logger); // Assert waitForExpect(() => { @@ -125,7 +129,7 @@ describe('CommandManager', () => { vscode.commands.executeCommand.mockResolvedValueOnce(null); // Run - commandManager = new CommandManager(vscode.mocks.extensionContext, avatarManager, dataSource, extensionState, repoManager, { path: '/path/to/git', version: '2.25.0' }, onDidChangeGitExecutable.subscribe, logger); + commandManager = new CommandManager(vscode.mocks.extensionContext, avatarManager, cicdManager, dataSource, extensionState, repoManager, { path: '/path/to/git', version: '2.25.0' }, onDidChangeGitExecutable.subscribe, logger); // Assert waitForExpect(() => { @@ -142,7 +146,7 @@ describe('CommandManager', () => { vscode.commands.executeCommand.mockRejectedValueOnce(null); // Run - commandManager = new CommandManager(vscode.mocks.extensionContext, avatarManager, dataSource, extensionState, repoManager, { path: '/path/to/git', version: '2.25.0' }, onDidChangeGitExecutable.subscribe, logger); + commandManager = new CommandManager(vscode.mocks.extensionContext, avatarManager, cicdManager, dataSource, extensionState, repoManager, { path: '/path/to/git', version: '2.25.0' }, onDidChangeGitExecutable.subscribe, logger); // Assert waitForExpect(() => { @@ -163,7 +167,7 @@ describe('CommandManager', () => { }); // Run - commandManager = new CommandManager(vscode.mocks.extensionContext, avatarManager, dataSource, extensionState, repoManager, { path: '/path/to/git', version: '2.25.0' }, onDidChangeGitExecutable.subscribe, logger); + commandManager = new CommandManager(vscode.mocks.extensionContext, avatarManager, cicdManager, dataSource, extensionState, repoManager, { path: '/path/to/git', version: '2.25.0' }, onDidChangeGitExecutable.subscribe, logger); // Assert waitForExpect(() => { @@ -183,7 +187,7 @@ describe('CommandManager', () => { // Assert await waitForExpect(() => { - expect(spyOnGitGraphViewCreateOrShow).toHaveBeenCalledWith('/path/to/extension', dataSource, extensionState, avatarManager, repoManager, logger, null); + expect(spyOnGitGraphViewCreateOrShow).toHaveBeenCalledWith('/path/to/extension', dataSource, extensionState, avatarManager, cicdManager, repoManager, logger, null); }); }); @@ -197,7 +201,7 @@ describe('CommandManager', () => { // Assert await waitForExpect(() => { - expect(spyOnGitGraphViewCreateOrShow).toHaveBeenCalledWith('/path/to/extension', dataSource, extensionState, avatarManager, repoManager, logger, { repo: '/path/to/workspace-folder/repo' }); + expect(spyOnGitGraphViewCreateOrShow).toHaveBeenCalledWith('/path/to/extension', dataSource, extensionState, avatarManager, cicdManager, repoManager, logger, { repo: '/path/to/workspace-folder/repo' }); }); }); @@ -212,7 +216,7 @@ describe('CommandManager', () => { // Assert await waitForExpect(() => { - expect(spyOnGitGraphViewCreateOrShow).toHaveBeenCalledWith('/path/to/extension', dataSource, extensionState, avatarManager, repoManager, logger, { repo: '/path/to/workspace-folder/repo' }); + expect(spyOnGitGraphViewCreateOrShow).toHaveBeenCalledWith('/path/to/extension', dataSource, extensionState, avatarManager, cicdManager, repoManager, logger, { repo: '/path/to/workspace-folder/repo' }); }); }); @@ -227,7 +231,7 @@ describe('CommandManager', () => { // Assert await waitForExpect(() => { - expect(spyOnGitGraphViewCreateOrShow).toHaveBeenCalledWith('/path/to/extension', dataSource, extensionState, avatarManager, repoManager, logger, { repo: '/path/to/workspace-folder' }); + expect(spyOnGitGraphViewCreateOrShow).toHaveBeenCalledWith('/path/to/extension', dataSource, extensionState, avatarManager, cicdManager, repoManager, logger, { repo: '/path/to/workspace-folder' }); }); }); }); @@ -551,7 +555,7 @@ describe('CommandManager', () => { canPickMany: false } ); - expect(spyOnGitGraphViewCreateOrShow).toHaveBeenCalledWith('/path/to/extension', dataSource, extensionState, avatarManager, repoManager, logger, { repo: '/path/to/repo1', runCommandOnLoad: 'fetch' }); + expect(spyOnGitGraphViewCreateOrShow).toHaveBeenCalledWith('/path/to/extension', dataSource, extensionState, avatarManager, cicdManager, repoManager, logger, { repo: '/path/to/repo1', runCommandOnLoad: 'fetch' }); }); }); @@ -588,7 +592,7 @@ describe('CommandManager', () => { canPickMany: false } ); - expect(spyOnGitGraphViewCreateOrShow).toHaveBeenCalledWith('/path/to/extension', dataSource, extensionState, avatarManager, repoManager, logger, { repo: '/path/to/repo1', runCommandOnLoad: 'fetch' }); + expect(spyOnGitGraphViewCreateOrShow).toHaveBeenCalledWith('/path/to/extension', dataSource, extensionState, avatarManager, cicdManager, repoManager, logger, { repo: '/path/to/repo1', runCommandOnLoad: 'fetch' }); }); }); @@ -625,7 +629,7 @@ describe('CommandManager', () => { canPickMany: false } ); - expect(spyOnGitGraphViewCreateOrShow).toHaveBeenCalledWith('/path/to/extension', dataSource, extensionState, avatarManager, repoManager, logger, { repo: '/path/to/repo1', runCommandOnLoad: 'fetch' }); + expect(spyOnGitGraphViewCreateOrShow).toHaveBeenCalledWith('/path/to/extension', dataSource, extensionState, avatarManager, cicdManager, repoManager, logger, { repo: '/path/to/repo1', runCommandOnLoad: 'fetch' }); }); }); @@ -710,7 +714,7 @@ describe('CommandManager', () => { // Assert await waitForExpect(() => { - expect(spyOnGitGraphViewCreateOrShow).toHaveBeenCalledWith('/path/to/extension', dataSource, extensionState, avatarManager, repoManager, logger, { repo: '/path/to/repo1', runCommandOnLoad: 'fetch' }); + expect(spyOnGitGraphViewCreateOrShow).toHaveBeenCalledWith('/path/to/extension', dataSource, extensionState, avatarManager, cicdManager, repoManager, logger, { repo: '/path/to/repo1', runCommandOnLoad: 'fetch' }); }); }); @@ -723,7 +727,7 @@ describe('CommandManager', () => { // Assert await waitForExpect(() => { - expect(spyOnGitGraphViewCreateOrShow).toHaveBeenCalledWith('/path/to/extension', dataSource, extensionState, avatarManager, repoManager, logger, null); + expect(spyOnGitGraphViewCreateOrShow).toHaveBeenCalledWith('/path/to/extension', dataSource, extensionState, avatarManager, cicdManager, repoManager, logger, null); }); }); }); @@ -948,7 +952,7 @@ describe('CommandManager', () => { // Assert await waitForExpect(() => { - expect(spyOnGitGraphViewCreateOrShow).toHaveBeenCalledWith('/path/to/extension', dataSource, extensionState, avatarManager, repoManager, logger, { + expect(spyOnGitGraphViewCreateOrShow).toHaveBeenCalledWith('/path/to/extension', dataSource, extensionState, avatarManager, cicdManager, repoManager, logger, { repo: '/path/to/repo', commitDetails: { commitHash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1004,7 +1008,7 @@ describe('CommandManager', () => { // Assert await waitForExpect(() => { - expect(spyOnGitGraphViewCreateOrShow).toHaveBeenCalledWith('/path/to/extension', dataSource, extensionState, avatarManager, repoManager, logger, { + expect(spyOnGitGraphViewCreateOrShow).toHaveBeenCalledWith('/path/to/extension', dataSource, extensionState, avatarManager, cicdManager, repoManager, logger, { repo: '/path/to/repo', commitDetails: { commitHash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', diff --git a/tests/dataSource.test.ts b/tests/dataSource.test.ts index 378fb759..10b9e940 100644 --- a/tests/dataSource.test.ts +++ b/tests/dataSource.test.ts @@ -498,7 +498,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], []); // Assert expect(result).toStrictEqual({ @@ -513,8 +513,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -530,8 +529,7 @@ describe('DataSource', () => { { name: 'origin/master', remote: 'origin' }, { name: 'other-remote/master', remote: null } ], - stash: null, - cicd: null + stash: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -543,8 +541,7 @@ describe('DataSource', () => { heads: ['develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -556,8 +553,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -598,7 +594,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', ['master', 'develop'], 300, true, true, false, false, CommitOrdering.AuthorDate, ['origin'], [], [], []); + const result = await dataSource.getCommits('/path/to/repo', ['master', 'develop'], 300, true, true, false, false, CommitOrdering.AuthorDate, ['origin'], [], []); // Assert expect(result).toStrictEqual({ @@ -613,8 +609,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -626,8 +621,7 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null, - cicd: null + stash: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -639,8 +633,7 @@ describe('DataSource', () => { heads: ['develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -652,8 +645,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -688,7 +680,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 2, true, true, false, false, CommitOrdering.Topological, ['origin'], [], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 2, true, true, false, false, CommitOrdering.Topological, ['origin'], [], []); // Assert expect(result).toStrictEqual({ @@ -703,8 +695,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -716,8 +707,7 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -729,8 +719,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -765,7 +754,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], []); // Assert expect(result).toStrictEqual({ @@ -780,8 +769,7 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null, - cicd: null + stash: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -793,8 +781,7 @@ describe('DataSource', () => { heads: ['develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -806,8 +793,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null } ], head: '4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e', @@ -842,7 +828,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], []); // Assert expect(result).toStrictEqual({ @@ -857,8 +843,7 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null, - cicd: null + stash: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -870,8 +855,7 @@ describe('DataSource', () => { heads: ['develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -883,8 +867,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -923,7 +906,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], []); // Assert expect(result).toStrictEqual({ @@ -938,8 +921,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -951,8 +933,7 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null, - cicd: null + stash: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -964,8 +945,7 @@ describe('DataSource', () => { heads: ['develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -977,8 +957,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1018,7 +997,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, false, true, false, false, CommitOrdering.Date, ['origin'], [], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, false, true, false, false, CommitOrdering.Date, ['origin'], [], []); // Assert expect(result).toStrictEqual({ @@ -1033,8 +1012,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1046,8 +1024,7 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null, - cicd: null + stash: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1059,8 +1036,7 @@ describe('DataSource', () => { heads: ['develop'], tags: [], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1072,8 +1048,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1113,7 +1088,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], []); // Assert expect(result).toStrictEqual({ @@ -1128,8 +1103,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1141,8 +1115,7 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null, - cicd: null + stash: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1154,8 +1127,7 @@ describe('DataSource', () => { heads: ['develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1167,8 +1139,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1207,7 +1178,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, false, false, false, CommitOrdering.Date, ['origin'], [], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, false, false, false, CommitOrdering.Date, ['origin'], [], []); // Assert expect(result).toStrictEqual({ @@ -1222,8 +1193,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1235,8 +1205,7 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1248,8 +1217,7 @@ describe('DataSource', () => { heads: ['develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1261,8 +1229,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1303,7 +1270,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, true, false, CommitOrdering.Date, ['origin'], [], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, true, false, CommitOrdering.Date, ['origin'], [], []); // Assert expect(result).toStrictEqual({ @@ -1318,8 +1285,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1331,8 +1297,7 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null, - cicd: null + stash: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1344,8 +1309,7 @@ describe('DataSource', () => { heads: ['develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1357,8 +1321,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1399,7 +1362,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, true, CommitOrdering.Date, ['origin'], [], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, true, CommitOrdering.Date, ['origin'], [], []); // Assert expect(result).toStrictEqual({ @@ -1414,8 +1377,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1427,8 +1389,7 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null, - cicd: null + stash: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1440,8 +1401,7 @@ describe('DataSource', () => { heads: ['develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1453,8 +1413,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1498,7 +1457,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], []); // Assert expect(result).toStrictEqual({ @@ -1513,8 +1472,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1529,8 +1487,7 @@ describe('DataSource', () => { { name: 'origin/master', remote: 'origin' }, { name: 'other-remote/master', remote: null } ], - stash: null, - cicd: null + stash: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1542,8 +1499,7 @@ describe('DataSource', () => { heads: ['develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1555,8 +1511,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1598,7 +1553,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin', 'other-remote'], ['other-remote'], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin', 'other-remote'], ['other-remote'], []); // Assert expect(result).toStrictEqual({ @@ -1613,8 +1568,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1626,8 +1580,7 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null, - cicd: null + stash: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1639,8 +1592,7 @@ describe('DataSource', () => { heads: ['develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1652,8 +1604,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1704,7 +1655,7 @@ describe('DataSource', () => { date: 1587559258, message: 'WIP' } - ], []); + ]); // Assert expect(result).toStrictEqual({ @@ -1719,8 +1670,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1736,8 +1686,7 @@ describe('DataSource', () => { baseHash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', selector: 'refs/stash@{0}', untrackedFilesHash: '5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f' - }, - cicd: null + } }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1749,8 +1698,7 @@ describe('DataSource', () => { heads: ['master', 'develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null, - cicd: null + stash: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1762,8 +1710,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null } ], head: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1822,7 +1769,7 @@ describe('DataSource', () => { date: 1587559258, message: 'WIP 2' } - ], []); + ]); // Assert expect(result).toStrictEqual({ @@ -1837,8 +1784,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2', @@ -1854,8 +1800,7 @@ describe('DataSource', () => { baseHash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', selector: 'refs/stash@{0}', untrackedFilesHash: '5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f' - }, - cicd: null + } }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1867,8 +1812,7 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null, - cicd: null + stash: null }, { hash: 'b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3', @@ -1884,8 +1828,7 @@ describe('DataSource', () => { baseHash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', selector: 'refs/stash@{1}', untrackedFilesHash: '6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a' - }, - cicd: null + } }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -1897,8 +1840,7 @@ describe('DataSource', () => { heads: ['develop'], tags: [], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -1910,8 +1852,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -1970,7 +1911,7 @@ describe('DataSource', () => { date: 1587559260, message: 'WIP 2' } - ], []); + ]); // Assert expect(result).toStrictEqual({ @@ -1985,8 +1926,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2', @@ -2002,8 +1942,7 @@ describe('DataSource', () => { baseHash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', selector: 'refs/stash@{0}', untrackedFilesHash: '5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f' - }, - cicd: null + } }, { hash: 'b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3', @@ -2019,8 +1958,7 @@ describe('DataSource', () => { baseHash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', selector: 'refs/stash@{1}', untrackedFilesHash: '6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a' - }, - cicd: null + } }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -2032,8 +1970,7 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null, - cicd: null + stash: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -2045,8 +1982,7 @@ describe('DataSource', () => { heads: ['develop'], tags: [], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -2058,8 +1994,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -2108,7 +2043,7 @@ describe('DataSource', () => { date: 1587559258, message: 'WIP 1' } - ], []); + ]); // Assert expect(result).toStrictEqual({ @@ -2123,8 +2058,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -2136,8 +2070,7 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null, - cicd: null + stash: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -2149,8 +2082,7 @@ describe('DataSource', () => { heads: ['develop'], tags: [], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -2162,8 +2094,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -2200,7 +2131,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], []); // Assert expect(result).toStrictEqual({ @@ -2215,8 +2146,7 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }], - stash: null, - cicd: null + stash: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -2228,8 +2158,7 @@ describe('DataSource', () => { heads: ['develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -2241,8 +2170,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -2263,7 +2191,7 @@ describe('DataSource', () => { vscode.mockExtensionSettingReturnValue('repository.showRemoteHeads', true); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], []); // Assert expect(result).toStrictEqual({ @@ -2307,7 +2235,7 @@ describe('DataSource', () => { date.setCurrentTime(1587559259); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], []); // Assert expect(result).toStrictEqual({ @@ -2322,8 +2250,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -2335,8 +2262,7 @@ describe('DataSource', () => { heads: ['master'], tags: [], remotes: [{ name: 'origin/master', remote: 'origin' }, { name: 'other-remote/master', remote: null }], - stash: null, - cicd: null + stash: null }, { hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', @@ -2348,8 +2274,7 @@ describe('DataSource', () => { heads: ['develop'], tags: [{ name: 'tag1', annotated: true }], remotes: [], - stash: null, - cicd: null + stash: null }, { hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', @@ -2361,8 +2286,7 @@ describe('DataSource', () => { heads: [], tags: [], remotes: [], - stash: null, - cicd: null + stash: null } ], head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', @@ -2391,7 +2315,7 @@ describe('DataSource', () => { vscode.mockExtensionSettingReturnValue('repository.showRemoteHeads', true); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], []); // Assert expect(result).toStrictEqual({ @@ -2415,7 +2339,7 @@ describe('DataSource', () => { vscode.mockExtensionSettingReturnValue('repository.showRemoteHeads', true); // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], [], []); + const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], []); // Assert expect(result).toStrictEqual({ @@ -2426,109 +2350,6 @@ describe('DataSource', () => { error: 'error message' }); }); - - it('Should return the commits (string)', async () => { - // Setup - mockGitSuccessOnce( - '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2bXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPb2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3cXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPbTest NameXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPbtest@mhutchie.comXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPb1587559258XX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPbCommit Message 3\n' + - '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3cXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPb3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4dXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPbTest NameXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPbtest@mhutchie.comXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPb1587559257XX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPbCommit Message 2\n' + - '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4dXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPbXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPbTest NameXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPbtest@mhutchie.comXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPb1587559256XX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPbCommit Message 1\n' - ); - mockGitSuccessOnce( - '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b HEAD\n' + - '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b refs/heads/master\n' + - '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c refs/heads/develop\n' + - '4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e refs/heads/feature\n' + - '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b refs/remotes/origin/HEAD\n' + - '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b refs/remotes/origin/master\n' + - '4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e refs/remotes/origin/feature\n' + - '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b refs/remotes/other-remote/master\n' + - 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2 refs/tags/tag1\n' + - '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c refs/tags/tag1^{}\n' + - '4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e refs/tags/tag2\n' - ); - mockGitSuccessOnce( - 'M modified.txt\n' + - '?? untracked.txt\n' - ); - vscode.mockExtensionSettingReturnValue('repository.showCommitsOnlyReferencedByTags', true); - vscode.mockExtensionSettingReturnValue('repository.showRemoteHeads', true); - vscode.mockExtensionSettingReturnValue('repository.showUncommittedChanges', true); - vscode.mockExtensionSettingReturnValue('repository.showUntrackedFiles', true); - date.setCurrentTime(1587559259); - - // Run - const result = await dataSource.getCommits('/path/to/repo', null, 300, true, true, false, false, CommitOrdering.Date, ['origin'], [], [], null); - - // Assert - expect(result).toStrictEqual({ - commits: [ - { - hash: '*', - parents: ['1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b'], - author: '*', - email: '', - date: 1587559259, - message: 'Uncommitted Changes (2)', - heads: [], - tags: [], - remotes: [], - stash: null, - cicd: null - }, - { - hash: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', - parents: ['2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c'], - author: 'Test Name', - email: 'test@mhutchie.com', - date: 1587559258, - message: 'Commit Message 3', - heads: ['master'], - tags: [], - remotes: [ - { name: 'origin/HEAD', remote: 'origin' }, - { name: 'origin/master', remote: 'origin' }, - { name: 'other-remote/master', remote: null } - ], - stash: null, - cicd: null - }, - { - hash: '2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c', - parents: ['3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d'], - author: 'Test Name', - email: 'test@mhutchie.com', - date: 1587559257, - message: 'Commit Message 2', - heads: ['develop'], - tags: [{ name: 'tag1', annotated: true }], - remotes: [], - stash: null, - cicd: null - }, - { - hash: '3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', - parents: [], - author: 'Test Name', - email: 'test@mhutchie.com', - date: 1587559256, - message: 'Commit Message 1', - heads: [], - tags: [], - remotes: [], - stash: null, - cicd: null - } - ], - head: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', - tags: ['tag1', 'tag2'], - moreCommitsAvailable: false, - error: null - }); - expect(spyOnSpawn).toBeCalledWith('/path/to/git', ['-c', 'log.showSignature=false', 'log', '--max-count=301', '--format=%HXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPb%PXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPb%anXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPb%aeXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPb%atXX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPb%s', '--date-order', '--branches', '--tags', '--remotes', 'HEAD', '--'], expect.objectContaining({ cwd: '/path/to/repo' })); - expect(spyOnSpawn).toBeCalledWith('/path/to/git', ['show-ref', '-d', '--head'], expect.objectContaining({ cwd: '/path/to/repo' })); - expect(spyOnSpawn).toBeCalledWith('/path/to/git', ['status', '--untracked-files=all', '--porcelain'], expect.objectContaining({ cwd: '/path/to/repo' })); - }); }); describe('getConfig', () => { diff --git a/web/main.ts b/web/main.ts index bf8e0966..f26e129e 100644 --- a/web/main.ts +++ b/web/main.ts @@ -379,6 +379,7 @@ class GitGraphView { this.finaliseLoadCommits(); this.requestAvatars(avatarsNeeded); + this.requestCICDs(); } private finaliseLoadCommits() { @@ -515,6 +516,19 @@ class GitGraphView { } } + public loadCicd(cicdData: GG.CICDData) { + // this.avatars[sha] = image; + // this.saveState(); + let cicdElems = >document.getElementsByClassName('cicd'), hash = cicdData.sha; + for (let i = 0; i < cicdElems.length; i++) { + if (cicdElems[i].dataset.hash === hash) { + cicdElems[i].innerHTML = (cicdData.status === 'success' ? '' + SVG_ICONS.passed + '' : + ((cicdData.status === 'failed' || cicdData.status === 'failure') ? '' + SVG_ICONS.failed + '' : + '' + SVG_ICONS.inconclusive + '')) + + '' + cicdData.status + ''; + } + } + } /* Getters */ @@ -615,8 +629,7 @@ class GitGraphView { commitOrdering: getCommitOrdering(repoState.commitOrdering), remotes: this.gitRemotes, hideRemotes: repoState.hideRemotes, - stashes: this.gitStashes, - cicdConfigs: repoState.cicdConfigs + stashes: this.gitStashes }); } @@ -690,6 +703,16 @@ class GitGraphView { } } + private requestCICDs() { + this.commits.forEach(commit => { + if (typeof this.currentRepo === 'string' && typeof this.gitRepos[this.currentRepo] !== 'undefined') { + let cicdConfigs = this.gitRepos[this.currentRepo].cicdConfigs; + if (cicdConfigs !== null) { + sendMessage({ command: 'fetchCICD', repo: this.currentRepo, sha: commit.hash, cicdConfigs: cicdConfigs }); + } + } + }); + } /* State */ @@ -821,7 +844,7 @@ class GitGraphView { (colVisibility.date ? 'Date' : '') + (colVisibility.author ? 'Author' : '') + (colVisibility.commit ? 'Commit' : '') + - (colVisibility.cicd ? 'CI/CD Status' : '') + + (colVisibility.cicd ? 'CI/CD' : '') + ''; for (let i = 0; i < this.commits.length; i++) { @@ -870,7 +893,7 @@ class GitGraphView { (colVisibility.date ? '' + date.formatted + '' : '') + (colVisibility.author ? '' + (this.config.fetchAvatars ? '' + (typeof this.avatars[commit.email] === 'string' ? '' : '') + '' : '') + escapeHtml(commit.author) + '' : '') + (colVisibility.commit ? '' + abbrevCommit(commit.hash) + '' : '') + - (colVisibility.cicd ? (commit.cicd ? '' + '' + commit.cicd.status + '' : '*') : '') + + (colVisibility.cicd ? (this.config.fetchCICDs ? '' + '*' + '' : '-') : '') + ''; } this.tableElem.innerHTML = '' + html + '
'; @@ -3171,6 +3194,9 @@ window.addEventListener('load', () => { gitGraph.loadAvatar(msg.email, resizedImage); }); break; + case 'fetchCICD': + gitGraph.loadCicd(msg.cicdData); + break; case 'fetchIntoLocalBranch': refreshOrDisplayError(msg.error, 'Unable to Fetch into Local Branch'); break; diff --git a/web/styles/main.css b/web/styles/main.css index 2da61699..770a27e1 100644 --- a/web/styles/main.css +++ b/web/styles/main.css @@ -448,6 +448,23 @@ body.selection-background-color-exists ::selection{ opacity:0.8; } +.cicdInfo{ + margin-left:4px; + vertical-align:middle; +} +.cicdInfo.G svg{ + fill:#009028; + opacity:0.9; +} +.cicdInfo.U svg{ + fill:#f09000; + opacity:1; +} +.cicdInfo.B svg{ + fill:#e00000; + opacity:0.8; +} + #cdvFiles{ left:50%; right:0; From 5c5aa0ce55c91515ef99c737a6d892de0a4266b3 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Mon, 15 Mar 2021 04:11:00 +0900 Subject: [PATCH 13/54] #462 Updated CI/CD style --- web/styles/main.css | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/web/styles/main.css b/web/styles/main.css index 770a27e1..2119adb0 100644 --- a/web/styles/main.css +++ b/web/styles/main.css @@ -449,8 +449,13 @@ body.selection-background-color-exists ::selection{ } .cicdInfo{ - margin-left:4px; - vertical-align:middle; + vertical-align:top; + display:inline-block; + width:13px; + height:13px; + vertical-align:top; + margin-top:3px; + margin-right:4px; } .cicdInfo.G svg{ fill:#009028; From fc963ed1155665db59ec22829f1643316c27dfb1 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Mon, 15 Mar 2021 04:39:58 +0900 Subject: [PATCH 14/54] #462 Updated caching timeing of CI/CD --- src/cicdManager.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/cicdManager.ts b/src/cicdManager.ts index 6a8c8093..e6265491 100644 --- a/src/cicdManager.ts +++ b/src/cicdManager.ts @@ -22,6 +22,8 @@ export class CicdManager extends Disposable { private githubTimeout: number = 0; private gitLabTimeout: number = 0; private initialState: boolean = true; + private requestPage: number = -1; + private requestPageTimeout: NodeJS.Timer | null = null; /** * Creates the Git Graph CICD Manager. @@ -37,9 +39,9 @@ export class CicdManager extends Disposable { this.queue = new CicdRequestQueue(() => { if (this.interval !== null) return; this.interval = setInterval(() => { - // Fetch cicds every 10 seconds + // Fetch cicds every 1 seconds this.fetchCICDsInterval(); - }, 10000); + }, 1000); this.fetchCICDsInterval(); }); @@ -75,7 +77,7 @@ export class CicdManager extends Disposable { // CICD couldn't be found, request it again this.removeCICDFromCache(hash); cicdConfigs.forEach(cicdConfig => { - this.queue.add(cicdConfig, -1, true); + this.queue.add(cicdConfig, this.requestPage, true); }); }); } else { @@ -83,13 +85,23 @@ export class CicdManager extends Disposable { if (this.initialState === true) { this.initialState = false; cicdConfigs.forEach(cicdConfig => { - this.queue.add(cicdConfig, -1, true); + this.queue.add(cicdConfig, this.requestPage, true); }); // Reset initial state for 10 seconds setTimeout(() => { this.logger.log('Reset initial timer of CICD'); this.initialState = true; }, 10000); + // set request page to top + this.requestPage = 1; + // Reset request page to all after 10 minutes + if (this.requestPageTimeout === null) { + this.requestPageTimeout = setTimeout(() => { + this.logger.log('Reset request page of CICD'); + this.requestPage = -1; + this.requestPageTimeout = null; + }, 600000); + } } } } From 85e3582efeaaa31730ba812f1c55c7645dd4c773 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Mon, 15 Mar 2021 05:13:57 +0900 Subject: [PATCH 15/54] #462 Updated caching timeing of CI/CD --- src/cicdManager.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/cicdManager.ts b/src/cicdManager.ts index e6265491..78e9002b 100644 --- a/src/cicdManager.ts +++ b/src/cicdManager.ts @@ -24,6 +24,7 @@ export class CicdManager extends Disposable { private initialState: boolean = true; private requestPage: number = -1; private requestPageTimeout: NodeJS.Timer | null = null; + private cicdConfigsPrev: CICDConfig[] = []; /** * Creates the Git Graph CICD Manager. @@ -74,13 +75,23 @@ export class CicdManager extends Disposable { if (typeof this.cicds[hash] !== 'undefined') { // CICD exists in the cache this.emitCICD(this.cicds[hash]).catch(() => { - // CICD couldn't be found, request it again + // CICD couldn't be found this.removeCICDFromCache(hash); - cicdConfigs.forEach(cicdConfig => { - this.queue.add(cicdConfig, this.requestPage, true); - }); }); } else { + + // Check update user config + const cicdConfigsJSON = Object.entries(cicdConfigs).sort().toString(); + const cicdConfigsPrevJSON = Object.entries(this.cicdConfigsPrev).sort().toString(); + if (cicdConfigsJSON !== cicdConfigsPrevJSON) { + this.initialState = true; + this.requestPage = -1; + if (this.requestPageTimeout !== null) { + clearTimeout(this.requestPageTimeout); + } + this.requestPageTimeout = null; + } + this.cicdConfigsPrev = Object.assign({}, cicdConfigs); // CICD not in the cache, request it if (this.initialState === true) { this.initialState = false; From 11758370d013cc583a20d0e66837eaf706b7f91e Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Mon, 15 Mar 2021 05:34:42 +0900 Subject: [PATCH 16/54] #462 Updated GitHub status of CI/CD --- src/cicdManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cicdManager.ts b/src/cicdManager.ts index 78e9002b..e46e3cf7 100644 --- a/src/cicdManager.ts +++ b/src/cicdManager.ts @@ -250,7 +250,7 @@ export class CicdManager extends Disposable { let ret: CICDData[] = respJson['workflow_runs'].map((elm: { [x: string]: any; }) => { return { id: elm['id'], - status: elm['conclusion'], + status: elm['conclusion'] === null ? elm['status'] : elm['conclusion'], ref: elm['name'], sha: elm['head_sha'], web_url: elm['html_url'], From d1435528a1cc81315df1cb9230443eb3a43953ad Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Mon, 15 Mar 2021 09:14:36 +0900 Subject: [PATCH 17/54] #462 Updated method of refresh CI/CD status --- package.json | 4 ++-- web/global.d.ts | 3 +++ web/main.ts | 17 +++++++++++++---- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 2af63870..8bc20699 100644 --- a/package.json +++ b/package.json @@ -943,7 +943,7 @@ "git-graph.repository.commits.fetchCICDs": { "type": "boolean", "default": true, - "description": "Fetch CI/CD status of commit. By enabling this setting, you consent to Access Token being sent GitHub or GitLab, depending on the repositories and user configuration." + "description": "Fetch CI/CD status of commit. By enabling this setting, you consent to Access Token being sent GitHub or GitLab, depending on the user configuration." }, "git-graph.repository.commits.initialLoad": { "type": "number", @@ -1251,7 +1251,7 @@ "git-graph.fetchCICDs": { "type": "boolean", "default": true, - "description": "Fetch CI/CD status of commit. By enabling this setting, you consent to Access Token being sent GitHub or GitLab, depending on the repositories and user configuration.", + "description": "Fetch CI/CD status of commit. By enabling this setting, you consent to Access Token being sent GitHub or GitLab, depending on the user configuration.", "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.fetchCICDs", "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.fetchCICDs#`" }, diff --git a/web/global.d.ts b/web/global.d.ts index e8a5cf5f..3dfb40dd 100644 --- a/web/global.d.ts +++ b/web/global.d.ts @@ -1,4 +1,5 @@ import * as GG from '../out/types'; // Import types from back-end (requires `npm run compile-src`) +import { CICDData } from '../out/types'; declare global { @@ -20,6 +21,7 @@ declare global { const workspaceState: GG.DeepReadonly; type AvatarImageCollection = { [email: string]: string }; + type CICDDataCollection = { [hash: string]: CICDData }; interface ExpandedCommit { index: number; @@ -57,6 +59,7 @@ declare global { readonly commits: GG.GitCommit[]; readonly commitHead: string | null; readonly avatars: AvatarImageCollection; + readonly cicdDatas: CICDDataCollection; readonly currentBranches: string[] | null; readonly moreCommitsAvailable: boolean; readonly maxCommits: number; diff --git a/web/main.ts b/web/main.ts index 540eaf13..8ccf528e 100644 --- a/web/main.ts +++ b/web/main.ts @@ -11,6 +11,7 @@ class GitGraphView { private commitLookup: { [hash: string]: number } = {}; private onlyFollowFirstParent: boolean = false; private avatars: AvatarImageCollection = {}; + private cicdDatas: CICDDataCollection = {}; private currentBranches: string[] | null = null; private currentRepo!: string; @@ -124,6 +125,7 @@ class GitGraphView { this.maxCommits = prevState.maxCommits; this.expandedCommit = prevState.expandedCommit; this.avatars = prevState.avatars; + this.cicdDatas = prevState.cicdDatas; this.gitConfig = prevState.gitConfig; this.loadRepoInfo(prevState.gitBranches, prevState.gitBranchHead, prevState.gitRemotes, prevState.gitStashes, true); this.loadCommits(prevState.commits, prevState.commitHead, prevState.gitTags, prevState.moreCommitsAvailable, prevState.onlyFollowFirstParent); @@ -519,15 +521,15 @@ class GitGraphView { } public loadCicd(cicdData: GG.CICDData) { - // this.avatars[sha] = image; - // this.saveState(); + this.cicdDatas[cicdData.sha] = cicdData; + this.saveState(); let cicdElems = >document.getElementsByClassName('cicd'), hash = cicdData.sha; for (let i = 0; i < cicdElems.length; i++) { if (cicdElems[i].dataset.hash === hash) { cicdElems[i].innerHTML = (cicdData.status === 'success' ? '' + SVG_ICONS.passed + '' : ((cicdData.status === 'failed' || cicdData.status === 'failure') ? '' + SVG_ICONS.failed + '' : '' + SVG_ICONS.inconclusive + '')) + - '' + cicdData.status + ''; + '' + cicdData.status + ''; } } } @@ -745,6 +747,7 @@ class GitGraphView { commits: this.commits, commitHead: this.commitHead, avatars: this.avatars, + cicdDatas: this.cicdDatas, currentBranches: this.currentBranches, moreCommitsAvailable: this.moreCommitsAvailable, maxCommits: this.maxCommits, @@ -895,7 +898,13 @@ class GitGraphView { (colVisibility.date ? '' + date.formatted + '' : '') + (colVisibility.author ? '' + (this.config.fetchAvatars ? '' + (typeof this.avatars[commit.email] === 'string' ? '' : '') + '' : '') + escapeHtml(commit.author) + '' : '') + (colVisibility.commit ? '' + abbrevCommit(commit.hash) + '' : '') + - (colVisibility.cicd ? (this.config.fetchCICDs ? '' + '*' + '' : '-') : '') + + (colVisibility.cicd ? (this.config.fetchCICDs ? '' + '' + + (typeof this.cicdDatas[commit.hash] === 'object' ? + ((this.cicdDatas[commit.hash].status === 'success' ? '' + SVG_ICONS.passed + '' : + ((this.cicdDatas[commit.hash].status === 'failed' || this.cicdDatas[commit.hash].status === 'failure') ? '' + SVG_ICONS.failed + '' : + '' + SVG_ICONS.inconclusive + '')) + + '' + this.cicdDatas[commit.hash].status + '') : '*') + + '' + '' : '-') : ':') + ''; } this.tableElem.innerHTML = '' + html + '
'; From 87625a45a6e7ca3da9d52b104ef3a52fff55bd05 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Mon, 15 Mar 2021 09:27:50 +0900 Subject: [PATCH 18/54] #462 Fiexd package.json typo CRLF to LF --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8bc20699..492c3ca6 100644 --- a/package.json +++ b/package.json @@ -943,7 +943,7 @@ "git-graph.repository.commits.fetchCICDs": { "type": "boolean", "default": true, - "description": "Fetch CI/CD status of commit. By enabling this setting, you consent to Access Token being sent GitHub or GitLab, depending on the user configuration." + "description": "Fetch CI/CD status of commit. By enabling this setting, you consent to Access Token being sent GitHub or GitLab, depending on the repositories and user configuration." }, "git-graph.repository.commits.initialLoad": { "type": "number", @@ -1251,7 +1251,7 @@ "git-graph.fetchCICDs": { "type": "boolean", "default": true, - "description": "Fetch CI/CD status of commit. By enabling this setting, you consent to Access Token being sent GitHub or GitLab, depending on the user configuration.", + "description": "Fetch CI/CD status of commit. By enabling this setting, you consent to Access Token being sent GitHub or GitLab, depending on the repositories and user configuration.", "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.fetchCICDs", "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.fetchCICDs#`" }, From ca269551be3d6a5387b5bea57aa5c601e46d4b68 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Mon, 15 Mar 2021 09:33:31 +0900 Subject: [PATCH 19/54] #462 Fiexd package.json typo CRLF to LF --- package.json | 3022 +++++++++++++++++++++++++------------------------- 1 file changed, 1511 insertions(+), 1511 deletions(-) diff --git a/package.json b/package.json index 492c3ca6..2ef06c6b 100644 --- a/package.json +++ b/package.json @@ -1,1512 +1,1512 @@ -{ - "name": "git-graph", - "displayName": "Git Graph", - "version": "1.29.0", - "publisher": "mhutchie", - "author": { - "name": "Michael Hutchison", - "email": "mhutchie@16right.com" - }, - "description": "View a Git Graph of your repository, and perform Git actions from the graph.", - "keywords": [ - "git", - "graph", - "visualise", - "diff", - "action" - ], - "categories": [ - "Other" - ], - "homepage": "https://github.com/mhutchie/vscode-git-graph", - "repository": { - "type": "git", - "url": "https://github.com/mhutchie/vscode-git-graph.git" - }, - "bugs": { - "url": "https://github.com/mhutchie/vscode-git-graph/issues" - }, - "qna": "https://github.com/mhutchie/vscode-git-graph/wiki/Support-Resources", - "license": "SEE LICENSE IN 'LICENSE'", - "icon": "resources/icon.png", - "engines": { - "vscode": "^1.38.0" - }, - "extensionKind": [ - "workspace" - ], - "activationEvents": [ - "*" - ], - "main": "./out/extension.js", - "contributes": { - "commands": [ - { - "category": "Git Graph", - "command": "git-graph.view", - "title": "View Git Graph (git log)", - "icon": { - "light": "resources/cmd-icon-light.svg", - "dark": "resources/cmd-icon-dark.svg" - } - }, - { - "category": "Git Graph", - "command": "git-graph.addGitRepository", - "title": "Add Git Repository..." - }, - { - "category": "Git Graph", - "command": "git-graph.clearAvatarCache", - "title": "Clear Avatar Cache" - }, - { - "category": "Git Graph", - "command": "git-graph.clearCICDCache", - "title": "Clear CI/CD Status Cache" - }, - { - "category": "Git Graph", - "command": "git-graph.endAllWorkspaceCodeReviews", - "title": "End All Code Reviews in Workspace" - }, - { - "category": "Git Graph", - "command": "git-graph.endSpecificWorkspaceCodeReview", - "title": "End a specific Code Review in Workspace..." - }, - { - "category": "Git Graph", - "command": "git-graph.fetch", - "title": "Fetch from Remote(s)" - }, - { - "category": "Git Graph", - "command": "git-graph.removeGitRepository", - "title": "Remove Git Repository..." - }, - { - "category": "Git Graph", - "command": "git-graph.resumeWorkspaceCodeReview", - "title": "Resume a specific Code Review in Workspace..." - }, - { - "category": "Git Graph", - "command": "git-graph.version", - "title": "Get Version Information" - }, - { - "category": "Git Graph", - "command": "git-graph.openFile", - "title": "Open File", - "icon": "$(go-to-file)", - "enablement": "isInDiffEditor && resourceScheme == git-graph && git-graph:codiconsSupported" - } - ], - "configuration": { - "type": "object", - "title": "Git Graph", - "properties": { - "git-graph.commitDetailsView.autoCenter": { - "type": "boolean", - "default": true, - "description": "Automatically center the Commit Details View when it is opened." - }, - "git-graph.commitDetailsView.fileView.fileTree.compactFolders": { - "type": "boolean", - "default": true, - "description": "Render the File Tree in the Commit Details View in a compacted form, such that folders with a single child folder are compressed into a single combined folder element." - }, - "git-graph.commitDetailsView.fileView.type": { - "type": "string", - "enum": [ - "File Tree", - "File List" - ], - "enumDescriptions": [ - "Display files in a tree structure.", - "Display files in a list (useful for repositories with deep folder structures)." - ], - "default": "File Tree", - "description": "Sets the default type of File View used in the Commit Details View. This can be overridden per repository using the controls on the right side of the Commit Details View." - }, - "git-graph.commitDetailsView.location": { - "type": "string", - "enum": [ - "Inline", - "Docked to Bottom" - ], - "enumDescriptions": [ - "Show the Commit Details View inline with the graph & commits.", - "Show the Commit Details View docked to the bottom of the Git Graph View." - ], - "default": "Inline", - "description": "Specifies where the Commit Details View is rendered in the Git Graph View." - }, - "git-graph.contextMenuActionsVisibility": { - "type": "object", - "default": {}, - "properties": { - "branch": { - "type": "object", - "properties": { - "checkout": { - "type": "boolean", - "title": "Checkout Branch" - }, - "rename": { - "type": "boolean", - "title": "Rename Branch..." - }, - "delete": { - "type": "boolean", - "title": "Delete Branch..." - }, - "merge": { - "type": "boolean", - "title": "Merge into current branch..." - }, - "rebase": { - "type": "boolean", - "title": "Rebase current branch on Branch..." - }, - "push": { - "type": "boolean", - "title": "Push Branch..." - }, - "createPullRequest": { - "type": "boolean", - "title": "Create Pull Request..." - }, - "createArchive": { - "type": "boolean", - "title": "Create Archive" - }, - "selectInBranchesDropdown": { - "type": "boolean", - "title": "Select in Branches Dropdown" - }, - "unselectInBranchesDropdown": { - "type": "boolean", - "title": "Unselect in Branches Dropdown" - }, - "copyName": { - "type": "boolean", - "title": "Copy Branch Name to Clipboard" - } - } - }, - "commit": { - "type": "object", - "properties": { - "addTag": { - "type": "boolean", - "title": "Add Tag..." - }, - "createBranch": { - "type": "boolean", - "title": "Create Branch..." - }, - "checkout": { - "type": "boolean", - "title": "Checkout..." - }, - "cherrypick": { - "type": "boolean", - "title": "Cherry Pick..." - }, - "revert": { - "type": "boolean", - "title": "Revert..." - }, - "drop": { - "type": "boolean", - "title": "Drop..." - }, - "merge": { - "type": "boolean", - "title": "Merge into current branch..." - }, - "rebase": { - "type": "boolean", - "title": "Rebase current branch on this Commit..." - }, - "reset": { - "type": "boolean", - "title": "Reset current branch to this Commit..." - }, - "copyHash": { - "type": "boolean", - "title": "Copy Commit Hash to Clipboard" - }, - "copySubject": { - "type": "boolean", - "title": "Copy Commit Subject to Clipboard" - } - } - }, - "remoteBranch": { - "type": "object", - "properties": { - "checkout": { - "type": "boolean", - "title": "Checkout Branch..." - }, - "delete": { - "type": "boolean", - "title": "Delete Remote Branch..." - }, - "fetch": { - "type": "boolean", - "title": "Fetch into local branch..." - }, - "merge": { - "type": "boolean", - "title": "Merge into current branch..." - }, - "pull": { - "type": "boolean", - "title": "Pull into current branch..." - }, - "createPullRequest": { - "type": "boolean", - "title": "Create Pull Request" - }, - "createArchive": { - "type": "boolean", - "title": "Create Archive" - }, - "selectInBranchesDropdown": { - "type": "boolean", - "title": "Select in Branches Dropdown" - }, - "unselectInBranchesDropdown": { - "type": "boolean", - "title": "Unselect in Branches Dropdown" - }, - "copyName": { - "type": "boolean", - "title": "Copy Branch Name to Clipboard" - } - } - }, - "stash": { - "type": "object", - "properties": { - "apply": { - "type": "boolean", - "title": "Apply Stash..." - }, - "createBranch": { - "type": "boolean", - "title": "Create Branch from Stash..." - }, - "pop": { - "type": "boolean", - "title": "Pop Stash..." - }, - "drop": { - "type": "boolean", - "title": "Drop Stash..." - }, - "copyName": { - "type": "boolean", - "title": "Copy Stash Name to Clipboard" - }, - "copyHash": { - "type": "boolean", - "title": "Copy Stash Hash to Clipboard" - } - } - }, - "tag": { - "type": "object", - "properties": { - "viewDetails": { - "type": "boolean", - "title": "View Details" - }, - "delete": { - "type": "boolean", - "title": "Delete Tag..." - }, - "push": { - "type": "boolean", - "title": "Push Tag..." - }, - "createArchive": { - "type": "boolean", - "title": "Create Archive" - }, - "copyName": { - "type": "boolean", - "title": "Copy Tag Name to Clipboard" - } - } - }, - "uncommittedChanges": { - "type": "object", - "properties": { - "stash": { - "type": "boolean", - "title": "Stash uncommitted changes..." - }, - "reset": { - "type": "boolean", - "title": "Reset uncommitted changes..." - }, - "clean": { - "type": "boolean", - "title": "Clean untracked files..." - }, - "openSourceControlView": { - "type": "boolean", - "title": "Open Source Control View" - } - } - } - }, - "markdownDescription": "Customise which context menu actions are visible. For example, if you want to hide the rebase action from the branch context menu, a suitable value for this setting is `{ \"branch\": { \"rebase\": false } }`. For more information of how to configure this setting, view the documentation [here](https://github.com/mhutchie/vscode-git-graph/wiki/Extension-Settings#context-menu-actions-visibility)." - }, - "git-graph.customBranchGlobPatterns": { - "type": "array", - "items": { - "type": "object", - "title": "Branch Glob Pattern", - "required": [ - "name", - "glob" - ], - "properties": { - "name": { - "type": "string", - "title": "Name of pattern", - "description": "Name used to reference the pattern in the 'Branches' dropdown" - }, - "glob": { - "type": "string", - "title": "Glob pattern", - "description": "The Glob Pattern , as used in 'git log --glob='. For example: heads/feature/*" - } - } - }, - "default": [], - "description": "An array of Custom Branch Glob Patterns to be shown in the 'Branches' dropdown. Example: [{\"name\": \"Feature Requests\", \"glob\": \"heads/feature/*\"}]" - }, - "git-graph.customEmojiShortcodeMappings": { - "type": "array", - "items": { - "type": "object", - "title": "Custom Emoji Shortcode Mapping", - "required": [ - "shortcode", - "emoji" - ], - "properties": { - "shortcode": { - "type": "string", - "title": "Emoji Shortcode", - "description": "Emoji Shortcode (e.g. \":sparkles:\")" - }, - "emoji": { - "type": "string", - "title": "Emoji", - "description": "Emoji (e.g. \"✨\")" - } - } - }, - "default": [], - "description": "An array of custom Emoji Shortcode mappings. Example: [{\"shortcode\": \":sparkles:\", \"emoji\":\"✨\"}]" - }, - "git-graph.customPullRequestProviders": { - "type": "array", - "items": { - "type": "object", - "title": "Pull Request Provider", - "required": [ - "name", - "templateUrl" - ], - "properties": { - "name": { - "type": "string", - "title": "Name of the Provider", - "description": "A unique, identifying, display name for the provider." - }, - "templateUrl": { - "type": "string", - "title": "Template URL", - "markdownDescription": "A template URL that can be used to create a Pull Request, after the $1 - $8 variables have been substituted to construct the final URL. For information on how to configure this setting, see the documentation [here](https://github.com/mhutchie/vscode-git-graph/wiki/Configuring-a-custom-Pull-Request-Provider)." - } - } - }, - "default": [], - "markdownDescription": "An array of custom Pull Request providers that can be used in the \"Pull Request Creation\" Integration. For information on how to configure this setting, see the documentation [here](https://github.com/mhutchie/vscode-git-graph/wiki/Configuring-a-custom-Pull-Request-Provider)." - }, - "git-graph.date.format": { - "type": "string", - "enum": [ - "Date & Time", - "Date Only", - "ISO Date & Time", - "ISO Date Only", - "Relative" - ], - "enumDescriptions": [ - "Show the date and time (e.g. \"24 Mar 2019 21:34\")", - "Show the date only (e.g. \"24 Mar 2019\")", - "Show the ISO date and time (e.g. \"2019-03-24 21:34\")", - "Show the ISO date only (e.g. \"2019-03-24\")", - "Show relative times (e.g. \"5 minutes ago\")" - ], - "default": "Date & Time", - "description": "Specifies the date format to be used in the \"Date\" column on the Git Graph View." - }, - "git-graph.date.type": { - "type": "string", - "enum": [ - "Author Date", - "Commit Date" - ], - "enumDescriptions": [ - "Use the author date of a commit.", - "Use the committer date of a commit." - ], - "default": "Author Date", - "description": "Specifies the date type to be displayed in the \"Date\" column on the Git Graph View." - }, - "git-graph.defaultColumnVisibility": { - "type": "object", - "properties": { - "Date": { - "type": "boolean", - "title": "Visibility of the Date column" - }, - "Author": { - "type": "boolean", - "title": "Visibility of the Author column" - }, - "Commit": { - "type": "boolean", - "title": "Visibility of the Commit column" - }, - "CICD": { - "type": "boolean", - "title": "Visibility of the CI/CD Status column" - } - }, - "default": { - "Date": true, - "Author": true, - "Commit": true, - "CICD": true - }, - "description": "An object specifying the default visibility of the Date, Author & Commit columns. Example: {\"Date\": true, \"Author\": true, \"Commit\": true}" - }, - "git-graph.dialog.addTag.pushToRemote": { - "type": "boolean", - "default": false, - "description": "Default state of the field indicating whether the tag should be pushed to a remote once it is added." - }, - "git-graph.dialog.addTag.type": { - "type": "string", - "enum": [ - "Annotated", - "Lightweight" - ], - "default": "Annotated", - "description": "Default type of the tag being added." - }, - "git-graph.dialog.applyStash.reinstateIndex": { - "type": "boolean", - "default": false, - "description": "Default state of the \"Reinstate Index\" checkbox." - }, - "git-graph.dialog.cherryPick.noCommit": { - "type": "boolean", - "default": false, - "description": "Default state of the \"No Commit\" checkbox." - }, - "git-graph.dialog.cherryPick.recordOrigin": { - "type": "boolean", - "default": false, - "description": "Default state of the \"Record Origin\" checkbox." - }, - "git-graph.dialog.createBranch.checkOut": { - "type": "boolean", - "default": false, - "description": "Default state of the \"Check out\" checkbox." - }, - "git-graph.dialog.deleteBranch.forceDelete": { - "type": "boolean", - "default": false, - "description": "Default state of the \"Force Delete\" checkbox." - }, - "git-graph.dialog.fetchIntoLocalBranch.forceFetch": { - "type": "boolean", - "default": false, - "description": "Default state of the \"Force Fetch\" checkbox." - }, - "git-graph.dialog.fetchRemote.prune": { - "type": "boolean", - "default": false, - "description": "Default state of the \"Prune\" checkbox." - }, - "git-graph.dialog.fetchRemote.pruneTags": { - "type": "boolean", - "default": false, - "description": "Default state of the \"Prune Tags\" checkbox." - }, - "git-graph.dialog.general.referenceInputSpaceSubstitution": { - "type": "string", - "enum": [ - "None", - "Hyphen", - "Underscore" - ], - "enumDescriptions": [ - "Don't replace spaces.", - "Replace space characters with hyphens, for example: \"new branch\" -> \"new-branch\".", - "Replace space characters with underscores, for example: \"new branch\" -> \"new_branch\"." - ], - "default": "None", - "description": "Specifies a substitution that is automatically performed when space characters are entered or pasted into reference inputs on dialogs (e.g. Create Branch, Add Tag, etc.)." - }, - "git-graph.dialog.merge.noCommit": { - "type": "boolean", - "default": false, - "description": "Default state of the \"No Commit\" checkbox." - }, - "git-graph.dialog.merge.noFastForward": { - "type": "boolean", - "default": true, - "description": "Default state of the \"Create a new commit even if fast-forward is possible\" checkbox." - }, - "git-graph.dialog.merge.squashCommits": { - "type": "boolean", - "default": false, - "description": "Default state of the \"Squash Commits\" checkbox." - }, - "git-graph.dialog.merge.squashMessageFormat": { - "type": "string", - "enum": [ - "Default", - "Git SQUASH_MSG" - ], - "enumDescriptions": [ - "Use the squash message generated by Git Graph.", - "Use the detailed squash message generated by Git (stored in .git/SQUASH_MSG)." - ], - "default": "Default", - "description": "Specifies the message format used for the squashed commit (when the \"Squash Commits\" option is selected)." - }, - "git-graph.dialog.popStash.reinstateIndex": { - "type": "boolean", - "default": false, - "description": "Default state of the \"Reinstate Index\" checkbox." - }, - "git-graph.dialog.pullBranch.noFastForward": { - "type": "boolean", - "default": false, - "description": "Default state of the \"Create a new commit even if fast-forward is possible\" checkbox." - }, - "git-graph.dialog.pullBranch.squashCommits": { - "type": "boolean", - "default": false, - "description": "Default state of the \"Squash Commits\" checkbox." - }, - "git-graph.dialog.pullBranch.squashMessageFormat": { - "type": "string", - "enum": [ - "Default", - "Git SQUASH_MSG" - ], - "enumDescriptions": [ - "Use the squash message generated by Git Graph.", - "Use the detailed squash message generated by Git (stored in .git/SQUASH_MSG)." - ], - "default": "Default", - "description": "Specifies the message format used for the squashed commit (when the \"Squash Commits\" option is selected)." - }, - "git-graph.dialog.rebase.ignoreDate": { - "type": "boolean", - "default": true, - "description": "Default state of the \"Ignore Date (non-interactive rebase only)\" checkbox." - }, - "git-graph.dialog.rebase.launchInteractiveRebase": { - "type": "boolean", - "default": false, - "description": "Default state of the \"Launch Interactive Rebase in new Terminal\" checkbox." - }, - "git-graph.dialog.resetCurrentBranchToCommit.mode": { - "type": "string", - "enum": [ - "Soft", - "Mixed", - "Hard" - ], - "enumDescriptions": [ - "Soft - Keep all changes, but reset head", - "Mixed - Keep working tree, but reset index", - "Hard - Discard all changes" - ], - "default": "Mixed", - "description": "Default mode to be used for the reset action." - }, - "git-graph.dialog.resetUncommittedChanges.mode": { - "type": "string", - "enum": [ - "Mixed", - "Hard" - ], - "enumDescriptions": [ - "Mixed - Keep working tree, but reset index", - "Hard - Discard all changes" - ], - "default": "Mixed", - "description": "Default mode to be used for the reset action." - }, - "git-graph.dialog.stashUncommittedChanges.includeUntracked": { - "type": "boolean", - "default": true, - "description": "Default state of the \"Include Untracked\" checkbox." - }, - "git-graph.enhancedAccessibility": { - "type": "boolean", - "default": false, - "description": "Visual file change A|M|D|R|U indicators in the Commit Details View for users with colour blindness. In the future, this setting will enable any additional accessibility related features of Git Graph that aren't enabled by default." - }, - "git-graph.fileEncoding": { - "type": "string", - "default": "utf8", - "markdownDescription": "The character set encoding used when retrieving a specific version of repository files (e.g. in the Diff View). A list of all supported encodings can be found [here](https://github.com/ashtuchkin/iconv-lite/wiki/Supported-Encodings).", - "scope": "resource" - }, - "git-graph.graph.colours": { - "type": "array", - "items": { - "type": "string", - "description": "Colour (HEX or RGB)", - "pattern": "^\\s*(#[0-9a-fA-F]{6}|#[0-9a-fA-F]{8}|rgb[a]?\\s*\\(\\d{1,3},\\s*\\d{1,3},\\s*\\d{1,3}\\))\\s*$" - }, - "default": [ - "#0085d9", - "#d9008f", - "#00d90a", - "#d98500", - "#a300d9", - "#ff0000", - "#00d9cc", - "#e138e8", - "#85d900", - "#dc5b23", - "#6f24d6", - "#ffcc00" - ], - "description": "Specifies the colours used on the graph." - }, - "git-graph.graph.style": { - "type": "string", - "enum": [ - "rounded", - "angular" - ], - "enumDescriptions": [ - "Use smooth curves when transitioning between branches on the graph.", - "Use angular lines when transitioning between branches on the graph." - ], - "default": "rounded", - "description": "Specifies the style of the graph." - }, - "git-graph.graph.uncommittedChanges": { - "type": "string", - "enum": [ - "Open Circle at the Uncommitted Changes", - "Open Circle at the Checked Out Commit" - ], - "enumDescriptions": [ - "Display the Uncommitted Changes as a grey open circle, connected to the commit referenced by HEAD with a solid grey line. The current file system's state is therefore always displayed as an open circle.", - "Display the Uncommitted Changes as a grey closed circle, connected to the commit referenced by HEAD with a dotted grey line. The commit referenced by HEAD is therefore always displayed as an open circle." - ], - "default": "Open Circle at the Uncommitted Changes", - "description": "Specifies how the Uncommitted Changes are displayed on the graph." - }, - "git-graph.integratedTerminalShell": { - "type": "string", - "default": "", - "description": "Specifies the path and filename of the Shell executable to be used by the Visual Studio Code Integrated Terminal, when it is opened by Git Graph. For example, to use Git Bash on Windows this setting would commonly be set to \"C:\\Program Files\\Git\\bin\\bash.exe\". If this setting is left blank, the default Shell is used.", - "scope": "machine" - }, - "git-graph.keyboardShortcut.find": { - "type": "string", - "enum": [ - "CTRL/CMD + A", - "CTRL/CMD + B", - "CTRL/CMD + C", - "CTRL/CMD + D", - "CTRL/CMD + E", - "CTRL/CMD + F", - "CTRL/CMD + G", - "CTRL/CMD + H", - "CTRL/CMD + I", - "CTRL/CMD + J", - "CTRL/CMD + K", - "CTRL/CMD + L", - "CTRL/CMD + M", - "CTRL/CMD + N", - "CTRL/CMD + O", - "CTRL/CMD + P", - "CTRL/CMD + Q", - "CTRL/CMD + R", - "CTRL/CMD + S", - "CTRL/CMD + T", - "CTRL/CMD + U", - "CTRL/CMD + V", - "CTRL/CMD + W", - "CTRL/CMD + X", - "CTRL/CMD + Y", - "CTRL/CMD + Z" - ], - "default": "CTRL/CMD + F", - "description": "The keybinding for the keyboard shortcut that opens the Find Widget in the Git Graph View." - }, - "git-graph.keyboardShortcut.refresh": { - "type": "string", - "enum": [ - "CTRL/CMD + A", - "CTRL/CMD + B", - "CTRL/CMD + C", - "CTRL/CMD + D", - "CTRL/CMD + E", - "CTRL/CMD + F", - "CTRL/CMD + G", - "CTRL/CMD + H", - "CTRL/CMD + I", - "CTRL/CMD + J", - "CTRL/CMD + K", - "CTRL/CMD + L", - "CTRL/CMD + M", - "CTRL/CMD + N", - "CTRL/CMD + O", - "CTRL/CMD + P", - "CTRL/CMD + Q", - "CTRL/CMD + R", - "CTRL/CMD + S", - "CTRL/CMD + T", - "CTRL/CMD + U", - "CTRL/CMD + V", - "CTRL/CMD + W", - "CTRL/CMD + X", - "CTRL/CMD + Y", - "CTRL/CMD + Z" - ], - "default": "CTRL/CMD + R", - "description": "The keybinding for the keyboard shortcut that refreshes the Git Graph View." - }, - "git-graph.keyboardShortcut.scrollToHead": { - "type": "string", - "enum": [ - "CTRL/CMD + A", - "CTRL/CMD + B", - "CTRL/CMD + C", - "CTRL/CMD + D", - "CTRL/CMD + E", - "CTRL/CMD + F", - "CTRL/CMD + G", - "CTRL/CMD + H", - "CTRL/CMD + I", - "CTRL/CMD + J", - "CTRL/CMD + K", - "CTRL/CMD + L", - "CTRL/CMD + M", - "CTRL/CMD + N", - "CTRL/CMD + O", - "CTRL/CMD + P", - "CTRL/CMD + Q", - "CTRL/CMD + R", - "CTRL/CMD + S", - "CTRL/CMD + T", - "CTRL/CMD + U", - "CTRL/CMD + V", - "CTRL/CMD + W", - "CTRL/CMD + X", - "CTRL/CMD + Y", - "CTRL/CMD + Z" - ], - "default": "CTRL/CMD + H", - "description": "The keybinding for the keyboard shortcut that scrolls the Git Graph View to be centered on the commit referenced by HEAD." - }, - "git-graph.keyboardShortcut.scrollToStash": { - "type": "string", - "enum": [ - "CTRL/CMD + A", - "CTRL/CMD + B", - "CTRL/CMD + C", - "CTRL/CMD + D", - "CTRL/CMD + E", - "CTRL/CMD + F", - "CTRL/CMD + G", - "CTRL/CMD + H", - "CTRL/CMD + I", - "CTRL/CMD + J", - "CTRL/CMD + K", - "CTRL/CMD + L", - "CTRL/CMD + M", - "CTRL/CMD + N", - "CTRL/CMD + O", - "CTRL/CMD + P", - "CTRL/CMD + Q", - "CTRL/CMD + R", - "CTRL/CMD + S", - "CTRL/CMD + T", - "CTRL/CMD + U", - "CTRL/CMD + V", - "CTRL/CMD + W", - "CTRL/CMD + X", - "CTRL/CMD + Y", - "CTRL/CMD + Z" - ], - "default": "CTRL/CMD + S", - "description": "The keybinding for the keyboard shortcut that scrolls the Git Graph View to the first (or next) stash in the loaded commits. The Shift Key Modifier can be applied to this keybinding to scroll the Git Graph View to the last (or previous) stash in the loaded commits." - }, - "git-graph.markdown": { - "type": "boolean", - "default": true, - "description": "Parse and render a frequently used subset of inline Markdown formatting rules in commit messages and tag details (bold, italics, bold & italics, and inline code blocks)." - }, - "git-graph.maxDepthOfRepoSearch": { - "type": "number", - "default": 0, - "description": "Specifies the maximum depth of subfolders to search when discovering repositories in the workspace. Note: Sub-repos are not automatically detected when searching subfolders, however they can be manually added by running the command \"Git Graph: Add Git Repository\" in the Command Palette." - }, - "git-graph.openNewTabEditorGroup": { - "type": "string", - "enum": [ - "Active", - "Beside", - "One", - "Two", - "Three", - "Four", - "Five", - "Six", - "Seven", - "Eight", - "Nine" - ], - "enumDescriptions": [ - "Open the new tab in the Active Editor Group.", - "Open the new tab beside the Active Editor Group.", - "Open the new tab in the First Editor Group.", - "Open the new tab in the Second Editor Group.", - "Open the new tab in the Third Editor Group.", - "Open the new tab in the Fourth Editor Group.", - "Open the new tab in the Fifth Editor Group.", - "Open the new tab in the Sixth Editor Group.", - "Open the new tab in the Seventh Editor Group.", - "Open the new tab in the Eighth Editor Group.", - "Open the new tab in the Ninth Editor Group." - ], - "default": "Active", - "description": "Specifies the Editor Group where Git Graph should open new tabs, when performing the following actions from the Git Graph View: Viewing the Visual Studio Code Diff View, Opening a File, Viewing a File at a Specific Revision." - }, - "git-graph.openToTheRepoOfTheActiveTextEditorDocument": { - "type": "boolean", - "default": false, - "description": "Open the Git Graph View to the repository containing the active Text Editor document." - }, - "git-graph.referenceLabels.alignment": { - "type": "string", - "enum": [ - "Normal", - "Branches (on the left) & Tags (on the right)", - "Branches (aligned to the graph) & Tags (on the right)" - ], - "enumDescriptions": [ - "Show branch & tag labels on the left of the commit message in the 'Description' column.", - "Show branch labels on the left of the commit message in the 'Description' column, and tag labels on the right.", - "Show branch labels aligned to the graph in the 'Graph' column, and tag labels on the right in the 'Description' column." - ], - "default": "Normal", - "description": "Specifies how branch and tag reference labels are aligned for each commit." - }, - "git-graph.referenceLabels.combineLocalAndRemoteBranchLabels": { - "type": "boolean", - "default": true, - "description": "Combine local and remote branch labels if they refer to the same branch, and are on the same commit." - }, - "git-graph.repository.commits.fetchAvatars": { - "type": "boolean", - "default": false, - "description": "Fetch avatars of commit authors and committers. By enabling this setting, you consent to commit author and committer email addresses being sent GitHub, GitLab or Gravatar, depending on the repositories remote origin." - }, - "git-graph.repository.commits.fetchCICDs": { - "type": "boolean", - "default": true, +{ + "name": "git-graph", + "displayName": "Git Graph", + "version": "1.29.0", + "publisher": "mhutchie", + "author": { + "name": "Michael Hutchison", + "email": "mhutchie@16right.com" + }, + "description": "View a Git Graph of your repository, and perform Git actions from the graph.", + "keywords": [ + "git", + "graph", + "visualise", + "diff", + "action" + ], + "categories": [ + "Other" + ], + "homepage": "https://github.com/mhutchie/vscode-git-graph", + "repository": { + "type": "git", + "url": "https://github.com/mhutchie/vscode-git-graph.git" + }, + "bugs": { + "url": "https://github.com/mhutchie/vscode-git-graph/issues" + }, + "qna": "https://github.com/mhutchie/vscode-git-graph/wiki/Support-Resources", + "license": "SEE LICENSE IN 'LICENSE'", + "icon": "resources/icon.png", + "engines": { + "vscode": "^1.38.0" + }, + "extensionKind": [ + "workspace" + ], + "activationEvents": [ + "*" + ], + "main": "./out/extension.js", + "contributes": { + "commands": [ + { + "category": "Git Graph", + "command": "git-graph.view", + "title": "View Git Graph (git log)", + "icon": { + "light": "resources/cmd-icon-light.svg", + "dark": "resources/cmd-icon-dark.svg" + } + }, + { + "category": "Git Graph", + "command": "git-graph.addGitRepository", + "title": "Add Git Repository..." + }, + { + "category": "Git Graph", + "command": "git-graph.clearAvatarCache", + "title": "Clear Avatar Cache" + }, + { + "category": "Git Graph", + "command": "git-graph.clearCICDCache", + "title": "Clear CI/CD Status Cache" + }, + { + "category": "Git Graph", + "command": "git-graph.endAllWorkspaceCodeReviews", + "title": "End All Code Reviews in Workspace" + }, + { + "category": "Git Graph", + "command": "git-graph.endSpecificWorkspaceCodeReview", + "title": "End a specific Code Review in Workspace..." + }, + { + "category": "Git Graph", + "command": "git-graph.fetch", + "title": "Fetch from Remote(s)" + }, + { + "category": "Git Graph", + "command": "git-graph.removeGitRepository", + "title": "Remove Git Repository..." + }, + { + "category": "Git Graph", + "command": "git-graph.resumeWorkspaceCodeReview", + "title": "Resume a specific Code Review in Workspace..." + }, + { + "category": "Git Graph", + "command": "git-graph.version", + "title": "Get Version Information" + }, + { + "category": "Git Graph", + "command": "git-graph.openFile", + "title": "Open File", + "icon": "$(go-to-file)", + "enablement": "isInDiffEditor && resourceScheme == git-graph && git-graph:codiconsSupported" + } + ], + "configuration": { + "type": "object", + "title": "Git Graph", + "properties": { + "git-graph.commitDetailsView.autoCenter": { + "type": "boolean", + "default": true, + "description": "Automatically center the Commit Details View when it is opened." + }, + "git-graph.commitDetailsView.fileView.fileTree.compactFolders": { + "type": "boolean", + "default": true, + "description": "Render the File Tree in the Commit Details View in a compacted form, such that folders with a single child folder are compressed into a single combined folder element." + }, + "git-graph.commitDetailsView.fileView.type": { + "type": "string", + "enum": [ + "File Tree", + "File List" + ], + "enumDescriptions": [ + "Display files in a tree structure.", + "Display files in a list (useful for repositories with deep folder structures)." + ], + "default": "File Tree", + "description": "Sets the default type of File View used in the Commit Details View. This can be overridden per repository using the controls on the right side of the Commit Details View." + }, + "git-graph.commitDetailsView.location": { + "type": "string", + "enum": [ + "Inline", + "Docked to Bottom" + ], + "enumDescriptions": [ + "Show the Commit Details View inline with the graph & commits.", + "Show the Commit Details View docked to the bottom of the Git Graph View." + ], + "default": "Inline", + "description": "Specifies where the Commit Details View is rendered in the Git Graph View." + }, + "git-graph.contextMenuActionsVisibility": { + "type": "object", + "default": {}, + "properties": { + "branch": { + "type": "object", + "properties": { + "checkout": { + "type": "boolean", + "title": "Checkout Branch" + }, + "rename": { + "type": "boolean", + "title": "Rename Branch..." + }, + "delete": { + "type": "boolean", + "title": "Delete Branch..." + }, + "merge": { + "type": "boolean", + "title": "Merge into current branch..." + }, + "rebase": { + "type": "boolean", + "title": "Rebase current branch on Branch..." + }, + "push": { + "type": "boolean", + "title": "Push Branch..." + }, + "createPullRequest": { + "type": "boolean", + "title": "Create Pull Request..." + }, + "createArchive": { + "type": "boolean", + "title": "Create Archive" + }, + "selectInBranchesDropdown": { + "type": "boolean", + "title": "Select in Branches Dropdown" + }, + "unselectInBranchesDropdown": { + "type": "boolean", + "title": "Unselect in Branches Dropdown" + }, + "copyName": { + "type": "boolean", + "title": "Copy Branch Name to Clipboard" + } + } + }, + "commit": { + "type": "object", + "properties": { + "addTag": { + "type": "boolean", + "title": "Add Tag..." + }, + "createBranch": { + "type": "boolean", + "title": "Create Branch..." + }, + "checkout": { + "type": "boolean", + "title": "Checkout..." + }, + "cherrypick": { + "type": "boolean", + "title": "Cherry Pick..." + }, + "revert": { + "type": "boolean", + "title": "Revert..." + }, + "drop": { + "type": "boolean", + "title": "Drop..." + }, + "merge": { + "type": "boolean", + "title": "Merge into current branch..." + }, + "rebase": { + "type": "boolean", + "title": "Rebase current branch on this Commit..." + }, + "reset": { + "type": "boolean", + "title": "Reset current branch to this Commit..." + }, + "copyHash": { + "type": "boolean", + "title": "Copy Commit Hash to Clipboard" + }, + "copySubject": { + "type": "boolean", + "title": "Copy Commit Subject to Clipboard" + } + } + }, + "remoteBranch": { + "type": "object", + "properties": { + "checkout": { + "type": "boolean", + "title": "Checkout Branch..." + }, + "delete": { + "type": "boolean", + "title": "Delete Remote Branch..." + }, + "fetch": { + "type": "boolean", + "title": "Fetch into local branch..." + }, + "merge": { + "type": "boolean", + "title": "Merge into current branch..." + }, + "pull": { + "type": "boolean", + "title": "Pull into current branch..." + }, + "createPullRequest": { + "type": "boolean", + "title": "Create Pull Request" + }, + "createArchive": { + "type": "boolean", + "title": "Create Archive" + }, + "selectInBranchesDropdown": { + "type": "boolean", + "title": "Select in Branches Dropdown" + }, + "unselectInBranchesDropdown": { + "type": "boolean", + "title": "Unselect in Branches Dropdown" + }, + "copyName": { + "type": "boolean", + "title": "Copy Branch Name to Clipboard" + } + } + }, + "stash": { + "type": "object", + "properties": { + "apply": { + "type": "boolean", + "title": "Apply Stash..." + }, + "createBranch": { + "type": "boolean", + "title": "Create Branch from Stash..." + }, + "pop": { + "type": "boolean", + "title": "Pop Stash..." + }, + "drop": { + "type": "boolean", + "title": "Drop Stash..." + }, + "copyName": { + "type": "boolean", + "title": "Copy Stash Name to Clipboard" + }, + "copyHash": { + "type": "boolean", + "title": "Copy Stash Hash to Clipboard" + } + } + }, + "tag": { + "type": "object", + "properties": { + "viewDetails": { + "type": "boolean", + "title": "View Details" + }, + "delete": { + "type": "boolean", + "title": "Delete Tag..." + }, + "push": { + "type": "boolean", + "title": "Push Tag..." + }, + "createArchive": { + "type": "boolean", + "title": "Create Archive" + }, + "copyName": { + "type": "boolean", + "title": "Copy Tag Name to Clipboard" + } + } + }, + "uncommittedChanges": { + "type": "object", + "properties": { + "stash": { + "type": "boolean", + "title": "Stash uncommitted changes..." + }, + "reset": { + "type": "boolean", + "title": "Reset uncommitted changes..." + }, + "clean": { + "type": "boolean", + "title": "Clean untracked files..." + }, + "openSourceControlView": { + "type": "boolean", + "title": "Open Source Control View" + } + } + } + }, + "markdownDescription": "Customise which context menu actions are visible. For example, if you want to hide the rebase action from the branch context menu, a suitable value for this setting is `{ \"branch\": { \"rebase\": false } }`. For more information of how to configure this setting, view the documentation [here](https://github.com/mhutchie/vscode-git-graph/wiki/Extension-Settings#context-menu-actions-visibility)." + }, + "git-graph.customBranchGlobPatterns": { + "type": "array", + "items": { + "type": "object", + "title": "Branch Glob Pattern", + "required": [ + "name", + "glob" + ], + "properties": { + "name": { + "type": "string", + "title": "Name of pattern", + "description": "Name used to reference the pattern in the 'Branches' dropdown" + }, + "glob": { + "type": "string", + "title": "Glob pattern", + "description": "The Glob Pattern , as used in 'git log --glob='. For example: heads/feature/*" + } + } + }, + "default": [], + "description": "An array of Custom Branch Glob Patterns to be shown in the 'Branches' dropdown. Example: [{\"name\": \"Feature Requests\", \"glob\": \"heads/feature/*\"}]" + }, + "git-graph.customEmojiShortcodeMappings": { + "type": "array", + "items": { + "type": "object", + "title": "Custom Emoji Shortcode Mapping", + "required": [ + "shortcode", + "emoji" + ], + "properties": { + "shortcode": { + "type": "string", + "title": "Emoji Shortcode", + "description": "Emoji Shortcode (e.g. \":sparkles:\")" + }, + "emoji": { + "type": "string", + "title": "Emoji", + "description": "Emoji (e.g. \"✨\")" + } + } + }, + "default": [], + "description": "An array of custom Emoji Shortcode mappings. Example: [{\"shortcode\": \":sparkles:\", \"emoji\":\"✨\"}]" + }, + "git-graph.customPullRequestProviders": { + "type": "array", + "items": { + "type": "object", + "title": "Pull Request Provider", + "required": [ + "name", + "templateUrl" + ], + "properties": { + "name": { + "type": "string", + "title": "Name of the Provider", + "description": "A unique, identifying, display name for the provider." + }, + "templateUrl": { + "type": "string", + "title": "Template URL", + "markdownDescription": "A template URL that can be used to create a Pull Request, after the $1 - $8 variables have been substituted to construct the final URL. For information on how to configure this setting, see the documentation [here](https://github.com/mhutchie/vscode-git-graph/wiki/Configuring-a-custom-Pull-Request-Provider)." + } + } + }, + "default": [], + "markdownDescription": "An array of custom Pull Request providers that can be used in the \"Pull Request Creation\" Integration. For information on how to configure this setting, see the documentation [here](https://github.com/mhutchie/vscode-git-graph/wiki/Configuring-a-custom-Pull-Request-Provider)." + }, + "git-graph.date.format": { + "type": "string", + "enum": [ + "Date & Time", + "Date Only", + "ISO Date & Time", + "ISO Date Only", + "Relative" + ], + "enumDescriptions": [ + "Show the date and time (e.g. \"24 Mar 2019 21:34\")", + "Show the date only (e.g. \"24 Mar 2019\")", + "Show the ISO date and time (e.g. \"2019-03-24 21:34\")", + "Show the ISO date only (e.g. \"2019-03-24\")", + "Show relative times (e.g. \"5 minutes ago\")" + ], + "default": "Date & Time", + "description": "Specifies the date format to be used in the \"Date\" column on the Git Graph View." + }, + "git-graph.date.type": { + "type": "string", + "enum": [ + "Author Date", + "Commit Date" + ], + "enumDescriptions": [ + "Use the author date of a commit.", + "Use the committer date of a commit." + ], + "default": "Author Date", + "description": "Specifies the date type to be displayed in the \"Date\" column on the Git Graph View." + }, + "git-graph.defaultColumnVisibility": { + "type": "object", + "properties": { + "Date": { + "type": "boolean", + "title": "Visibility of the Date column" + }, + "Author": { + "type": "boolean", + "title": "Visibility of the Author column" + }, + "Commit": { + "type": "boolean", + "title": "Visibility of the Commit column" + }, + "CICD": { + "type": "boolean", + "title": "Visibility of the CI/CD Status column" + } + }, + "default": { + "Date": true, + "Author": true, + "Commit": true, + "CICD": true + }, + "description": "An object specifying the default visibility of the Date, Author & Commit columns. Example: {\"Date\": true, \"Author\": true, \"Commit\": true}" + }, + "git-graph.dialog.addTag.pushToRemote": { + "type": "boolean", + "default": false, + "description": "Default state of the field indicating whether the tag should be pushed to a remote once it is added." + }, + "git-graph.dialog.addTag.type": { + "type": "string", + "enum": [ + "Annotated", + "Lightweight" + ], + "default": "Annotated", + "description": "Default type of the tag being added." + }, + "git-graph.dialog.applyStash.reinstateIndex": { + "type": "boolean", + "default": false, + "description": "Default state of the \"Reinstate Index\" checkbox." + }, + "git-graph.dialog.cherryPick.noCommit": { + "type": "boolean", + "default": false, + "description": "Default state of the \"No Commit\" checkbox." + }, + "git-graph.dialog.cherryPick.recordOrigin": { + "type": "boolean", + "default": false, + "description": "Default state of the \"Record Origin\" checkbox." + }, + "git-graph.dialog.createBranch.checkOut": { + "type": "boolean", + "default": false, + "description": "Default state of the \"Check out\" checkbox." + }, + "git-graph.dialog.deleteBranch.forceDelete": { + "type": "boolean", + "default": false, + "description": "Default state of the \"Force Delete\" checkbox." + }, + "git-graph.dialog.fetchIntoLocalBranch.forceFetch": { + "type": "boolean", + "default": false, + "description": "Default state of the \"Force Fetch\" checkbox." + }, + "git-graph.dialog.fetchRemote.prune": { + "type": "boolean", + "default": false, + "description": "Default state of the \"Prune\" checkbox." + }, + "git-graph.dialog.fetchRemote.pruneTags": { + "type": "boolean", + "default": false, + "description": "Default state of the \"Prune Tags\" checkbox." + }, + "git-graph.dialog.general.referenceInputSpaceSubstitution": { + "type": "string", + "enum": [ + "None", + "Hyphen", + "Underscore" + ], + "enumDescriptions": [ + "Don't replace spaces.", + "Replace space characters with hyphens, for example: \"new branch\" -> \"new-branch\".", + "Replace space characters with underscores, for example: \"new branch\" -> \"new_branch\"." + ], + "default": "None", + "description": "Specifies a substitution that is automatically performed when space characters are entered or pasted into reference inputs on dialogs (e.g. Create Branch, Add Tag, etc.)." + }, + "git-graph.dialog.merge.noCommit": { + "type": "boolean", + "default": false, + "description": "Default state of the \"No Commit\" checkbox." + }, + "git-graph.dialog.merge.noFastForward": { + "type": "boolean", + "default": true, + "description": "Default state of the \"Create a new commit even if fast-forward is possible\" checkbox." + }, + "git-graph.dialog.merge.squashCommits": { + "type": "boolean", + "default": false, + "description": "Default state of the \"Squash Commits\" checkbox." + }, + "git-graph.dialog.merge.squashMessageFormat": { + "type": "string", + "enum": [ + "Default", + "Git SQUASH_MSG" + ], + "enumDescriptions": [ + "Use the squash message generated by Git Graph.", + "Use the detailed squash message generated by Git (stored in .git/SQUASH_MSG)." + ], + "default": "Default", + "description": "Specifies the message format used for the squashed commit (when the \"Squash Commits\" option is selected)." + }, + "git-graph.dialog.popStash.reinstateIndex": { + "type": "boolean", + "default": false, + "description": "Default state of the \"Reinstate Index\" checkbox." + }, + "git-graph.dialog.pullBranch.noFastForward": { + "type": "boolean", + "default": false, + "description": "Default state of the \"Create a new commit even if fast-forward is possible\" checkbox." + }, + "git-graph.dialog.pullBranch.squashCommits": { + "type": "boolean", + "default": false, + "description": "Default state of the \"Squash Commits\" checkbox." + }, + "git-graph.dialog.pullBranch.squashMessageFormat": { + "type": "string", + "enum": [ + "Default", + "Git SQUASH_MSG" + ], + "enumDescriptions": [ + "Use the squash message generated by Git Graph.", + "Use the detailed squash message generated by Git (stored in .git/SQUASH_MSG)." + ], + "default": "Default", + "description": "Specifies the message format used for the squashed commit (when the \"Squash Commits\" option is selected)." + }, + "git-graph.dialog.rebase.ignoreDate": { + "type": "boolean", + "default": true, + "description": "Default state of the \"Ignore Date (non-interactive rebase only)\" checkbox." + }, + "git-graph.dialog.rebase.launchInteractiveRebase": { + "type": "boolean", + "default": false, + "description": "Default state of the \"Launch Interactive Rebase in new Terminal\" checkbox." + }, + "git-graph.dialog.resetCurrentBranchToCommit.mode": { + "type": "string", + "enum": [ + "Soft", + "Mixed", + "Hard" + ], + "enumDescriptions": [ + "Soft - Keep all changes, but reset head", + "Mixed - Keep working tree, but reset index", + "Hard - Discard all changes" + ], + "default": "Mixed", + "description": "Default mode to be used for the reset action." + }, + "git-graph.dialog.resetUncommittedChanges.mode": { + "type": "string", + "enum": [ + "Mixed", + "Hard" + ], + "enumDescriptions": [ + "Mixed - Keep working tree, but reset index", + "Hard - Discard all changes" + ], + "default": "Mixed", + "description": "Default mode to be used for the reset action." + }, + "git-graph.dialog.stashUncommittedChanges.includeUntracked": { + "type": "boolean", + "default": true, + "description": "Default state of the \"Include Untracked\" checkbox." + }, + "git-graph.enhancedAccessibility": { + "type": "boolean", + "default": false, + "description": "Visual file change A|M|D|R|U indicators in the Commit Details View for users with colour blindness. In the future, this setting will enable any additional accessibility related features of Git Graph that aren't enabled by default." + }, + "git-graph.fileEncoding": { + "type": "string", + "default": "utf8", + "markdownDescription": "The character set encoding used when retrieving a specific version of repository files (e.g. in the Diff View). A list of all supported encodings can be found [here](https://github.com/ashtuchkin/iconv-lite/wiki/Supported-Encodings).", + "scope": "resource" + }, + "git-graph.graph.colours": { + "type": "array", + "items": { + "type": "string", + "description": "Colour (HEX or RGB)", + "pattern": "^\\s*(#[0-9a-fA-F]{6}|#[0-9a-fA-F]{8}|rgb[a]?\\s*\\(\\d{1,3},\\s*\\d{1,3},\\s*\\d{1,3}\\))\\s*$" + }, + "default": [ + "#0085d9", + "#d9008f", + "#00d90a", + "#d98500", + "#a300d9", + "#ff0000", + "#00d9cc", + "#e138e8", + "#85d900", + "#dc5b23", + "#6f24d6", + "#ffcc00" + ], + "description": "Specifies the colours used on the graph." + }, + "git-graph.graph.style": { + "type": "string", + "enum": [ + "rounded", + "angular" + ], + "enumDescriptions": [ + "Use smooth curves when transitioning between branches on the graph.", + "Use angular lines when transitioning between branches on the graph." + ], + "default": "rounded", + "description": "Specifies the style of the graph." + }, + "git-graph.graph.uncommittedChanges": { + "type": "string", + "enum": [ + "Open Circle at the Uncommitted Changes", + "Open Circle at the Checked Out Commit" + ], + "enumDescriptions": [ + "Display the Uncommitted Changes as a grey open circle, connected to the commit referenced by HEAD with a solid grey line. The current file system's state is therefore always displayed as an open circle.", + "Display the Uncommitted Changes as a grey closed circle, connected to the commit referenced by HEAD with a dotted grey line. The commit referenced by HEAD is therefore always displayed as an open circle." + ], + "default": "Open Circle at the Uncommitted Changes", + "description": "Specifies how the Uncommitted Changes are displayed on the graph." + }, + "git-graph.integratedTerminalShell": { + "type": "string", + "default": "", + "description": "Specifies the path and filename of the Shell executable to be used by the Visual Studio Code Integrated Terminal, when it is opened by Git Graph. For example, to use Git Bash on Windows this setting would commonly be set to \"C:\\Program Files\\Git\\bin\\bash.exe\". If this setting is left blank, the default Shell is used.", + "scope": "machine" + }, + "git-graph.keyboardShortcut.find": { + "type": "string", + "enum": [ + "CTRL/CMD + A", + "CTRL/CMD + B", + "CTRL/CMD + C", + "CTRL/CMD + D", + "CTRL/CMD + E", + "CTRL/CMD + F", + "CTRL/CMD + G", + "CTRL/CMD + H", + "CTRL/CMD + I", + "CTRL/CMD + J", + "CTRL/CMD + K", + "CTRL/CMD + L", + "CTRL/CMD + M", + "CTRL/CMD + N", + "CTRL/CMD + O", + "CTRL/CMD + P", + "CTRL/CMD + Q", + "CTRL/CMD + R", + "CTRL/CMD + S", + "CTRL/CMD + T", + "CTRL/CMD + U", + "CTRL/CMD + V", + "CTRL/CMD + W", + "CTRL/CMD + X", + "CTRL/CMD + Y", + "CTRL/CMD + Z" + ], + "default": "CTRL/CMD + F", + "description": "The keybinding for the keyboard shortcut that opens the Find Widget in the Git Graph View." + }, + "git-graph.keyboardShortcut.refresh": { + "type": "string", + "enum": [ + "CTRL/CMD + A", + "CTRL/CMD + B", + "CTRL/CMD + C", + "CTRL/CMD + D", + "CTRL/CMD + E", + "CTRL/CMD + F", + "CTRL/CMD + G", + "CTRL/CMD + H", + "CTRL/CMD + I", + "CTRL/CMD + J", + "CTRL/CMD + K", + "CTRL/CMD + L", + "CTRL/CMD + M", + "CTRL/CMD + N", + "CTRL/CMD + O", + "CTRL/CMD + P", + "CTRL/CMD + Q", + "CTRL/CMD + R", + "CTRL/CMD + S", + "CTRL/CMD + T", + "CTRL/CMD + U", + "CTRL/CMD + V", + "CTRL/CMD + W", + "CTRL/CMD + X", + "CTRL/CMD + Y", + "CTRL/CMD + Z" + ], + "default": "CTRL/CMD + R", + "description": "The keybinding for the keyboard shortcut that refreshes the Git Graph View." + }, + "git-graph.keyboardShortcut.scrollToHead": { + "type": "string", + "enum": [ + "CTRL/CMD + A", + "CTRL/CMD + B", + "CTRL/CMD + C", + "CTRL/CMD + D", + "CTRL/CMD + E", + "CTRL/CMD + F", + "CTRL/CMD + G", + "CTRL/CMD + H", + "CTRL/CMD + I", + "CTRL/CMD + J", + "CTRL/CMD + K", + "CTRL/CMD + L", + "CTRL/CMD + M", + "CTRL/CMD + N", + "CTRL/CMD + O", + "CTRL/CMD + P", + "CTRL/CMD + Q", + "CTRL/CMD + R", + "CTRL/CMD + S", + "CTRL/CMD + T", + "CTRL/CMD + U", + "CTRL/CMD + V", + "CTRL/CMD + W", + "CTRL/CMD + X", + "CTRL/CMD + Y", + "CTRL/CMD + Z" + ], + "default": "CTRL/CMD + H", + "description": "The keybinding for the keyboard shortcut that scrolls the Git Graph View to be centered on the commit referenced by HEAD." + }, + "git-graph.keyboardShortcut.scrollToStash": { + "type": "string", + "enum": [ + "CTRL/CMD + A", + "CTRL/CMD + B", + "CTRL/CMD + C", + "CTRL/CMD + D", + "CTRL/CMD + E", + "CTRL/CMD + F", + "CTRL/CMD + G", + "CTRL/CMD + H", + "CTRL/CMD + I", + "CTRL/CMD + J", + "CTRL/CMD + K", + "CTRL/CMD + L", + "CTRL/CMD + M", + "CTRL/CMD + N", + "CTRL/CMD + O", + "CTRL/CMD + P", + "CTRL/CMD + Q", + "CTRL/CMD + R", + "CTRL/CMD + S", + "CTRL/CMD + T", + "CTRL/CMD + U", + "CTRL/CMD + V", + "CTRL/CMD + W", + "CTRL/CMD + X", + "CTRL/CMD + Y", + "CTRL/CMD + Z" + ], + "default": "CTRL/CMD + S", + "description": "The keybinding for the keyboard shortcut that scrolls the Git Graph View to the first (or next) stash in the loaded commits. The Shift Key Modifier can be applied to this keybinding to scroll the Git Graph View to the last (or previous) stash in the loaded commits." + }, + "git-graph.markdown": { + "type": "boolean", + "default": true, + "description": "Parse and render a frequently used subset of inline Markdown formatting rules in commit messages and tag details (bold, italics, bold & italics, and inline code blocks)." + }, + "git-graph.maxDepthOfRepoSearch": { + "type": "number", + "default": 0, + "description": "Specifies the maximum depth of subfolders to search when discovering repositories in the workspace. Note: Sub-repos are not automatically detected when searching subfolders, however they can be manually added by running the command \"Git Graph: Add Git Repository\" in the Command Palette." + }, + "git-graph.openNewTabEditorGroup": { + "type": "string", + "enum": [ + "Active", + "Beside", + "One", + "Two", + "Three", + "Four", + "Five", + "Six", + "Seven", + "Eight", + "Nine" + ], + "enumDescriptions": [ + "Open the new tab in the Active Editor Group.", + "Open the new tab beside the Active Editor Group.", + "Open the new tab in the First Editor Group.", + "Open the new tab in the Second Editor Group.", + "Open the new tab in the Third Editor Group.", + "Open the new tab in the Fourth Editor Group.", + "Open the new tab in the Fifth Editor Group.", + "Open the new tab in the Sixth Editor Group.", + "Open the new tab in the Seventh Editor Group.", + "Open the new tab in the Eighth Editor Group.", + "Open the new tab in the Ninth Editor Group." + ], + "default": "Active", + "description": "Specifies the Editor Group where Git Graph should open new tabs, when performing the following actions from the Git Graph View: Viewing the Visual Studio Code Diff View, Opening a File, Viewing a File at a Specific Revision." + }, + "git-graph.openToTheRepoOfTheActiveTextEditorDocument": { + "type": "boolean", + "default": false, + "description": "Open the Git Graph View to the repository containing the active Text Editor document." + }, + "git-graph.referenceLabels.alignment": { + "type": "string", + "enum": [ + "Normal", + "Branches (on the left) & Tags (on the right)", + "Branches (aligned to the graph) & Tags (on the right)" + ], + "enumDescriptions": [ + "Show branch & tag labels on the left of the commit message in the 'Description' column.", + "Show branch labels on the left of the commit message in the 'Description' column, and tag labels on the right.", + "Show branch labels aligned to the graph in the 'Graph' column, and tag labels on the right in the 'Description' column." + ], + "default": "Normal", + "description": "Specifies how branch and tag reference labels are aligned for each commit." + }, + "git-graph.referenceLabels.combineLocalAndRemoteBranchLabels": { + "type": "boolean", + "default": true, + "description": "Combine local and remote branch labels if they refer to the same branch, and are on the same commit." + }, + "git-graph.repository.commits.fetchAvatars": { + "type": "boolean", + "default": false, + "description": "Fetch avatars of commit authors and committers. By enabling this setting, you consent to commit author and committer email addresses being sent GitHub, GitLab or Gravatar, depending on the repositories remote origin." + }, + "git-graph.repository.commits.fetchCICDs": { + "type": "boolean", + "default": true, "description": "Fetch CI/CD status of commit. By enabling this setting, you consent to Access Token being sent GitHub or GitLab, depending on the repositories and user configuration." - }, - "git-graph.repository.commits.initialLoad": { - "type": "number", - "default": 300, - "description": "Specifies the number of commits to initially load." - }, - "git-graph.repository.commits.loadMore": { - "type": "number", - "default": 100, - "description": "Specifies the number of additional commits to load when the \"Load More Commits\" button is pressed, or more commits are automatically loaded." - }, - "git-graph.repository.commits.loadMoreAutomatically": { - "type": "boolean", - "default": true, - "description": "When the view has been scrolled to the bottom, automatically load more commits if they exist (instead of having to press the \"Load More Commits\" button)." - }, - "git-graph.repository.commits.mute.commitsThatAreNotAncestorsOfHead": { - "type": "boolean", - "default": false, - "description": "Display commits that aren't ancestors of the checked-out branch / commit with a muted text color. Muting will only occur if the commit referenced by HEAD is within the loaded commits on the Git Graph View." - }, - "git-graph.repository.commits.mute.mergeCommits": { - "type": "boolean", - "default": true, - "description": "Display merge commits with a muted text color." - }, - "git-graph.repository.commits.order": { - "type": "string", - "enum": [ - "date", - "author-date", - "topo" - ], - "enumDescriptions": [ - "Show commits in the commit timestamp order.", - "Show commits in the author timestamp order.", - "Avoid showing commits on multiple lines of history intermixed." - ], - "default": "date", - "markdownDescription": "Specifies the order of commits on the Git Graph View. See [git log](https://git-scm.com/docs/git-log#_commit_ordering) for more information on each order option. This can be overridden per repository via the Git Graph View's Column Header Context Menu." - }, - "git-graph.repository.commits.showSignatureStatus": { - "type": "boolean", - "default": false, - "description": "Show the commit's signature status to the right of the Committer in the Commit Details View (only for signed commits). Hovering over the signature icon displays a tooltip with the signature details. Requires Git (>= 2.4.0) & GPG (or equivalent) to be installed on the same machine that is running Visual Studio Code." - }, - "git-graph.repository.fetchAndPrune": { - "type": "boolean", - "default": false, - "description": "Before fetching from remote(s) using the Fetch button on the Git Graph View Control Bar, remove any remote-tracking references that no longer exist on the remote(s)." - }, - "git-graph.repository.fetchAndPruneTags": { - "type": "boolean", - "default": false, - "description": "Before fetching from remote(s) using the Fetch button on the Git Graph View Control Bar, remove any local tags that no longer exist on the remote(s). Requires Git >= 2.17.0, and the \"Repository: Fetch And Prune\" setting to be enabled. Caution: If you work in repositories that have multiple remotes, it is not recommended to use this setting (instead you can prune tags for a specific remote via \"Fetch Remote\" Dialog from the Repository Settings Widget on the Git Graph View)." - }, - "git-graph.repository.includeCommitsMentionedByReflogs": { - "type": "boolean", - "default": false, - "description": "Include commits only mentioned by reflogs in the Git Graph View (only applies when showing all branches). This can be overridden per repository in the Git Graph View's Repository Settings Widget." - }, - "git-graph.repository.onLoad.scrollToHead": { - "type": "boolean", - "default": false, - "description": "Automatically scroll the Git Graph View to be centered on the commit referenced by HEAD. This will only occur if the commit referenced by HEAD is within the loaded commits on the Git Graph View." - }, - "git-graph.repository.onLoad.showCheckedOutBranch": { - "type": "boolean", - "default": false, - "description": "Show the checked out branch when a repository is loaded in the Git Graph View. This setting can be used in conjunction with \"Repository > On Load: Show Specific Branches\". Default: false (show all branches)" - }, - "git-graph.repository.onLoad.showSpecificBranches": { - "type": "array", - "items": { - "type": "string", - "description": "A local branch name (e.g. \"master\"), a remote-tracking branch name prefixed with \"remotes/\" (e.g. \"remotes/origin/master\"), or a glob pattern defined in git-graph.customBranchGlobPatterns prefixed with \"--glob=\" (e.g. \"--glob=heads/feature/*\")." - }, - "default": [], - "markdownDescription": "Show specific branches when a repository is loaded in the Git Graph View. Branches can be specified as follows: A local branch name (e.g. `master`), a remote-tracking branch name prefixed with \"remotes/\" (e.g. `remotes/origin/master`), or a glob pattern defined in `git-graph.customBranchGlobPatterns` prefixed with \"--glob=\" (e.g. `--glob=heads/feature/*`). This setting can be used in conjunction with \"Repository > On Load: Show Checked Out Branch\". Default: [] (show all branches)" - }, - "git-graph.repository.onlyFollowFirstParent": { - "type": "boolean", - "default": false, - "markdownDescription": "Only follow the first parent of commits when discovering the commits to load in the Git Graph View. See [--first-parent](https://git-scm.com/docs/git-log#Documentation/git-log.txt---first-parent) to find out more about this setting. This can be overridden per repository in the Git Graph View's Repository Settings Widget." - }, - "git-graph.repository.showCommitsOnlyReferencedByTags": { - "type": "boolean", - "default": true, - "description": "Show Commits that are only referenced by tags in Git Graph." - }, - "git-graph.repository.showRemoteBranches": { - "type": "boolean", - "default": true, - "description": "Show Remote Branches in Git Graph by default. This can be overridden per repository from the Git Graph View's Control Bar." - }, - "git-graph.repository.showRemoteHeads": { - "type": "boolean", - "default": true, - "description": "Show Remote HEAD Symbolic References in Git Graph (e.g. \"origin/HEAD\")." - }, - "git-graph.repository.showStashes": { - "type": "boolean", - "default": true, - "description": "Show Stashes in Git Graph by default. This can be overridden per repository in the Git Graph View's Repository Settings Widget." - }, - "git-graph.repository.showTags": { - "type": "boolean", - "default": true, - "description": "Show Tags in Git Graph by default. This can be overridden per repository in the Git Graph View's Repository Settings Widget." - }, - "git-graph.repository.showUncommittedChanges": { - "type": "boolean", - "default": true, - "description": "Show uncommitted changes. If you work on large repositories, disabling this setting can reduce the load time of the Git Graph View." - }, - "git-graph.repository.showUntrackedFiles": { - "type": "boolean", - "default": true, - "description": "Show untracked files when viewing the uncommitted changes. If you work on large repositories, disabling this setting can reduce the load time of the Git Graph View." - }, - "git-graph.repository.sign.commits": { - "type": "boolean", - "default": false, - "description": "Enables commit signing with GPG or X.509." - }, - "git-graph.repository.sign.tags": { - "type": "boolean", - "default": false, - "description": "Enables tag signing with GPG or X.509." - }, - "git-graph.repository.useMailmap": { - "type": "boolean", - "default": false, - "markdownDescription": "Respect [.mailmap](https://git-scm.com/docs/git-check-mailmap#_mapping_authors) files when displaying author & committer names and email addresses." - }, - "git-graph.repositoryDropdownOrder": { - "type": "string", - "enum": [ - "Full Path", - "Name", - "Workspace Full Path" - ], - "enumDescriptions": [ - "Sort repositories alphabetically by the full path of the repository.", - "Sort repositories alphabetically by the name of the repository.", - "Sort repositories according to the workspace folder order, then alphabetically by the full path of the repository." - ], - "default": "Workspace Full Path", - "description": "Specifies the order that repositories are sorted in the repository dropdown on the Git Graph View (only visible when more than one repository exists in the current Visual Studio Code Workspace)." - }, - "git-graph.retainContextWhenHidden": { - "type": "boolean", - "default": true, - "description": "Specifies if the Git Graph View's Visual Studio Code context is kept when the panel is no longer visible (e.g. moved to background tab). Enabling this setting will make Git Graph load significantly faster when switching back to the Git Graph tab, however has a higher memory overhead." - }, - "git-graph.showStatusBarItem": { - "type": "boolean", - "default": true, - "description": "Show a Status Bar Item that opens the Git Graph View when clicked." - }, - "git-graph.sourceCodeProviderIntegrationLocation": { - "type": "string", - "enum": [ - "Inline", - "More Actions" - ], - "enumDescriptions": [ - "Show the 'View Git Graph' action on the title of SCM Providers", - "Show the 'View Git Graph' action in the 'More Actions...' menu on the title of SCM Providers" - ], - "default": "Inline", - "description": "Specifies where the \"View Git Graph\" action appears on the title of SCM Providers." - }, - "git-graph.tabIconColourTheme": { - "type": "string", - "enum": [ - "colour", - "grey" - ], - "enumDescriptions": [ - "Show a colour icon which suits most Visual Studio Code colour themes", - "Show a grey icon which suits Visual Studio Code colour themes that are predominantly grayscale" - ], - "default": "colour", - "description": "Specifies the colour theme of the icon displayed on the Git Graph tab." - }, - "git-graph.autoCenterCommitDetailsView": { - "type": "boolean", - "default": true, - "description": "Automatically center the commit details view when it is opened.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.commitDetailsView.autoCenter", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.commitDetailsView.autoCenter#`" - }, - "git-graph.combineLocalAndRemoteBranchLabels": { - "type": "boolean", - "default": true, - "description": "Combine local and remote branch labels if they refer to the same branch, and are on the same commit.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.referenceLabels.combineLocalAndRemoteBranchLabels", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.referenceLabels.combineLocalAndRemoteBranchLabels#`" - }, - "git-graph.commitDetailsViewFileTreeCompactFolders": { - "type": "boolean", - "default": true, - "description": "Render the File Tree in the Commit Details / Comparison View in a compacted form, such that folders with a single child folder are compressed into a single combined folder element.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.commitDetailsView.fileView.fileTree.compactFolders", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.commitDetailsView.fileView.fileTree.compactFolders#`" - }, - "git-graph.commitDetailsViewLocation": { - "type": "string", - "enum": [ - "Inline", - "Docked to Bottom" - ], - "enumDescriptions": [ - "Show the Commit Details View inline with the graph", - "Show the Commit Details View docked to the bottom of the Git Graph view" - ], - "default": "Inline", - "description": "Specifies where the Commit Details View is rendered in the Git Graph view.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.commitDetailsView.location", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.commitDetailsView.location#`" - }, - "git-graph.commitOrdering": { - "type": "string", - "enum": [ - "date", - "author-date", - "topo" - ], - "enumDescriptions": [ - "Show commits in the commit timestamp order.", - "Show commits in the author timestamp order.", - "Avoid showing commits on multiple lines of history intermixed." - ], - "default": "date", - "markdownDescription": "Specifies the order of commits on the Git Graph view. See [git log](https://git-scm.com/docs/git-log#_commit_ordering) for more information on each order option.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.order", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.order#`" - }, - "git-graph.dateFormat": { - "type": "string", - "enum": [ - "Date & Time", - "Date Only", - "ISO Date & Time", - "ISO Date Only", - "Relative" - ], - "enumDescriptions": [ - "Show the date and time, for example \"24 Mar 2019 21:34\"", - "Show the date only, for example \"24 Mar 2019\"", - "Show the ISO date and time, for example \"2019-03-24 21:34\"", - "Show the ISO date only, for example \"2019-03-24\"", - "Show relative times, for example \"5 minutes ago\"" - ], - "default": "Date & Time", - "description": "Specifies the date format to be used in the \"Date\" column on the Git Graph View.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.date.format", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.date.format#`" - }, - "git-graph.dateType": { - "type": "string", - "enum": [ - "Author Date", - "Commit Date" - ], - "enumDescriptions": [ - "Use the author date of a commit", - "Use the committer date of a commit" - ], - "default": "Author Date", - "description": "Specifies the date type to be displayed in the \"Date\" column on the Git Graph View.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.date.type", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.date.type#`" - }, - "git-graph.defaultFileViewType": { - "type": "string", - "enum": [ - "File Tree", - "File List" - ], - "enumDescriptions": [ - "Display files in a tree structure", - "Display files in a list (useful for repositories with deep folder structures)" - ], - "default": "File Tree", - "description": "Sets the default type of File View used in the Commit Details / Comparison Views. This can be overridden per repository using the controls on the right side of the Commit Details / Comparison Views.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.commitDetailsView.fileView.type", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.commitDetailsView.fileView.type#`" - }, - "git-graph.fetchAndPrune": { - "type": "boolean", - "default": false, - "description": "Before fetching from remote(s) using the Fetch button on the Git Graph View Control Bar, remove any remote-tracking references that no longer exist on the remote(s).", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.fetchAndPrune", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.fetchAndPrune#`" - }, - "git-graph.fetchAvatars": { - "type": "boolean", - "default": false, - "description": "Fetch avatars of commit authors and committers. By enabling this setting, you consent to commit author and committer email addresses being sent GitHub, GitLab or Gravatar, depending on the repositories remote origin.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.fetchAvatars", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.fetchAvatars#`" - }, - "git-graph.fetchCICDs": { - "type": "boolean", - "default": true, - "description": "Fetch CI/CD status of commit. By enabling this setting, you consent to Access Token being sent GitHub or GitLab, depending on the repositories and user configuration.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.fetchCICDs", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.fetchCICDs#`" - }, - "git-graph.graphColours": { - "type": "array", - "items": { - "type": "string", - "description": "Colour (HEX or RGB)", - "pattern": "^\\s*(#[0-9a-fA-F]{6}|#[0-9a-fA-F]{8}|rgb[a]?\\s*\\(\\d{1,3},\\s*\\d{1,3},\\s*\\d{1,3}\\))\\s*$" - }, - "default": [ - "#0085d9", - "#d9008f", - "#00d90a", - "#d98500", - "#a300d9", - "#ff0000", - "#00d9cc", - "#e138e8", - "#85d900", - "#dc5b23", - "#6f24d6", - "#ffcc00" - ], - "description": "Specifies the colours used on the graph.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.graph.colours", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.graph.colours#`" - }, - "git-graph.graphStyle": { - "type": "string", - "enum": [ - "rounded", - "angular" - ], - "enumDescriptions": [ - "Use smooth curves when transitioning between branches on the graph", - "Use angular lines when transitioning between branches on the graph" - ], - "default": "rounded", - "description": "Specifies the style of the graph.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.graph.style", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.graph.style#`" - }, - "git-graph.includeCommitsMentionedByReflogs": { - "type": "boolean", - "default": false, - "description": "Include commits only mentioned by reflogs in the Git Graph View (only applies when showing all branches). This can be overridden per repository in the Git Graph View's Repository Settings Widget.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.includeCommitsMentionedByReflogs", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.includeCommitsMentionedByReflogs#`" - }, - "git-graph.initialLoadCommits": { - "type": "number", - "default": 300, - "description": "Specifies the number of commits to initially load.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.initialLoad", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.initialLoad#`" - }, - "git-graph.loadMoreCommits": { - "type": "number", - "default": 100, - "description": "Specifies the number of additional commits to load when the \"Load More Commits\" button is pressed, or more commits are automatically loaded.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.loadMore", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.loadMore#`" - }, - "git-graph.loadMoreCommitsAutomatically": { - "type": "boolean", - "default": true, - "description": "When the view has been scrolled to the bottom, automatically load more commits if they exist (instead of having to press the \"Load More Commits\" button).", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.loadMoreAutomatically", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.loadMoreAutomatically#`" - }, - "git-graph.muteCommitsThatAreNotAncestorsOfHead": { - "type": "boolean", - "default": false, - "description": "Display commits that aren't ancestors of the checked-out branch / commit with a muted text color. Muting will only occur if the commit referenced by HEAD is within the loaded commits on the Git Graph View.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.mute.commitsThatAreNotAncestorsOfHead", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.mute.commitsThatAreNotAncestorsOfHead#`" - }, - "git-graph.muteMergeCommits": { - "type": "boolean", - "default": true, - "description": "Display merge commits with a muted text color.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.mute.mergeCommits", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.mute.mergeCommits#`" - }, - "git-graph.onlyFollowFirstParent": { - "type": "boolean", - "default": false, - "markdownDescription": "Only follow the first parent of commits when discovering the commits to load in the Git Graph View. See [--first-parent](https://git-scm.com/docs/git-log#Documentation/git-log.txt---first-parent) to find out more about this setting.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.onlyFollowFirstParent", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.onlyFollowFirstParent#`" - }, - "git-graph.openDiffTabLocation": { - "type": "string", - "enum": [ - "Active", - "Beside", - "One", - "Two", - "Three", - "Four", - "Five", - "Six", - "Seven", - "Eight", - "Nine" - ], - "enumDescriptions": [ - "Open the Visual Studio Code Diff View in the Active Editor Group.", - "Open the Visual Studio Code Diff View beside the Active Editor Group.", - "Open the Visual Studio Code Diff View in the First Editor Group.", - "Open the Visual Studio Code Diff View in the Second Editor Group.", - "Open the Visual Studio Code Diff View in the Third Editor Group.", - "Open the Visual Studio Code Diff View in the Fourth Editor Group.", - "Open the Visual Studio Code Diff View in the Fifth Editor Group.", - "Open the Visual Studio Code Diff View in the Sixth Editor Group.", - "Open the Visual Studio Code Diff View in the Seventh Editor Group.", - "Open the Visual Studio Code Diff View in the Eighth Editor Group.", - "Open the Visual Studio Code Diff View in the Ninth Editor Group." - ], - "default": "Active", - "description": "Specifies which Editor Group the Visual Studio Code Diff View is opened in.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.openNewTabEditorGroup", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.openNewTabEditorGroup#`" - }, - "git-graph.openRepoToHead": { - "type": "boolean", - "default": false, - "description": "When opening or switching repositories in the Git Graph View, automatically scroll the view to be centered on the commit referenced by HEAD. This will only occur if the commit referenced by HEAD is within the loaded commits on the Git Graph View.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.onLoad.scrollToHead", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.onLoad.scrollToHead#`" - }, - "git-graph.referenceLabelAlignment": { - "type": "string", - "enum": [ - "Normal", - "Branches (on the left) & Tags (on the right)", - "Branches (aligned to the graph) & Tags (on the right)" - ], - "enumDescriptions": [ - "Show branch & tag labels on the left of the commit message in the 'Description' column.", - "Show branch labels on the left of the commit message in the 'Description' column, and tag labels on the right.", - "Show branch labels aligned to the graph in the 'Graph' column, and tag labels on the right in the 'Description' column." - ], - "default": "Normal", - "description": "Specifies how branch and tag reference labels are aligned for each commit.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.referenceLabels.alignment", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.referenceLabels.alignment#`" - }, - "git-graph.showCommitsOnlyReferencedByTags": { - "type": "boolean", - "default": true, - "description": "Show commits that are only referenced by tags in Git Graph.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.showCommitsOnlyReferencedByTags", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.showCommitsOnlyReferencedByTags#`" - }, - "git-graph.showCurrentBranchByDefault": { - "type": "boolean", - "default": false, - "description": "Show the current branch by default when Git Graph is opened. Default: false (show all branches)", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.onLoad.showCheckedOutBranch", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.onLoad.showCheckedOutBranch#`" - }, - "git-graph.showSignatureStatus": { - "type": "boolean", - "default": false, - "description": "Show the commit's signature status to the right of the Committer in the Commit Details View (only for signed commits). Hovering over the signature icon displays a tooltip with the signature details. Requires Git (>= 2.4.0) & GPG (or equivalent) to be installed on the same machine that is running Visual Studio Code.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.showSignatureStatus", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.showSignatureStatus#`" - }, - "git-graph.showTags": { - "type": "boolean", - "default": true, - "description": "Show Tags in Git Graph by default. This can be overridden per repository in the Git Graph View's Repository Settings Widget.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.showTags", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.showTags#`" - }, - "git-graph.showUncommittedChanges": { - "type": "boolean", - "default": true, - "description": "Show uncommitted changes. If you work on large repositories, disabling this setting can reduce the load time of the Git Graph View.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.showUncommittedChanges", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.showUncommittedChanges#`" - }, - "git-graph.showUntrackedFiles": { - "type": "boolean", - "default": true, - "description": "Show untracked files when viewing the uncommitted changes. If you work on large repositories, disabling this setting can reduce the load time of the Git Graph View.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.showUntrackedFiles", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.showUntrackedFiles#`" - }, - "git-graph.useMailmap": { - "type": "boolean", - "default": false, - "markdownDescription": "Respect [.mailmap](https://git-scm.com/docs/git-check-mailmap#_mapping_authors) files when displaying author & committer names and email addresses.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.useMailmap", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.useMailmap#`" - } - } - }, - "menus": { - "commandPalette": [ - { - "command": "git-graph.openFile", - "when": "isInDiffEditor && resourceScheme == git-graph && git-graph:codiconsSupported" - } - ], - "editor/title": [ - { - "command": "git-graph.openFile", - "group": "navigation", - "when": "isInDiffEditor && resourceScheme == git-graph && git-graph:codiconsSupported" - } - ], - "scm/title": [ - { - "when": "scmProvider == git && config.git-graph.sourceCodeProviderIntegrationLocation == 'Inline'", - "command": "git-graph.view", - "group": "navigation" - }, - { - "when": "scmProvider == git && config.git-graph.sourceCodeProviderIntegrationLocation == 'More Actions'", - "command": "git-graph.view", - "group": "inline" - } - ] - } - }, - "scripts": { - "vscode:prepublish": "npm run compile", - "vscode:uninstall": "node ./out/life-cycle/uninstall.js", - "clean": "node ./.vscode/clean.js", - "compile": "npm run lint && npm run clean && npm run compile-src && npm run compile-web", - "compile-src": "tsc -p ./src && node ./.vscode/package-src.js", - "compile-web": "tsc -p ./web && node ./.vscode/package-web.js", - "compile-web-debug": "tsc -p ./web && node ./.vscode/package-web.js debug", - "lint": "eslint -c .eslintrc.json --ext .ts ./src ./tests ./web", - "package": "npm run clean && vsce package", - "package-and-install": "npm run package && node ./.vscode/install-package.js", - "test": "jest --verbose", - "test-and-report-coverage": "jest --verbose --coverage" - }, - "dependencies": { - "iconv-lite": "0.5.0" - }, - "devDependencies": { - "@types/jest": "26.0.19", - "@types/node": "8.10.62", - "@types/vscode": "1.38.0", - "@typescript-eslint/eslint-plugin": "4.10.0", - "@typescript-eslint/parser": "4.10.0", - "eslint": "7.15.0", - "jest": "26.6.3", - "ts-jest": "26.4.4", - "typescript": "4.0.2", - "uglify-js": "3.10.0" - } -} + }, + "git-graph.repository.commits.initialLoad": { + "type": "number", + "default": 300, + "description": "Specifies the number of commits to initially load." + }, + "git-graph.repository.commits.loadMore": { + "type": "number", + "default": 100, + "description": "Specifies the number of additional commits to load when the \"Load More Commits\" button is pressed, or more commits are automatically loaded." + }, + "git-graph.repository.commits.loadMoreAutomatically": { + "type": "boolean", + "default": true, + "description": "When the view has been scrolled to the bottom, automatically load more commits if they exist (instead of having to press the \"Load More Commits\" button)." + }, + "git-graph.repository.commits.mute.commitsThatAreNotAncestorsOfHead": { + "type": "boolean", + "default": false, + "description": "Display commits that aren't ancestors of the checked-out branch / commit with a muted text color. Muting will only occur if the commit referenced by HEAD is within the loaded commits on the Git Graph View." + }, + "git-graph.repository.commits.mute.mergeCommits": { + "type": "boolean", + "default": true, + "description": "Display merge commits with a muted text color." + }, + "git-graph.repository.commits.order": { + "type": "string", + "enum": [ + "date", + "author-date", + "topo" + ], + "enumDescriptions": [ + "Show commits in the commit timestamp order.", + "Show commits in the author timestamp order.", + "Avoid showing commits on multiple lines of history intermixed." + ], + "default": "date", + "markdownDescription": "Specifies the order of commits on the Git Graph View. See [git log](https://git-scm.com/docs/git-log#_commit_ordering) for more information on each order option. This can be overridden per repository via the Git Graph View's Column Header Context Menu." + }, + "git-graph.repository.commits.showSignatureStatus": { + "type": "boolean", + "default": false, + "description": "Show the commit's signature status to the right of the Committer in the Commit Details View (only for signed commits). Hovering over the signature icon displays a tooltip with the signature details. Requires Git (>= 2.4.0) & GPG (or equivalent) to be installed on the same machine that is running Visual Studio Code." + }, + "git-graph.repository.fetchAndPrune": { + "type": "boolean", + "default": false, + "description": "Before fetching from remote(s) using the Fetch button on the Git Graph View Control Bar, remove any remote-tracking references that no longer exist on the remote(s)." + }, + "git-graph.repository.fetchAndPruneTags": { + "type": "boolean", + "default": false, + "description": "Before fetching from remote(s) using the Fetch button on the Git Graph View Control Bar, remove any local tags that no longer exist on the remote(s). Requires Git >= 2.17.0, and the \"Repository: Fetch And Prune\" setting to be enabled. Caution: If you work in repositories that have multiple remotes, it is not recommended to use this setting (instead you can prune tags for a specific remote via \"Fetch Remote\" Dialog from the Repository Settings Widget on the Git Graph View)." + }, + "git-graph.repository.includeCommitsMentionedByReflogs": { + "type": "boolean", + "default": false, + "description": "Include commits only mentioned by reflogs in the Git Graph View (only applies when showing all branches). This can be overridden per repository in the Git Graph View's Repository Settings Widget." + }, + "git-graph.repository.onLoad.scrollToHead": { + "type": "boolean", + "default": false, + "description": "Automatically scroll the Git Graph View to be centered on the commit referenced by HEAD. This will only occur if the commit referenced by HEAD is within the loaded commits on the Git Graph View." + }, + "git-graph.repository.onLoad.showCheckedOutBranch": { + "type": "boolean", + "default": false, + "description": "Show the checked out branch when a repository is loaded in the Git Graph View. This setting can be used in conjunction with \"Repository > On Load: Show Specific Branches\". Default: false (show all branches)" + }, + "git-graph.repository.onLoad.showSpecificBranches": { + "type": "array", + "items": { + "type": "string", + "description": "A local branch name (e.g. \"master\"), a remote-tracking branch name prefixed with \"remotes/\" (e.g. \"remotes/origin/master\"), or a glob pattern defined in git-graph.customBranchGlobPatterns prefixed with \"--glob=\" (e.g. \"--glob=heads/feature/*\")." + }, + "default": [], + "markdownDescription": "Show specific branches when a repository is loaded in the Git Graph View. Branches can be specified as follows: A local branch name (e.g. `master`), a remote-tracking branch name prefixed with \"remotes/\" (e.g. `remotes/origin/master`), or a glob pattern defined in `git-graph.customBranchGlobPatterns` prefixed with \"--glob=\" (e.g. `--glob=heads/feature/*`). This setting can be used in conjunction with \"Repository > On Load: Show Checked Out Branch\". Default: [] (show all branches)" + }, + "git-graph.repository.onlyFollowFirstParent": { + "type": "boolean", + "default": false, + "markdownDescription": "Only follow the first parent of commits when discovering the commits to load in the Git Graph View. See [--first-parent](https://git-scm.com/docs/git-log#Documentation/git-log.txt---first-parent) to find out more about this setting. This can be overridden per repository in the Git Graph View's Repository Settings Widget." + }, + "git-graph.repository.showCommitsOnlyReferencedByTags": { + "type": "boolean", + "default": true, + "description": "Show Commits that are only referenced by tags in Git Graph." + }, + "git-graph.repository.showRemoteBranches": { + "type": "boolean", + "default": true, + "description": "Show Remote Branches in Git Graph by default. This can be overridden per repository from the Git Graph View's Control Bar." + }, + "git-graph.repository.showRemoteHeads": { + "type": "boolean", + "default": true, + "description": "Show Remote HEAD Symbolic References in Git Graph (e.g. \"origin/HEAD\")." + }, + "git-graph.repository.showStashes": { + "type": "boolean", + "default": true, + "description": "Show Stashes in Git Graph by default. This can be overridden per repository in the Git Graph View's Repository Settings Widget." + }, + "git-graph.repository.showTags": { + "type": "boolean", + "default": true, + "description": "Show Tags in Git Graph by default. This can be overridden per repository in the Git Graph View's Repository Settings Widget." + }, + "git-graph.repository.showUncommittedChanges": { + "type": "boolean", + "default": true, + "description": "Show uncommitted changes. If you work on large repositories, disabling this setting can reduce the load time of the Git Graph View." + }, + "git-graph.repository.showUntrackedFiles": { + "type": "boolean", + "default": true, + "description": "Show untracked files when viewing the uncommitted changes. If you work on large repositories, disabling this setting can reduce the load time of the Git Graph View." + }, + "git-graph.repository.sign.commits": { + "type": "boolean", + "default": false, + "description": "Enables commit signing with GPG or X.509." + }, + "git-graph.repository.sign.tags": { + "type": "boolean", + "default": false, + "description": "Enables tag signing with GPG or X.509." + }, + "git-graph.repository.useMailmap": { + "type": "boolean", + "default": false, + "markdownDescription": "Respect [.mailmap](https://git-scm.com/docs/git-check-mailmap#_mapping_authors) files when displaying author & committer names and email addresses." + }, + "git-graph.repositoryDropdownOrder": { + "type": "string", + "enum": [ + "Full Path", + "Name", + "Workspace Full Path" + ], + "enumDescriptions": [ + "Sort repositories alphabetically by the full path of the repository.", + "Sort repositories alphabetically by the name of the repository.", + "Sort repositories according to the workspace folder order, then alphabetically by the full path of the repository." + ], + "default": "Workspace Full Path", + "description": "Specifies the order that repositories are sorted in the repository dropdown on the Git Graph View (only visible when more than one repository exists in the current Visual Studio Code Workspace)." + }, + "git-graph.retainContextWhenHidden": { + "type": "boolean", + "default": true, + "description": "Specifies if the Git Graph View's Visual Studio Code context is kept when the panel is no longer visible (e.g. moved to background tab). Enabling this setting will make Git Graph load significantly faster when switching back to the Git Graph tab, however has a higher memory overhead." + }, + "git-graph.showStatusBarItem": { + "type": "boolean", + "default": true, + "description": "Show a Status Bar Item that opens the Git Graph View when clicked." + }, + "git-graph.sourceCodeProviderIntegrationLocation": { + "type": "string", + "enum": [ + "Inline", + "More Actions" + ], + "enumDescriptions": [ + "Show the 'View Git Graph' action on the title of SCM Providers", + "Show the 'View Git Graph' action in the 'More Actions...' menu on the title of SCM Providers" + ], + "default": "Inline", + "description": "Specifies where the \"View Git Graph\" action appears on the title of SCM Providers." + }, + "git-graph.tabIconColourTheme": { + "type": "string", + "enum": [ + "colour", + "grey" + ], + "enumDescriptions": [ + "Show a colour icon which suits most Visual Studio Code colour themes", + "Show a grey icon which suits Visual Studio Code colour themes that are predominantly grayscale" + ], + "default": "colour", + "description": "Specifies the colour theme of the icon displayed on the Git Graph tab." + }, + "git-graph.autoCenterCommitDetailsView": { + "type": "boolean", + "default": true, + "description": "Automatically center the commit details view when it is opened.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.commitDetailsView.autoCenter", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.commitDetailsView.autoCenter#`" + }, + "git-graph.combineLocalAndRemoteBranchLabels": { + "type": "boolean", + "default": true, + "description": "Combine local and remote branch labels if they refer to the same branch, and are on the same commit.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.referenceLabels.combineLocalAndRemoteBranchLabels", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.referenceLabels.combineLocalAndRemoteBranchLabels#`" + }, + "git-graph.commitDetailsViewFileTreeCompactFolders": { + "type": "boolean", + "default": true, + "description": "Render the File Tree in the Commit Details / Comparison View in a compacted form, such that folders with a single child folder are compressed into a single combined folder element.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.commitDetailsView.fileView.fileTree.compactFolders", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.commitDetailsView.fileView.fileTree.compactFolders#`" + }, + "git-graph.commitDetailsViewLocation": { + "type": "string", + "enum": [ + "Inline", + "Docked to Bottom" + ], + "enumDescriptions": [ + "Show the Commit Details View inline with the graph", + "Show the Commit Details View docked to the bottom of the Git Graph view" + ], + "default": "Inline", + "description": "Specifies where the Commit Details View is rendered in the Git Graph view.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.commitDetailsView.location", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.commitDetailsView.location#`" + }, + "git-graph.commitOrdering": { + "type": "string", + "enum": [ + "date", + "author-date", + "topo" + ], + "enumDescriptions": [ + "Show commits in the commit timestamp order.", + "Show commits in the author timestamp order.", + "Avoid showing commits on multiple lines of history intermixed." + ], + "default": "date", + "markdownDescription": "Specifies the order of commits on the Git Graph view. See [git log](https://git-scm.com/docs/git-log#_commit_ordering) for more information on each order option.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.order", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.order#`" + }, + "git-graph.dateFormat": { + "type": "string", + "enum": [ + "Date & Time", + "Date Only", + "ISO Date & Time", + "ISO Date Only", + "Relative" + ], + "enumDescriptions": [ + "Show the date and time, for example \"24 Mar 2019 21:34\"", + "Show the date only, for example \"24 Mar 2019\"", + "Show the ISO date and time, for example \"2019-03-24 21:34\"", + "Show the ISO date only, for example \"2019-03-24\"", + "Show relative times, for example \"5 minutes ago\"" + ], + "default": "Date & Time", + "description": "Specifies the date format to be used in the \"Date\" column on the Git Graph View.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.date.format", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.date.format#`" + }, + "git-graph.dateType": { + "type": "string", + "enum": [ + "Author Date", + "Commit Date" + ], + "enumDescriptions": [ + "Use the author date of a commit", + "Use the committer date of a commit" + ], + "default": "Author Date", + "description": "Specifies the date type to be displayed in the \"Date\" column on the Git Graph View.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.date.type", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.date.type#`" + }, + "git-graph.defaultFileViewType": { + "type": "string", + "enum": [ + "File Tree", + "File List" + ], + "enumDescriptions": [ + "Display files in a tree structure", + "Display files in a list (useful for repositories with deep folder structures)" + ], + "default": "File Tree", + "description": "Sets the default type of File View used in the Commit Details / Comparison Views. This can be overridden per repository using the controls on the right side of the Commit Details / Comparison Views.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.commitDetailsView.fileView.type", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.commitDetailsView.fileView.type#`" + }, + "git-graph.fetchAndPrune": { + "type": "boolean", + "default": false, + "description": "Before fetching from remote(s) using the Fetch button on the Git Graph View Control Bar, remove any remote-tracking references that no longer exist on the remote(s).", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.fetchAndPrune", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.fetchAndPrune#`" + }, + "git-graph.fetchAvatars": { + "type": "boolean", + "default": false, + "description": "Fetch avatars of commit authors and committers. By enabling this setting, you consent to commit author and committer email addresses being sent GitHub, GitLab or Gravatar, depending on the repositories remote origin.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.fetchAvatars", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.fetchAvatars#`" + }, + "git-graph.fetchCICDs": { + "type": "boolean", + "default": true, + "description": "Fetch CI/CD status of commit. By enabling this setting, you consent to Access Token being sent GitHub or GitLab, depending on the repositories and user configuration.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.fetchCICDs", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.fetchCICDs#`" + }, + "git-graph.graphColours": { + "type": "array", + "items": { + "type": "string", + "description": "Colour (HEX or RGB)", + "pattern": "^\\s*(#[0-9a-fA-F]{6}|#[0-9a-fA-F]{8}|rgb[a]?\\s*\\(\\d{1,3},\\s*\\d{1,3},\\s*\\d{1,3}\\))\\s*$" + }, + "default": [ + "#0085d9", + "#d9008f", + "#00d90a", + "#d98500", + "#a300d9", + "#ff0000", + "#00d9cc", + "#e138e8", + "#85d900", + "#dc5b23", + "#6f24d6", + "#ffcc00" + ], + "description": "Specifies the colours used on the graph.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.graph.colours", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.graph.colours#`" + }, + "git-graph.graphStyle": { + "type": "string", + "enum": [ + "rounded", + "angular" + ], + "enumDescriptions": [ + "Use smooth curves when transitioning between branches on the graph", + "Use angular lines when transitioning between branches on the graph" + ], + "default": "rounded", + "description": "Specifies the style of the graph.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.graph.style", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.graph.style#`" + }, + "git-graph.includeCommitsMentionedByReflogs": { + "type": "boolean", + "default": false, + "description": "Include commits only mentioned by reflogs in the Git Graph View (only applies when showing all branches). This can be overridden per repository in the Git Graph View's Repository Settings Widget.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.includeCommitsMentionedByReflogs", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.includeCommitsMentionedByReflogs#`" + }, + "git-graph.initialLoadCommits": { + "type": "number", + "default": 300, + "description": "Specifies the number of commits to initially load.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.initialLoad", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.initialLoad#`" + }, + "git-graph.loadMoreCommits": { + "type": "number", + "default": 100, + "description": "Specifies the number of additional commits to load when the \"Load More Commits\" button is pressed, or more commits are automatically loaded.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.loadMore", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.loadMore#`" + }, + "git-graph.loadMoreCommitsAutomatically": { + "type": "boolean", + "default": true, + "description": "When the view has been scrolled to the bottom, automatically load more commits if they exist (instead of having to press the \"Load More Commits\" button).", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.loadMoreAutomatically", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.loadMoreAutomatically#`" + }, + "git-graph.muteCommitsThatAreNotAncestorsOfHead": { + "type": "boolean", + "default": false, + "description": "Display commits that aren't ancestors of the checked-out branch / commit with a muted text color. Muting will only occur if the commit referenced by HEAD is within the loaded commits on the Git Graph View.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.mute.commitsThatAreNotAncestorsOfHead", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.mute.commitsThatAreNotAncestorsOfHead#`" + }, + "git-graph.muteMergeCommits": { + "type": "boolean", + "default": true, + "description": "Display merge commits with a muted text color.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.mute.mergeCommits", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.mute.mergeCommits#`" + }, + "git-graph.onlyFollowFirstParent": { + "type": "boolean", + "default": false, + "markdownDescription": "Only follow the first parent of commits when discovering the commits to load in the Git Graph View. See [--first-parent](https://git-scm.com/docs/git-log#Documentation/git-log.txt---first-parent) to find out more about this setting.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.onlyFollowFirstParent", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.onlyFollowFirstParent#`" + }, + "git-graph.openDiffTabLocation": { + "type": "string", + "enum": [ + "Active", + "Beside", + "One", + "Two", + "Three", + "Four", + "Five", + "Six", + "Seven", + "Eight", + "Nine" + ], + "enumDescriptions": [ + "Open the Visual Studio Code Diff View in the Active Editor Group.", + "Open the Visual Studio Code Diff View beside the Active Editor Group.", + "Open the Visual Studio Code Diff View in the First Editor Group.", + "Open the Visual Studio Code Diff View in the Second Editor Group.", + "Open the Visual Studio Code Diff View in the Third Editor Group.", + "Open the Visual Studio Code Diff View in the Fourth Editor Group.", + "Open the Visual Studio Code Diff View in the Fifth Editor Group.", + "Open the Visual Studio Code Diff View in the Sixth Editor Group.", + "Open the Visual Studio Code Diff View in the Seventh Editor Group.", + "Open the Visual Studio Code Diff View in the Eighth Editor Group.", + "Open the Visual Studio Code Diff View in the Ninth Editor Group." + ], + "default": "Active", + "description": "Specifies which Editor Group the Visual Studio Code Diff View is opened in.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.openNewTabEditorGroup", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.openNewTabEditorGroup#`" + }, + "git-graph.openRepoToHead": { + "type": "boolean", + "default": false, + "description": "When opening or switching repositories in the Git Graph View, automatically scroll the view to be centered on the commit referenced by HEAD. This will only occur if the commit referenced by HEAD is within the loaded commits on the Git Graph View.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.onLoad.scrollToHead", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.onLoad.scrollToHead#`" + }, + "git-graph.referenceLabelAlignment": { + "type": "string", + "enum": [ + "Normal", + "Branches (on the left) & Tags (on the right)", + "Branches (aligned to the graph) & Tags (on the right)" + ], + "enumDescriptions": [ + "Show branch & tag labels on the left of the commit message in the 'Description' column.", + "Show branch labels on the left of the commit message in the 'Description' column, and tag labels on the right.", + "Show branch labels aligned to the graph in the 'Graph' column, and tag labels on the right in the 'Description' column." + ], + "default": "Normal", + "description": "Specifies how branch and tag reference labels are aligned for each commit.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.referenceLabels.alignment", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.referenceLabels.alignment#`" + }, + "git-graph.showCommitsOnlyReferencedByTags": { + "type": "boolean", + "default": true, + "description": "Show commits that are only referenced by tags in Git Graph.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.showCommitsOnlyReferencedByTags", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.showCommitsOnlyReferencedByTags#`" + }, + "git-graph.showCurrentBranchByDefault": { + "type": "boolean", + "default": false, + "description": "Show the current branch by default when Git Graph is opened. Default: false (show all branches)", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.onLoad.showCheckedOutBranch", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.onLoad.showCheckedOutBranch#`" + }, + "git-graph.showSignatureStatus": { + "type": "boolean", + "default": false, + "description": "Show the commit's signature status to the right of the Committer in the Commit Details View (only for signed commits). Hovering over the signature icon displays a tooltip with the signature details. Requires Git (>= 2.4.0) & GPG (or equivalent) to be installed on the same machine that is running Visual Studio Code.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.showSignatureStatus", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.showSignatureStatus#`" + }, + "git-graph.showTags": { + "type": "boolean", + "default": true, + "description": "Show Tags in Git Graph by default. This can be overridden per repository in the Git Graph View's Repository Settings Widget.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.showTags", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.showTags#`" + }, + "git-graph.showUncommittedChanges": { + "type": "boolean", + "default": true, + "description": "Show uncommitted changes. If you work on large repositories, disabling this setting can reduce the load time of the Git Graph View.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.showUncommittedChanges", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.showUncommittedChanges#`" + }, + "git-graph.showUntrackedFiles": { + "type": "boolean", + "default": true, + "description": "Show untracked files when viewing the uncommitted changes. If you work on large repositories, disabling this setting can reduce the load time of the Git Graph View.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.showUntrackedFiles", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.showUntrackedFiles#`" + }, + "git-graph.useMailmap": { + "type": "boolean", + "default": false, + "markdownDescription": "Respect [.mailmap](https://git-scm.com/docs/git-check-mailmap#_mapping_authors) files when displaying author & committer names and email addresses.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.useMailmap", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.useMailmap#`" + } + } + }, + "menus": { + "commandPalette": [ + { + "command": "git-graph.openFile", + "when": "isInDiffEditor && resourceScheme == git-graph && git-graph:codiconsSupported" + } + ], + "editor/title": [ + { + "command": "git-graph.openFile", + "group": "navigation", + "when": "isInDiffEditor && resourceScheme == git-graph && git-graph:codiconsSupported" + } + ], + "scm/title": [ + { + "when": "scmProvider == git && config.git-graph.sourceCodeProviderIntegrationLocation == 'Inline'", + "command": "git-graph.view", + "group": "navigation" + }, + { + "when": "scmProvider == git && config.git-graph.sourceCodeProviderIntegrationLocation == 'More Actions'", + "command": "git-graph.view", + "group": "inline" + } + ] + } + }, + "scripts": { + "vscode:prepublish": "npm run compile", + "vscode:uninstall": "node ./out/life-cycle/uninstall.js", + "clean": "node ./.vscode/clean.js", + "compile": "npm run lint && npm run clean && npm run compile-src && npm run compile-web", + "compile-src": "tsc -p ./src && node ./.vscode/package-src.js", + "compile-web": "tsc -p ./web && node ./.vscode/package-web.js", + "compile-web-debug": "tsc -p ./web && node ./.vscode/package-web.js debug", + "lint": "eslint -c .eslintrc.json --ext .ts ./src ./tests ./web", + "package": "npm run clean && vsce package", + "package-and-install": "npm run package && node ./.vscode/install-package.js", + "test": "jest --verbose", + "test-and-report-coverage": "jest --verbose --coverage" + }, + "dependencies": { + "iconv-lite": "0.5.0" + }, + "devDependencies": { + "@types/jest": "26.0.19", + "@types/node": "8.10.62", + "@types/vscode": "1.38.0", + "@typescript-eslint/eslint-plugin": "4.10.0", + "@typescript-eslint/parser": "4.10.0", + "eslint": "7.15.0", + "jest": "26.6.3", + "ts-jest": "26.4.4", + "typescript": "4.0.2", + "uglify-js": "3.10.0" + } +} From f233c8362dcd889ccb3dff9696c2810d30a3a27f Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Mon, 15 Mar 2021 09:51:25 +0900 Subject: [PATCH 20/54] #462 Fixed method of table data in CI/CD status --- web/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/main.ts b/web/main.ts index 8ccf528e..49d77132 100644 --- a/web/main.ts +++ b/web/main.ts @@ -904,7 +904,7 @@ class GitGraphView { ((this.cicdDatas[commit.hash].status === 'failed' || this.cicdDatas[commit.hash].status === 'failure') ? '' + SVG_ICONS.failed + '' : '' + SVG_ICONS.inconclusive + '')) + '' + this.cicdDatas[commit.hash].status + '') : '*') + - '' + '' : '-') : ':') + + '' + '' : '-') : '') + ''; } this.tableElem.innerHTML = '' + html + '
'; From 91abb1e9e0d49a4166d7f3bf836c509ca6aa70f1 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sat, 20 Mar 2021 11:19:07 +0900 Subject: [PATCH 21/54] #462 Updated CI/CD status Added CI/CD datail for GitHub Added multiple CI/CD view Added debug message of API Added maximum page for CD/CD fetch --- package.json | 12 +++ src/cicdManager.ts | 227 +++++++++++++++++++++++++++++------------- src/config.ts | 7 ++ src/extensionState.ts | 12 ++- src/gitGraphView.ts | 14 ++- src/types.ts | 20 +++- web/global.d.ts | 3 +- web/main.ts | 62 ++++++++---- 8 files changed, 256 insertions(+), 101 deletions(-) diff --git a/package.json b/package.json index 2ef06c6b..2a5ad2dc 100644 --- a/package.json +++ b/package.json @@ -945,6 +945,11 @@ "default": true, "description": "Fetch CI/CD status of commit. By enabling this setting, you consent to Access Token being sent GitHub or GitLab, depending on the repositories and user configuration." }, + "git-graph.repository.commits.fetchCICDsPage": { + "type": "number", + "default": 10, + "description": "Specifies the number of CI/CD fetch maximum page." + }, "git-graph.repository.commits.initialLoad": { "type": "number", "default": 300, @@ -1255,6 +1260,13 @@ "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.fetchCICDs", "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.fetchCICDs#`" }, + "git-graph.fetchCICDsPage": { + "type": "number", + "default": 10, + "description": "Specifies the number of CI/CD fetch maximum page.", + "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.fetchCICDsPage", + "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.fetchCICDsPage#`" + }, "git-graph.graphColours": { "type": "array", "items": { diff --git a/src/cicdManager.ts b/src/cicdManager.ts index e46e3cf7..034fdf63 100644 --- a/src/cicdManager.ts +++ b/src/cicdManager.ts @@ -1,9 +1,11 @@ // import * as crypto from 'crypto'; +import { IncomingMessage } from 'http'; import * as https from 'https'; +import { getConfig } from './config'; // import * as url from 'url'; import { ExtensionState } from './extensionState'; import { Logger } from './logger'; -import { CICDConfig, CICDData, CICDProvider } from './types'; +import { CICDConfig, CICDData, CICDDataSave, CICDProvider } from './types'; import { Disposable, toDisposable } from './utils/disposable'; import { EventEmitter } from './utils/event'; @@ -69,12 +71,13 @@ export class CicdManager extends Disposable { /** * Fetch an cicd, either from the cache if it already exists, or queue it to be fetched. - * @param hash The hash identifying the cicd. + * @param hash The hash identifying the cicd commit. + * @param cicdConfigs The CICDConfigs. */ public fetchCICDStatus(hash: string, cicdConfigs: CICDConfig[]) { if (typeof this.cicds[hash] !== 'undefined') { // CICD exists in the cache - this.emitCICD(this.cicds[hash]).catch(() => { + this.emitCICD(hash, this.cicds[hash]).catch(() => { // CICD couldn't be found this.removeCICDFromCache(hash); }); @@ -119,11 +122,15 @@ export class CicdManager extends Disposable { /** * Get the data of an cicd. - * @param hash The hash identifying the cicd. + * @param hash The hash identifying the cicd commit. + * @param cicdConfigs The CICDConfigs. * @returns A JSON encoded data of an cicd if the cicd exists, otherwise NULL. */ - public getCICDImage(hash: string) { + public getCICDDetail(hash: string, cicdConfigs: CICDConfig[]) { return new Promise((resolve) => { + cicdConfigs.forEach(cicdConfig => { + this.queue.add(cicdConfig, -1, true, true, hash); + }); if (typeof this.cicds[hash] !== 'undefined' && this.cicds[hash] !== null) { resolve(JSON.stringify(this.cicds[hash])); } else { @@ -142,7 +149,7 @@ export class CicdManager extends Disposable { /** * Remove an cicd from the cache. - * @param hash The hash identifying the cicd. + * @param hash The hash identifying the cicd commit. */ private removeCICDFromCache(hash: string) { delete this.cicds[hash]; @@ -196,7 +203,7 @@ export class CicdManager extends Disposable { } let cicdConfig = cicdRequest.cicdConfig; - this.logger.log('Requesting CICD for ' + cicdConfig.gitUrl + ' page=' + cicdRequest.page + ' from GitHub'); + this.logger.log('Requesting CICD for ' + cicdConfig.gitUrl + ' detail=' + cicdRequest.detail + ' page=' + cicdRequest.page + ' from GitHub'); const match1 = cicdConfig.gitUrl.match(/^(https?:\/\/|git@)((?=[^/]+@)[^@]+@|(?![^/]+@))([^/:]+)/); let hostRootUrl = match1 !== null ? 'api.' + match1[3] : ''; @@ -206,6 +213,9 @@ export class CicdManager extends Disposable { let sourceRepo = match2 !== null ? match2[3] : ''; let cicdRootPath = `/repos/${sourceOwner}/${sourceRepo.replace(/\//g, '%2F')}/actions/runs?per_page=100`; + if (cicdRequest.detail) { + cicdRootPath = `/repos/${sourceOwner}/${sourceRepo.replace(/\//g, '%2F')}/commits/${cicdRequest.hash}/check-runs?per_page=100`; + } if (cicdRequest.page > 1) { cicdRootPath = `${cicdRootPath}&page=${cicdRequest.page}`; } @@ -240,70 +250,57 @@ export class CicdManager extends Disposable { if (res.headers['x-ratelimit-remaining'] === '0') { // If the GitHub Api rate limit was reached, store the github timeout to prevent subsequent requests this.githubTimeout = parseInt(res.headers['x-ratelimit-reset']) * 1000; - this.logger.log('GitHub API Rate Limit Reached - Paused fetching from GitHub until the Rate Limit is reset'); + this.logger.log('GitHub API Rate Limit Reached - Paused fetching from GitLab until the Rate Limit is reset (RateLimit=' + res.headers['x-ratelimit-limit'] + '(1 hour)/' + new Date(this.githubTimeout).toString() + ')'); + if (cicdRequest.cicdConfig.glToken === '') { + this.logger.log('GitHub API Rate Limit can upgrade by Access Token.'); + } } if (res.statusCode === 200) { // Success try { let respJson: any = JSON.parse(respBody); - if (typeof respJson['workflow_runs'] !== 'undefined' && respJson['workflow_runs'].length >= 1) { // url found + if (typeof respJson['check_runs'] !== 'undefined' && respJson['check_runs'].length >= 1) { // url found + let ret: CICDData[] = respJson['check_runs'].map((elm: { [x: string]: any; }) => { + return { + id: elm['id'], + status: elm['conclusion'] === null ? elm['status'] : elm['conclusion'], + ref: '', + sha: elm['head_sha'], + web_url: elm['html_url'], + created_at: elm['created_at'], + updated_at: elm['updated_at'], + name: elm['name'], + event: '', + detail: cicdRequest.detail + }; + }); + ret.forEach(element => { + let save = this.convCICDData2CICDDataSave(element); + this.saveCICD(element.sha, element.id, save); + }); + this.reFetchPage(cicdRequest, res, cicdConfig); + return; + } else if (typeof respJson['workflow_runs'] !== 'undefined' && respJson['workflow_runs'].length >= 1) { // url found let ret: CICDData[] = respJson['workflow_runs'].map((elm: { [x: string]: any; }) => { return { id: elm['id'], status: elm['conclusion'] === null ? elm['status'] : elm['conclusion'], - ref: elm['name'], + ref: elm['head_branch'], sha: elm['head_sha'], web_url: elm['html_url'], created_at: elm['created_at'], - updated_at: elm['updated_at'] + updated_at: elm['updated_at'], + name: elm['name'], + event: elm['event'], + detail: cicdRequest.detail }; }); ret.forEach(element => { - this.saveCICD(element); + let save = this.convCICDData2CICDDataSave(element); + this.saveCICD(element.sha, element.id, save); }); - if (cicdRequest.page === -1) { - let last = 1; - if (typeof res.headers['link'] === 'string') { - const DELIM_LINKS = ','; - const DELIM_LINK_PARAM = ';'; - let links = res.headers['link'].split(DELIM_LINKS); - links.forEach(link => { - let segments = link.split(DELIM_LINK_PARAM); - - let linkPart = segments[0].trim(); - if (!linkPart.startsWith('<') || !linkPart.endsWith('>')) { - return true; - } - linkPart = linkPart.substring(1, linkPart.length - 1); - let match3 = linkPart.match(/&page=(\d+).*$/); - let linkPage = match3 !== null ? match3[1] : '0'; - - for (let i = 1; i < segments.length; i++) { - let rel = segments[i].trim().split('='); - if (rel.length < 2) { - continue; - } - - let relValue = rel[1]; - if (relValue.startsWith('"') && relValue.endsWith('"')) { - relValue = relValue.substring(1, relValue.length - 1); - } - - if (relValue === 'last') { - last = parseInt(linkPage); - } - } - }); - } - - for (var i = 1; i < last; i++) { - // let cicdRequestNew = Object.assign({}, cicdRequest);; - // cicdRequestNew.page = i + 1; - // this.queue.addItem(cicdRequestNew, 0, false); - this.queue.add(cicdRequest.cicdConfig, i + 1, true); - } - } + this.reFetchPage(cicdRequest, res, cicdConfig); return; } } catch (e) { @@ -337,6 +334,56 @@ export class CicdManager extends Disposable { }).on('error', onError); } + private reFetchPage(cicdRequest: CICDRequestItem, res: IncomingMessage, cicdConfig: CICDConfig) { + if (cicdRequest.page === -1) { + let last = 1; + if (typeof res.headers['link'] === 'string') { + const DELIM_LINKS = ','; + const DELIM_LINK_PARAM = ';'; + let links = res.headers['link'].split(DELIM_LINKS); + links.forEach(link => { + let segments = link.split(DELIM_LINK_PARAM); + + let linkPart = segments[0].trim(); + if (!linkPart.startsWith('<') || !linkPart.endsWith('>')) { + return true; + } + linkPart = linkPart.substring(1, linkPart.length - 1); + let match3 = linkPart.match(/&page=(\d+).*$/); + let linkPage = match3 !== null ? match3[1] : '0'; + + for (let i = 1; i < segments.length; i++) { + let rel = segments[i].trim().split('='); + if (rel.length < 2) { + continue; + } + + let relValue = rel[1]; + if (relValue.startsWith('"') && relValue.endsWith('"')) { + relValue = relValue.substring(1, relValue.length - 1); + } + + if (relValue === 'last') { + last = parseInt(linkPage); + } + } + }); + } + if (last > cicdRequest.maxPage) { + last = cicdRequest.maxPage; + this.logger.log('CICD Maximum page(pages=' + cicdRequest.maxPage + ') reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsPage'); + } + + this.logger.log('Added CICD for ' + cicdConfig.gitUrl + ' last_page=' + last + '(RateLimit=' + res.headers['x-ratelimit-limit'] + '(1 hour)/Remaining=' + res.headers['x-ratelimit-remaining'] + '/' + new Date(parseInt(res.headers['x-ratelimit-reset']) * 1000).toString() + ') from GitHub'); + for (var i = 1; i < last; i++) { + // let cicdRequestNew = Object.assign({}, cicdRequest);; + // cicdRequestNew.page = i + 1; + // this.queue.addItem(cicdRequestNew, 0, false); + this.queue.add(cicdRequest.cicdConfig, i + 1, true); + } + } + } + /** * Fetch an cicd from GitLab. * @param cicdRequest The cicd request to fetch. @@ -391,7 +438,7 @@ export class CicdManager extends Disposable { if (res.headers['ratelimit-remaining'] === '0') { // If the GitLab Api rate limit was reached, store the gitlab timeout to prevent subsequent requests this.gitLabTimeout = parseInt(res.headers['ratelimit-reset']) * 1000; - this.logger.log('GitLab API Rate Limit Reached - Paused fetching from GitLab until the Rate Limit is reset'); + this.logger.log('GitLab API Rate Limit Reached - Paused fetching from GitLab until the Rate Limit is reset (RateLimit=' + res.headers['ratelimit-limit'] + '(every minute)/' + new Date(this.gitLabTimeout).toString() + ')'); } if (res.statusCode === 200) { // Success @@ -401,11 +448,17 @@ export class CicdManager extends Disposable { if (parseInt(res.headers['x-total']) !== 0 && respJson.length && respJson[0].id) { // url found let ret: CICDData[] = respJson; ret.forEach(element => { - this.saveCICD(element); + let save = this.convCICDData2CICDDataSave(element); + this.saveCICD(element.sha, element.id, save); }); - let last = parseInt(res.headers['x-total-pages']); if (cicdRequest.page === -1) { + let last = parseInt(res.headers['x-total-pages']); + if (last > cicdRequest.maxPage) { + last = cicdRequest.maxPage; + this.logger.log('CICD Maximum page(pages=' + cicdRequest.maxPage + ') reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsPage'); + } + this.logger.log('Added CICD for ' + cicdConfig.gitUrl + ' last_page=' + last + '(RateLimit=' + res.headers['ratelimit-limit'] + '(every minute)/Remaining=' + res.headers['ratelimit-remaining'] + '/' + new Date(parseInt(res.headers['ratelimit-reset']) * 1000).toString() + ') from GitLab'); for (var i = 1; i < last; i++) { // let cicdRequestNew = Object.assign({}, cicdRequest);; // cicdRequestNew.page = i + 1; @@ -443,15 +496,32 @@ export class CicdManager extends Disposable { } /** - * Emit an CICDEvent to any listeners. + * Fetch an cicd from GitHub. * @param cicdData The CICDData. + * @returns The CICDDataSave. + */ + private convCICDData2CICDDataSave(cicdData: CICDData): CICDDataSave { + return { + name: cicdData.name, + ref: cicdData.ref, + status: cicdData.status, + web_url: cicdData.web_url, + event: cicdData.event, + detail: cicdData.detail + }; + } + /** + * Emit an CICDEvent to any listeners. + * @param hash The hash identifying the cicd commit. + * @param cicdDataSaves The hash of CICDDataSave. * @returns A promise indicating if the event was emitted successfully. */ - private emitCICD(cicdData: CICDData) { + private emitCICD(hash: string, cicdDataSaves: { [id: string]: CICDDataSave }) { return new Promise((resolve, _reject) => { if (this.cicdEventEmitter.hasSubscribers()) { this.cicdEventEmitter.emit({ - cicdData: cicdData + hash: hash, + cicdDataSaves: cicdDataSaves }); resolve(true); } else { @@ -462,19 +532,24 @@ export class CicdManager extends Disposable { /** * Save an cicd in the cache. - * @param cicdData The CICDData. + * @param hash The hash identifying the cicd commit. + * @param id The identifying the cicdDataSave. + * @param cicdDataSave The CICDDataSave. */ - private saveCICD(cicdData: CICDData) { - this.cicds[cicdData.sha] = cicdData; - this.extensionState.saveCICD(this.cicds[cicdData.sha]); + private saveCICD(hash: string, id: string, cicdDataSave: CICDDataSave) { + if (typeof this.cicds[hash] === 'undefined') { + this.cicds[hash] = {}; + } + this.cicds[hash][id] = cicdDataSave; + this.extensionState.saveCICD(hash, id, cicdDataSave); // this.logger.log('Saved CICD for ' + cicdData.sha); - this.emitCICD(this.cicds[cicdData.sha]).then( + this.emitCICD(hash, this.cicds[hash]).then( // (sent) => this.logger.log(sent // ? 'Sent CICD for ' + cicdData.sha + ' to the Git Graph View' // : 'CICD for ' + cicdData.sha + ' is ready to be used the next time the Git Graph View is opened' // ), () => { }, - () => this.logger.log('Failed to Send CICD for ' + cicdData.sha + ' to the Git Graph View') + () => this.logger.log('Failed to Send CICD for ' + hash + ' to the Git Graph View') ); } } @@ -499,18 +574,24 @@ class CicdRequestQueue { * @param cicdConfig The CICDConfig. * @param page The page of cicd request. * @param immediate Whether the avatar should be fetched immediately. + * @param detail Flag of fetch detail. + * @param hash hash for fetch detail. */ - public add(cicdConfig: CICDConfig, page: number, immediate: boolean) { - const existingRequest = this.queue.find((request) => request.cicdConfig.gitUrl === cicdConfig.gitUrl && request.page === page); + public add(cicdConfig: CICDConfig, page: number, immediate: boolean, detail: boolean = false, hash: string = '') { + const existingRequest = this.queue.find((request) => request.cicdConfig.gitUrl === cicdConfig.gitUrl && request.page === page && request.detail === detail && request.hash === hash); if (existingRequest) { } else { + const config = getConfig(); this.insertItem({ cicdConfig: cicdConfig, page: page, checkAfter: immediate || this.queue.length === 0 ? 0 : this.queue[this.queue.length - 1].checkAfter + 1, - attempts: 0 + attempts: 0, + detail: detail, + hash: hash, + maxPage: config.fetchCICDsPage }); } } @@ -564,7 +645,7 @@ class CicdRequestQueue { } -export type CICDCache = { [hash: string]: CICDData }; +export type CICDCache = { [hash: string]: { [id: string]: CICDDataSave } }; // Request item to CicdRequestQueue @@ -573,9 +654,13 @@ interface CICDRequestItem { page: number; checkAfter: number; attempts: number; + detail: boolean; + hash: string; + maxPage: number; } // Event to GitGraphView export interface CICDEvent { - cicdData: CICDData; + hash: string; + cicdDataSaves: { [id: string]: CICDDataSave }; } diff --git a/src/config.ts b/src/config.ts index ddbf147b..e4e7c796 100644 --- a/src/config.ts +++ b/src/config.ts @@ -363,6 +363,13 @@ class Config { return !!this.getRenamedExtensionSetting('repository.commits.fetchCICDs', 'fetchCICDs', true); } + /** + * Get the value of the `git-graph.repository.commits.fetchCICDsPage` Extension Setting. + */ + get fetchCICDsPage() { + return this.getRenamedExtensionSetting('repository.commits.fetchCICDsPage', 'fetchCICDsPage', 10); + } + /** * Get the value of the `git-graph.repository.commits.initialLoad` Extension Setting. */ diff --git a/src/extensionState.ts b/src/extensionState.ts index 1c623537..25859e9e 100644 --- a/src/extensionState.ts +++ b/src/extensionState.ts @@ -3,7 +3,7 @@ import * as vscode from 'vscode'; import { Avatar, AvatarCache } from './avatarManager'; import { CICDCache } from './cicdManager'; import { getConfig } from './config'; -import { BooleanOverride, CICDData, CodeReview, ErrorInfo, FileViewType, GitGraphViewGlobalState, GitGraphViewWorkspaceState, GitRepoSet, GitRepoState, RepoCommitOrdering } from './types'; +import { BooleanOverride, CICDDataSave, CodeReview, ErrorInfo, FileViewType, GitGraphViewGlobalState, GitGraphViewWorkspaceState, GitRepoSet, GitRepoState, RepoCommitOrdering } from './types'; import { GitExecutable, getPathFromStr } from './utils'; import { Disposable } from './utils/disposable'; import { Event } from './utils/event'; @@ -324,11 +324,15 @@ export class ExtensionState extends Disposable { /** * Add a new cicd to the cache of cicds known to Git Graph. - * @param cicdData The CICDData. + * @param hash The hash identifying the cicd commit. + * @param cicdDataSave The CICDDataSave. */ - public saveCICD(cicdData: CICDData) { + public saveCICD(hash: string, id: string, cicdDataSave: CICDDataSave) { let cicds = this.getCICDCache(); - cicds[cicdData.sha] = cicdData; + if (typeof cicds[hash] === 'undefined') { + cicds[hash] = {}; + } + cicds[hash][id] = cicdDataSave; this.updateWorkspaceState(CICD_CACHE, cicds); } diff --git a/src/gitGraphView.ts b/src/gitGraphView.ts index e0b9a2e2..4b9b8704 100644 --- a/src/gitGraphView.ts +++ b/src/gitGraphView.ts @@ -152,7 +152,8 @@ export class GitGraphView extends Disposable { cicdManager.onCICD((event) => { this.sendMessage({ command: 'fetchCICD', - cicdData: event.cicdData + hash: event.hash, + cicdDataSaves: event.cicdDataSaves }); }), @@ -241,20 +242,22 @@ export class GitGraphView extends Disposable { }); break; case 'commitDetails': - let data = await Promise.all([ + let data = await Promise.all([ msg.commitHash === UNCOMMITTED ? this.dataSource.getUncommittedDetails(msg.repo) : msg.stash === null ? this.dataSource.getCommitDetails(msg.repo, msg.commitHash, msg.hasParents) : this.dataSource.getStashDetails(msg.repo, msg.commitHash, msg.stash), - msg.avatarEmail !== null ? this.avatarManager.getAvatarImage(msg.avatarEmail) : Promise.resolve(null) + msg.avatarEmail !== null ? this.avatarManager.getAvatarImage(msg.avatarEmail) : Promise.resolve(null), + msg.cicdConfigs !== null ? this.cicdManager.getCICDDetail(msg.commitHash, msg.cicdConfigs) : Promise.resolve(null) ]); this.sendMessage({ command: 'commitDetails', ...data[0], avatar: data[1], codeReview: msg.commitHash !== UNCOMMITTED ? this.extensionState.getCodeReview(msg.repo, msg.commitHash) : null, - refresh: msg.refresh + refresh: msg.refresh, + cicdDataSaves: JSON.parse(data[2] || '{}') }); break; case 'compareCommits': @@ -394,7 +397,7 @@ export class GitGraphView extends Disposable { this.avatarManager.fetchAvatarImage(msg.email, msg.repo, msg.remote, msg.commits); break; case 'fetchCICD': - this.cicdManager.fetchCICDStatus(msg.sha, msg.cicdConfigs); + this.cicdManager.fetchCICDStatus(msg.hash, msg.cicdConfigs); break; case 'fetchIntoLocalBranch': this.sendMessage({ @@ -661,6 +664,7 @@ export class GitGraphView extends Disposable { fetchAndPruneTags: config.fetchAndPruneTags, fetchAvatars: config.fetchAvatars && this.extensionState.isAvatarStorageAvailable(), fetchCICDs: config.fetchCICDs, + fetchCICDsPage: config.fetchCICDsPage, graph: config.graph, includeCommitsMentionedByReflogs: config.includeCommitsMentionedByReflogs, initialLoadCommits: config.initialLoadCommits, diff --git a/src/types.ts b/src/types.ts index af078320..5ac19fb8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -196,6 +196,18 @@ export interface CICDData { web_url: string; created_at: string; updated_at: string; + name: string; + event: string; + detail: boolean; +} + +export interface CICDDataSave { + name: string; + status: string; + ref: string; + web_url: string; + event: string; + detail: boolean; } export interface CICDConfigBase { @@ -276,6 +288,7 @@ export interface GitGraphViewConfig { readonly fetchAndPruneTags: boolean; readonly fetchAvatars: boolean; readonly fetchCICDs: boolean; + readonly fetchCICDsPage: number; readonly graph: GraphConfig; readonly includeCommitsMentionedByReflogs: boolean; readonly initialLoadCommits: number; @@ -695,6 +708,7 @@ export interface RequestCommitDetails extends RepoRequest { readonly stash: GitCommitStash | null; // null => request is for a commit, otherwise => request is for a stash readonly avatarEmail: string | null; // string => fetch avatar with the given email, null => don't fetch avatar readonly refresh: boolean; + readonly cicdConfigs: CICDConfig[] | null; } export interface ResponseCommitDetails extends ResponseWithErrorInfo { readonly command: 'commitDetails'; @@ -702,6 +716,7 @@ export interface ResponseCommitDetails extends ResponseWithErrorInfo { readonly avatar: string | null; readonly codeReview: CodeReview | null; readonly refresh: boolean; + readonly cicdDataSaves: { [id: string]: CICDDataSave }; } export interface RequestCompareCommits extends RepoRequest { @@ -898,12 +913,13 @@ export interface ResponseFetchAvatar extends BaseMessage { } export interface RequestFetchCICD extends RepoRequest { readonly command: 'fetchCICD'; - readonly sha: string; + readonly hash: string; readonly cicdConfigs: CICDConfig[]; } export interface ResponseFetchCICD extends BaseMessage { readonly command: 'fetchCICD'; - readonly cicdData: CICDData; + readonly hash: string; + readonly cicdDataSaves: { [id: string]: CICDDataSave }; } export interface RequestFetchIntoLocalBranch extends RepoRequest { diff --git a/web/global.d.ts b/web/global.d.ts index 3dfb40dd..a0364c7d 100644 --- a/web/global.d.ts +++ b/web/global.d.ts @@ -1,5 +1,4 @@ import * as GG from '../out/types'; // Import types from back-end (requires `npm run compile-src`) -import { CICDData } from '../out/types'; declare global { @@ -21,7 +20,7 @@ declare global { const workspaceState: GG.DeepReadonly; type AvatarImageCollection = { [email: string]: string }; - type CICDDataCollection = { [hash: string]: CICDData }; + type CICDDataCollection = { [hash: string]: { [id: string]: GG.CICDDataSave } }; interface ExpandedCommit { index: number; diff --git a/web/main.ts b/web/main.ts index 49d77132..5b68e725 100644 --- a/web/main.ts +++ b/web/main.ts @@ -520,18 +520,47 @@ class GitGraphView { } } - public loadCicd(cicdData: GG.CICDData) { - this.cicdDatas[cicdData.sha] = cicdData; + public loadCicd(hash: string, cicdDataSaves: { [id: string]: GG.CICDDataSave }) { + this.cicdDatas[hash] = cicdDataSaves; this.saveState(); - let cicdElems = >document.getElementsByClassName('cicd'), hash = cicdData.sha; + let cicdElems = >document.getElementsByClassName('cicd'); for (let i = 0; i < cicdElems.length; i++) { if (cicdElems[i].dataset.hash === hash) { - cicdElems[i].innerHTML = (cicdData.status === 'success' ? '' + SVG_ICONS.passed + '' : - ((cicdData.status === 'failed' || cicdData.status === 'failure') ? '' + SVG_ICONS.failed + '' : - '' + SVG_ICONS.inconclusive + '')) + - '' + cicdData.status + ''; + cicdElems[i].innerHTML = this.getCicdHtml(cicdDataSaves); } } + let cicdDetailElems = >document.getElementsByClassName('cicdDetail'); + for (let i = 0; i < cicdDetailElems.length; i++) { + if (cicdDetailElems[i].dataset.hash === hash) { + cicdDetailElems[i].innerHTML = this.getCicdHtml(cicdDataSaves, true); + } + } + } + + private getCicdHtml(cicdDataSaves: { [id: string]: GG.CICDDataSave }, detail: boolean = false) { + let ret: string = ''; + for (let name in cicdDataSaves) { + // Multiplicity + // GitHub: Commit 1 - 0..n Workflow 0..n - 1 PullRequest 0..1 - 1..n Commit + // GitLab: Commit 1 - 0..n Pipeline 0..n - 1 MergeRequest 0..1 - 1..n Commit + let cicdDataSave = cicdDataSaves[name]; + let event = cicdDataSave.event || ''; + let detailCurrent = cicdDataSave.detail || false; + let title = (typeof cicdDataSave.name !== 'undefined' ? cicdDataSave.name + '/' : '') + cicdDataSave.status + '/' + event; + if (detailCurrent === detail && event === 'push' || event === 'pull_request') { + // if (detailCurrent === detail && event !== 'issues' && event !== 'issue_comment' && event !== 'schedule' && event !== 'workflow_run') { + ret += + '' + + (cicdDataSave.status === 'success' ? '' + SVG_ICONS.passed + '' : + ((cicdDataSave.status === 'failed' || cicdDataSave.status === 'failure') ? '' + SVG_ICONS.failed + '' : + '' + SVG_ICONS.inconclusive + '')) + + ''; + } + } + if (ret === '') { + ret = '-'; + } + return ret; } /* Getters */ @@ -685,7 +714,8 @@ class GitGraphView { hasParents: commit.parents.length > 0, stash: commit.stash, avatarEmail: this.config.fetchAvatars && hash !== UNCOMMITTED ? commit.email : null, - refresh: refresh + refresh: refresh, + cicdConfigs: this.gitRepos[this.currentRepo].cicdConfigs }); } @@ -712,7 +742,7 @@ class GitGraphView { if (typeof this.currentRepo === 'string' && typeof this.gitRepos[this.currentRepo] !== 'undefined') { let cicdConfigs = this.gitRepos[this.currentRepo].cicdConfigs; if (cicdConfigs !== null) { - sendMessage({ command: 'fetchCICD', repo: this.currentRepo, sha: commit.hash, cicdConfigs: cicdConfigs }); + sendMessage({ command: 'fetchCICD', repo: this.currentRepo, hash: commit.hash, cicdConfigs: cicdConfigs }); } } }); @@ -899,11 +929,7 @@ class GitGraphView { (colVisibility.author ? '' + (this.config.fetchAvatars ? '' + (typeof this.avatars[commit.email] === 'string' ? '' : '') + '' : '') + escapeHtml(commit.author) + '' : '') + (colVisibility.commit ? '' + abbrevCommit(commit.hash) + '' : '') + (colVisibility.cicd ? (this.config.fetchCICDs ? '' + '' + - (typeof this.cicdDatas[commit.hash] === 'object' ? - ((this.cicdDatas[commit.hash].status === 'success' ? '' + SVG_ICONS.passed + '' : - ((this.cicdDatas[commit.hash].status === 'failed' || this.cicdDatas[commit.hash].status === 'failure') ? '' + SVG_ICONS.failed + '' : - '' + SVG_ICONS.inconclusive + '')) + - '' + this.cicdDatas[commit.hash].status + '') : '*') + + (typeof this.cicdDatas[commit.hash] === 'object' ? (this.getCicdHtml(this.cicdDatas[commit.hash])) : '*') + '' + '' : '-') : '') + ''; } @@ -2539,8 +2565,10 @@ class GitGraphView { + 'Author: ' + escapeHtml(commitDetails.author) + (commitDetails.authorEmail !== '' ? ' <' + escapeHtml(commitDetails.authorEmail) + '>' : '') + '
' + (commitDetails.authorDate !== commitDetails.committerDate ? 'Author Date: ' + formatLongDate(commitDetails.authorDate) + '
' : '') + 'Committer: ' + escapeHtml(commitDetails.committer) + (commitDetails.committerEmail !== '' ? ' <' + escapeHtml(commitDetails.committerEmail) + '>' : '') + (commitDetails.signature !== null ? generateSignatureHtml(commitDetails.signature) : '') + '
' - + '' + (commitDetails.authorDate !== commitDetails.committerDate ? 'Committer ' : '') + 'Date: ' + formatLongDate(commitDetails.committerDate) - + '' + + '' + (commitDetails.authorDate !== commitDetails.committerDate ? 'Committer ' : '') + 'Date: ' + formatLongDate(commitDetails.committerDate) + '
' + + (this.config.fetchCICDs ? 'CI/CD detail: ' + '' + + (typeof this.cicdDatas[commitDetails.hash] === 'object' ? (this.getCicdHtml(this.cicdDatas[commitDetails.hash], true)) : '*') + + '' : '') + (expandedCommit.avatar !== null ? '' : '') + '

' + textFormatter.format(commitDetails.body); } else { @@ -3244,7 +3272,7 @@ window.addEventListener('load', () => { }); break; case 'fetchCICD': - gitGraph.loadCicd(msg.cicdData); + gitGraph.loadCicd(msg.hash, msg.cicdDataSaves); break; case 'fetchIntoLocalBranch': refreshOrDisplayError(msg.error, 'Unable to Fetch into Local Branch'); From c881f4bf741c95c82322e8eee4c4b8cf0df7cb1e Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sun, 28 Mar 2021 01:33:27 +0900 Subject: [PATCH 22/54] Added Jenkins, multiple job, tooltip for CI/CD --- src/cicdManager.ts | 243 ++++++++++++++++++++++++++++++++++-------- src/types.ts | 2 +- web/main.ts | 29 +++-- web/settingsWidget.ts | 9 +- web/styles/main.css | 81 +++++++++++++- 5 files changed, 305 insertions(+), 59 deletions(-) diff --git a/src/cicdManager.ts b/src/cicdManager.ts index 034fdf63..f653d59f 100644 --- a/src/cicdManager.ts +++ b/src/cicdManager.ts @@ -1,6 +1,7 @@ // import * as crypto from 'crypto'; import { IncomingMessage } from 'http'; import * as https from 'https'; +import * as http from 'http'; import { getConfig } from './config'; // import * as url from 'url'; import { ExtensionState } from './extensionState'; @@ -23,6 +24,7 @@ export class CicdManager extends Disposable { private githubTimeout: number = 0; private gitLabTimeout: number = 0; + private jenkinsTimeout: number = 0; private initialState: boolean = true; private requestPage: number = -1; private requestPageTimeout: NodeJS.Timer | null = null; @@ -180,6 +182,9 @@ export class CicdManager extends Disposable { case CICDProvider.GitLabV4: this.fetchFromGitLab(cicdRequest); break; + case CICDProvider.JenkinsV2: + this.fetchFromJenkins(cicdRequest); + break; default: break; } @@ -203,7 +208,6 @@ export class CicdManager extends Disposable { } let cicdConfig = cicdRequest.cicdConfig; - this.logger.log('Requesting CICD for ' + cicdConfig.gitUrl + ' detail=' + cicdRequest.detail + ' page=' + cicdRequest.page + ' from GitHub'); const match1 = cicdConfig.gitUrl.match(/^(https?:\/\/|git@)((?=[^/]+@)[^@]+@|(?![^/]+@))([^/:]+)/); let hostRootUrl = match1 !== null ? 'api.' + match1[3] : ''; @@ -239,6 +243,7 @@ export class CicdManager extends Disposable { } }; + this.logger.log('Requesting CICD for https://' + hostRootUrl + cicdRootPath + ' detail=' + cicdRequest.detail + ' page=' + cicdRequest.page + ' from GitHub'); https.get({ hostname: hostRootUrl, path: cicdRootPath, headers: headers, @@ -257,6 +262,7 @@ export class CicdManager extends Disposable { } if (res.statusCode === 200) { // Success + this.logger.log('GitHub API - (' + res.statusCode + ')' + 'https://' + hostRootUrl + cicdRootPath); try { let respJson: any = JSON.parse(respBody); if (typeof respJson['check_runs'] !== 'undefined' && respJson['check_runs'].length >= 1) { // url found @@ -269,7 +275,7 @@ export class CicdManager extends Disposable { web_url: elm['html_url'], created_at: elm['created_at'], updated_at: elm['updated_at'], - name: elm['name'], + name: elm['app']!['name'] + '(' + elm['name'] + ')', event: '', detail: cicdRequest.detail }; @@ -278,7 +284,7 @@ export class CicdManager extends Disposable { let save = this.convCICDData2CICDDataSave(element); this.saveCICD(element.sha, element.id, save); }); - this.reFetchPage(cicdRequest, res, cicdConfig); + this.reFetchPageGitHub(cicdRequest, res, cicdConfig); return; } else if (typeof respJson['workflow_runs'] !== 'undefined' && respJson['workflow_runs'].length >= 1) { // url found let ret: CICDData[] = respJson['workflow_runs'].map((elm: { [x: string]: any; }) => { @@ -299,12 +305,11 @@ export class CicdManager extends Disposable { let save = this.convCICDData2CICDDataSave(element); this.saveCICD(element.sha, element.id, save); }); - - this.reFetchPage(cicdRequest, res, cicdConfig); + this.reFetchPageGitHub(cicdRequest, res, cicdConfig); return; } } catch (e) { - this.logger.log('GitHub API Error - (' + res.statusCode + ')API Result error.'); + this.logger.log('GitHub API Error - (' + res.statusCode + ')API Result error. : ' + e.message); } return; } else if (res.statusCode === 403) { @@ -334,7 +339,13 @@ export class CicdManager extends Disposable { }).on('error', onError); } - private reFetchPage(cicdRequest: CICDRequestItem, res: IncomingMessage, cicdConfig: CICDConfig) { + /** + * ReFetch an cicd from GitHub. + * @param cicdRequest The cicd request to fetch. + * @param res The IncomingMessage. + * @param cicdConfig The CICDConfig. + */ + private reFetchPageGitHub(cicdRequest: CICDRequestItem, res: IncomingMessage, cicdConfig: CICDConfig) { if (cicdRequest.page === -1) { let last = 1; if (typeof res.headers['link'] === 'string') { @@ -376,9 +387,6 @@ export class CicdManager extends Disposable { this.logger.log('Added CICD for ' + cicdConfig.gitUrl + ' last_page=' + last + '(RateLimit=' + res.headers['x-ratelimit-limit'] + '(1 hour)/Remaining=' + res.headers['x-ratelimit-remaining'] + '/' + new Date(parseInt(res.headers['x-ratelimit-reset']) * 1000).toString() + ') from GitHub'); for (var i = 1; i < last; i++) { - // let cicdRequestNew = Object.assign({}, cicdRequest);; - // cicdRequestNew.page = i + 1; - // this.queue.addItem(cicdRequestNew, 0, false); this.queue.add(cicdRequest.cicdConfig, i + 1, true); } } @@ -398,16 +406,21 @@ export class CicdManager extends Disposable { } let cicdConfig = cicdRequest.cicdConfig; - this.logger.log('Requesting CICD for ' + cicdConfig.gitUrl + ' page=' + cicdRequest.page + ' from GitLab'); - const match1 = cicdConfig.gitUrl.match(/^(https?:\/\/|git@)((?=[^/]+@)[^@]+@|(?![^/]+@))([^/:]+)/); - let hostRootUrl = match1 !== null ? '' + match1[3] : ''; + let gitUrl = cicdConfig.gitUrl.replace(/\/$/g, ''); + gitUrl = gitUrl.replace(/.git$/g, ''); + const match1 = gitUrl.match(/^(.+?):\/\/(.+?):?(\d+)?(\/.*)?$/); + let hostProtocol = match1 !== null ? '' + match1[1] : ''; + let hostRootUrl = match1 !== null ? '' + match1[2] : ''; + let hostPort = match1 !== null ? (match1[3] || '') : ''; + let hostpath = match1 !== null ? '' + match1[4].replace(/^\//, '').replace(/\//g, '%2F') : ''; - const match2 = cicdConfig.gitUrl.match(/^(https?:\/\/|git@)[^/:]+[/:]([^/]+)\/([^/]*?)(.git|)$/); - let sourceOwner = match2 !== null ? match2[2] : ''; - let sourceRepo = match2 !== null ? match2[3] : ''; - - const cicdRootPath = `/api/v4/projects/${sourceOwner}%2F${sourceRepo.replace(/\//g, '%2F')}/pipelines?per_page=100`; + // Pipelines API https://docs.gitlab.com/ee/api/pipelines.html#list-project-pipelines + let cicdRootPath = `/api/v4/projects/${hostpath}/pipelines?per_page=100`; + if (cicdRequest.detail) { + // Commits API https://docs.gitlab.com/ee/api/commits.html#list-the-statuses-of-a-commit + cicdRootPath = `/api/v4/projects/${hostpath}/repository/commits/${cicdRequest.hash}/statuses?per_page=100`; + } let headers: any = { 'User-Agent': 'vscode-git-graph' @@ -418,7 +431,7 @@ export class CicdManager extends Disposable { let triggeredOnError = false; const onError = (err: Error) => { - this.logger.log('GitLab API HTTPS Error - ' + err.message); + this.logger.log('GitLab API ' + hostProtocol + ' Error - ' + err.message); if (!triggeredOnError) { // If an error occurs, try again after 5 minutes triggeredOnError = true; @@ -427,8 +440,10 @@ export class CicdManager extends Disposable { } }; - https.get({ + this.logger.log('Requesting CICD for ' + hostProtocol + '://' + hostRootUrl + cicdRootPath + ' page=' + cicdRequest.page + ' from GitLab'); + (hostProtocol === 'http' ? http : https).get({ hostname: hostRootUrl, path: cicdRootPath, + port: hostPort, headers: headers, agent: false, timeout: 15000 }, (res) => { @@ -443,34 +458,37 @@ export class CicdManager extends Disposable { if (res.statusCode === 200) { // Success try { + this.logger.log('GitLab API - (' + res.statusCode + ')' + hostProtocol + '://' + hostRootUrl + cicdRootPath); if (typeof res.headers['x-page'] === 'string' && typeof res.headers['x-total-pages'] === 'string' && typeof res.headers['x-total'] === 'string') { let respJson: any = JSON.parse(respBody); if (parseInt(res.headers['x-total']) !== 0 && respJson.length && respJson[0].id) { // url found let ret: CICDData[] = respJson; ret.forEach(element => { - let save = this.convCICDData2CICDDataSave(element); + let save: CICDDataSave; + if (cicdRequest.detail) { + save = this.convComitStatuses2CICDDataSave(element, cicdRequest.detail); + } else { + save = this.convCICDData2CICDDataSave(element); + } this.saveCICD(element.sha, element.id, save); }); + } - if (cicdRequest.page === -1) { - let last = parseInt(res.headers['x-total-pages']); - if (last > cicdRequest.maxPage) { - last = cicdRequest.maxPage; - this.logger.log('CICD Maximum page(pages=' + cicdRequest.maxPage + ') reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsPage'); - } - this.logger.log('Added CICD for ' + cicdConfig.gitUrl + ' last_page=' + last + '(RateLimit=' + res.headers['ratelimit-limit'] + '(every minute)/Remaining=' + res.headers['ratelimit-remaining'] + '/' + new Date(parseInt(res.headers['ratelimit-reset']) * 1000).toString() + ') from GitLab'); - for (var i = 1; i < last; i++) { - // let cicdRequestNew = Object.assign({}, cicdRequest);; - // cicdRequestNew.page = i + 1; - // this.queue.addItem(cicdRequestNew, 0, false); - this.queue.add(cicdRequest.cicdConfig, i + 1, true); - } + if (cicdRequest.page === -1) { + let last = parseInt(res.headers['x-total-pages']); + if (last > cicdRequest.maxPage) { + last = cicdRequest.maxPage; + this.logger.log('CICD Maximum page(pages=' + cicdRequest.maxPage + ') reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsPage'); + } + this.logger.log('Added CICD for ' + cicdConfig.gitUrl + ' last_page=' + last + '(RateLimit=' + res.headers['ratelimit-limit'] + '(every minute)/Remaining=' + res.headers['ratelimit-remaining'] + '/' + new Date(parseInt(res.headers['ratelimit-reset']) * 1000).toString() + ') from GitLab'); + for (var i = 1; i < last; i++) { + this.queue.add(cicdRequest.cicdConfig, i + 1, true); } - return; } + return; } } catch (e) { - this.logger.log('GitLab API Error - (' + res.statusCode + ')API Result error.'); + this.logger.log('GitLab API Error - (' + res.statusCode + ')API Result error. : ' + e.message); } } else if (res.statusCode === 429) { // Rate limit reached, try again after timeout @@ -495,6 +513,129 @@ export class CicdManager extends Disposable { }).on('error', onError); } + /** + * Fetch an cicd from Jenkins. + * @param cicdRequest The cicd request to fetch. + */ + private fetchFromJenkins(cicdRequest: CICDRequestItem) { + if (cicdRequest.detail) { + return; + } + let t = (new Date()).getTime(); + if (cicdRequest.checkAfter !== 0 && t < this.jenkinsTimeout) { + // Defer request until after timeout + this.queue.addItem(cicdRequest, this.jenkinsTimeout, false); + this.fetchCICDsInterval(); + return; + } + + let cicdConfig = cicdRequest.cicdConfig; + + let gitUrl = cicdConfig.gitUrl.replace(/\/$/g, ''); + gitUrl = gitUrl.replace(/.git$/g, ''); + const match1 = gitUrl.match(/^(.+?):\/\/(.+?):?(\d+)?(\/.*)?$/); + let hostProtocol = match1 !== null ? '' + match1[1] : ''; + let hostRootUrl = match1 !== null ? '' + match1[2] : ''; + let hostPort = match1 !== null ? (match1[3] || '') : ''; + let hostpath = match1 !== null ? '' + match1[4].replace(/^\//, '') : ''; + + let cicdRootPath = `/${hostpath}/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]]`; + + let headers: any = { + 'User-Agent': 'vscode-git-graph' + }; + if (cicdConfig.glToken !== '') { + // headers['Authorization'] = 'Basic ' + new Buffer(username + ':' + passw).toString('base64'); + headers['Authorization'] = 'Basic ' + new Buffer(cicdConfig.glToken).toString('base64'); + } + + let triggeredOnError = false; + const onError = (err: Error) => { + this.logger.log('Jenkins API HTTPS Error - ' + err.message); + if (!triggeredOnError) { + // If an error occurs, try again after 5 minutes + triggeredOnError = true; + this.jenkinsTimeout = t + 300000; + this.queue.addItem(cicdRequest, this.jenkinsTimeout, false); + } + }; + + this.logger.log('Requesting CICD for ' + hostProtocol + '://' + hostRootUrl + cicdRootPath + ' page=' + cicdRequest.page + ' from Jenkins'); + (hostProtocol === 'http' ? http : https).get({ + hostname: hostRootUrl, path: cicdRootPath, + port: hostPort, + headers: headers, + agent: false, timeout: 15000 + }, (res) => { + let respBody = ''; + res.on('data', (chunk: Buffer) => { respBody += chunk; }); + res.on('end', async () => { + if (res.headers['ratelimit-remaining'] === '0') { + // If the GitLab Api rate limit was reached, store the gitlab timeout to prevent subsequent requests + this.jenkinsTimeout = parseInt(res.headers['ratelimit-reset']) * 1000; + this.logger.log('GitLab API Rate Limit Reached - Paused fetching from GitLab until the Rate Limit is reset (RateLimit=' + res.headers['ratelimit-limit'] + '(every minute)/' + new Date(this.jenkinsTimeout).toString() + ')'); + } + + if (res.statusCode === 200) { // Success + try { + if (typeof res.headers['x-jenkins'] === 'string' && res.headers['x-jenkins'].startsWith('2.')) { + let respJson: any = JSON.parse(respBody); + if (respJson['builds'].length) { // url found + let ret: CICDData[] = []; + respJson['builds'].forEach((elmBuild: { [x: string]: any; })=> { + elmBuild['actions'].forEach((elmAction: { [x: string]: any; }) => { + if (typeof elmAction['lastBuiltRevision'] !== 'undefined' && typeof elmAction['lastBuiltRevision']['branch'] !== 'undefined' + && typeof elmAction['lastBuiltRevision']['branch'][0] !== 'undefined' && typeof elmAction['lastBuiltRevision']['branch'][0].SHA1 !== 'undefined') { + ret.push( + { + id: elmBuild['id'], + status: elmBuild['result'], + ref: elmAction['lastBuiltRevision']!['branch']![0]!.name, + sha: elmAction['lastBuiltRevision']!['branch']![0]!.SHA1, + web_url: elmBuild['url'], + created_at: '', + updated_at: elmBuild['timestamp'], + name: elmBuild['fullDisplayName'], + event: '', + detail: cicdRequest.detail + } + ); + } + }); + }); + ret.forEach(element => { + let save = this.convCICDData2CICDDataSave(element); + this.saveCICD(element.sha, element.id, save); + }); + return; + } + } + } catch (e) { + this.logger.log('Jenkins API Error - (' + res.statusCode + ')API Result error. : ' + e.message); + } + } else if (res.statusCode === 429) { + // Rate limit reached, try again after timeout + this.queue.addItem(cicdRequest, this.jenkinsTimeout, false); + return; + } else if (res.statusCode! >= 500) { + // If server error, try again after 10 minutes + this.jenkinsTimeout = t + 600000; + this.queue.addItem(cicdRequest, this.jenkinsTimeout, false); + return; + } else { + // API Error + try { + let respJson: any = JSON.parse(respBody); + this.logger.log('GitLab API Error - (' + res.statusCode + ')' + respJson.message); + } catch (e) { + this.logger.log('GitLab API Error - (' + res.statusCode + ')' + res.statusMessage); + } + } + }); + res.on('error', onError); + }).on('error', onError); + } + /** * Fetch an cicd from GitHub. * @param cicdData The CICDData. @@ -502,14 +643,32 @@ export class CicdManager extends Disposable { */ private convCICDData2CICDDataSave(cicdData: CICDData): CICDDataSave { return { - name: cicdData.name, - ref: cicdData.ref, - status: cicdData.status, - web_url: cicdData.web_url, - event: cicdData.event, - detail: cicdData.detail + name: cicdData!.name, + ref: cicdData!.ref, + status: cicdData!.status, + web_url: cicdData!.web_url, + event: cicdData!.event, + detail: cicdData!.detail }; } + + /** + * Fetch an cicd from GitHub. + * @param cicdData The CICDData. + * @param detail Detail fetch flag. + * @returns The CICDDataSave. + */ + private convComitStatuses2CICDDataSave(data: any, detail: boolean): CICDDataSave { + return { + name: data!.name, + ref: data!.ref, + status: data!.status, + web_url: data!.target_url, + event: '', + detail: detail + }; + } + /** * Emit an CICDEvent to any listeners. * @param hash The hash identifying the cicd commit. diff --git a/src/types.ts b/src/types.ts index 5ac19fb8..e8434403 100644 --- a/src/types.ts +++ b/src/types.ts @@ -220,7 +220,7 @@ export const enum CICDProvider { Custom, GitHubV3, GitLabV4, - Jenkins + JenkinsV2 } interface CICDConfigBuiltIn extends CICDConfigBase { diff --git a/web/main.ts b/web/main.ts index 5b68e725..b24f2288 100644 --- a/web/main.ts +++ b/web/main.ts @@ -546,14 +546,18 @@ class GitGraphView { let cicdDataSave = cicdDataSaves[name]; let event = cicdDataSave.event || ''; let detailCurrent = cicdDataSave.detail || false; - let title = (typeof cicdDataSave.name !== 'undefined' ? cicdDataSave.name + '/' : '') + cicdDataSave.status + '/' + event; - if (detailCurrent === detail && event === 'push' || event === 'pull_request') { - // if (detailCurrent === detail && event !== 'issues' && event !== 'issue_comment' && event !== 'schedule' && event !== 'workflow_run') { + let status = ((cicdDataSave.status === 'success' || cicdDataSave.status === 'SUCCESS') ? 'G' : ((cicdDataSave.status === 'failed' || cicdDataSave.status === 'failure') ? 'B' : 'U')); + // if (detailCurrent === detail && event === 'push' || event === 'pull_request' || event === '') { + if (detailCurrent === detail && event !== 'issues' && event !== 'issue_comment' && event !== 'schedule' && event !== 'workflow_run') { ret += - '' + - (cicdDataSave.status === 'success' ? '' + SVG_ICONS.passed + '' : - ((cicdDataSave.status === 'failed' || cicdDataSave.status === 'failure') ? '' + SVG_ICONS.failed + '' : - '' + SVG_ICONS.inconclusive + '')) + + '' + + // '
' + + `
` + + (typeof cicdDataSave.name !== 'undefined' ? `
${cicdDataSave.name}
` : '') + + (typeof cicdDataSave.status !== 'undefined' ? `
Status: ${cicdDataSave.status}
` : '') + + ((typeof cicdDataSave.event !== 'undefined' && cicdDataSave.event !== '') ? `
Event: ${cicdDataSave.event}
` : '') + + '
' + + `${(status === 'G' ? SVG_ICONS.passed : (status === 'B' ? SVG_ICONS.failed : SVG_ICONS.inconclusive))}` + '
'; } } @@ -928,9 +932,9 @@ class GitGraphView { (colVisibility.date ? '' + date.formatted + '' : '') + (colVisibility.author ? '' + (this.config.fetchAvatars ? '' + (typeof this.avatars[commit.email] === 'string' ? '' : '') + '' : '') + escapeHtml(commit.author) + '' : '') + (colVisibility.commit ? '' + abbrevCommit(commit.hash) + '' : '') + - (colVisibility.cicd ? (this.config.fetchCICDs ? '' + '' + + (colVisibility.cicd ? (this.config.fetchCICDs ? '' + '' + (typeof this.cicdDatas[commit.hash] === 'object' ? (this.getCicdHtml(this.cicdDatas[commit.hash])) : '*') + - '' + '' : '-') : '') + + '' + '' : '-') : '') + ''; } this.tableElem.innerHTML = '' + html + '
'; @@ -2217,6 +2221,8 @@ class GitGraphView { contextMenu.close(); } + } if ((eventElem = eventTarget.closest('.cicdAnchor')) !== null) { + // .cicdAnchor was clicked } else if ((eventElem = eventTarget.closest('.commit')) !== null) { // .commit was clicked if (this.expandedCommit !== null) { @@ -2566,11 +2572,12 @@ class GitGraphView { + (commitDetails.authorDate !== commitDetails.committerDate ? 'Author Date: ' + formatLongDate(commitDetails.authorDate) + '
' : '') + 'Committer: ' + escapeHtml(commitDetails.committer) + (commitDetails.committerEmail !== '' ? ' <' + escapeHtml(commitDetails.committerEmail) + '>' : '') + (commitDetails.signature !== null ? generateSignatureHtml(commitDetails.signature) : '') + '
' + '' + (commitDetails.authorDate !== commitDetails.committerDate ? 'Committer ' : '') + 'Date: ' + formatLongDate(commitDetails.committerDate) + '
' + + (expandedCommit.avatar !== null ? '' : '') + + '' + (this.config.fetchCICDs ? 'CI/CD detail: ' + '' + (typeof this.cicdDatas[commitDetails.hash] === 'object' ? (this.getCicdHtml(this.cicdDatas[commitDetails.hash], true)) : '*') + '' : '') - + (expandedCommit.avatar !== null ? '' : '') - + '

' + textFormatter.format(commitDetails.body); + + '

' + textFormatter.format(commitDetails.body); } else { html += 'Displaying all uncommitted changes.'; } diff --git a/web/settingsWidget.ts b/web/settingsWidget.ts index 0db87ea2..dad7ce84 100644 --- a/web/settingsWidget.ts +++ b/web/settingsWidget.ts @@ -246,7 +246,8 @@ class SettingsWidget { cicdConfigs.forEach((cicdConfig, i) => { let providerOptions:any = {}; providerOptions[(GG.CICDProvider.GitHubV3).toString()] = 'GitHub'; - providerOptions[(GG.CICDProvider.GitLabV4).toString()] = 'GitLab APIv4(ver8.11-)'; + providerOptions[(GG.CICDProvider.GitLabV4).toString()] = 'GitLab API v4(ver8.11-)'; + providerOptions[(GG.CICDProvider.JenkinsV2).toString()] = 'Jenkins v2'; const gitUrl = escapeHtml(cicdConfig.gitUrl || 'Not Set'); html += '' + '' + escapeHtml(providerOptions[cicdConfig.provider]) + '' + @@ -496,7 +497,8 @@ class SettingsWidget { let providerOptions = [ // { name: 'Bitbucket', value: (GG.CICDProvider.Bitbucket).toString() }, { name: 'GitHub', value: (GG.CICDProvider.GitHubV3).toString() }, - { name: 'GitLab APIv4(ver8.11-)', value: (GG.CICDProvider.GitLabV4).toString() } + { name: 'GitLab API v4(ver8.11-)', value: (GG.CICDProvider.GitLabV4).toString() }, + { name: 'Jenkins v2', value: (GG.CICDProvider.JenkinsV2).toString() } ]; dialog.showForm('Add a new cicd to this repository:', [ { @@ -520,7 +522,8 @@ class SettingsWidget { let providerOptions = [ // { name: 'Bitbucket', value: (GG.CICDProvider.Bitbucket).toString() }, { name: 'GitHub', value: (GG.CICDProvider.GitHubV3).toString() }, - { name: 'GitLab APIv4(ver8.11-)', value: (GG.CICDProvider.GitLabV4).toString() } + { name: 'GitLab API v4(ver8.11-)', value: (GG.CICDProvider.GitLabV4).toString() }, + { name: 'Jenkins v2', value: (GG.CICDProvider.JenkinsV2).toString() } ]; dialog.showForm('Edit the CI/CD ' + escapeHtml(cicdConfig.gitUrl || 'Not Set') + ':', [ { diff --git a/web/styles/main.css b/web/styles/main.css index 2119adb0..6c45ff55 100644 --- a/web/styles/main.css +++ b/web/styles/main.css @@ -217,7 +217,10 @@ body.selection-background-color-exists ::selection{ #commitTable tr.commit td{ cursor:pointer; } - +#commitTable tr.commit td.cicdCol{ + cursor:pointer; + overflow: visible; +} #commitTable tr.commit.current span.description .text{ font-weight:bold; } @@ -1034,4 +1037,78 @@ label > input[type=radio]:checked ~ .customRadio:after{ #commitGraph, #commitTable th, #commitTable td, .gitRef, #loadingHeader, .unselectable, .roundedBtn, #controls label{ -webkit-user-select:none; user-select:none; -} \ No newline at end of file +} + +/* cicdTooltip */ + +.cicdTooltip{ + display: inline-block; + position: relative; + cursor: pointer; +} +.cicdTooltip .cicdTooltipContent{ + position: absolute; + border-style: solid; + /* transform: translateY(-50%); */ + visibility: hidden; + z-index: 1; +} +/* .cicdTooltip .cicdTooltipContent::before{ + position: absolute; + border-style: solid; + transform: translateY(-50%); + content: ""; + height: 0; + width: 0; + top: 50%; +} */ +.cicdTooltip .cicdTooltipContent{ + background-color:var(--vscode-menu-background); + border-radius: 5px; + border-width: 2px; + color:var(--vscode-menu-foreground); + display: block; + font-size: 13px; + font-weight: 600; + /* top: 50%; */ + top: calc(100%); + white-space: nowrap; +} +.cicdTooltip .cicdTooltipContent.G{ + border-color: #009028; +} +.cicdTooltip .cicdTooltipContent.U{ + border-color: #f09000; +} +.cicdTooltip .cicdTooltipContent.B{ + border-color: #e00000; +} +/* .cicdTooltip .cicdTooltipContent.right::before{ + border-color: transparent rgba(128,128,128,1.0) transparent transparent; + border-width: 5px 9px 5px 0; + right: 100%; +} */ +.cicdTooltip .cicdTooltipContent.right{ + left: calc(100% + 13px); +} +/* .cicdTooltip .cicdTooltipContent.left::before{ + border-color: transparent transparent transparent rgba(128,128,128,1.0); + border-width: 5px 0 5px 9px; + left: 100%; +} */ +.cicdTooltip .cicdTooltipContent.left{ + right: calc(100% + 13px); +} +.cicdTooltip:hover .cicdTooltipContent{ + visibility: visible; +} +.cicdTooltipTitle, .cicdTooltipSection{ + padding:3px 10px; +} +.cicdTooltipTitle{ + text-align:center; + font-weight:700; +} +.cicdTooltipSection{ + border-top:1px solid rgba(128,128,128,0.5); +} From b3bdba94a55662e55bfdb15564dee21d6cdd70d9 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sun, 28 Mar 2021 02:03:12 +0900 Subject: [PATCH 23/54] #462 Fixed Lint check --- src/cicdManager.ts | 12 ++++++------ web/main.ts | 2 +- web/settingsWidget.ts | 1 - 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/cicdManager.ts b/src/cicdManager.ts index f653d59f..33ce45bd 100644 --- a/src/cicdManager.ts +++ b/src/cicdManager.ts @@ -98,7 +98,7 @@ export class CicdManager extends Disposable { } this.cicdConfigsPrev = Object.assign({}, cicdConfigs); // CICD not in the cache, request it - if (this.initialState === true) { + if (this.initialState) { this.initialState = false; cicdConfigs.forEach(cicdConfig => { this.queue.add(cicdConfig, this.requestPage, true); @@ -108,7 +108,7 @@ export class CicdManager extends Disposable { this.logger.log('Reset initial timer of CICD'); this.initialState = true; }, 10000); - // set request page to top + // set request page to top this.requestPage = 1; // Reset request page to all after 10 minutes if (this.requestPageTimeout === null) { @@ -386,7 +386,7 @@ export class CicdManager extends Disposable { } this.logger.log('Added CICD for ' + cicdConfig.gitUrl + ' last_page=' + last + '(RateLimit=' + res.headers['x-ratelimit-limit'] + '(1 hour)/Remaining=' + res.headers['x-ratelimit-remaining'] + '/' + new Date(parseInt(res.headers['x-ratelimit-reset']) * 1000).toString() + ') from GitHub'); - for (var i = 1; i < last; i++) { + for (let i = 1; i < last; i++) { this.queue.add(cicdRequest.cicdConfig, i + 1, true); } } @@ -481,7 +481,7 @@ export class CicdManager extends Disposable { this.logger.log('CICD Maximum page(pages=' + cicdRequest.maxPage + ') reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsPage'); } this.logger.log('Added CICD for ' + cicdConfig.gitUrl + ' last_page=' + last + '(RateLimit=' + res.headers['ratelimit-limit'] + '(every minute)/Remaining=' + res.headers['ratelimit-remaining'] + '/' + new Date(parseInt(res.headers['ratelimit-reset']) * 1000).toString() + ') from GitLab'); - for (var i = 1; i < last; i++) { + for (let i = 1; i < last; i++) { this.queue.add(cicdRequest.cicdConfig, i + 1, true); } } @@ -582,7 +582,7 @@ export class CicdManager extends Disposable { let respJson: any = JSON.parse(respBody); if (respJson['builds'].length) { // url found let ret: CICDData[] = []; - respJson['builds'].forEach((elmBuild: { [x: string]: any; })=> { + respJson['builds'].forEach((elmBuild: { [x: string]: any; }) => { elmBuild['actions'].forEach((elmAction: { [x: string]: any; }) => { if (typeof elmAction['lastBuiltRevision'] !== 'undefined' && typeof elmAction['lastBuiltRevision']['branch'] !== 'undefined' && typeof elmAction['lastBuiltRevision']['branch'][0] !== 'undefined' && typeof elmAction['lastBuiltRevision']['branch'][0].SHA1 !== 'undefined') { @@ -789,7 +789,7 @@ class CicdRequestQueue { * @param item The cicd request item. */ private insertItem(item: CICDRequestItem) { - var l = 0, r = this.queue.length - 1, c, prevLength = this.queue.length; + let l = 0, r = this.queue.length - 1, c, prevLength = this.queue.length; while (l <= r) { c = l + r >> 1; if (this.queue[c].checkAfter <= item.checkAfter) { diff --git a/web/main.ts b/web/main.ts index 22c48798..0c93b97e 100644 --- a/web/main.ts +++ b/web/main.ts @@ -552,7 +552,7 @@ class GitGraphView { ret += '' + // '
' + - `
` + + `
` + (typeof cicdDataSave.name !== 'undefined' ? `
${cicdDataSave.name}
` : '') + (typeof cicdDataSave.status !== 'undefined' ? `
Status: ${cicdDataSave.status}
` : '') + ((typeof cicdDataSave.event !== 'undefined' && cicdDataSave.event !== '') ? `
Event: ${cicdDataSave.event}
` : '') + diff --git a/web/settingsWidget.ts b/web/settingsWidget.ts index dad7ce84..015ab575 100644 --- a/web/settingsWidget.ts +++ b/web/settingsWidget.ts @@ -472,7 +472,6 @@ class SettingsWidget { this.view.saveRepoStateValue(this.currentRepo, 'hideRemotes', this.repo.hideRemotes); this.view.refresh(true); }); - const updateConfigWithFormValues = (values: DialogInputValue[]) => { let config: GG.CICDConfig = { provider: parseInt(values[0]), gitUrl: values[1], From 6520d49064a77e23990e28b564fa1050e2ea3df7 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sun, 28 Mar 2021 02:20:53 +0900 Subject: [PATCH 24/54] #462 Updated help message --- web/settingsWidget.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/settingsWidget.ts b/web/settingsWidget.ts index 015ab575..4d0b6619 100644 --- a/web/settingsWidget.ts +++ b/web/settingsWidget.ts @@ -505,8 +505,8 @@ class SettingsWidget { options: providerOptions, default: defaultProvider, info: 'In addition to the built-in publicly hosted CI/CD providers.' }, - { type: DialogInputType.Text, name: 'Git URL', default: '', placeholder: null, info: 'The CI/CD provider\'s Git URL (e.g. https://gitlab.com/OWNER/REPO.git).' }, - { type: DialogInputType.Password, name: 'Access Token', default: '', info: 'The GitHub/GitLab personal access token or project access token.' } + { type: DialogInputType.Text, name: 'Git/Jenkins URL', default: '', placeholder: null, info: 'The CI/CD provider\'s Git URL (e.g. https://gitlab.com/OWNER/REPO.git) / Jenkins Job URL.' }, + { type: DialogInputType.Password, name: 'Access Token', default: '', info: 'The GitHub/GitLab personal or project access token / The Jenkin user_name:password or user_name:access_token' } ], 'Add CI/CD', (values) => { let configs: GG.CICDConfig[] = copyConfigs(); let config: GG.CICDConfig = updateConfigWithFormValues(values); @@ -530,8 +530,8 @@ class SettingsWidget { options: providerOptions, default: cicdConfig.provider.toString(), info: 'In addition to the built-in publicly hosted CI/CD providers.' }, - { type: DialogInputType.Text, name: 'Git URL', default: cicdConfig.gitUrl || '', placeholder: null, info: 'The CI/CD provider\'s Git URL (e.g. https://gitlab.com/OWNER/REPO.git).' }, - { type: DialogInputType.Password, name: 'Access Token', default: cicdConfig.glToken, info: 'The GitHub/GitLab personal access token or project access token.' } + { type: DialogInputType.Text, name: 'Git/Jenkins URL', default: cicdConfig.gitUrl || '', placeholder: null, info: 'The CI/CD provider\'s Git URL (e.g. https://gitlab.com/OWNER/REPO.git) / Jenkins Job URL.' }, + { type: DialogInputType.Password, name: 'Access Token', default: cicdConfig.glToken, info: 'The GitHub/GitLab personal or project access token / The Jenkin user_name:password or user_name:access_token' } ], 'Save Changes', (values) => { let index = parseInt(((e.target).closest('.cicdBtns')!).dataset.index!); let configs: GG.CICDConfig[] = copyConfigs(); From 149ecc50e5c223251f80a0223cfbbd9822307224 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sun, 28 Mar 2021 16:53:24 +0900 Subject: [PATCH 25/54] #462 Bug fixed opacity of CI/CD column --- web/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/main.ts b/web/main.ts index 0c93b97e..fd2dfd17 100644 --- a/web/main.ts +++ b/web/main.ts @@ -932,7 +932,7 @@ class GitGraphView { (colVisibility.date ? '' + date.formatted + '' : '') + (colVisibility.author ? '' + (this.config.fetchAvatars ? '' + (typeof this.avatars[commit.email] === 'string' ? '' : '') + '' : '') + escapeHtml(commit.author) + '' : '') + (colVisibility.commit ? '' + abbrevCommit(commit.hash) + '' : '') + - (colVisibility.cicd ? (this.config.fetchCICDs ? '' + '' + + (colVisibility.cicd ? (this.config.fetchCICDs ? '' + '' + (typeof this.cicdDatas[commit.hash] === 'object' ? (this.getCicdHtml(this.cicdDatas[commit.hash])) : '*') + '' + '' : '-') : '') + ''; From 4af7f2f15ff58751e219d55e2bb8d8757cefc176 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sat, 10 Apr 2021 00:55:26 +0900 Subject: [PATCH 26/54] #462 Restored position of commit author avatars --- web/main.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/web/main.ts b/web/main.ts index fd2dfd17..aa5fcd0c 100644 --- a/web/main.ts +++ b/web/main.ts @@ -2571,9 +2571,10 @@ class GitGraphView { + 'Author: ' + escapeHtml(commitDetails.author) + (commitDetails.authorEmail !== '' ? ' <
' + escapeHtml(commitDetails.authorEmail) + '>' : '') + '
' + (commitDetails.authorDate !== commitDetails.committerDate ? 'Author Date: ' + formatLongDate(commitDetails.authorDate) + '
' : '') + 'Committer: ' + escapeHtml(commitDetails.committer) + (commitDetails.committerEmail !== '' ? ' <' + escapeHtml(commitDetails.committerEmail) + '>' : '') + (commitDetails.signature !== null ? generateSignatureHtml(commitDetails.signature) : '') + '
' - + '' + (commitDetails.authorDate !== commitDetails.committerDate ? 'Committer ' : '') + 'Date: ' + formatLongDate(commitDetails.committerDate) + '
' + + '' + (commitDetails.authorDate !== commitDetails.committerDate ? 'Committer ' : '') + 'Date: ' + formatLongDate(commitDetails.committerDate) + + '' + (expandedCommit.avatar !== null ? '' : '') - + '' + + '
' + (this.config.fetchCICDs ? 'CI/CD detail: ' + '' + (typeof this.cicdDatas[commitDetails.hash] === 'object' ? (this.getCicdHtml(this.cicdDatas[commitDetails.hash], true)) : '*') + '' : '') From baea4821a9b85a5127899b7f136d74d739f775f0 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sat, 10 Apr 2021 00:56:22 +0900 Subject: [PATCH 27/54] #462 Changed API message --- src/cicdManager.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cicdManager.ts b/src/cicdManager.ts index 33ce45bd..fa1182b2 100644 --- a/src/cicdManager.ts +++ b/src/cicdManager.ts @@ -385,7 +385,7 @@ export class CicdManager extends Disposable { this.logger.log('CICD Maximum page(pages=' + cicdRequest.maxPage + ') reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsPage'); } - this.logger.log('Added CICD for ' + cicdConfig.gitUrl + ' last_page=' + last + '(RateLimit=' + res.headers['x-ratelimit-limit'] + '(1 hour)/Remaining=' + res.headers['x-ratelimit-remaining'] + '/' + new Date(parseInt(res.headers['x-ratelimit-reset']) * 1000).toString() + ') from GitHub'); + this.logger.log('Added CICD for ' + cicdConfig.gitUrl + ' last_page=' + last + '(RateLimit=' + (res.headers['x-ratelimit-limit'] || 'None') + '(1 hour)/Remaining=' + (res.headers['x-ratelimit-remaining'] || 'None') + (res.headers['x-ratelimit-reset'] ? '/' + new Date(parseInt(res.headers['x-ratelimit-reset']) * 1000).toString() : '') + ') from GitHub'); for (let i = 1; i < last; i++) { this.queue.add(cicdRequest.cicdConfig, i + 1, true); } @@ -480,7 +480,8 @@ export class CicdManager extends Disposable { last = cicdRequest.maxPage; this.logger.log('CICD Maximum page(pages=' + cicdRequest.maxPage + ') reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsPage'); } - this.logger.log('Added CICD for ' + cicdConfig.gitUrl + ' last_page=' + last + '(RateLimit=' + res.headers['ratelimit-limit'] + '(every minute)/Remaining=' + res.headers['ratelimit-remaining'] + '/' + new Date(parseInt(res.headers['ratelimit-reset']) * 1000).toString() + ') from GitLab'); + + this.logger.log('Added CICD for ' + cicdConfig.gitUrl + ' last_page=' + last + '(RateLimit=' + (res.headers['ratelimit-limit'] || 'None') + '(every minute)/Remaining=' + (res.headers['ratelimit-remaining'] || 'None') + (res.headers['ratelimit-reset'] ? '/' + new Date(parseInt(res.headers['ratelimit-reset']) * 1000).toString() : '') + ') from GitLab'); for (let i = 1; i < last; i++) { this.queue.add(cicdRequest.cicdConfig, i + 1, true); } From e772fbffc48865d07680dbd71c2b8a115397fe7b Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sat, 10 Apr 2021 01:16:56 +0900 Subject: [PATCH 28/54] #462 Removed rejection handling code of emitCICD --- src/cicdManager.ts | 23 ++--------------------- src/extensionState.ts | 10 ---------- 2 files changed, 2 insertions(+), 31 deletions(-) diff --git a/src/cicdManager.ts b/src/cicdManager.ts index fa1182b2..9df450dd 100644 --- a/src/cicdManager.ts +++ b/src/cicdManager.ts @@ -79,10 +79,7 @@ export class CicdManager extends Disposable { public fetchCICDStatus(hash: string, cicdConfigs: CICDConfig[]) { if (typeof this.cicds[hash] !== 'undefined') { // CICD exists in the cache - this.emitCICD(hash, this.cicds[hash]).catch(() => { - // CICD couldn't be found - this.removeCICDFromCache(hash); - }); + this.emitCICD(hash, this.cicds[hash]); } else { // Check update user config @@ -149,15 +146,6 @@ export class CicdManager extends Disposable { return this.cicdEventEmitter.subscribe; } - /** - * Remove an cicd from the cache. - * @param hash The hash identifying the cicd commit. - */ - private removeCICDFromCache(hash: string) { - delete this.cicds[hash]; - this.extensionState.removeCICDFromCache(hash); - } - /** * Remove all cicds from the cache. */ @@ -703,14 +691,7 @@ export class CicdManager extends Disposable { this.cicds[hash][id] = cicdDataSave; this.extensionState.saveCICD(hash, id, cicdDataSave); // this.logger.log('Saved CICD for ' + cicdData.sha); - this.emitCICD(hash, this.cicds[hash]).then( - // (sent) => this.logger.log(sent - // ? 'Sent CICD for ' + cicdData.sha + ' to the Git Graph View' - // : 'CICD for ' + cicdData.sha + ' is ready to be used the next time the Git Graph View is opened' - // ), - () => { }, - () => this.logger.log('Failed to Send CICD for ' + hash + ' to the Git Graph View') - ); + this.emitCICD(hash, this.cicds[hash]); } } diff --git a/src/extensionState.ts b/src/extensionState.ts index 25859e9e..7a18a190 100644 --- a/src/extensionState.ts +++ b/src/extensionState.ts @@ -336,16 +336,6 @@ export class ExtensionState extends Disposable { this.updateWorkspaceState(CICD_CACHE, cicds); } - /** - * Removes an cicd from the cache of cicds known to Git Graph. - * @param hash The hash of the cicd to remove. - */ - public removeCICDFromCache(hash: string) { - let cicds = this.getCICDCache(); - delete cicds[hash]; - this.updateWorkspaceState(CICD_CACHE, cicds); - } - /** * Clear all cicds from the cache of cicds known to Git Graph. */ From c98dfe9eea5e05c6239fc44fc51cc4f817d6e96a Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sat, 10 Apr 2021 01:27:33 +0900 Subject: [PATCH 29/54] #462 Removed git-graph.fetchCICDs and Page --- package.json | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/package.json b/package.json index 660da8e5..b58567fb 100644 --- a/package.json +++ b/package.json @@ -1257,20 +1257,6 @@ "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.fetchAvatars", "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.fetchAvatars#`" }, - "git-graph.fetchCICDs": { - "type": "boolean", - "default": true, - "description": "Fetch CI/CD status of commit. By enabling this setting, you consent to Access Token being sent GitHub or GitLab, depending on the repositories and user configuration.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.fetchCICDs", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.fetchCICDs#`" - }, - "git-graph.fetchCICDsPage": { - "type": "number", - "default": 10, - "description": "Specifies the number of CI/CD fetch maximum page.", - "deprecationMessage": "Depreciated: This setting has been renamed to git-graph.repository.commits.fetchCICDsPage", - "markdownDeprecationMessage": "Depreciated: This setting has been renamed to `#git-graph.repository.commits.fetchCICDsPage#`" - }, "git-graph.graphColours": { "type": "array", "items": { From 311cc798f6604fe28d1a70d00e78adc9577fbcad Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sat, 10 Apr 2021 01:54:35 +0900 Subject: [PATCH 30/54] #462 Removed commits.fetchCICDs --- src/config.ts | 7 ------- src/gitGraphView.ts | 1 - src/types.ts | 1 - web/main.ts | 8 ++++---- 4 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/config.ts b/src/config.ts index 32aed6a5..2bfbab07 100644 --- a/src/config.ts +++ b/src/config.ts @@ -356,13 +356,6 @@ class Config { return !!this.getRenamedExtensionSetting('repository.commits.fetchAvatars', 'fetchAvatars', false); } - /** - * Get the value of the `git-graph.repository.commits.fetchCICDs` Extension Setting. - */ - get fetchCICDs() { - return !!this.getRenamedExtensionSetting('repository.commits.fetchCICDs', 'fetchCICDs', true); - } - /** * Get the value of the `git-graph.repository.commits.fetchCICDsPage` Extension Setting. */ diff --git a/src/gitGraphView.ts b/src/gitGraphView.ts index ea9de3e2..67bd9a8f 100644 --- a/src/gitGraphView.ts +++ b/src/gitGraphView.ts @@ -663,7 +663,6 @@ export class GitGraphView extends Disposable { fetchAndPrune: config.fetchAndPrune, fetchAndPruneTags: config.fetchAndPruneTags, fetchAvatars: config.fetchAvatars && this.extensionState.isAvatarStorageAvailable(), - fetchCICDs: config.fetchCICDs, fetchCICDsPage: config.fetchCICDsPage, graph: config.graph, includeCommitsMentionedByReflogs: config.includeCommitsMentionedByReflogs, diff --git a/src/types.ts b/src/types.ts index 5bf216a9..f932f440 100644 --- a/src/types.ts +++ b/src/types.ts @@ -287,7 +287,6 @@ export interface GitGraphViewConfig { readonly fetchAndPrune: boolean; readonly fetchAndPruneTags: boolean; readonly fetchAvatars: boolean; - readonly fetchCICDs: boolean; readonly fetchCICDsPage: number; readonly graph: GraphConfig; readonly includeCommitsMentionedByReflogs: boolean; diff --git a/web/main.ts b/web/main.ts index aa5fcd0c..9b16142b 100644 --- a/web/main.ts +++ b/web/main.ts @@ -932,9 +932,9 @@ class GitGraphView { (colVisibility.date ? '' + date.formatted + '' : '') + (colVisibility.author ? '' + (this.config.fetchAvatars ? '' + (typeof this.avatars[commit.email] === 'string' ? '' : '') + '' : '') + escapeHtml(commit.author) + '' : '') + (colVisibility.commit ? '' + abbrevCommit(commit.hash) + '' : '') + - (colVisibility.cicd ? (this.config.fetchCICDs ? '' + '' + + (colVisibility.cicd ? '' + '' + (typeof this.cicdDatas[commit.hash] === 'object' ? (this.getCicdHtml(this.cicdDatas[commit.hash])) : '*') + - '' + '' : '-') : '') + + '' + '' : '') + ''; } this.tableElem.innerHTML = '' + html + '
'; @@ -2575,9 +2575,9 @@ class GitGraphView { + '' + (expandedCommit.avatar !== null ? '' : '') + '
' - + (this.config.fetchCICDs ? 'CI/CD detail: ' + '' + + ('CI/CD detail: ' + '' + (typeof this.cicdDatas[commitDetails.hash] === 'object' ? (this.getCicdHtml(this.cicdDatas[commitDetails.hash], true)) : '*') - + '' : '') + + '' ) + '

' + textFormatter.format(commitDetails.body); } else { html += 'Displaying all uncommitted changes.'; From 3c1342f1b6d6f16a71e0f24b6cac597861e2f46b Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sat, 10 Apr 2021 02:08:39 +0900 Subject: [PATCH 31/54] #462 Changed fetchCICDsPage to MaximumStatuses --- package.json | 11 +++-------- src/cicdManager.ts | 26 ++++++++++++++------------ src/config.ts | 6 +++--- src/gitGraphView.ts | 2 +- src/types.ts | 2 +- 5 files changed, 22 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index b58567fb..cc9c19f7 100644 --- a/package.json +++ b/package.json @@ -944,15 +944,10 @@ "default": false, "description": "Fetch avatars of commit authors and committers. By enabling this setting, you consent to commit author and committer email addresses being sent GitHub, GitLab or Gravatar, depending on the repositories remote origin." }, - "git-graph.repository.commits.fetchCICDs": { - "type": "boolean", - "default": true, - "description": "Fetch CI/CD status of commit. By enabling this setting, you consent to Access Token being sent GitHub or GitLab, depending on the repositories and user configuration." - }, - "git-graph.repository.commits.fetchCICDsPage": { + "git-graph.repository.commits.fetchCICDsMaximumStatuses": { "type": "number", - "default": 10, - "description": "Specifies the number of CI/CD fetch maximum page." + "default": 1000, + "description": "Specifies the number of CI/CD fetch maximum statuses." }, "git-graph.repository.commits.initialLoad": { "type": "number", diff --git a/src/cicdManager.ts b/src/cicdManager.ts index 9df450dd..90b8fd08 100644 --- a/src/cicdManager.ts +++ b/src/cicdManager.ts @@ -30,6 +30,8 @@ export class CicdManager extends Disposable { private requestPageTimeout: NodeJS.Timer | null = null; private cicdConfigsPrev: CICDConfig[] = []; + private per_page: number = 100; + /** * Creates the Git Graph CICD Manager. * @param extensionState The Git Graph ExtensionState instance. @@ -204,9 +206,9 @@ export class CicdManager extends Disposable { let sourceOwner = match2 !== null ? match2[2] : ''; let sourceRepo = match2 !== null ? match2[3] : ''; - let cicdRootPath = `/repos/${sourceOwner}/${sourceRepo.replace(/\//g, '%2F')}/actions/runs?per_page=100`; + let cicdRootPath = `/repos/${sourceOwner}/${sourceRepo.replace(/\//g, '%2F')}/actions/runs?per_page=${this.per_page}`; if (cicdRequest.detail) { - cicdRootPath = `/repos/${sourceOwner}/${sourceRepo.replace(/\//g, '%2F')}/commits/${cicdRequest.hash}/check-runs?per_page=100`; + cicdRootPath = `/repos/${sourceOwner}/${sourceRepo.replace(/\//g, '%2F')}/commits/${cicdRequest.hash}/check-runs?per_page=${this.per_page}`; } if (cicdRequest.page > 1) { cicdRootPath = `${cicdRootPath}&page=${cicdRequest.page}`; @@ -368,9 +370,9 @@ export class CicdManager extends Disposable { } }); } - if (last > cicdRequest.maxPage) { - last = cicdRequest.maxPage; - this.logger.log('CICD Maximum page(pages=' + cicdRequest.maxPage + ') reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsPage'); + if (last > Math.ceil(cicdRequest.maximumStatuses / this.per_page)) { + last = Math.ceil(cicdRequest.maximumStatuses / this.per_page); + this.logger.log('CICD Maximum Statuses(maximumStatuses=' + cicdRequest.maximumStatuses + ') reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); } this.logger.log('Added CICD for ' + cicdConfig.gitUrl + ' last_page=' + last + '(RateLimit=' + (res.headers['x-ratelimit-limit'] || 'None') + '(1 hour)/Remaining=' + (res.headers['x-ratelimit-remaining'] || 'None') + (res.headers['x-ratelimit-reset'] ? '/' + new Date(parseInt(res.headers['x-ratelimit-reset']) * 1000).toString() : '') + ') from GitHub'); @@ -404,10 +406,10 @@ export class CicdManager extends Disposable { let hostpath = match1 !== null ? '' + match1[4].replace(/^\//, '').replace(/\//g, '%2F') : ''; // Pipelines API https://docs.gitlab.com/ee/api/pipelines.html#list-project-pipelines - let cicdRootPath = `/api/v4/projects/${hostpath}/pipelines?per_page=100`; + let cicdRootPath = `/api/v4/projects/${hostpath}/pipelines?per_page=${this.per_page}`; if (cicdRequest.detail) { // Commits API https://docs.gitlab.com/ee/api/commits.html#list-the-statuses-of-a-commit - cicdRootPath = `/api/v4/projects/${hostpath}/repository/commits/${cicdRequest.hash}/statuses?per_page=100`; + cicdRootPath = `/api/v4/projects/${hostpath}/repository/commits/${cicdRequest.hash}/statuses?per_page=${this.per_page}`; } let headers: any = { @@ -464,9 +466,9 @@ export class CicdManager extends Disposable { if (cicdRequest.page === -1) { let last = parseInt(res.headers['x-total-pages']); - if (last > cicdRequest.maxPage) { - last = cicdRequest.maxPage; - this.logger.log('CICD Maximum page(pages=' + cicdRequest.maxPage + ') reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsPage'); + if (last > Math.ceil(cicdRequest.maximumStatuses / this.per_page)) { + last = Math.ceil(cicdRequest.maximumStatuses / this.per_page); + this.logger.log('CICD Maximum Statuses(maximumStatuses=' + cicdRequest.maximumStatuses + ') reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); } this.logger.log('Added CICD for ' + cicdConfig.gitUrl + ' last_page=' + last + '(RateLimit=' + (res.headers['ratelimit-limit'] || 'None') + '(every minute)/Remaining=' + (res.headers['ratelimit-remaining'] || 'None') + (res.headers['ratelimit-reset'] ? '/' + new Date(parseInt(res.headers['ratelimit-reset']) * 1000).toString() : '') + ') from GitLab'); @@ -732,7 +734,7 @@ class CicdRequestQueue { attempts: 0, detail: detail, hash: hash, - maxPage: config.fetchCICDsPage + maximumStatuses: config.fetchCICDsMaximumStatuses }); } } @@ -797,7 +799,7 @@ interface CICDRequestItem { attempts: number; detail: boolean; hash: string; - maxPage: number; + maximumStatuses: number; } // Event to GitGraphView diff --git a/src/config.ts b/src/config.ts index 2bfbab07..9f364293 100644 --- a/src/config.ts +++ b/src/config.ts @@ -357,10 +357,10 @@ class Config { } /** - * Get the value of the `git-graph.repository.commits.fetchCICDsPage` Extension Setting. + * Get the value of the `git-graph.repository.commits.fetchCICDsMaximumStatuses` Extension Setting. */ - get fetchCICDsPage() { - return this.getRenamedExtensionSetting('repository.commits.fetchCICDsPage', 'fetchCICDsPage', 10); + get fetchCICDsMaximumStatuses() { + return this.getRenamedExtensionSetting('repository.commits.fetchCICDsMaximumStatuses', 'fetchCICDsMaximumStatuses', 1000); } /** diff --git a/src/gitGraphView.ts b/src/gitGraphView.ts index 67bd9a8f..e7abd490 100644 --- a/src/gitGraphView.ts +++ b/src/gitGraphView.ts @@ -663,7 +663,7 @@ export class GitGraphView extends Disposable { fetchAndPrune: config.fetchAndPrune, fetchAndPruneTags: config.fetchAndPruneTags, fetchAvatars: config.fetchAvatars && this.extensionState.isAvatarStorageAvailable(), - fetchCICDsPage: config.fetchCICDsPage, + fetchCICDsMaximumStatuses: config.fetchCICDsMaximumStatuses, graph: config.graph, includeCommitsMentionedByReflogs: config.includeCommitsMentionedByReflogs, initialLoadCommits: config.initialLoadCommits, diff --git a/src/types.ts b/src/types.ts index f932f440..cc5743da 100644 --- a/src/types.ts +++ b/src/types.ts @@ -287,7 +287,7 @@ export interface GitGraphViewConfig { readonly fetchAndPrune: boolean; readonly fetchAndPruneTags: boolean; readonly fetchAvatars: boolean; - readonly fetchCICDsPage: number; + readonly fetchCICDsMaximumStatuses: number; readonly graph: GraphConfig; readonly includeCommitsMentionedByReflogs: boolean; readonly initialLoadCommits: number; From 73451dd56ce9c9d6b94575f85e3c2429b269fa89 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sat, 10 Apr 2021 02:26:30 +0900 Subject: [PATCH 32/54] #462 Updated evaluates of requestCICDs --- web/main.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/main.ts b/web/main.ts index 9b16142b..825b9f7f 100644 --- a/web/main.ts +++ b/web/main.ts @@ -742,14 +742,14 @@ class GitGraphView { } private requestCICDs() { - this.commits.forEach(commit => { if (typeof this.currentRepo === 'string' && typeof this.gitRepos[this.currentRepo] !== 'undefined') { + this.commits.forEach(commit => { let cicdConfigs = this.gitRepos[this.currentRepo].cicdConfigs; if (cicdConfigs !== null) { sendMessage({ command: 'fetchCICD', repo: this.currentRepo, hash: commit.hash, cicdConfigs: cicdConfigs }); } - } }); + } } /* State */ From c7c1d5f5ef4f12d25d7e2e99e1120028f660c681 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sat, 10 Apr 2021 02:28:52 +0900 Subject: [PATCH 33/54] #462 Fixed compare of object --- src/cicdManager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cicdManager.ts b/src/cicdManager.ts index 90b8fd08..97fc925e 100644 --- a/src/cicdManager.ts +++ b/src/cicdManager.ts @@ -85,8 +85,8 @@ export class CicdManager extends Disposable { } else { // Check update user config - const cicdConfigsJSON = Object.entries(cicdConfigs).sort().toString(); - const cicdConfigsPrevJSON = Object.entries(this.cicdConfigsPrev).sort().toString(); + const cicdConfigsJSON = JSON.stringify(Object.entries(cicdConfigs).sort()); + const cicdConfigsPrevJSON = JSON.stringify(Object.entries(this.cicdConfigsPrev).sort()); if (cicdConfigsJSON !== cicdConfigsPrevJSON) { this.initialState = true; this.requestPage = -1; From 7378bfee09bfa996245c06bea7d5f60380f20b43 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sat, 10 Apr 2021 02:33:36 +0900 Subject: [PATCH 34/54] #462 Updated evaluates of requestCICDs --- web/main.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/main.ts b/web/main.ts index 825b9f7f..49eb3e70 100644 --- a/web/main.ts +++ b/web/main.ts @@ -742,13 +742,13 @@ class GitGraphView { } private requestCICDs() { - if (typeof this.currentRepo === 'string' && typeof this.gitRepos[this.currentRepo] !== 'undefined') { + if (typeof this.currentRepo === 'string' && typeof this.gitRepos[this.currentRepo] !== 'undefined') { this.commits.forEach(commit => { let cicdConfigs = this.gitRepos[this.currentRepo].cicdConfigs; if (cicdConfigs !== null) { sendMessage({ command: 'fetchCICD', repo: this.currentRepo, hash: commit.hash, cicdConfigs: cicdConfigs }); } - }); + }); } } From fe50010d44b363dc9b48ada80805aeeada71d0eb Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sat, 10 Apr 2021 08:24:40 +0900 Subject: [PATCH 35/54] #462 Changed data of CicdManager per repository --- src/cicdManager.ts | 58 ++++++++++++++++++++++++++----------------- src/extensionState.ts | 12 ++++++--- src/gitGraphView.ts | 5 ++-- src/types.ts | 2 ++ web/global.d.ts | 2 +- web/main.ts | 15 ++++++----- 6 files changed, 58 insertions(+), 36 deletions(-) diff --git a/src/cicdManager.ts b/src/cicdManager.ts index 97fc925e..3c60f0b7 100644 --- a/src/cicdManager.ts +++ b/src/cicdManager.ts @@ -75,13 +75,14 @@ export class CicdManager extends Disposable { /** * Fetch an cicd, either from the cache if it already exists, or queue it to be fetched. + * @param repo The repository that the cicd is used in. * @param hash The hash identifying the cicd commit. * @param cicdConfigs The CICDConfigs. */ - public fetchCICDStatus(hash: string, cicdConfigs: CICDConfig[]) { - if (typeof this.cicds[hash] !== 'undefined') { + public fetchCICDStatus(repo: string, hash: string, cicdConfigs: CICDConfig[]) { + if (typeof this.cicds[repo] !== 'undefined' && typeof this.cicds[repo][hash] !== 'undefined') { // CICD exists in the cache - this.emitCICD(hash, this.cicds[hash]); + this.emitCICD(repo, hash, this.cicds[repo][hash]); } else { // Check update user config @@ -100,7 +101,7 @@ export class CicdManager extends Disposable { if (this.initialState) { this.initialState = false; cicdConfigs.forEach(cicdConfig => { - this.queue.add(cicdConfig, this.requestPage, true); + this.queue.add(repo, cicdConfig, this.requestPage, true); }); // Reset initial state for 10 seconds setTimeout(() => { @@ -123,17 +124,18 @@ export class CicdManager extends Disposable { /** * Get the data of an cicd. + * @param repo The repository that the cicd is used in. * @param hash The hash identifying the cicd commit. * @param cicdConfigs The CICDConfigs. * @returns A JSON encoded data of an cicd if the cicd exists, otherwise NULL. */ - public getCICDDetail(hash: string, cicdConfigs: CICDConfig[]) { + public getCICDDetail(repo: string, hash: string, cicdConfigs: CICDConfig[]) { return new Promise((resolve) => { cicdConfigs.forEach(cicdConfig => { - this.queue.add(cicdConfig, -1, true, true, hash); + this.queue.add(repo, cicdConfig, -1, true, true, hash); }); - if (typeof this.cicds[hash] !== 'undefined' && this.cicds[hash] !== null) { - resolve(JSON.stringify(this.cicds[hash])); + if (typeof this.cicds[repo] !== 'undefined' && typeof this.cicds[repo][hash] !== 'undefined' && this.cicds[repo][hash] !== null) { + resolve(JSON.stringify(this.cicds[repo][hash])); } else { resolve(null); } @@ -272,7 +274,7 @@ export class CicdManager extends Disposable { }); ret.forEach(element => { let save = this.convCICDData2CICDDataSave(element); - this.saveCICD(element.sha, element.id, save); + this.saveCICD(cicdRequest.repo, element.sha, element.id, save); }); this.reFetchPageGitHub(cicdRequest, res, cicdConfig); return; @@ -293,7 +295,7 @@ export class CicdManager extends Disposable { }); ret.forEach(element => { let save = this.convCICDData2CICDDataSave(element); - this.saveCICD(element.sha, element.id, save); + this.saveCICD(cicdRequest.repo, element.sha, element.id, save); }); this.reFetchPageGitHub(cicdRequest, res, cicdConfig); return; @@ -377,7 +379,7 @@ export class CicdManager extends Disposable { this.logger.log('Added CICD for ' + cicdConfig.gitUrl + ' last_page=' + last + '(RateLimit=' + (res.headers['x-ratelimit-limit'] || 'None') + '(1 hour)/Remaining=' + (res.headers['x-ratelimit-remaining'] || 'None') + (res.headers['x-ratelimit-reset'] ? '/' + new Date(parseInt(res.headers['x-ratelimit-reset']) * 1000).toString() : '') + ') from GitHub'); for (let i = 1; i < last; i++) { - this.queue.add(cicdRequest.cicdConfig, i + 1, true); + this.queue.add(cicdRequest.repo, cicdRequest.cicdConfig, i + 1, true); } } } @@ -460,7 +462,7 @@ export class CicdManager extends Disposable { } else { save = this.convCICDData2CICDDataSave(element); } - this.saveCICD(element.sha, element.id, save); + this.saveCICD(cicdRequest.repo, element.sha, element.id, save); }); } @@ -473,7 +475,7 @@ export class CicdManager extends Disposable { this.logger.log('Added CICD for ' + cicdConfig.gitUrl + ' last_page=' + last + '(RateLimit=' + (res.headers['ratelimit-limit'] || 'None') + '(every minute)/Remaining=' + (res.headers['ratelimit-remaining'] || 'None') + (res.headers['ratelimit-reset'] ? '/' + new Date(parseInt(res.headers['ratelimit-reset']) * 1000).toString() : '') + ') from GitLab'); for (let i = 1; i < last; i++) { - this.queue.add(cicdRequest.cicdConfig, i + 1, true); + this.queue.add(cicdRequest.repo, cicdRequest.cicdConfig, i + 1, true); } } return; @@ -596,7 +598,7 @@ export class CicdManager extends Disposable { }); ret.forEach(element => { let save = this.convCICDData2CICDDataSave(element); - this.saveCICD(element.sha, element.id, save); + this.saveCICD(cicdRequest.repo, element.sha, element.id, save); }); return; } @@ -662,14 +664,16 @@ export class CicdManager extends Disposable { /** * Emit an CICDEvent to any listeners. + * @param repo The repository that the cicd is used in. * @param hash The hash identifying the cicd commit. * @param cicdDataSaves The hash of CICDDataSave. * @returns A promise indicating if the event was emitted successfully. */ - private emitCICD(hash: string, cicdDataSaves: { [id: string]: CICDDataSave }) { + private emitCICD(repo: string, hash: string, cicdDataSaves: { [id: string]: CICDDataSave }) { return new Promise((resolve, _reject) => { if (this.cicdEventEmitter.hasSubscribers()) { this.cicdEventEmitter.emit({ + repo: repo, hash: hash, cicdDataSaves: cicdDataSaves }); @@ -682,18 +686,22 @@ export class CicdManager extends Disposable { /** * Save an cicd in the cache. + * @param repo The repository that the cicd is used in. * @param hash The hash identifying the cicd commit. * @param id The identifying the cicdDataSave. * @param cicdDataSave The CICDDataSave. */ - private saveCICD(hash: string, id: string, cicdDataSave: CICDDataSave) { - if (typeof this.cicds[hash] === 'undefined') { - this.cicds[hash] = {}; + private saveCICD(repo: string, hash: string, id: string, cicdDataSave: CICDDataSave) { + if (typeof this.cicds[repo] === 'undefined') { + this.cicds[repo] = {}; } - this.cicds[hash][id] = cicdDataSave; - this.extensionState.saveCICD(hash, id, cicdDataSave); + if (typeof this.cicds[repo][hash] === 'undefined') { + this.cicds[repo][hash] = {}; + } + this.cicds[repo][hash][id] = cicdDataSave; + this.extensionState.saveCICD(repo, hash, id, cicdDataSave); // this.logger.log('Saved CICD for ' + cicdData.sha); - this.emitCICD(hash, this.cicds[hash]); + this.emitCICD(repo, hash, this.cicds[repo][hash]); } } @@ -714,18 +722,20 @@ class CicdRequestQueue { /** * Create and add a new cicd request to the queue. + * @param repo The repository that the cicd is used in. * @param cicdConfig The CICDConfig. * @param page The page of cicd request. * @param immediate Whether the avatar should be fetched immediately. * @param detail Flag of fetch detail. * @param hash hash for fetch detail. */ - public add(cicdConfig: CICDConfig, page: number, immediate: boolean, detail: boolean = false, hash: string = '') { + public add(repo: string, cicdConfig: CICDConfig, page: number, immediate: boolean, detail: boolean = false, hash: string = '') { const existingRequest = this.queue.find((request) => request.cicdConfig.gitUrl === cicdConfig.gitUrl && request.page === page && request.detail === detail && request.hash === hash); if (existingRequest) { } else { const config = getConfig(); this.insertItem({ + repo: repo, cicdConfig: cicdConfig, page: page, checkAfter: immediate || this.queue.length === 0 @@ -788,11 +798,12 @@ class CicdRequestQueue { } -export type CICDCache = { [hash: string]: { [id: string]: CICDDataSave } }; +export type CICDCache = { [repo: string]: { [hash: string]: { [id: string]: CICDDataSave } } }; // Request item to CicdRequestQueue interface CICDRequestItem { + repo: string; cicdConfig: CICDConfig; page: number; checkAfter: number; @@ -804,6 +815,7 @@ interface CICDRequestItem { // Event to GitGraphView export interface CICDEvent { + repo: string; hash: string; cicdDataSaves: { [id: string]: CICDDataSave }; } diff --git a/src/extensionState.ts b/src/extensionState.ts index 7a18a190..2e7c8490 100644 --- a/src/extensionState.ts +++ b/src/extensionState.ts @@ -324,15 +324,19 @@ export class ExtensionState extends Disposable { /** * Add a new cicd to the cache of cicds known to Git Graph. + * @param repo The repository that the cicd is used in. * @param hash The hash identifying the cicd commit. * @param cicdDataSave The CICDDataSave. */ - public saveCICD(hash: string, id: string, cicdDataSave: CICDDataSave) { + public saveCICD(repo: string, hash: string, id: string, cicdDataSave: CICDDataSave) { let cicds = this.getCICDCache(); - if (typeof cicds[hash] === 'undefined') { - cicds[hash] = {}; + if (typeof cicds[repo] === 'undefined') { + cicds[repo] = {}; } - cicds[hash][id] = cicdDataSave; + if (typeof cicds[repo][hash] === 'undefined') { + cicds[repo][hash] = {}; + } + cicds[repo][hash][id] = cicdDataSave; this.updateWorkspaceState(CICD_CACHE, cicds); } diff --git a/src/gitGraphView.ts b/src/gitGraphView.ts index e7abd490..76147a85 100644 --- a/src/gitGraphView.ts +++ b/src/gitGraphView.ts @@ -152,6 +152,7 @@ export class GitGraphView extends Disposable { cicdManager.onCICD((event) => { this.sendMessage({ command: 'fetchCICD', + repo: event.repo, hash: event.hash, cicdDataSaves: event.cicdDataSaves }); @@ -249,7 +250,7 @@ export class GitGraphView extends Disposable { ? this.dataSource.getCommitDetails(msg.repo, msg.commitHash, msg.hasParents) : this.dataSource.getStashDetails(msg.repo, msg.commitHash, msg.stash), msg.avatarEmail !== null ? this.avatarManager.getAvatarImage(msg.avatarEmail) : Promise.resolve(null), - msg.cicdConfigs !== null ? this.cicdManager.getCICDDetail(msg.commitHash, msg.cicdConfigs) : Promise.resolve(null) + msg.cicdConfigs !== null ? this.cicdManager.getCICDDetail(msg.repo, msg.commitHash, msg.cicdConfigs) : Promise.resolve(null) ]); this.sendMessage({ command: 'commitDetails', @@ -397,7 +398,7 @@ export class GitGraphView extends Disposable { this.avatarManager.fetchAvatarImage(msg.email, msg.repo, msg.remote, msg.commits); break; case 'fetchCICD': - this.cicdManager.fetchCICDStatus(msg.hash, msg.cicdConfigs); + this.cicdManager.fetchCICDStatus(msg.repo, msg.hash, msg.cicdConfigs); break; case 'fetchIntoLocalBranch': this.sendMessage({ diff --git a/src/types.ts b/src/types.ts index cc5743da..7c39d0c1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -912,11 +912,13 @@ export interface ResponseFetchAvatar extends BaseMessage { } export interface RequestFetchCICD extends RepoRequest { readonly command: 'fetchCICD'; + readonly repo: string; readonly hash: string; readonly cicdConfigs: CICDConfig[]; } export interface ResponseFetchCICD extends BaseMessage { readonly command: 'fetchCICD'; + readonly repo: string; readonly hash: string; readonly cicdDataSaves: { [id: string]: CICDDataSave }; } diff --git a/web/global.d.ts b/web/global.d.ts index 414660a7..c7aa9694 100644 --- a/web/global.d.ts +++ b/web/global.d.ts @@ -20,7 +20,7 @@ declare global { const workspaceState: GG.DeepReadonly; type AvatarImageCollection = { [email: string]: string }; - type CICDDataCollection = { [hash: string]: { [id: string]: GG.CICDDataSave } }; + type CICDDataCollection = { [repo: string]: { [hash: string]: { [id: string]: GG.CICDDataSave } } }; interface ExpandedCommit { index: number; diff --git a/web/main.ts b/web/main.ts index 49eb3e70..441dfa12 100644 --- a/web/main.ts +++ b/web/main.ts @@ -520,8 +520,11 @@ class GitGraphView { } } - public loadCicd(hash: string, cicdDataSaves: { [id: string]: GG.CICDDataSave }) { - this.cicdDatas[hash] = cicdDataSaves; + public loadCicd(repo: string, hash: string, cicdDataSaves: { [id: string]: GG.CICDDataSave }) { + if (typeof this.cicdDatas[repo] === 'undefined') { + this.cicdDatas[repo] = {}; + } + this.cicdDatas[repo][hash] = cicdDataSaves; this.saveState(); let cicdElems = >document.getElementsByClassName('cicd'); for (let i = 0; i < cicdElems.length; i++) { @@ -933,7 +936,7 @@ class GitGraphView { (colVisibility.author ? '' + (this.config.fetchAvatars ? '' + (typeof this.avatars[commit.email] === 'string' ? '' : '') + '' : '') + escapeHtml(commit.author) + '' : '') + (colVisibility.commit ? '' + abbrevCommit(commit.hash) + '' : '') + (colVisibility.cicd ? '' + '' + - (typeof this.cicdDatas[commit.hash] === 'object' ? (this.getCicdHtml(this.cicdDatas[commit.hash])) : '*') + + (typeof this.cicdDatas[commit.hash] === 'object' ? (this.getCicdHtml(this.cicdDatas[this.currentRepo][commit.hash])) : '*') + '' + '' : '') + ''; } @@ -2576,8 +2579,8 @@ class GitGraphView { + (expandedCommit.avatar !== null ? '' : '') + '
' + ('CI/CD detail: ' + '' - + (typeof this.cicdDatas[commitDetails.hash] === 'object' ? (this.getCicdHtml(this.cicdDatas[commitDetails.hash], true)) : '*') - + '' ) + + (typeof this.cicdDatas[commitDetails.hash] === 'object' ? (this.getCicdHtml(this.cicdDatas[this.currentRepo][commitDetails.hash], true)) : '*') + + '') + '

' + textFormatter.format(commitDetails.body); } else { html += 'Displaying all uncommitted changes.'; @@ -3280,7 +3283,7 @@ window.addEventListener('load', () => { }); break; case 'fetchCICD': - gitGraph.loadCicd(msg.hash, msg.cicdDataSaves); + gitGraph.loadCicd(msg.repo, msg.hash, msg.cicdDataSaves); break; case 'fetchIntoLocalBranch': refreshOrDisplayError(msg.error, 'Unable to Fetch into Local Branch'); From 9bdaa9140379a16289b6296db7d94425698650bb Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sat, 10 Apr 2021 18:22:21 +0900 Subject: [PATCH 36/54] #462 Updated CI/CD column visibility --- package.json | 2 +- src/cicdManager.ts | 2 ++ src/config.ts | 2 +- web/main.ts | 12 ++++++++++++ web/settingsWidget.ts | 1 + 5 files changed, 17 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index cc9c19f7..35d54f85 100644 --- a/package.json +++ b/package.json @@ -499,7 +499,7 @@ "Date": true, "Author": true, "Commit": true, - "CICD": true + "CICD": false }, "description": "An object specifying the default visibility of the Date, Author & Commit columns. Example: {\"Date\": true, \"Author\": true, \"Commit\": true}" }, diff --git a/src/cicdManager.ts b/src/cicdManager.ts index 3c60f0b7..1c88e184 100644 --- a/src/cicdManager.ts +++ b/src/cicdManager.ts @@ -208,8 +208,10 @@ export class CicdManager extends Disposable { let sourceOwner = match2 !== null ? match2[2] : ''; let sourceRepo = match2 !== null ? match2[3] : ''; + // https://docs.github.com/en/rest/reference/actions#list-workflow-runs-for-a-repository let cicdRootPath = `/repos/${sourceOwner}/${sourceRepo.replace(/\//g, '%2F')}/actions/runs?per_page=${this.per_page}`; if (cicdRequest.detail) { + // https://docs.github.com/en/rest/reference/checks#list-check-runs-for-a-git-reference cicdRootPath = `/repos/${sourceOwner}/${sourceRepo.replace(/\//g, '%2F')}/commits/${cicdRequest.hash}/check-runs?per_page=${this.per_page}`; } if (cicdRequest.page > 1) { diff --git a/src/config.ts b/src/config.ts index 9f364293..ab54e980 100644 --- a/src/config.ts +++ b/src/config.ts @@ -163,7 +163,7 @@ class Config { if (typeof obj === 'object' && obj !== null && typeof obj['Date'] === 'boolean' && typeof obj['Author'] === 'boolean' && typeof obj['Commit'] === 'boolean' && typeof obj['CICD'] === 'boolean') { return { author: obj['Author'], commit: obj['Commit'], date: obj['Date'], cicd: obj['CICD'] }; } else { - return { author: true, commit: true, date: true, cicd: true }; + return { author: true, commit: true, date: true, cicd: false }; } } diff --git a/web/main.ts b/web/main.ts index 441dfa12..183b9ffb 100644 --- a/web/main.ts +++ b/web/main.ts @@ -1875,6 +1875,18 @@ class GitGraphView { }); } + public setColumnVisibility(column: number, columnWidth: number ) { + let colWidths = this.gitRepos[this.currentRepo].columnWidths; + if (colWidths !== null) { + if (column < colWidths.length) { + colWidths[column] = columnWidth; + let columnWidths = [colWidths[0], COLUMN_AUTO, colWidths[1], colWidths[2], colWidths[3], colWidths[4]]; + this.saveColumnWidths(columnWidths); + this.render(); + } + } + } + public getColumnVisibility() { let colWidths = this.gitRepos[this.currentRepo].columnWidths; if (colWidths !== null) { diff --git a/web/settingsWidget.ts b/web/settingsWidget.ts index 4d0b6619..0e0f52ba 100644 --- a/web/settingsWidget.ts +++ b/web/settingsWidget.ts @@ -680,6 +680,7 @@ class SettingsWidget { if (this.currentRepo === null) return; this.view.saveRepoStateValue(this.currentRepo, 'cicdConfigs', config); this.render(); + this.view.setColumnVisibility(4, COLUMN_AUTO); } /** From 08874f034f77beff701b0ad625a55751f43731d9 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sat, 10 Apr 2021 23:05:00 +0900 Subject: [PATCH 37/54] #462 Changed data of CicdManager per repository --- web/main.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/main.ts b/web/main.ts index 183b9ffb..7d1dc434 100644 --- a/web/main.ts +++ b/web/main.ts @@ -936,7 +936,7 @@ class GitGraphView { (colVisibility.author ? '' + (this.config.fetchAvatars ? '' + (typeof this.avatars[commit.email] === 'string' ? '' : '') + '' : '') + escapeHtml(commit.author) + '' : '') + (colVisibility.commit ? '' + abbrevCommit(commit.hash) + '' : '') + (colVisibility.cicd ? '' + '' + - (typeof this.cicdDatas[commit.hash] === 'object' ? (this.getCicdHtml(this.cicdDatas[this.currentRepo][commit.hash])) : '*') + + ((typeof this.cicdDatas[this.currentRepo] === 'object' && typeof this.cicdDatas[this.currentRepo][commit.hash] === 'object') ? this.getCicdHtml(this.cicdDatas[this.currentRepo][commit.hash]) : '*') + '' + '' : '') + ''; } @@ -2591,7 +2591,7 @@ class GitGraphView { + (expandedCommit.avatar !== null ? '' : '') + '
' + ('CI/CD detail: ' + '' - + (typeof this.cicdDatas[commitDetails.hash] === 'object' ? (this.getCicdHtml(this.cicdDatas[this.currentRepo][commitDetails.hash], true)) : '*') + + ((typeof this.cicdDatas[this.currentRepo] === 'object' && typeof this.cicdDatas[this.currentRepo][commitDetails.hash] === 'object') ? this.getCicdHtml(this.cicdDatas[this.currentRepo][commitDetails.hash], true) : '*') + '') + '

' + textFormatter.format(commitDetails.body); } else { From c0b3cdf22a5b6b9ea9b10260b6cbbacb117a732b Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sat, 10 Apr 2021 23:48:02 +0900 Subject: [PATCH 38/54] #462 Reduced cicdConfigs from RequestFetchCICD --- src/cicdManager.ts | 74 +++++++++++++++++++++++++-------------------- src/gitGraphView.ts | 2 +- src/types.ts | 1 - web/main.ts | 12 ++++---- 4 files changed, 48 insertions(+), 41 deletions(-) diff --git a/src/cicdManager.ts b/src/cicdManager.ts index 1c88e184..69e6ce91 100644 --- a/src/cicdManager.ts +++ b/src/cicdManager.ts @@ -79,44 +79,52 @@ export class CicdManager extends Disposable { * @param hash The hash identifying the cicd commit. * @param cicdConfigs The CICDConfigs. */ - public fetchCICDStatus(repo: string, hash: string, cicdConfigs: CICDConfig[]) { + public fetchCICDStatus(repo: string, hash: string) { if (typeof this.cicds[repo] !== 'undefined' && typeof this.cicds[repo][hash] !== 'undefined') { // CICD exists in the cache this.emitCICD(repo, hash, this.cicds[repo][hash]); } else { - // Check update user config - const cicdConfigsJSON = JSON.stringify(Object.entries(cicdConfigs).sort()); - const cicdConfigsPrevJSON = JSON.stringify(Object.entries(this.cicdConfigsPrev).sort()); - if (cicdConfigsJSON !== cicdConfigsPrevJSON) { - this.initialState = true; - this.requestPage = -1; - if (this.requestPageTimeout !== null) { - clearTimeout(this.requestPageTimeout); - } - this.requestPageTimeout = null; - } - this.cicdConfigsPrev = Object.assign({}, cicdConfigs); - // CICD not in the cache, request it - if (this.initialState) { - this.initialState = false; - cicdConfigs.forEach(cicdConfig => { - this.queue.add(repo, cicdConfig, this.requestPage, true); - }); - // Reset initial state for 10 seconds - setTimeout(() => { - this.logger.log('Reset initial timer of CICD'); - this.initialState = true; - }, 10000); - // set request page to top - this.requestPage = 1; - // Reset request page to all after 10 minutes - if (this.requestPageTimeout === null) { - this.requestPageTimeout = setTimeout(() => { - this.logger.log('Reset request page of CICD'); - this.requestPage = -1; - this.requestPageTimeout = null; - }, 600000); + let repos = this.extensionState.getRepos(); + if (typeof repos[repo] !== 'undefined') { + if (repos[repo].cicdConfigs !== null) { + let cicdConfigs = repos[repo].cicdConfigs; + if (cicdConfigs !== null) { + // Check update user config + const cicdConfigsJSON = JSON.stringify(Object.entries(cicdConfigs).sort()); + const cicdConfigsPrevJSON = JSON.stringify(Object.entries(this.cicdConfigsPrev).sort()); + if (cicdConfigsJSON !== cicdConfigsPrevJSON) { + this.initialState = true; + this.requestPage = -1; + if (this.requestPageTimeout !== null) { + clearTimeout(this.requestPageTimeout); + } + this.requestPageTimeout = null; + } + this.cicdConfigsPrev = Object.assign({}, cicdConfigs); + // CICD not in the cache, request it + if (this.initialState) { + this.initialState = false; + cicdConfigs.forEach(cicdConfig => { + this.queue.add(repo, cicdConfig, this.requestPage, true); + }); + // Reset initial state for 10 seconds + setTimeout(() => { + this.logger.log('Reset initial timer of CICD'); + this.initialState = true; + }, 10000); + // set request page to top + this.requestPage = 1; + // Reset request page to all after 10 minutes + if (this.requestPageTimeout === null) { + this.requestPageTimeout = setTimeout(() => { + this.logger.log('Reset request page of CICD'); + this.requestPage = -1; + this.requestPageTimeout = null; + }, 600000); + } + } + } } } } diff --git a/src/gitGraphView.ts b/src/gitGraphView.ts index 76147a85..469bef04 100644 --- a/src/gitGraphView.ts +++ b/src/gitGraphView.ts @@ -398,7 +398,7 @@ export class GitGraphView extends Disposable { this.avatarManager.fetchAvatarImage(msg.email, msg.repo, msg.remote, msg.commits); break; case 'fetchCICD': - this.cicdManager.fetchCICDStatus(msg.repo, msg.hash, msg.cicdConfigs); + this.cicdManager.fetchCICDStatus(msg.repo, msg.hash); break; case 'fetchIntoLocalBranch': this.sendMessage({ diff --git a/src/types.ts b/src/types.ts index 7c39d0c1..e18051b8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -914,7 +914,6 @@ export interface RequestFetchCICD extends RepoRequest { readonly command: 'fetchCICD'; readonly repo: string; readonly hash: string; - readonly cicdConfigs: CICDConfig[]; } export interface ResponseFetchCICD extends BaseMessage { readonly command: 'fetchCICD'; diff --git a/web/main.ts b/web/main.ts index 7d1dc434..8da7260e 100644 --- a/web/main.ts +++ b/web/main.ts @@ -746,12 +746,12 @@ class GitGraphView { private requestCICDs() { if (typeof this.currentRepo === 'string' && typeof this.gitRepos[this.currentRepo] !== 'undefined') { - this.commits.forEach(commit => { - let cicdConfigs = this.gitRepos[this.currentRepo].cicdConfigs; - if (cicdConfigs !== null) { - sendMessage({ command: 'fetchCICD', repo: this.currentRepo, hash: commit.hash, cicdConfigs: cicdConfigs }); - } - }); + let cicdConfigs = this.gitRepos[this.currentRepo].cicdConfigs; + if (cicdConfigs !== null && cicdConfigs.length >= 1) { + this.commits.forEach(commit => { + sendMessage({ command: 'fetchCICD', repo: this.currentRepo, hash: commit.hash }); + }); + } } } From 7b7051dc7fc5266473b9b2bd3c74a8ef87bfed0b Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Mon, 12 Apr 2021 23:13:35 +0900 Subject: [PATCH 39/54] #462 Updated CI/CD column visibility --- tests/config.test.ts | 8 ++++---- web/main.ts | 7 ++++++- web/settingsWidget.ts | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/tests/config.test.ts b/tests/config.test.ts index dc766223..b8c2b75c 100644 --- a/tests/config.test.ts +++ b/tests/config.test.ts @@ -775,7 +775,7 @@ describe('Config', () => { // Assert expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); - expect(value).toStrictEqual({ date: true, author: true, commit: true, cicd: true }); + expect(value).toStrictEqual({ date: true, author: true, commit: true, cicd: false }); }); it('Should return the default value when the configuration value is invalid (NULL)', () => { @@ -787,7 +787,7 @@ describe('Config', () => { // Assert expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); - expect(value).toStrictEqual({ date: true, author: true, commit: true, cicd: true }); + expect(value).toStrictEqual({ date: true, author: true, commit: true, cicd: false }); }); it('Should return the default value when the configuration value is invalid (column value is not a boolean)', () => { @@ -799,7 +799,7 @@ describe('Config', () => { // Assert expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); - expect(value).toStrictEqual({ date: true, author: true, commit: true, cicd: true }); + expect(value).toStrictEqual({ date: true, author: true, commit: true, cicd: false }); }); it('Should return the default value when the configuration value is not set', () => { @@ -808,7 +808,7 @@ describe('Config', () => { // Assert expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); - expect(value).toStrictEqual({ date: true, author: true, commit: true, cicd: true }); + expect(value).toStrictEqual({ date: true, author: true, commit: true, cicd: false }); }); }); diff --git a/web/main.ts b/web/main.ts index 8da7260e..018f786d 100644 --- a/web/main.ts +++ b/web/main.ts @@ -1875,12 +1875,17 @@ class GitGraphView { }); } - public setColumnVisibility(column: number, columnWidth: number ) { + public setColumnVisibility(column: number, columnWidth: number) { let colWidths = this.gitRepos[this.currentRepo].columnWidths; if (colWidths !== null) { if (column < colWidths.length) { colWidths[column] = columnWidth; let columnWidths = [colWidths[0], COLUMN_AUTO, colWidths[1], colWidths[2], colWidths[3], colWidths[4]]; + let col = column; + if (column >= 1) { + col = column + 1; + } + columnWidths[col] = columnWidths[0] === COLUMN_AUTO ? COLUMN_AUTO : columnWidth - COLUMN_LEFT_RIGHT_PADDING; this.saveColumnWidths(columnWidths); this.render(); } diff --git a/web/settingsWidget.ts b/web/settingsWidget.ts index 0e0f52ba..a6eba4c3 100644 --- a/web/settingsWidget.ts +++ b/web/settingsWidget.ts @@ -680,7 +680,7 @@ class SettingsWidget { if (this.currentRepo === null) return; this.view.saveRepoStateValue(this.currentRepo, 'cicdConfigs', config); this.render(); - this.view.setColumnVisibility(4, COLUMN_AUTO); + this.view.setColumnVisibility(4, 80); } /** From d0ec92ae49c54a497a6e031a8c6eed57329e5671 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Mon, 12 Apr 2021 23:15:24 +0900 Subject: [PATCH 40/54] #462 Reduced cicdConfigs from RequestFetchCICD --- src/cicdManager.ts | 10 +++++++--- src/extension.ts | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/cicdManager.ts b/src/cicdManager.ts index 69e6ce91..6d1a3fc9 100644 --- a/src/cicdManager.ts +++ b/src/cicdManager.ts @@ -9,6 +9,7 @@ import { Logger } from './logger'; import { CICDConfig, CICDData, CICDDataSave, CICDProvider } from './types'; import { Disposable, toDisposable } from './utils/disposable'; import { EventEmitter } from './utils/event'; +import { RepoManager } from './repoManager'; /** * Manages fetching and caching CICDs. @@ -17,6 +18,7 @@ export class CicdManager extends Disposable { private readonly extensionState: ExtensionState; private readonly logger: Logger; private readonly cicdEventEmitter: EventEmitter; + private readonly repoManager: RepoManager; private cicds: CICDCache; private queue: CicdRequestQueue; @@ -37,9 +39,10 @@ export class CicdManager extends Disposable { * @param extensionState The Git Graph ExtensionState instance. * @param logger The Git Graph Logger instance. */ - constructor(extensionState: ExtensionState, logger: Logger) { + constructor(extensionState: ExtensionState, repoManager: RepoManager, logger: Logger) { super(); this.extensionState = extensionState; + this.repoManager = repoManager; this.logger = logger; this.cicdEventEmitter = new EventEmitter(); this.cicds = this.extensionState.getCICDCache(); @@ -85,7 +88,7 @@ export class CicdManager extends Disposable { this.emitCICD(repo, hash, this.cicds[repo][hash]); } else { - let repos = this.extensionState.getRepos(); + let repos = this.repoManager.getRepos(); if (typeof repos[repo] !== 'undefined') { if (repos[repo].cicdConfigs !== null) { let cicdConfigs = repos[repo].cicdConfigs; @@ -101,7 +104,8 @@ export class CicdManager extends Disposable { } this.requestPageTimeout = null; } - this.cicdConfigsPrev = Object.assign({}, cicdConfigs); + // Deep Clone cicdConfigs + this.cicdConfigsPrev = JSON.parse(JSON.stringify(cicdConfigs)); // CICD not in the cache, request it if (this.initialState) { this.initialState = false; diff --git a/src/extension.ts b/src/extension.ts index be581cbb..ba1d179e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -42,8 +42,8 @@ export async function activate(context: vscode.ExtensionContext) { const dataSource = new DataSource(gitExecutable, onDidChangeConfiguration, onDidChangeGitExecutable, logger); const avatarManager = new AvatarManager(dataSource, extensionState, logger); - const cicdManager = new CicdManager(extensionState, logger); const repoManager = new RepoManager(dataSource, extensionState, onDidChangeConfiguration, logger); + const cicdManager = new CicdManager(extensionState, repoManager, logger); const statusBarItem = new StatusBarItem(repoManager.getNumRepos(), repoManager.onDidChangeRepos, onDidChangeConfiguration, logger); const commandManager = new CommandManager(context, avatarManager, cicdManager, dataSource, extensionState, repoManager, gitExecutable, onDidChangeGitExecutable, logger); const diffDocProvider = new DiffDocProvider(dataSource); From 571d9484007598e9328c3e54b82c8a3d38db2783 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Mon, 12 Apr 2021 23:21:13 +0900 Subject: [PATCH 41/54] #462 Token encryption / decryption --- src/cicdManager.ts | 28 ++++++++-------- src/extensionState.ts | 62 ++++++++++++++++++++++++++++++++++-- src/types.ts | 5 +-- tests/extensionState.test.ts | 8 +++++ tests/repoManager.test.ts | 4 +++ web/settingsWidget.ts | 16 +++++----- 6 files changed, 96 insertions(+), 27 deletions(-) diff --git a/src/cicdManager.ts b/src/cicdManager.ts index 6d1a3fc9..d3bc0bea 100644 --- a/src/cicdManager.ts +++ b/src/cicdManager.ts @@ -213,10 +213,10 @@ export class CicdManager extends Disposable { let cicdConfig = cicdRequest.cicdConfig; - const match1 = cicdConfig.gitUrl.match(/^(https?:\/\/|git@)((?=[^/]+@)[^@]+@|(?![^/]+@))([^/:]+)/); + const match1 = cicdConfig.cicdUrl.match(/^(https?:\/\/|git@)((?=[^/]+@)[^@]+@|(?![^/]+@))([^/:]+)/); let hostRootUrl = match1 !== null ? 'api.' + match1[3] : ''; - const match2 = cicdConfig.gitUrl.match(/^(https?:\/\/|git@)[^/:]+[/:]([^/]+)\/([^/]*?)(.git|)$/); + const match2 = cicdConfig.cicdUrl.match(/^(https?:\/\/|git@)[^/:]+[/:]([^/]+)\/([^/]*?)(.git|)$/); let sourceOwner = match2 !== null ? match2[2] : ''; let sourceRepo = match2 !== null ? match2[3] : ''; @@ -234,8 +234,8 @@ export class CicdManager extends Disposable { 'Accept': 'application/vnd.github.v3+json', 'User-Agent': 'vscode-git-graph' }; - if (cicdConfig.glToken !== '') { - headers['Authorization'] = `token ${cicdConfig.glToken}`; + if (cicdConfig.cicdToken !== '') { + headers['Authorization'] = `token ${cicdConfig.cicdToken}`; } let triggeredOnError = false; @@ -262,7 +262,7 @@ export class CicdManager extends Disposable { // If the GitHub Api rate limit was reached, store the github timeout to prevent subsequent requests this.githubTimeout = parseInt(res.headers['x-ratelimit-reset']) * 1000; this.logger.log('GitHub API Rate Limit Reached - Paused fetching from GitLab until the Rate Limit is reset (RateLimit=' + res.headers['x-ratelimit-limit'] + '(1 hour)/' + new Date(this.githubTimeout).toString() + ')'); - if (cicdRequest.cicdConfig.glToken === '') { + if (cicdRequest.cicdConfig.cicdToken === '') { this.logger.log('GitHub API Rate Limit can upgrade by Access Token.'); } } @@ -391,7 +391,7 @@ export class CicdManager extends Disposable { this.logger.log('CICD Maximum Statuses(maximumStatuses=' + cicdRequest.maximumStatuses + ') reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); } - this.logger.log('Added CICD for ' + cicdConfig.gitUrl + ' last_page=' + last + '(RateLimit=' + (res.headers['x-ratelimit-limit'] || 'None') + '(1 hour)/Remaining=' + (res.headers['x-ratelimit-remaining'] || 'None') + (res.headers['x-ratelimit-reset'] ? '/' + new Date(parseInt(res.headers['x-ratelimit-reset']) * 1000).toString() : '') + ') from GitHub'); + this.logger.log('Added CICD for ' + cicdConfig.cicdUrl + ' last_page=' + last + '(RateLimit=' + (res.headers['x-ratelimit-limit'] || 'None') + '(1 hour)/Remaining=' + (res.headers['x-ratelimit-remaining'] || 'None') + (res.headers['x-ratelimit-reset'] ? '/' + new Date(parseInt(res.headers['x-ratelimit-reset']) * 1000).toString() : '') + ') from GitHub'); for (let i = 1; i < last; i++) { this.queue.add(cicdRequest.repo, cicdRequest.cicdConfig, i + 1, true); } @@ -413,7 +413,7 @@ export class CicdManager extends Disposable { let cicdConfig = cicdRequest.cicdConfig; - let gitUrl = cicdConfig.gitUrl.replace(/\/$/g, ''); + let gitUrl = cicdConfig.cicdUrl.replace(/\/$/g, ''); gitUrl = gitUrl.replace(/.git$/g, ''); const match1 = gitUrl.match(/^(.+?):\/\/(.+?):?(\d+)?(\/.*)?$/); let hostProtocol = match1 !== null ? '' + match1[1] : ''; @@ -431,8 +431,8 @@ export class CicdManager extends Disposable { let headers: any = { 'User-Agent': 'vscode-git-graph' }; - if (cicdConfig.glToken !== '') { - headers['PRIVATE-TOKEN'] = cicdConfig.glToken; + if (cicdConfig.cicdToken !== '') { + headers['PRIVATE-TOKEN'] = cicdConfig.cicdToken; } let triggeredOnError = false; @@ -487,7 +487,7 @@ export class CicdManager extends Disposable { this.logger.log('CICD Maximum Statuses(maximumStatuses=' + cicdRequest.maximumStatuses + ') reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); } - this.logger.log('Added CICD for ' + cicdConfig.gitUrl + ' last_page=' + last + '(RateLimit=' + (res.headers['ratelimit-limit'] || 'None') + '(every minute)/Remaining=' + (res.headers['ratelimit-remaining'] || 'None') + (res.headers['ratelimit-reset'] ? '/' + new Date(parseInt(res.headers['ratelimit-reset']) * 1000).toString() : '') + ') from GitLab'); + this.logger.log('Added CICD for ' + cicdConfig.cicdUrl + ' last_page=' + last + '(RateLimit=' + (res.headers['ratelimit-limit'] || 'None') + '(every minute)/Remaining=' + (res.headers['ratelimit-remaining'] || 'None') + (res.headers['ratelimit-reset'] ? '/' + new Date(parseInt(res.headers['ratelimit-reset']) * 1000).toString() : '') + ') from GitLab'); for (let i = 1; i < last; i++) { this.queue.add(cicdRequest.repo, cicdRequest.cicdConfig, i + 1, true); } @@ -538,7 +538,7 @@ export class CicdManager extends Disposable { let cicdConfig = cicdRequest.cicdConfig; - let gitUrl = cicdConfig.gitUrl.replace(/\/$/g, ''); + let gitUrl = cicdConfig.cicdUrl.replace(/\/$/g, ''); gitUrl = gitUrl.replace(/.git$/g, ''); const match1 = gitUrl.match(/^(.+?):\/\/(.+?):?(\d+)?(\/.*)?$/); let hostProtocol = match1 !== null ? '' + match1[1] : ''; @@ -551,9 +551,9 @@ export class CicdManager extends Disposable { let headers: any = { 'User-Agent': 'vscode-git-graph' }; - if (cicdConfig.glToken !== '') { + if (cicdConfig.cicdToken !== '') { // headers['Authorization'] = 'Basic ' + new Buffer(username + ':' + passw).toString('base64'); - headers['Authorization'] = 'Basic ' + new Buffer(cicdConfig.glToken).toString('base64'); + headers['Authorization'] = 'Basic ' + new Buffer(cicdConfig.cicdToken).toString('base64'); } let triggeredOnError = false; @@ -744,7 +744,7 @@ class CicdRequestQueue { * @param hash hash for fetch detail. */ public add(repo: string, cicdConfig: CICDConfig, page: number, immediate: boolean, detail: boolean = false, hash: string = '') { - const existingRequest = this.queue.find((request) => request.cicdConfig.gitUrl === cicdConfig.gitUrl && request.page === page && request.detail === detail && request.hash === hash); + const existingRequest = this.queue.find((request) => request.cicdConfig.cicdUrl === cicdConfig.cicdUrl && request.page === page && request.detail === detail && request.hash === hash); if (existingRequest) { } else { const config = getConfig(); diff --git a/src/extensionState.ts b/src/extensionState.ts index 2e7c8490..6171bfd1 100644 --- a/src/extensionState.ts +++ b/src/extensionState.ts @@ -1,10 +1,11 @@ +import * as crypto from 'crypto'; import * as fs from 'fs'; import * as vscode from 'vscode'; import { Avatar, AvatarCache } from './avatarManager'; import { CICDCache } from './cicdManager'; import { getConfig } from './config'; -import { BooleanOverride, CICDDataSave, CodeReview, ErrorInfo, FileViewType, GitGraphViewGlobalState, GitGraphViewWorkspaceState, GitRepoSet, GitRepoState, RepoCommitOrdering } from './types'; -import { GitExecutable, getPathFromStr } from './utils'; +import { BooleanOverride, CICDConfig, CICDDataSave, CodeReview, ErrorInfo, FileViewType, GitGraphViewGlobalState, GitGraphViewWorkspaceState, GitRepoSet, GitRepoState, RepoCommitOrdering } from './types'; +import { GitExecutable, getNonce, getPathFromStr } from './utils'; import { Disposable } from './utils/disposable'; import { Event } from './utils/event'; @@ -35,6 +36,7 @@ export const DEFAULT_REPO_STATE: GitRepoState = { onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, cicdConfigs: null, + cicdNonce: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, @@ -123,6 +125,31 @@ export class ExtensionState extends Disposable { outputSet[repo].showRemoteBranchesV2 = repoSet[repo].showRemoteBranches ? BooleanOverride.Enabled : BooleanOverride.Disabled; } } + if (typeof repoSet[repo].cicdConfigs !== 'undefined' && repoSet[repo].cicdConfigs !== null) { + outputSet[repo].cicdConfigs = []; + if (typeof repoSet[repo].cicdNonce !== 'undefined' && repoSet[repo].cicdNonce !== null) { + let ENCRYPTION_KEY = repoSet[repo].cicdNonce; // Must be 256 bits (32 characters) + repoSet[repo].cicdConfigs?.forEach(element => { + if (typeof element.cicdUrl !== 'undefined' && typeof element.cicdToken !== 'undefined') { + let textParts: string[] = element.cicdToken.split(':'); + let iv = Buffer.from(textParts[0], 'hex'); + let encryptedText = Buffer.from(textParts[1], 'hex'); + + let decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(ENCRYPTION_KEY), iv); + let decrypted = decipher.update(encryptedText); + + decrypted = Buffer.concat([decrypted, decipher.final()]); + let config: CICDConfig = { + provider: element.provider, + cicdUrl: element.cicdUrl, + cicdToken: decrypted.toString(), + custom: null + }; + outputSet[repo].cicdConfigs?.push(config); + } + }); + } + } }); return outputSet; } @@ -132,7 +159,36 @@ export class ExtensionState extends Disposable { * @param gitRepoSet The set of repositories. */ public saveRepos(gitRepoSet: GitRepoSet) { - this.updateWorkspaceState(REPO_STATES, gitRepoSet); + // Deep Clone gitRepoSet + let gitRepoSetTemp = JSON.parse(JSON.stringify(gitRepoSet)); + + Object.keys(gitRepoSetTemp).forEach((repo) => { + let ENCRYPTION_KEY = getNonce(); // Must be 256 bits (32 characters) + const IV_LENGTH = 16; // For AES, this is always 16 + gitRepoSetTemp[repo].cicdNonce = ENCRYPTION_KEY; + + // Create Encrypted cicdToken + let cicdConfigsEncrypto: CICDConfig[] = []; + gitRepoSetTemp[repo].cicdConfigs?.forEach((cicdConfig: CICDConfig) => { + let iv = crypto.randomBytes(IV_LENGTH); + let cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(ENCRYPTION_KEY), iv); + let plain = Buffer.from(cicdConfig.cicdToken); + let encrypted = Buffer.concat([ + cipher.update(plain), + cipher.final() + ]); + let config: CICDConfig = { + provider: cicdConfig.provider, + cicdUrl: cicdConfig.cicdUrl, + cicdToken: iv.toString('hex') + ':' + encrypted.toString('hex'), + custom: null + }; + cicdConfigsEncrypto.push(config); + }); + // Replace Encrypted cicdToken + gitRepoSetTemp[repo].cicdConfigs = cicdConfigsEncrypto; + }); + this.updateWorkspaceState(REPO_STATES, gitRepoSetTemp); } /** diff --git a/src/types.ts b/src/types.ts index e18051b8..deed5abb 100644 --- a/src/types.ts +++ b/src/types.ts @@ -211,8 +211,8 @@ export interface CICDDataSave { } export interface CICDConfigBase { - readonly gitUrl: string; - readonly glToken: string; + readonly cicdUrl: string; + readonly cicdToken: string; } export const enum CICDProvider { @@ -254,6 +254,7 @@ export interface GitRepoState { onRepoLoadShowSpecificBranches: string[] | null; pullRequestConfig: PullRequestConfig | null; cicdConfigs: CICDConfig[] | null; + cicdNonce: string | null; showRemoteBranches: boolean; showRemoteBranchesV2: BooleanOverride; showStashes: BooleanOverride; diff --git a/tests/extensionState.test.ts b/tests/extensionState.test.ts index d77be3cd..e49cec64 100644 --- a/tests/extensionState.test.ts +++ b/tests/extensionState.test.ts @@ -72,6 +72,7 @@ describe('ExtensionState', () => { onRepoLoadShowSpecificBranches: ['master'], pullRequestConfig: null, cicdConfigs: null, + cicdNonce: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Enabled, showStashes: BooleanOverride.Enabled, @@ -121,6 +122,7 @@ describe('ExtensionState', () => { onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, cicdConfigs: null, + cicdNonce: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, @@ -160,6 +162,7 @@ describe('ExtensionState', () => { onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, cicdConfigs: null, + cicdNonce: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, @@ -199,6 +202,7 @@ describe('ExtensionState', () => { onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, cicdConfigs: null, + cicdNonce: null, showRemoteBranches: false, showRemoteBranchesV2: BooleanOverride.Disabled, showStashes: BooleanOverride.Default, @@ -238,6 +242,7 @@ describe('ExtensionState', () => { onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, cicdConfigs: null, + cicdNonce: null, showRemoteBranches: false, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, @@ -277,6 +282,7 @@ describe('ExtensionState', () => { onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, cicdConfigs: null, + cicdNonce: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Enabled, showStashes: BooleanOverride.Default, @@ -319,6 +325,7 @@ describe('ExtensionState', () => { onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, cicdConfigs: null, + cicdNonce: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, @@ -341,6 +348,7 @@ describe('ExtensionState', () => { onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, cicdConfigs: null, + cicdNonce: null, showRemoteBranches: false, showRemoteBranchesV2: BooleanOverride.Disabled, showStashes: BooleanOverride.Default, diff --git a/tests/repoManager.test.ts b/tests/repoManager.test.ts index cda6c3e2..76187f7f 100644 --- a/tests/repoManager.test.ts +++ b/tests/repoManager.test.ts @@ -1053,6 +1053,7 @@ describe('RepoManager', () => { onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, cicdConfigs: null, + cicdNonce: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, @@ -1812,6 +1813,7 @@ describe('RepoManager', () => { onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, cicdConfigs: null, + cicdNonce: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, @@ -2165,6 +2167,7 @@ describe('RepoManager', () => { onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, cicdConfigs: null, + cicdNonce: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, @@ -2242,6 +2245,7 @@ describe('RepoManager', () => { onRepoLoadShowSpecificBranches: null, pullRequestConfig: null, cicdConfigs: null, + cicdNonce: null, showRemoteBranches: true, showRemoteBranchesV2: BooleanOverride.Default, showStashes: BooleanOverride.Default, diff --git a/web/settingsWidget.ts b/web/settingsWidget.ts index a6eba4c3..c9bb2ea3 100644 --- a/web/settingsWidget.ts +++ b/web/settingsWidget.ts @@ -248,10 +248,10 @@ class SettingsWidget { providerOptions[(GG.CICDProvider.GitHubV3).toString()] = 'GitHub'; providerOptions[(GG.CICDProvider.GitLabV4).toString()] = 'GitLab API v4(ver8.11-)'; providerOptions[(GG.CICDProvider.JenkinsV2).toString()] = 'Jenkins v2'; - const gitUrl = escapeHtml(cicdConfig.gitUrl || 'Not Set'); + const cicdUrl = escapeHtml(cicdConfig.cicdUrl || 'Not Set'); html += '' + '' + escapeHtml(providerOptions[cicdConfig.provider]) + '' + - '' + gitUrl + '' + + '' + cicdUrl + '' + '
' + SVG_ICONS.pencil + '
' + SVG_ICONS.close + '
' + ''; }); @@ -474,8 +474,8 @@ class SettingsWidget { }); const updateConfigWithFormValues = (values: DialogInputValue[]) => { let config: GG.CICDConfig = { - provider: parseInt(values[0]), gitUrl: values[1], - glToken: values[2], + provider: parseInt(values[0]), cicdUrl: values[1], + cicdToken: values[2], custom: null }; return config; @@ -524,14 +524,14 @@ class SettingsWidget { { name: 'GitLab API v4(ver8.11-)', value: (GG.CICDProvider.GitLabV4).toString() }, { name: 'Jenkins v2', value: (GG.CICDProvider.JenkinsV2).toString() } ]; - dialog.showForm('Edit the CI/CD ' + escapeHtml(cicdConfig.gitUrl || 'Not Set') + ':', [ + dialog.showForm('Edit the CI/CD ' + escapeHtml(cicdConfig.cicdUrl || 'Not Set') + ':', [ { type: DialogInputType.Select, name: 'Provider', options: providerOptions, default: cicdConfig.provider.toString(), info: 'In addition to the built-in publicly hosted CI/CD providers.' }, - { type: DialogInputType.Text, name: 'Git/Jenkins URL', default: cicdConfig.gitUrl || '', placeholder: null, info: 'The CI/CD provider\'s Git URL (e.g. https://gitlab.com/OWNER/REPO.git) / Jenkins Job URL.' }, - { type: DialogInputType.Password, name: 'Access Token', default: cicdConfig.glToken, info: 'The GitHub/GitLab personal or project access token / The Jenkin user_name:password or user_name:access_token' } + { type: DialogInputType.Text, name: 'Git/Jenkins URL', default: cicdConfig.cicdUrl || '', placeholder: null, info: 'The CI/CD provider\'s Git URL (e.g. https://gitlab.com/OWNER/REPO.git) / Jenkins Job URL.' }, + { type: DialogInputType.Password, name: 'Access Token', default: cicdConfig.cicdToken, info: 'The GitHub/GitLab personal or project access token / The Jenkin user_name:password or user_name:access_token' } ], 'Save Changes', (values) => { let index = parseInt(((e.target).closest('.cicdBtns')!).dataset.index!); let configs: GG.CICDConfig[] = copyConfigs(); @@ -544,7 +544,7 @@ class SettingsWidget { addListenerToClass('deleteCICD', 'click', (e) => { const cicdConfig = this.getCICDForBtnEvent(e); if (cicdConfig === null) return; - dialog.showConfirmation('Are you sure you want to delete the CI/CD ' + escapeHtml(cicdConfig.gitUrl) + '?', 'Yes, delete', () => { + dialog.showConfirmation('Are you sure you want to delete the CI/CD ' + escapeHtml(cicdConfig.cicdUrl) + '?', 'Yes, delete', () => { let index = parseInt(((e.target).closest('.cicdBtns')!).dataset.index!); let configs: GG.CICDConfig[] = copyConfigs(); configs.splice(index, 1); From 8404bb08a6d29ebf717e904ca24e411c746171df Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Mon, 12 Apr 2021 23:34:11 +0900 Subject: [PATCH 42/54] #462 Updated CI/CD column visibility --- web/main.ts | 7 +------ web/settingsWidget.ts | 6 +++++- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/web/main.ts b/web/main.ts index 018f786d..9c220576 100644 --- a/web/main.ts +++ b/web/main.ts @@ -1879,13 +1879,8 @@ class GitGraphView { let colWidths = this.gitRepos[this.currentRepo].columnWidths; if (colWidths !== null) { if (column < colWidths.length) { - colWidths[column] = columnWidth; + colWidths[column] = columnWidth === COLUMN_HIDDEN ? COLUMN_HIDDEN : (colWidths[0] === COLUMN_AUTO ? COLUMN_AUTO : columnWidth - COLUMN_LEFT_RIGHT_PADDING); let columnWidths = [colWidths[0], COLUMN_AUTO, colWidths[1], colWidths[2], colWidths[3], colWidths[4]]; - let col = column; - if (column >= 1) { - col = column + 1; - } - columnWidths[col] = columnWidths[0] === COLUMN_AUTO ? COLUMN_AUTO : columnWidth - COLUMN_LEFT_RIGHT_PADDING; this.saveColumnWidths(columnWidths); this.render(); } diff --git a/web/settingsWidget.ts b/web/settingsWidget.ts index c9bb2ea3..2c5a72e9 100644 --- a/web/settingsWidget.ts +++ b/web/settingsWidget.ts @@ -680,7 +680,11 @@ class SettingsWidget { if (this.currentRepo === null) return; this.view.saveRepoStateValue(this.currentRepo, 'cicdConfigs', config); this.render(); - this.view.setColumnVisibility(4, 80); + if (config?.length !== 0) { + this.view.setColumnVisibility(4, 80); + } else { + this.view.setColumnVisibility(4, COLUMN_HIDDEN); + } } /** From b723d8b117697011d6bc26d49c6c840a67ae19aa Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Fri, 30 Apr 2021 08:45:13 +0900 Subject: [PATCH 43/54] #462 Updated CI/CD timeout --- src/cicdManager.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cicdManager.ts b/src/cicdManager.ts index d3bc0bea..67a4882c 100644 --- a/src/cicdManager.ts +++ b/src/cicdManager.ts @@ -204,7 +204,7 @@ export class CicdManager extends Disposable { */ private fetchFromGitHub(cicdRequest: CICDRequestItem) { let t = (new Date()).getTime(); - if (cicdRequest.checkAfter !== 0 && t < this.githubTimeout) { + if (t < this.githubTimeout) { // Defer request until after timeout this.queue.addItem(cicdRequest, this.githubTimeout, false); this.fetchCICDsInterval(); @@ -240,7 +240,7 @@ export class CicdManager extends Disposable { let triggeredOnError = false; const onError = (err: Error) => { - this.logger.log('GitHub API HTTPS Error - ' + err.message); + this.logger.log('GitHub API HTTPS Error - ' + err?.message); if (!triggeredOnError) { // If an error occurs, try again after 5 minutes triggeredOnError = true; @@ -404,7 +404,7 @@ export class CicdManager extends Disposable { */ private fetchFromGitLab(cicdRequest: CICDRequestItem) { let t = (new Date()).getTime(); - if (cicdRequest.checkAfter !== 0 && t < this.gitLabTimeout) { + if (t < this.gitLabTimeout) { // Defer request until after timeout this.queue.addItem(cicdRequest, this.gitLabTimeout, false); this.fetchCICDsInterval(); From 86574514bf4096b5fc32b9c3656dcde8a26d013b Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sat, 1 May 2021 00:22:25 +0900 Subject: [PATCH 44/54] #462 Removed CI/CD column --- package.json | 7 +----- src/config.ts | 6 ++--- src/extensionState.ts | 1 + src/types.ts | 1 - tests/config.test.ts | 34 +++++++++------------------ web/main.ts | 54 ++++++------------------------------------- web/settingsWidget.ts | 5 ---- web/styles/main.css | 5 +--- 8 files changed, 24 insertions(+), 89 deletions(-) diff --git a/package.json b/package.json index 2bafa77e..842b446b 100644 --- a/package.json +++ b/package.json @@ -497,17 +497,12 @@ "Commit": { "type": "boolean", "title": "Visibility of the Commit column" - }, - "CICD": { - "type": "boolean", - "title": "Visibility of the CI/CD Status column" } }, "default": { "Date": true, "Author": true, - "Commit": true, - "CICD": false + "Commit": true }, "description": "An object specifying the default visibility of the Date, Author & Commit columns. Example: {\"Date\": true, \"Author\": true, \"Commit\": true}" }, diff --git a/src/config.ts b/src/config.ts index 677578e8..343e413d 100644 --- a/src/config.ts +++ b/src/config.ts @@ -160,10 +160,10 @@ class Config { */ get defaultColumnVisibility(): DefaultColumnVisibility { let obj: any = this.config.get('defaultColumnVisibility', {}); - if (typeof obj === 'object' && obj !== null && typeof obj['Date'] === 'boolean' && typeof obj['Author'] === 'boolean' && typeof obj['Commit'] === 'boolean' && typeof obj['CICD'] === 'boolean') { - return { author: obj['Author'], commit: obj['Commit'], date: obj['Date'], cicd: obj['CICD'] }; + if (typeof obj === 'object' && obj !== null && typeof obj['Date'] === 'boolean' && typeof obj['Author'] === 'boolean' && typeof obj['Commit'] === 'boolean') { + return { author: obj['Author'], commit: obj['Commit'], date: obj['Date'] }; } else { - return { author: true, commit: true, date: true, cicd: false }; + return { author: true, commit: true, date: true }; } } diff --git a/src/extensionState.ts b/src/extensionState.ts index 4d11c065..39bf750b 100644 --- a/src/extensionState.ts +++ b/src/extensionState.ts @@ -387,6 +387,7 @@ export class ExtensionState extends Disposable { * Add a new cicd to the cache of cicds known to Git Graph. * @param repo The repository that the cicd is used in. * @param hash The hash identifying the cicd commit. + * @param id The identifying that the cicd job. * @param cicdDataSave The CICDDataSave. */ public saveCICD(repo: string, hash: string, id: string, cicdDataSave: CICDDataSave) { diff --git a/src/types.ts b/src/types.ts index 31740a7b..35ab3b10 100644 --- a/src/types.ts +++ b/src/types.ts @@ -492,7 +492,6 @@ export interface DefaultColumnVisibility { readonly date: boolean; readonly author: boolean; readonly commit: boolean; - readonly cicd: boolean; } export interface DialogDefaults { diff --git a/tests/config.test.ts b/tests/config.test.ts index fd4f8028..62e0e39f 100644 --- a/tests/config.test.ts +++ b/tests/config.test.ts @@ -726,50 +726,38 @@ describe('Config', () => { describe('defaultColumnVisibility', () => { it('Should successfully parse the configuration value (Date column disabled)', () => { // Setup - vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: false, Author: true, Commit: true, CICD: true }); + vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: false, Author: true, Commit: true }); // Run const value = config.defaultColumnVisibility; // Assert expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); - expect(value).toStrictEqual({ date: false, author: true, commit: true, cicd: true }); + expect(value).toStrictEqual({ date: false, author: true, commit: true }); }); it('Should successfully parse the configuration value (Author column disabled)', () => { // Setup - vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: true, Author: false, Commit: true, CICD: true }); + vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: true, Author: false, Commit: true }); // Run const value = config.defaultColumnVisibility; // Assert expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); - expect(value).toStrictEqual({ date: true, author: false, commit: true, cicd: true }); + expect(value).toStrictEqual({ date: true, author: false, commit: true }); }); it('Should successfully parse the configuration value (Commit column disabled)', () => { // Setup - vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: true, Author: true, Commit: false, CICD: true }); + vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: true, Author: true, Commit: false }); // Run const value = config.defaultColumnVisibility; // Assert expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); - expect(value).toStrictEqual({ date: true, author: true, commit: false, cicd: true }); - }); - - it('Should successfully parse the configuration value (CI/CD Status column disabled)', () => { - // Setup - vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: true, Author: true, Commit: true, CICD: false }); - - // Run - const value = config.defaultColumnVisibility; - - // Assert - expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); - expect(value).toStrictEqual({ date: true, author: true, commit: true, cicd: false }); + expect(value).toStrictEqual({ date: true, author: true, commit: false }); }); it('Should return the default value when the configuration value is invalid (not an object)', () => { @@ -781,7 +769,7 @@ describe('Config', () => { // Assert expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); - expect(value).toStrictEqual({ date: true, author: true, commit: true, cicd: false }); + expect(value).toStrictEqual({ date: true, author: true, commit: true }); }); it('Should return the default value when the configuration value is invalid (NULL)', () => { @@ -793,19 +781,19 @@ describe('Config', () => { // Assert expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); - expect(value).toStrictEqual({ date: true, author: true, commit: true, cicd: false }); + expect(value).toStrictEqual({ date: true, author: true, commit: true }); }); it('Should return the default value when the configuration value is invalid (column value is not a boolean)', () => { // Setup - vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: true, Author: true, Commit: 5, CICD: true }); + vscode.mockExtensionSettingReturnValue('defaultColumnVisibility', { Date: true, Author: true, Commit: 5 }); // Run const value = config.defaultColumnVisibility; // Assert expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); - expect(value).toStrictEqual({ date: true, author: true, commit: true, cicd: false }); + expect(value).toStrictEqual({ date: true, author: true, commit: true }); }); it('Should return the default value when the configuration value is not set', () => { @@ -814,7 +802,7 @@ describe('Config', () => { // Assert expect(workspaceConfiguration.get).toBeCalledWith('defaultColumnVisibility', {}); - expect(value).toStrictEqual({ date: true, author: true, commit: true, cicd: false }); + expect(value).toStrictEqual({ date: true, author: true, commit: true }); }); }); diff --git a/web/main.ts b/web/main.ts index db005aa4..f5f14669 100644 --- a/web/main.ts +++ b/web/main.ts @@ -383,7 +383,6 @@ class GitGraphView { this.finaliseLoadCommits(); this.requestAvatars(avatarsNeeded); - this.requestCICDs(); } private finaliseLoadCommits() { @@ -526,12 +525,6 @@ class GitGraphView { } this.cicdDatas[repo][hash] = cicdDataSaves; this.saveState(); - let cicdElems = >document.getElementsByClassName('cicd'); - for (let i = 0; i < cicdElems.length; i++) { - if (cicdElems[i].dataset.hash === hash) { - cicdElems[i].innerHTML = this.getCicdHtml(cicdDataSaves); - } - } let cicdDetailElems = >document.getElementsByClassName('cicdDetail'); for (let i = 0; i < cicdDetailElems.length; i++) { if (cicdDetailElems[i].dataset.hash === hash) { @@ -744,16 +737,6 @@ class GitGraphView { } } - private requestCICDs() { - if (typeof this.currentRepo === 'string' && typeof this.gitRepos[this.currentRepo] !== 'undefined') { - let cicdConfigs = this.gitRepos[this.currentRepo].cicdConfigs; - if (cicdConfigs !== null && cicdConfigs.length >= 1) { - this.commits.forEach(commit => { - sendMessage({ command: 'fetchCICD', repo: this.currentRepo, hash: commit.hash }); - }); - } - } - } /* State */ @@ -801,7 +784,7 @@ class GitGraphView { } private saveColumnWidths(columnWidths: GG.ColumnWidth[]) { - this.gitRepos[this.currentRepo].columnWidths = [columnWidths[0], columnWidths[2], columnWidths[3], columnWidths[4], columnWidths[5]]; + this.gitRepos[this.currentRepo].columnWidths = [columnWidths[0], columnWidths[2], columnWidths[3], columnWidths[4]]; this.saveRepoState(); } @@ -886,7 +869,6 @@ class GitGraphView { (colVisibility.date ? 'Date' : '') + (colVisibility.author ? 'Author' : '') + (colVisibility.commit ? 'Commit' : '') + - (colVisibility.cicd ? 'CI/CD' : '') + ''; for (let i = 0; i < this.commits.length; i++) { @@ -935,9 +917,6 @@ class GitGraphView { (colVisibility.date ? '' + date.formatted + '' : '') + (colVisibility.author ? '' + (this.config.fetchAvatars ? '' + (typeof this.avatars[commit.email] === 'string' ? '' : '') + '' : '') + escapeHtml(commit.author) + '' : '') + (colVisibility.commit ? '' + abbrevCommit(commit.hash) + '' : '') + - (colVisibility.cicd ? '' + '' + - ((typeof this.cicdDatas[this.currentRepo] === 'object' && typeof this.cicdDatas[this.currentRepo][commit.hash] === 'object') ? this.getCicdHtml(this.cicdDatas[this.currentRepo][commit.hash]) : '*') + - '' + '' : '') + ''; } this.tableElem.innerHTML = '' + html + '
'; @@ -995,8 +974,7 @@ class GitGraphView { document.getElementById('uncommittedChanges')!.innerHTML = '' + escapeHtml(this.commits[0].message) + '' + (colVisibility.date ? '' + date.formatted + '' : '') + (colVisibility.author ? '*' : '') + - (colVisibility.commit ? '*' : '') + - (colVisibility.cicd ? '*' : ''); + (colVisibility.commit ? '*' : ''); } private renderFetchButton() { @@ -1765,10 +1743,10 @@ class GitGraphView { let cWidths = this.gitRepos[this.currentRepo].columnWidths; if (cWidths === null) { // Initialise auto column layout if it is the first time viewing the repo. let defaults = this.config.defaultColumnVisibility; - columnWidths = [COLUMN_AUTO, COLUMN_AUTO, defaults.date ? COLUMN_AUTO : COLUMN_HIDDEN, defaults.author ? COLUMN_AUTO : COLUMN_HIDDEN, defaults.commit ? COLUMN_AUTO : COLUMN_HIDDEN, defaults.cicd ? COLUMN_AUTO : COLUMN_HIDDEN]; + columnWidths = [COLUMN_AUTO, COLUMN_AUTO, defaults.date ? COLUMN_AUTO : COLUMN_HIDDEN, defaults.author ? COLUMN_AUTO : COLUMN_HIDDEN, defaults.commit ? COLUMN_AUTO : COLUMN_HIDDEN]; this.saveColumnWidths(columnWidths); } else { - columnWidths = [cWidths[0], COLUMN_AUTO, cWidths[1], cWidths[2], cWidths[3], cWidths[4]]; + columnWidths = [cWidths[0], COLUMN_AUTO, cWidths[1], cWidths[2], cWidths[3]]; } if (columnWidths[0] !== COLUMN_AUTO) { @@ -1883,12 +1861,6 @@ class GitGraphView { visible: true, checked: columnWidths[4] !== COLUMN_HIDDEN, onClick: () => toggleColumnState(4, 80) - }, - { - title: 'CI/CD Status', - visible: true, - checked: columnWidths[5] !== COLUMN_HIDDEN, - onClick: () => toggleColumnState(5, 80) } ], [ @@ -1915,31 +1887,19 @@ class GitGraphView { }); } - public setColumnVisibility(column: number, columnWidth: number) { - let colWidths = this.gitRepos[this.currentRepo].columnWidths; - if (colWidths !== null) { - if (column < colWidths.length) { - colWidths[column] = columnWidth === COLUMN_HIDDEN ? COLUMN_HIDDEN : (colWidths[0] === COLUMN_AUTO ? COLUMN_AUTO : columnWidth - COLUMN_LEFT_RIGHT_PADDING); - let columnWidths = [colWidths[0], COLUMN_AUTO, colWidths[1], colWidths[2], colWidths[3], colWidths[4]]; - this.saveColumnWidths(columnWidths); - this.render(); - } - } - } - public getColumnVisibility() { let colWidths = this.gitRepos[this.currentRepo].columnWidths; if (colWidths !== null) { - return { date: colWidths[1] !== COLUMN_HIDDEN, author: colWidths[2] !== COLUMN_HIDDEN, commit: colWidths[3] !== COLUMN_HIDDEN, cicd: colWidths[4] !== COLUMN_HIDDEN }; + return { date: colWidths[1] !== COLUMN_HIDDEN, author: colWidths[2] !== COLUMN_HIDDEN, commit: colWidths[3] !== COLUMN_HIDDEN }; } else { let defaults = this.config.defaultColumnVisibility; - return { date: defaults.date, author: defaults.author, commit: defaults.commit, cicd: defaults.cicd }; + return { date: defaults.date, author: defaults.author, commit: defaults.commit }; } } private getNumColumns() { let colVisibility = this.getColumnVisibility(); - return 2 + (colVisibility.date ? 1 : 0) + (colVisibility.author ? 1 : 0) + (colVisibility.commit ? 1 : 0) + (colVisibility.cicd ? 1 : 0); + return 2 + (colVisibility.date ? 1 : 0) + (colVisibility.author ? 1 : 0) + (colVisibility.commit ? 1 : 0); } /** diff --git a/web/settingsWidget.ts b/web/settingsWidget.ts index 6511d605..b621a867 100644 --- a/web/settingsWidget.ts +++ b/web/settingsWidget.ts @@ -680,11 +680,6 @@ class SettingsWidget { if (this.currentRepo === null) return; this.view.saveRepoStateValue(this.currentRepo, 'cicdConfigs', config); this.render(); - if (config?.length !== 0) { - this.view.setColumnVisibility(4, 80); - } else { - this.view.setColumnVisibility(4, COLUMN_HIDDEN); - } } /** diff --git a/web/styles/main.css b/web/styles/main.css index 3a7e6e7d..e2d1a3a0 100644 --- a/web/styles/main.css +++ b/web/styles/main.css @@ -221,10 +221,7 @@ code{ #commitTable tr.commit td{ cursor:pointer; } -#commitTable tr.commit td.cicdCol{ - cursor:pointer; - overflow: visible; -} + #commitTable tr.commit.current span.description .text{ font-weight:bold; } From 2dc18ad1cc918774b000446f43f6c9aa95b2b744 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sat, 1 May 2021 01:00:59 +0900 Subject: [PATCH 45/54] #462 Updated GitLab detail url --- src/cicdManager.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/cicdManager.ts b/src/cicdManager.ts index 67a4882c..018e7d3f 100644 --- a/src/cicdManager.ts +++ b/src/cicdManager.ts @@ -472,7 +472,8 @@ export class CicdManager extends Disposable { ret.forEach(element => { let save: CICDDataSave; if (cicdRequest.detail) { - save = this.convComitStatuses2CICDDataSave(element, cicdRequest.detail); + save = this.convGitLabComitStatuses2CICDDataSave(element, cicdRequest.detail, + `${hostProtocol}://${hostRootUrl}${hostPort === '' ? '' : ':' + hostPort}/${hostpath.replace(/%2F/g, '/')}/-/jobs/`); } else { save = this.convCICDData2CICDDataSave(element); } @@ -644,7 +645,7 @@ export class CicdManager extends Disposable { } /** - * Fetch an cicd from GitHub. + * Fetch an cicd from GitHub/GitLab/Jenkins. * @param cicdData The CICDData. * @returns The CICDDataSave. */ @@ -660,13 +661,13 @@ export class CicdManager extends Disposable { } /** - * Fetch an cicd from GitHub. - * @param cicdData The CICDData. + * Fetch an cicd from GitLab. + * @param data The result of GitLab commit statuses API. * @param detail Detail fetch flag. * @returns The CICDDataSave. */ - private convComitStatuses2CICDDataSave(data: any, detail: boolean): CICDDataSave { - return { + private convGitLabComitStatuses2CICDDataSave(data: any, detail: boolean, url: string): CICDDataSave { + let ret = { name: data!.name, ref: data!.ref, status: data!.status, @@ -674,6 +675,10 @@ export class CicdManager extends Disposable { event: '', detail: detail }; + if (typeof ret.web_url === 'undefined' || ret.web_url === null) { + ret.web_url = url + data.id; + } + return ret; } /** From beec767781351bcec5ac689267d64cb94e24c587 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sun, 2 May 2021 20:57:18 +0900 Subject: [PATCH 46/54] #462 Removed CI/CD column --- src/cicdManager.ts | 76 +++++--------------------------------- src/extensionState.ts | 6 +-- src/gitGraphView.ts | 5 +-- src/types.ts | 11 +----- tests/gitGraphView.test.ts | 12 +++--- web/main.ts | 3 +- web/settingsWidget.ts | 3 +- 7 files changed, 21 insertions(+), 95 deletions(-) diff --git a/src/cicdManager.ts b/src/cicdManager.ts index 018e7d3f..15fe905c 100644 --- a/src/cicdManager.ts +++ b/src/cicdManager.ts @@ -27,10 +27,6 @@ export class CicdManager extends Disposable { private githubTimeout: number = 0; private gitLabTimeout: number = 0; private jenkinsTimeout: number = 0; - private initialState: boolean = true; - private requestPage: number = -1; - private requestPageTimeout: NodeJS.Timer | null = null; - private cicdConfigsPrev: CICDConfig[] = []; private per_page: number = 100; @@ -77,75 +73,23 @@ export class CicdManager extends Disposable { } /** - * Fetch an cicd, either from the cache if it already exists, or queue it to be fetched. + * Get the data of an cicd. * @param repo The repository that the cicd is used in. * @param hash The hash identifying the cicd commit. * @param cicdConfigs The CICDConfigs. + * @returns A JSON encoded data of an cicd if the cicd exists, otherwise NULL. */ - public fetchCICDStatus(repo: string, hash: string) { - if (typeof this.cicds[repo] !== 'undefined' && typeof this.cicds[repo][hash] !== 'undefined') { - // CICD exists in the cache - this.emitCICD(repo, hash, this.cicds[repo][hash]); - } else { - + public getCICDDetail(repo: string, hash: string) { + return new Promise((resolve) => { let repos = this.repoManager.getRepos(); if (typeof repos[repo] !== 'undefined') { - if (repos[repo].cicdConfigs !== null) { - let cicdConfigs = repos[repo].cicdConfigs; - if (cicdConfigs !== null) { - // Check update user config - const cicdConfigsJSON = JSON.stringify(Object.entries(cicdConfigs).sort()); - const cicdConfigsPrevJSON = JSON.stringify(Object.entries(this.cicdConfigsPrev).sort()); - if (cicdConfigsJSON !== cicdConfigsPrevJSON) { - this.initialState = true; - this.requestPage = -1; - if (this.requestPageTimeout !== null) { - clearTimeout(this.requestPageTimeout); - } - this.requestPageTimeout = null; - } - // Deep Clone cicdConfigs - this.cicdConfigsPrev = JSON.parse(JSON.stringify(cicdConfigs)); - // CICD not in the cache, request it - if (this.initialState) { - this.initialState = false; - cicdConfigs.forEach(cicdConfig => { - this.queue.add(repo, cicdConfig, this.requestPage, true); - }); - // Reset initial state for 10 seconds - setTimeout(() => { - this.logger.log('Reset initial timer of CICD'); - this.initialState = true; - }, 10000); - // set request page to top - this.requestPage = 1; - // Reset request page to all after 10 minutes - if (this.requestPageTimeout === null) { - this.requestPageTimeout = setTimeout(() => { - this.logger.log('Reset request page of CICD'); - this.requestPage = -1; - this.requestPageTimeout = null; - }, 600000); - } - } - } + let cicdConfigs = repos[repo].cicdConfigs; + if (cicdConfigs !== null) { + cicdConfigs.forEach(cicdConfig => { + this.queue.add(repo, cicdConfig, -1, true, true, hash); + }); } } - } - } - - /** - * Get the data of an cicd. - * @param repo The repository that the cicd is used in. - * @param hash The hash identifying the cicd commit. - * @param cicdConfigs The CICDConfigs. - * @returns A JSON encoded data of an cicd if the cicd exists, otherwise NULL. - */ - public getCICDDetail(repo: string, hash: string, cicdConfigs: CICDConfig[]) { - return new Promise((resolve) => { - cicdConfigs.forEach(cicdConfig => { - this.queue.add(repo, cicdConfig, -1, true, true, hash); - }); if (typeof this.cicds[repo] !== 'undefined' && typeof this.cicds[repo][hash] !== 'undefined' && this.cicds[repo][hash] !== null) { resolve(JSON.stringify(this.cicds[repo][hash])); } else { @@ -261,7 +205,7 @@ export class CicdManager extends Disposable { if (res.headers['x-ratelimit-remaining'] === '0') { // If the GitHub Api rate limit was reached, store the github timeout to prevent subsequent requests this.githubTimeout = parseInt(res.headers['x-ratelimit-reset']) * 1000; - this.logger.log('GitHub API Rate Limit Reached - Paused fetching from GitLab until the Rate Limit is reset (RateLimit=' + res.headers['x-ratelimit-limit'] + '(1 hour)/' + new Date(this.githubTimeout).toString() + ')'); + this.logger.log('GitHub API Rate Limit Reached - Paused fetching from GitHub until the Rate Limit is reset (RateLimit=' + res.headers['x-ratelimit-limit'] + '(1 hour)/' + new Date(this.githubTimeout).toString() + ')'); if (cicdRequest.cicdConfig.cicdToken === '') { this.logger.log('GitHub API Rate Limit can upgrade by Access Token.'); } diff --git a/src/extensionState.ts b/src/extensionState.ts index 39bf750b..cd0a465b 100644 --- a/src/extensionState.ts +++ b/src/extensionState.ts @@ -142,8 +142,7 @@ export class ExtensionState extends Disposable { let config: CICDConfig = { provider: element.provider, cicdUrl: element.cicdUrl, - cicdToken: decrypted.toString(), - custom: null + cicdToken: decrypted.toString() }; outputSet[repo].cicdConfigs?.push(config); } @@ -180,8 +179,7 @@ export class ExtensionState extends Disposable { let config: CICDConfig = { provider: cicdConfig.provider, cicdUrl: cicdConfig.cicdUrl, - cicdToken: iv.toString('hex') + ':' + encrypted.toString('hex'), - custom: null + cicdToken: iv.toString('hex') + ':' + encrypted.toString('hex') }; cicdConfigsEncrypto.push(config); }); diff --git a/src/gitGraphView.ts b/src/gitGraphView.ts index 8feb1f3b..31b619c3 100644 --- a/src/gitGraphView.ts +++ b/src/gitGraphView.ts @@ -250,7 +250,7 @@ export class GitGraphView extends Disposable { ? this.dataSource.getCommitDetails(msg.repo, msg.commitHash, msg.hasParents) : this.dataSource.getStashDetails(msg.repo, msg.commitHash, msg.stash), msg.avatarEmail !== null ? this.avatarManager.getAvatarImage(msg.avatarEmail) : Promise.resolve(null), - msg.cicdConfigs !== null ? this.cicdManager.getCICDDetail(msg.repo, msg.commitHash, msg.cicdConfigs) : Promise.resolve(null) + this.cicdManager.getCICDDetail(msg.repo, msg.commitHash) ]); this.sendMessage({ command: 'commitDetails', @@ -407,9 +407,6 @@ export class GitGraphView extends Disposable { case 'fetchAvatar': this.avatarManager.fetchAvatarImage(msg.email, msg.repo, msg.remote, msg.commits); break; - case 'fetchCICD': - this.cicdManager.fetchCICDStatus(msg.repo, msg.hash); - break; case 'fetchIntoLocalBranch': this.sendMessage({ command: 'fetchIntoLocalBranch', diff --git a/src/types.ts b/src/types.ts index 35ab3b10..dbd9174e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -234,18 +234,10 @@ export const enum CICDProvider { interface CICDConfigBuiltIn extends CICDConfigBase { readonly provider: CICDProvider; - readonly custom: null; } -interface CICDConfigCustom extends CICDConfigBase { - readonly provider: CICDProvider.Custom; - readonly custom: { - readonly name: string, - readonly templateUrl: string - }; -} -export type CICDConfig = CICDConfigBuiltIn | CICDConfigCustom; +export type CICDConfig = CICDConfigBuiltIn; export interface GitRepoState { cdvDivider: number; @@ -718,7 +710,6 @@ export interface RequestCommitDetails extends RepoRequest { readonly stash: GitCommitStash | null; // null => request is for a commit, otherwise => request is for a stash readonly avatarEmail: string | null; // string => fetch avatar with the given email, null => don't fetch avatar readonly refresh: boolean; - readonly cicdConfigs: CICDConfig[] | null; } export interface ResponseCommitDetails extends ResponseWithErrorInfo { readonly command: 'commitDetails'; diff --git a/tests/gitGraphView.test.ts b/tests/gitGraphView.test.ts index 69361adf..59685eaf 100644 --- a/tests/gitGraphView.test.ts +++ b/tests/gitGraphView.test.ts @@ -49,9 +49,10 @@ describe('GitGraphView', () => { logger = new Logger(); dataSource = new DataSource({ path: '/path/to/git', version: '2.25.0' }, onDidChangeConfiguration.subscribe, onDidChangeGitExecutable.subscribe, logger); extensionState = new ExtensionState(vscode.mocks.extensionContext, onDidChangeGitExecutable.subscribe); + jest.spyOn(extensionState, 'getCICDCache').mockReturnValue({}); avatarManager = new AvatarManager(dataSource, extensionState, logger); - cicdManager = new CicdManager(extensionState, repoManager, logger); repoManager = new RepoManager(dataSource, extensionState, onDidChangeConfiguration.subscribe, logger); + cicdManager = new CicdManager(extensionState, repoManager, logger); spyOnLog = jest.spyOn(logger, 'log'); spyOnLogError = jest.spyOn(logger, 'logError'); @@ -966,8 +967,7 @@ describe('GitGraphView', () => { hasParents: true, stash: null, avatarEmail: 'user@mhutchie.com', - refresh: false, - cicdConfigs: null + refresh: false }); // Assert @@ -1005,8 +1005,7 @@ describe('GitGraphView', () => { hasParents: true, stash: null, avatarEmail: null, - refresh: false, - cicdConfigs: null + refresh: false }); // Assert @@ -1051,8 +1050,7 @@ describe('GitGraphView', () => { hasParents: true, stash: stash, avatarEmail: null, - refresh: false, - cicdConfigs: null + refresh: false }); // Assert diff --git a/web/main.ts b/web/main.ts index f5f14669..1b72919e 100644 --- a/web/main.ts +++ b/web/main.ts @@ -714,8 +714,7 @@ class GitGraphView { hasParents: commit.parents.length > 0, stash: commit.stash, avatarEmail: this.config.fetchAvatars && hash !== UNCOMMITTED ? commit.email : null, - refresh: refresh, - cicdConfigs: this.gitRepos[this.currentRepo].cicdConfigs + refresh: refresh }); } diff --git a/web/settingsWidget.ts b/web/settingsWidget.ts index b621a867..7233f638 100644 --- a/web/settingsWidget.ts +++ b/web/settingsWidget.ts @@ -475,8 +475,7 @@ class SettingsWidget { const updateConfigWithFormValues = (values: DialogInputValue[]) => { let config: GG.CICDConfig = { provider: parseInt(values[0]), cicdUrl: values[1], - cicdToken: values[2], - custom: null + cicdToken: values[2] }; return config; }; From 6f8736305a62b06bd54fd997a33566cff8680233 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Mon, 3 May 2021 02:47:58 +0900 Subject: [PATCH 47/54] #462 Added allow_failure style for GitLab --- src/cicdManager.ts | 6 ++++-- src/types.ts | 1 + web/main.ts | 8 ++++++-- web/styles/main.css | 4 ++-- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/cicdManager.ts b/src/cicdManager.ts index 15fe905c..9b10cdec 100644 --- a/src/cicdManager.ts +++ b/src/cicdManager.ts @@ -600,7 +600,8 @@ export class CicdManager extends Disposable { status: cicdData!.status, web_url: cicdData!.web_url, event: cicdData!.event, - detail: cicdData!.detail + detail: cicdData!.detail, + allow_failure: false }; } @@ -617,7 +618,8 @@ export class CicdManager extends Disposable { status: data!.status, web_url: data!.target_url, event: '', - detail: detail + detail: detail, + allow_failure: data!.allow_failure }; if (typeof ret.web_url === 'undefined' || ret.web_url === null) { ret.web_url = url + data.id; diff --git a/src/types.ts b/src/types.ts index dbd9174e..4aaf03a2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -217,6 +217,7 @@ export interface CICDDataSave { web_url: string; event: string; detail: boolean; + allow_failure: boolean; } export interface CICDConfigBase { diff --git a/web/main.ts b/web/main.ts index 1b72919e..28072cfe 100644 --- a/web/main.ts +++ b/web/main.ts @@ -542,7 +542,11 @@ class GitGraphView { let cicdDataSave = cicdDataSaves[name]; let event = cicdDataSave.event || ''; let detailCurrent = cicdDataSave.detail || false; - let status = ((cicdDataSave.status === 'success' || cicdDataSave.status === 'SUCCESS') ? 'G' : ((cicdDataSave.status === 'failed' || cicdDataSave.status === 'failure') ? 'B' : 'U')); + let status = ((cicdDataSave.status === 'success' || cicdDataSave.status === 'SUCCESS') ? 'G' : + (((typeof cicdDataSave.allow_failure === 'undefined' || !cicdDataSave.allow_failure) && + (cicdDataSave.status === 'failed' || cicdDataSave.status === 'failure')) ? 'B' : + ((typeof cicdDataSave.allow_failure !== 'undefined' || cicdDataSave.allow_failure) && + (cicdDataSave.status === 'failed' || cicdDataSave.status === 'failure')) ? 'A' : 'U')); // if (detailCurrent === detail && event === 'push' || event === 'pull_request' || event === '') { if (detailCurrent === detail && event !== 'issues' && event !== 'issue_comment' && event !== 'schedule' && event !== 'workflow_run') { ret += @@ -553,7 +557,7 @@ class GitGraphView { (typeof cicdDataSave.status !== 'undefined' ? `
Status: ${cicdDataSave.status}
` : '') + ((typeof cicdDataSave.event !== 'undefined' && cicdDataSave.event !== '') ? `
Event: ${cicdDataSave.event}
` : '') + '
' + - `${(status === 'G' ? SVG_ICONS.passed : (status === 'B' ? SVG_ICONS.failed : SVG_ICONS.inconclusive))}` + + `${(status === 'G' ? SVG_ICONS.passed : (status === 'B' ? SVG_ICONS.failed : (status === 'A' ? SVG_ICONS.alert : SVG_ICONS.inconclusive)))}` + ''; } } diff --git a/web/styles/main.css b/web/styles/main.css index e2d1a3a0..6dec24a0 100644 --- a/web/styles/main.css +++ b/web/styles/main.css @@ -469,7 +469,7 @@ code{ fill:#009028; opacity:0.9; } -.cicdInfo.U svg{ +.cicdInfo.U svg, .cicdInfo.A svg{ fill:#f09000; opacity:1; } @@ -1082,7 +1082,7 @@ label > input[type=radio]:checked ~ .customRadio:after{ .cicdTooltip .cicdTooltipContent.G{ border-color: #009028; } -.cicdTooltip .cicdTooltipContent.U{ +.cicdTooltip .cicdTooltipContent.U, .cicdTooltip .cicdTooltipContent.A{ border-color: #f09000; } .cicdTooltip .cicdTooltipContent.B{ From cb58c4eb0ea85af2e72b756745c1fd2c6f27fbdf Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Mon, 3 May 2021 07:47:00 +0900 Subject: [PATCH 48/54] #462 Added allow_failure tooltips for GitLab --- web/main.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/web/main.ts b/web/main.ts index 28072cfe..2ea5234b 100644 --- a/web/main.ts +++ b/web/main.ts @@ -556,6 +556,7 @@ class GitGraphView { (typeof cicdDataSave.name !== 'undefined' ? `
${cicdDataSave.name}
` : '') + (typeof cicdDataSave.status !== 'undefined' ? `
Status: ${cicdDataSave.status}
` : '') + ((typeof cicdDataSave.event !== 'undefined' && cicdDataSave.event !== '') ? `
Event: ${cicdDataSave.event}
` : '') + + ((typeof cicdDataSave.allow_failure !== 'undefined' && cicdDataSave.allow_failure) ? `
Allow Failure: ${cicdDataSave.allow_failure}
` : '') + '
' + `${(status === 'G' ? SVG_ICONS.passed : (status === 'B' ? SVG_ICONS.failed : (status === 'A' ? SVG_ICONS.alert : SVG_ICONS.inconclusive)))}` + ''; From 9871083f16f837a57f58db7dd09c8fef4e880250 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Mon, 3 May 2021 08:24:10 +0900 Subject: [PATCH 49/54] #462 Added comment of Encrypt/Decrypt --- src/extensionState.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/extensionState.ts b/src/extensionState.ts index cd0a465b..c0f31e85 100644 --- a/src/extensionState.ts +++ b/src/extensionState.ts @@ -125,6 +125,7 @@ export class ExtensionState extends Disposable { outputSet[repo].showRemoteBranchesV2 = repoSet[repo].showRemoteBranches ? BooleanOverride.Enabled : BooleanOverride.Disabled; } } + // Decrypt cicdToken if (typeof repoSet[repo].cicdConfigs !== 'undefined' && repoSet[repo].cicdConfigs !== null) { outputSet[repo].cicdConfigs = []; if (typeof repoSet[repo].cicdNonce !== 'undefined' && repoSet[repo].cicdNonce !== null) { @@ -161,6 +162,7 @@ export class ExtensionState extends Disposable { // Deep Clone gitRepoSet let gitRepoSetTemp = JSON.parse(JSON.stringify(gitRepoSet)); + // Encrypt cicdToken Object.keys(gitRepoSetTemp).forEach((repo) => { let ENCRYPTION_KEY = getNonce(); // Must be 256 bits (32 characters) const IV_LENGTH = 16; // For AES, this is always 16 From 411f80bc033fffdae84cab8265f3a034c823e20f Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sun, 23 May 2021 02:00:50 +0900 Subject: [PATCH 50/54] #462 Updated error check for CICD --- src/cicdManager.ts | 560 ++++++++++++++++++++++-------------------- src/extensionState.ts | 9 +- 2 files changed, 304 insertions(+), 265 deletions(-) diff --git a/src/cicdManager.ts b/src/cicdManager.ts index 9b10cdec..af7d5965 100644 --- a/src/cicdManager.ts +++ b/src/cicdManager.ts @@ -24,7 +24,7 @@ export class CicdManager extends Disposable { private queue: CicdRequestQueue; private interval: NodeJS.Timer | null = null; - private githubTimeout: number = 0; + private gitHubTimeout: number = 0; private gitLabTimeout: number = 0; private jenkinsTimeout: number = 0; @@ -134,6 +134,7 @@ export class CicdManager extends Disposable { this.fetchFromJenkins(cicdRequest); break; default: + this.logger.log('Unknown provider Error'); break; } } else { @@ -148,27 +149,28 @@ export class CicdManager extends Disposable { */ private fetchFromGitHub(cicdRequest: CICDRequestItem) { let t = (new Date()).getTime(); - if (t < this.githubTimeout) { + if (t < this.gitHubTimeout) { // Defer request until after timeout - this.queue.addItem(cicdRequest, this.githubTimeout, false); + this.queue.addItem(cicdRequest, this.gitHubTimeout, false); this.fetchCICDsInterval(); return; } let cicdConfig = cicdRequest.cicdConfig; - const match1 = cicdConfig.cicdUrl.match(/^(https?:\/\/|git@)((?=[^/]+@)[^@]+@|(?![^/]+@))([^/:]+)/); - let hostRootUrl = match1 !== null ? 'api.' + match1[3] : ''; - - const match2 = cicdConfig.cicdUrl.match(/^(https?:\/\/|git@)[^/:]+[/:]([^/]+)\/([^/]*?)(.git|)$/); - let sourceOwner = match2 !== null ? match2[2] : ''; - let sourceRepo = match2 !== null ? match2[3] : ''; + let gitUrl = cicdConfig.cicdUrl.replace(/\/$/g, ''); + gitUrl = gitUrl.replace(/.git$/g, ''); + const match1 = gitUrl.match(/^(.+?):\/\/(.+?):?(\d+)?(\/.*)?$/); + let hostProtocol = (match1 !== null && typeof match1[1] !== 'undefined') ? '' + match1[1].toLowerCase() : ''; + let hostRootUrl = (match1 !== null && typeof match1[2] !== 'undefined') ? 'api.' + match1[2] : ''; + let hostPort = (match1 !== null && typeof match1[3] !== 'undefined') ? '' + match1[3] : ''; + let hostPath = (match1 !== null && typeof match1[4] !== 'undefined') ? '' + match1[4].replace(/^\//, '') : ''; // https://docs.github.com/en/rest/reference/actions#list-workflow-runs-for-a-repository - let cicdRootPath = `/repos/${sourceOwner}/${sourceRepo.replace(/\//g, '%2F')}/actions/runs?per_page=${this.per_page}`; + let cicdRootPath = `/repos/${hostPath}/actions/runs?per_page=${this.per_page}`; if (cicdRequest.detail) { // https://docs.github.com/en/rest/reference/checks#list-check-runs-for-a-git-reference - cicdRootPath = `/repos/${sourceOwner}/${sourceRepo.replace(/\//g, '%2F')}/commits/${cicdRequest.hash}/check-runs?per_page=${this.per_page}`; + cicdRootPath = `/repos/${hostPath}/commits/${cicdRequest.hash}/check-runs?per_page=${this.per_page}`; } if (cicdRequest.page > 1) { cicdRootPath = `${cicdRootPath}&page=${cicdRequest.page}`; @@ -184,109 +186,116 @@ export class CicdManager extends Disposable { let triggeredOnError = false; const onError = (err: Error) => { - this.logger.log('GitHub API HTTPS Error - ' + err?.message); + this.logger.log('GitHub API ' + hostProtocol + ' Error - ' + err?.message); if (!triggeredOnError) { // If an error occurs, try again after 5 minutes triggeredOnError = true; - this.githubTimeout = t + 300000; - this.queue.addItem(cicdRequest, this.githubTimeout, false); + this.gitHubTimeout = t + 300000; + this.queue.addItem(cicdRequest, this.gitHubTimeout, false); } }; - - this.logger.log('Requesting CICD for https://' + hostRootUrl + cicdRootPath + ' detail=' + cicdRequest.detail + ' page=' + cicdRequest.page + ' from GitHub'); - https.get({ - hostname: hostRootUrl, path: cicdRootPath, - headers: headers, - agent: false, timeout: 15000 - }, (res) => { - let respBody = ''; - res.on('data', (chunk: Buffer) => { respBody += chunk; }); - res.on('end', async () => { - if (res.headers['x-ratelimit-remaining'] === '0') { - // If the GitHub Api rate limit was reached, store the github timeout to prevent subsequent requests - this.githubTimeout = parseInt(res.headers['x-ratelimit-reset']) * 1000; - this.logger.log('GitHub API Rate Limit Reached - Paused fetching from GitHub until the Rate Limit is reset (RateLimit=' + res.headers['x-ratelimit-limit'] + '(1 hour)/' + new Date(this.githubTimeout).toString() + ')'); - if (cicdRequest.cicdConfig.cicdToken === '') { - this.logger.log('GitHub API Rate Limit can upgrade by Access Token.'); + if (!(hostProtocol === 'http' || hostProtocol === 'https') || hostRootUrl === '') { + this.logger.log('Requesting CICD is not match URL (' + cicdConfig.cicdUrl + ') for GitHub'); + } else { + this.logger.log('Requesting CICD for ' + hostProtocol + '://' + hostRootUrl + cicdRootPath + ' detail=' + cicdRequest.detail + ' page=' + cicdRequest.page + ' from GitHub'); + (hostProtocol === 'http' ? http : https).get({ + hostname: hostRootUrl, path: cicdRootPath, + port: hostPort, + headers: headers, + agent: false, timeout: 15000 + }, (res) => { + let respBody = ''; + res.on('data', (chunk: Buffer) => { respBody += chunk; }); + res.on('end', async () => { + if (res.headers['x-ratelimit-remaining'] === '0') { + // If the GitHub Api rate limit was reached, store the github timeout to prevent subsequent requests + this.gitHubTimeout = parseInt(res.headers['x-ratelimit-reset']) * 1000; + this.logger.log('GitHub API Rate Limit Reached - Paused fetching from GitHub until the Rate Limit is reset (RateLimit=' + res.headers['x-ratelimit-limit'] + '(1 hour)/' + new Date(this.gitHubTimeout).toString() + ')'); + if (cicdRequest.cicdConfig.cicdToken === '') { + this.logger.log('GitHub API Rate Limit can upgrade by Access Token.'); + } } - } - if (res.statusCode === 200) { // Success - this.logger.log('GitHub API - (' + res.statusCode + ')' + 'https://' + hostRootUrl + cicdRootPath); - try { - let respJson: any = JSON.parse(respBody); - if (typeof respJson['check_runs'] !== 'undefined' && respJson['check_runs'].length >= 1) { // url found - let ret: CICDData[] = respJson['check_runs'].map((elm: { [x: string]: any; }) => { - return { - id: elm['id'], - status: elm['conclusion'] === null ? elm['status'] : elm['conclusion'], - ref: '', - sha: elm['head_sha'], - web_url: elm['html_url'], - created_at: elm['created_at'], - updated_at: elm['updated_at'], - name: elm['app']!['name'] + '(' + elm['name'] + ')', - event: '', - detail: cicdRequest.detail - }; - }); - ret.forEach(element => { - let save = this.convCICDData2CICDDataSave(element); - this.saveCICD(cicdRequest.repo, element.sha, element.id, save); - }); - this.reFetchPageGitHub(cicdRequest, res, cicdConfig); - return; - } else if (typeof respJson['workflow_runs'] !== 'undefined' && respJson['workflow_runs'].length >= 1) { // url found - let ret: CICDData[] = respJson['workflow_runs'].map((elm: { [x: string]: any; }) => { - return { - id: elm['id'], - status: elm['conclusion'] === null ? elm['status'] : elm['conclusion'], - ref: elm['head_branch'], - sha: elm['head_sha'], - web_url: elm['html_url'], - created_at: elm['created_at'], - updated_at: elm['updated_at'], - name: elm['name'], - event: elm['event'], - detail: cicdRequest.detail - }; - }); - ret.forEach(element => { - let save = this.convCICDData2CICDDataSave(element); - this.saveCICD(cicdRequest.repo, element.sha, element.id, save); - }); - this.reFetchPageGitHub(cicdRequest, res, cicdConfig); - return; + if (res.statusCode === 200) { // Success + this.logger.log('GitHub API - (' + res.statusCode + ')' + hostProtocol + '://' + hostRootUrl + cicdRootPath); + try { + let respJson: any = JSON.parse(respBody); + if (typeof respJson['check_runs'] !== 'undefined' && respJson['check_runs'].length >= 1) { // url found + let ret: CICDData[] = respJson['check_runs'].map((elm: { [x: string]: any; }) => { + return { + id: elm['id'], + status: elm['conclusion'] === null ? elm['status'] : elm['conclusion'], + ref: '', + sha: elm['head_sha'], + web_url: elm['html_url'], + created_at: elm['created_at'], + updated_at: elm['updated_at'], + name: elm['app']!['name'] + '(' + elm['name'] + ')', + event: '', + detail: cicdRequest.detail + }; + }); + ret.forEach(element => { + let save = this.convCICDData2CICDDataSave(element); + this.saveCICD(cicdRequest.repo, element.sha, element.id, save); + }); + this.reFetchPageGitHub(cicdRequest, res, cicdConfig); + return; + } else if (typeof respJson['workflow_runs'] !== 'undefined' && respJson['workflow_runs'].length >= 1) { // url found + let ret: CICDData[] = respJson['workflow_runs'].map((elm: { [x: string]: any; }) => { + return { + id: elm['id'], + status: elm['conclusion'] === null ? elm['status'] : elm['conclusion'], + ref: elm['head_branch'], + sha: elm['head_sha'], + web_url: elm['html_url'], + created_at: elm['created_at'], + updated_at: elm['updated_at'], + name: elm['name'], + event: elm['event'], + detail: cicdRequest.detail + }; + }); + ret.forEach(element => { + let save = this.convCICDData2CICDDataSave(element); + this.saveCICD(cicdRequest.repo, element.sha, element.id, save); + }); + this.reFetchPageGitHub(cicdRequest, res, cicdConfig); + return; + } + } catch (e) { + this.logger.log('GitHub API Error - (' + res.statusCode + ')API Result error. : ' + e.message); + } + return; + } else if (res.statusCode === 403) { + // Rate limit reached, try again after timeout + this.queue.addItem(cicdRequest, this.gitHubTimeout, false); + return; + } else if (res.statusCode === 422 && cicdRequest.attempts < 4) { + // Commit not found on remote, try again with the next commit if less than 5 attempts have been made + this.queue.addItem(cicdRequest, 0, true); + return; + } else if (res.statusCode! >= 500) { + // If server error, try again after 10 minutes + this.gitHubTimeout = t + 600000; + this.queue.addItem(cicdRequest, this.gitHubTimeout, false); + return; + } else { + // API Error + try { + let respJson: any = JSON.parse(respBody); + if (typeof respJson.message === 'undefined') { + throw new Error('message is undefined!'); + } + this.logger.log('GitHub API Error - (' + res.statusCode + ')' + respJson.message); + } catch (e) { + this.logger.log('GitHub API Error - (' + res.statusCode + ')' + res.statusMessage); } - } catch (e) { - this.logger.log('GitHub API Error - (' + res.statusCode + ')API Result error. : ' + e.message); - } - return; - } else if (res.statusCode === 403) { - // Rate limit reached, try again after timeout - this.queue.addItem(cicdRequest, this.githubTimeout, false); - return; - } else if (res.statusCode === 422 && cicdRequest.attempts < 4) { - // Commit not found on remote, try again with the next commit if less than 5 attempts have been made - this.queue.addItem(cicdRequest, 0, true); - return; - } else if (res.statusCode! >= 500) { - // If server error, try again after 10 minutes - this.githubTimeout = t + 600000; - this.queue.addItem(cicdRequest, this.githubTimeout, false); - return; - } else { - // API Error - try { - let respJson: any = JSON.parse(respBody); - this.logger.log('GitHub API Error - (' + res.statusCode + ')' + respJson.message); - } catch (e) { - this.logger.log('GitHub API Error - (' + res.statusCode + ')' + res.statusMessage); } - } - }); - res.on('error', onError); - }).on('error', onError); + }); + res.on('error', onError); + }).on('error', onError); + } } /** @@ -337,7 +346,7 @@ export class CicdManager extends Disposable { this.logger.log('Added CICD for ' + cicdConfig.cicdUrl + ' last_page=' + last + '(RateLimit=' + (res.headers['x-ratelimit-limit'] || 'None') + '(1 hour)/Remaining=' + (res.headers['x-ratelimit-remaining'] || 'None') + (res.headers['x-ratelimit-reset'] ? '/' + new Date(parseInt(res.headers['x-ratelimit-reset']) * 1000).toString() : '') + ') from GitHub'); for (let i = 1; i < last; i++) { - this.queue.add(cicdRequest.repo, cicdRequest.cicdConfig, i + 1, true); + this.queue.add(cicdRequest.repo, cicdRequest.cicdConfig, i + 1, true, cicdRequest.detail, cicdRequest.hash); } } } @@ -360,16 +369,19 @@ export class CicdManager extends Disposable { let gitUrl = cicdConfig.cicdUrl.replace(/\/$/g, ''); gitUrl = gitUrl.replace(/.git$/g, ''); const match1 = gitUrl.match(/^(.+?):\/\/(.+?):?(\d+)?(\/.*)?$/); - let hostProtocol = match1 !== null ? '' + match1[1] : ''; - let hostRootUrl = match1 !== null ? '' + match1[2] : ''; - let hostPort = match1 !== null ? (match1[3] || '') : ''; - let hostpath = match1 !== null ? '' + match1[4].replace(/^\//, '').replace(/\//g, '%2F') : ''; + let hostProtocol = (match1 !== null && typeof match1[1] !== 'undefined') ? '' + match1[1].toLowerCase() : ''; + let hostRootUrl = (match1 !== null && typeof match1[2] !== 'undefined') ? '' + match1[2] : ''; + let hostPort = (match1 !== null && typeof match1[3] !== 'undefined') ? '' + match1[3] : ''; + let hostPath = (match1 !== null && typeof match1[4] !== 'undefined') ? '' + match1[4].replace(/^\//, '').replace(/\//g, '%2F') : ''; // Pipelines API https://docs.gitlab.com/ee/api/pipelines.html#list-project-pipelines - let cicdRootPath = `/api/v4/projects/${hostpath}/pipelines?per_page=${this.per_page}`; + let cicdRootPath = `/api/v4/projects/${hostPath}/pipelines?per_page=${this.per_page}`; if (cicdRequest.detail) { // Commits API https://docs.gitlab.com/ee/api/commits.html#list-the-statuses-of-a-commit - cicdRootPath = `/api/v4/projects/${hostpath}/repository/commits/${cicdRequest.hash}/statuses?per_page=${this.per_page}`; + cicdRootPath = `/api/v4/projects/${hostPath}/repository/commits/${cicdRequest.hash}/statuses?per_page=${this.per_page}`; + } + if (cicdRequest.page > 1) { + cicdRootPath = `${cicdRootPath}&page=${cicdRequest.page}`; } let headers: any = { @@ -381,7 +393,7 @@ export class CicdManager extends Disposable { let triggeredOnError = false; const onError = (err: Error) => { - this.logger.log('GitLab API ' + hostProtocol + ' Error - ' + err.message); + this.logger.log('GitLab API ' + hostProtocol + ' Error - ' + err?.message); if (!triggeredOnError) { // If an error occurs, try again after 5 minutes triggeredOnError = true; @@ -390,79 +402,96 @@ export class CicdManager extends Disposable { } }; - this.logger.log('Requesting CICD for ' + hostProtocol + '://' + hostRootUrl + cicdRootPath + ' page=' + cicdRequest.page + ' from GitLab'); - (hostProtocol === 'http' ? http : https).get({ - hostname: hostRootUrl, path: cicdRootPath, - port: hostPort, - headers: headers, - agent: false, timeout: 15000 - }, (res) => { - let respBody = ''; - res.on('data', (chunk: Buffer) => { respBody += chunk; }); - res.on('end', async () => { - if (res.headers['ratelimit-remaining'] === '0') { - // If the GitLab Api rate limit was reached, store the gitlab timeout to prevent subsequent requests - this.gitLabTimeout = parseInt(res.headers['ratelimit-reset']) * 1000; - this.logger.log('GitLab API Rate Limit Reached - Paused fetching from GitLab until the Rate Limit is reset (RateLimit=' + res.headers['ratelimit-limit'] + '(every minute)/' + new Date(this.gitLabTimeout).toString() + ')'); - } - - if (res.statusCode === 200) { // Success - try { - this.logger.log('GitLab API - (' + res.statusCode + ')' + hostProtocol + '://' + hostRootUrl + cicdRootPath); - if (typeof res.headers['x-page'] === 'string' && typeof res.headers['x-total-pages'] === 'string' && typeof res.headers['x-total'] === 'string') { - let respJson: any = JSON.parse(respBody); - if (parseInt(res.headers['x-total']) !== 0 && respJson.length && respJson[0].id) { // url found - let ret: CICDData[] = respJson; - ret.forEach(element => { - let save: CICDDataSave; - if (cicdRequest.detail) { - save = this.convGitLabComitStatuses2CICDDataSave(element, cicdRequest.detail, - `${hostProtocol}://${hostRootUrl}${hostPort === '' ? '' : ':' + hostPort}/${hostpath.replace(/%2F/g, '/')}/-/jobs/`); - } else { - save = this.convCICDData2CICDDataSave(element); - } - this.saveCICD(cicdRequest.repo, element.sha, element.id, save); - }); - } + if (!(hostProtocol === 'http' || hostProtocol === 'https') || hostRootUrl === '') { + this.logger.log('Requesting CICD is not match URL (' + cicdConfig.cicdUrl + ') for GitLab'); + } else { + this.logger.log('Requesting CICD for ' + hostProtocol + '://' + hostRootUrl + cicdRootPath + ' detail=' + cicdRequest.detail + ' page=' + cicdRequest.page + ' from GitLab'); + (hostProtocol === 'http' ? http : https).get({ + hostname: hostRootUrl, path: cicdRootPath, + port: hostPort, + headers: headers, + agent: false, timeout: 15000 + }, (res) => { + let respBody = ''; + res.on('data', (chunk: Buffer) => { respBody += chunk; }); + res.on('end', async () => { + if (res.headers['ratelimit-remaining'] === '0') { + // If the GitLab Api rate limit was reached, store the gitlab timeout to prevent subsequent requests + this.gitLabTimeout = parseInt(res.headers['ratelimit-reset']) * 1000; + this.logger.log('GitLab API Rate Limit Reached - Paused fetching from GitLab until the Rate Limit is reset (RateLimit=' + res.headers['ratelimit-limit'] + '(every minute)/' + new Date(this.gitLabTimeout).toString() + ')'); + } - if (cicdRequest.page === -1) { - let last = parseInt(res.headers['x-total-pages']); - if (last > Math.ceil(cicdRequest.maximumStatuses / this.per_page)) { - last = Math.ceil(cicdRequest.maximumStatuses / this.per_page); - this.logger.log('CICD Maximum Statuses(maximumStatuses=' + cicdRequest.maximumStatuses + ') reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); + if (res.statusCode === 200) { // Success + try { + this.logger.log('GitLab API - (' + res.statusCode + ')' + hostProtocol + '://' + hostRootUrl + cicdRootPath); + if (typeof res.headers['x-page'] === 'string' && typeof res.headers['x-total-pages'] === 'string' && typeof res.headers['x-total'] === 'string') { + let respJson: any = JSON.parse(respBody); + if (parseInt(res.headers['x-total']) !== 0 && respJson.length && respJson[0].id) { // url found + let ret: CICDData[] = respJson; + ret.forEach(element => { + let save: CICDDataSave; + if (cicdRequest.detail) { + save = this.convGitLabComitStatuses2CICDDataSave(element, cicdRequest.detail, + `${hostProtocol}://${hostRootUrl}${hostPort === '' ? '' : ':' + hostPort}/${hostPath.replace(/%2F/g, '/')}/-/jobs/`); + } else { + save = this.convCICDData2CICDDataSave( + Object.assign({}, + { + name: '', + ref: '', + web_url: '', + event: '', + detail: cicdRequest.detail + }, + element + )); + } + this.saveCICD(cicdRequest.repo, element.sha, element.id, save); + }); } - this.logger.log('Added CICD for ' + cicdConfig.cicdUrl + ' last_page=' + last + '(RateLimit=' + (res.headers['ratelimit-limit'] || 'None') + '(every minute)/Remaining=' + (res.headers['ratelimit-remaining'] || 'None') + (res.headers['ratelimit-reset'] ? '/' + new Date(parseInt(res.headers['ratelimit-reset']) * 1000).toString() : '') + ') from GitLab'); - for (let i = 1; i < last; i++) { - this.queue.add(cicdRequest.repo, cicdRequest.cicdConfig, i + 1, true); + if (cicdRequest.page === -1) { + let last = parseInt(res.headers['x-total-pages']); + if (last > Math.ceil(cicdRequest.maximumStatuses / this.per_page)) { + last = Math.ceil(cicdRequest.maximumStatuses / this.per_page); + this.logger.log('CICD Maximum Statuses(maximumStatuses=' + cicdRequest.maximumStatuses + ') reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); + } + + this.logger.log('Added CICD for ' + cicdConfig.cicdUrl + ' last_page=' + last + '(RateLimit=' + (res.headers['ratelimit-limit'] || 'None') + '(every minute)/Remaining=' + (res.headers['ratelimit-remaining'] || 'None') + (res.headers['ratelimit-reset'] ? '/' + new Date(parseInt(res.headers['ratelimit-reset']) * 1000).toString() : '') + ') from GitLab'); + for (let i = 1; i < last; i++) { + this.queue.add(cicdRequest.repo, cicdRequest.cicdConfig, i + 1, true, cicdRequest.detail, cicdRequest.hash); + } } + return; } - return; + } catch (e) { + this.logger.log('GitLab API Error - (' + res.statusCode + ')API Result error. : ' + e.message); + } + } else if (res.statusCode === 429) { + // Rate limit reached, try again after timeout + this.queue.addItem(cicdRequest, this.gitLabTimeout, false); + return; + } else if (res.statusCode! >= 500) { + // If server error, try again after 10 minutes + this.gitLabTimeout = t + 600000; + this.queue.addItem(cicdRequest, this.gitLabTimeout, false); + return; + } else { + // API Error + try { + let respJson: any = JSON.parse(respBody); + if (typeof respJson.message === 'undefined') { + throw new Error('message is undefined!'); + } + this.logger.log('GitLab API Error - (' + res.statusCode + ')' + respJson.message); + } catch (e) { + this.logger.log('GitLab API Error - (' + res.statusCode + ')' + res.statusMessage); } - } catch (e) { - this.logger.log('GitLab API Error - (' + res.statusCode + ')API Result error. : ' + e.message); - } - } else if (res.statusCode === 429) { - // Rate limit reached, try again after timeout - this.queue.addItem(cicdRequest, this.gitLabTimeout, false); - return; - } else if (res.statusCode! >= 500) { - // If server error, try again after 10 minutes - this.gitLabTimeout = t + 600000; - this.queue.addItem(cicdRequest, this.gitLabTimeout, false); - return; - } else { - // API Error - try { - let respJson: any = JSON.parse(respBody); - this.logger.log('GitLab API Error - (' + res.statusCode + ')' + respJson.message); - } catch (e) { - this.logger.log('GitLab API Error - (' + res.statusCode + ')' + res.statusMessage); } - } - }); - res.on('error', onError); - }).on('error', onError); + }); + res.on('error', onError); + }).on('error', onError); + } } /** @@ -470,11 +499,11 @@ export class CicdManager extends Disposable { * @param cicdRequest The cicd request to fetch. */ private fetchFromJenkins(cicdRequest: CICDRequestItem) { - if (cicdRequest.detail) { - return; - } + // if (cicdRequest.detail) { + // return; + // } let t = (new Date()).getTime(); - if (cicdRequest.checkAfter !== 0 && t < this.jenkinsTimeout) { + if (t < this.jenkinsTimeout) { // Defer request until after timeout this.queue.addItem(cicdRequest, this.jenkinsTimeout, false); this.fetchCICDsInterval(); @@ -486,12 +515,13 @@ export class CicdManager extends Disposable { let gitUrl = cicdConfig.cicdUrl.replace(/\/$/g, ''); gitUrl = gitUrl.replace(/.git$/g, ''); const match1 = gitUrl.match(/^(.+?):\/\/(.+?):?(\d+)?(\/.*)?$/); - let hostProtocol = match1 !== null ? '' + match1[1] : ''; - let hostRootUrl = match1 !== null ? '' + match1[2] : ''; - let hostPort = match1 !== null ? (match1[3] || '') : ''; - let hostpath = match1 !== null ? '' + match1[4].replace(/^\//, '') : ''; + let hostProtocol = (match1 !== null && typeof match1[1] !== 'undefined') ? '' + match1[1].toLowerCase() : ''; + let hostRootUrl = (match1 !== null && typeof match1[2] !== 'undefined') ? '' + match1[2] : ''; + let hostPort = (match1 !== null && typeof match1[3] !== 'undefined') ? '' + match1[3] : ''; + let hostPath = (match1 !== null && typeof match1[4] !== 'undefined') ? '' + match1[4].replace(/^\//, '') : ''; - let cicdRootPath = `/${hostpath}/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]]`; + // https://www.jenkins.io/doc/book/using/remote-access-api/ + let cicdRootPath = `/${hostPath}/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]]`; let headers: any = { 'User-Agent': 'vscode-git-graph' @@ -503,7 +533,7 @@ export class CicdManager extends Disposable { let triggeredOnError = false; const onError = (err: Error) => { - this.logger.log('Jenkins API HTTPS Error - ' + err.message); + this.logger.log('Jenkins API ' + hostProtocol + ' Error - ' + err?.message); if (!triggeredOnError) { // If an error occurs, try again after 5 minutes triggeredOnError = true; @@ -511,81 +541,88 @@ export class CicdManager extends Disposable { this.queue.addItem(cicdRequest, this.jenkinsTimeout, false); } }; + if (!(hostProtocol === 'http' || hostProtocol === 'https') || hostRootUrl === '') { + this.logger.log('Requesting CICD is not match URL (' + cicdConfig.cicdUrl + ') for Jenkins'); + } else { + this.logger.log('Requesting CICD for ' + hostProtocol + '://' + hostRootUrl + cicdRootPath + ' page=' + cicdRequest.page + ' from Jenkins'); + (hostProtocol === 'http' ? http : https).get({ + hostname: hostRootUrl, path: cicdRootPath, + port: hostPort, + headers: headers, + agent: false, timeout: 15000 + }, (res) => { + let respBody = ''; + res.on('data', (chunk: Buffer) => { respBody += chunk; }); + res.on('end', async () => { + if (res.headers['ratelimit-remaining'] === '0') { + // If the Jenkins Api rate limit was reached, store the Jenkins timeout to prevent subsequent requests + this.jenkinsTimeout = parseInt(res.headers['ratelimit-reset']) * 1000; + this.logger.log('Jenkins API Rate Limit Reached - Paused fetching from Jenkins until the Rate Limit is reset (RateLimit=' + res.headers['ratelimit-limit'] + '(every minute)/' + new Date(this.jenkinsTimeout).toString() + ')'); + } - this.logger.log('Requesting CICD for ' + hostProtocol + '://' + hostRootUrl + cicdRootPath + ' page=' + cicdRequest.page + ' from Jenkins'); - (hostProtocol === 'http' ? http : https).get({ - hostname: hostRootUrl, path: cicdRootPath, - port: hostPort, - headers: headers, - agent: false, timeout: 15000 - }, (res) => { - let respBody = ''; - res.on('data', (chunk: Buffer) => { respBody += chunk; }); - res.on('end', async () => { - if (res.headers['ratelimit-remaining'] === '0') { - // If the GitLab Api rate limit was reached, store the gitlab timeout to prevent subsequent requests - this.jenkinsTimeout = parseInt(res.headers['ratelimit-reset']) * 1000; - this.logger.log('GitLab API Rate Limit Reached - Paused fetching from GitLab until the Rate Limit is reset (RateLimit=' + res.headers['ratelimit-limit'] + '(every minute)/' + new Date(this.jenkinsTimeout).toString() + ')'); - } - - if (res.statusCode === 200) { // Success - try { - if (typeof res.headers['x-jenkins'] === 'string' && res.headers['x-jenkins'].startsWith('2.')) { - let respJson: any = JSON.parse(respBody); - if (respJson['builds'].length) { // url found - let ret: CICDData[] = []; - respJson['builds'].forEach((elmBuild: { [x: string]: any; }) => { - elmBuild['actions'].forEach((elmAction: { [x: string]: any; }) => { - if (typeof elmAction['lastBuiltRevision'] !== 'undefined' && typeof elmAction['lastBuiltRevision']['branch'] !== 'undefined' - && typeof elmAction['lastBuiltRevision']['branch'][0] !== 'undefined' && typeof elmAction['lastBuiltRevision']['branch'][0].SHA1 !== 'undefined') { - ret.push( - { - id: elmBuild['id'], - status: elmBuild['result'], - ref: elmAction['lastBuiltRevision']!['branch']![0]!.name, - sha: elmAction['lastBuiltRevision']!['branch']![0]!.SHA1, - web_url: elmBuild['url'], - created_at: '', - updated_at: elmBuild['timestamp'], - name: elmBuild['fullDisplayName'], - event: '', - detail: cicdRequest.detail - } - ); - } + if (res.statusCode === 200) { // Success + this.logger.log('Jenkins API - (' + res.statusCode + ')' + hostProtocol + '://' + hostRootUrl + cicdRootPath); + try { + if (typeof res.headers['x-jenkins'] === 'string' && res.headers['x-jenkins'].startsWith('2.')) { + let respJson: any = JSON.parse(respBody); + if (respJson['builds'].length) { // url found + let ret: CICDData[] = []; + respJson['builds'].forEach((elmBuild: { [x: string]: any; }) => { + elmBuild['actions'].forEach((elmAction: { [x: string]: any; }) => { + if (typeof elmAction['lastBuiltRevision'] !== 'undefined' && typeof elmAction['lastBuiltRevision']['branch'] !== 'undefined' + && typeof elmAction['lastBuiltRevision']['branch'][0] !== 'undefined' && typeof elmAction['lastBuiltRevision']['branch'][0].SHA1 !== 'undefined') { + ret.push( + { + id: elmBuild['id'], + status: elmBuild['result'], + ref: elmAction['lastBuiltRevision']!['branch']![0]!.name, + sha: elmAction['lastBuiltRevision']!['branch']![0]!.SHA1, + web_url: elmBuild['url'] || '', + created_at: '', + updated_at: elmBuild['timestamp'], + name: elmBuild['fullDisplayName'], + event: '', + detail: cicdRequest.detail + } + ); + } + }); }); - }); - ret.forEach(element => { - let save = this.convCICDData2CICDDataSave(element); - this.saveCICD(cicdRequest.repo, element.sha, element.id, save); - }); - return; + ret.forEach(element => { + let save = this.convCICDData2CICDDataSave(element); + this.saveCICD(cicdRequest.repo, element.sha, element.id, save); + }); + return; + } } + } catch (e) { + this.logger.log('Jenkins API Error - (' + res.statusCode + ')API Result error. : ' + e.message); + } + } else if (res.statusCode === 429) { + // Rate limit reached, try again after timeout + this.queue.addItem(cicdRequest, this.jenkinsTimeout, false); + return; + } else if (res.statusCode! >= 500) { + // If server error, try again after 10 minutes + this.jenkinsTimeout = t + 600000; + this.queue.addItem(cicdRequest, this.jenkinsTimeout, false); + return; + } else { + // API Error + try { + let respJson: any = JSON.parse(respBody); + if (typeof respJson.message === 'undefined') { + throw new Error('message is undefined!'); + } + this.logger.log('Jenkins API Error - (' + res.statusCode + ')' + respJson.message); + } catch (e) { + this.logger.log('Jenkins API Error - (' + res.statusCode + ')' + res.statusMessage); } - } catch (e) { - this.logger.log('Jenkins API Error - (' + res.statusCode + ')API Result error. : ' + e.message); - } - } else if (res.statusCode === 429) { - // Rate limit reached, try again after timeout - this.queue.addItem(cicdRequest, this.jenkinsTimeout, false); - return; - } else if (res.statusCode! >= 500) { - // If server error, try again after 10 minutes - this.jenkinsTimeout = t + 600000; - this.queue.addItem(cicdRequest, this.jenkinsTimeout, false); - return; - } else { - // API Error - try { - let respJson: any = JSON.parse(respBody); - this.logger.log('GitLab API Error - (' + res.statusCode + ')' + respJson.message); - } catch (e) { - this.logger.log('GitLab API Error - (' + res.statusCode + ')' + res.statusMessage); } - } - }); - res.on('error', onError); - }).on('error', onError); + }); + res.on('error', onError); + }).on('error', onError); + } } /** @@ -694,10 +731,9 @@ class CicdRequestQueue { * @param detail Flag of fetch detail. * @param hash hash for fetch detail. */ - public add(repo: string, cicdConfig: CICDConfig, page: number, immediate: boolean, detail: boolean = false, hash: string = '') { + public add(repo: string, cicdConfig: CICDConfig, page: number, immediate: boolean, detail: boolean, hash: string) { const existingRequest = this.queue.find((request) => request.cicdConfig.cicdUrl === cicdConfig.cicdUrl && request.page === page && request.detail === detail && request.hash === hash); - if (existingRequest) { - } else { + if (!existingRequest) { const config = getConfig(); this.insertItem({ repo: repo, diff --git a/src/extensionState.ts b/src/extensionState.ts index c0f31e85..993385ad 100644 --- a/src/extensionState.ts +++ b/src/extensionState.ts @@ -126,11 +126,13 @@ export class ExtensionState extends Disposable { } } // Decrypt cicdToken - if (typeof repoSet[repo].cicdConfigs !== 'undefined' && repoSet[repo].cicdConfigs !== null) { + const cicdConfigs = repoSet[repo].cicdConfigs; + if (typeof repoSet[repo].cicdConfigs !== 'undefined' && cicdConfigs !== null) { outputSet[repo].cicdConfigs = []; + const cicdConfigsOut: CICDConfig[] = []; if (typeof repoSet[repo].cicdNonce !== 'undefined' && repoSet[repo].cicdNonce !== null) { let ENCRYPTION_KEY = repoSet[repo].cicdNonce; // Must be 256 bits (32 characters) - repoSet[repo].cicdConfigs?.forEach(element => { + cicdConfigs.forEach(element => { if (typeof element.cicdUrl !== 'undefined' && typeof element.cicdToken !== 'undefined') { let textParts: string[] = element.cicdToken.split(':'); let iv = Buffer.from(textParts[0], 'hex'); @@ -145,10 +147,11 @@ export class ExtensionState extends Disposable { cicdUrl: element.cicdUrl, cicdToken: decrypted.toString() }; - outputSet[repo].cicdConfigs?.push(config); + cicdConfigsOut.push(config); } }); } + outputSet[repo].cicdConfigs = cicdConfigsOut; } }); return outputSet; From 378ab9f075881b69e029c32ab2734f9b275e4623 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sun, 23 May 2021 02:33:20 +0900 Subject: [PATCH 51/54] #462 Added test case for CICD --- tests/cicdManager.test.ts | 3790 ++++++++++++++++++++++++++++++++++ tests/commands.test.ts | 20 + tests/extensionState.test.ts | 433 +++- tests/gitGraphView.test.ts | 33 +- 4 files changed, 4274 insertions(+), 2 deletions(-) create mode 100644 tests/cicdManager.test.ts diff --git a/tests/cicdManager.test.ts b/tests/cicdManager.test.ts new file mode 100644 index 00000000..66de6519 --- /dev/null +++ b/tests/cicdManager.test.ts @@ -0,0 +1,3790 @@ +import * as date from './mocks/date'; +import * as vscode from './mocks/vscode'; +jest.mock('vscode', () => vscode, { virtual: true }); +jest.mock('fs'); +jest.mock('https'); +jest.mock('http'); +jest.mock('../src/dataSource'); +jest.mock('../src/extensionState'); +jest.mock('../src/logger'); +jest.mock('../src/repoManager'); + +import { ClientRequest, IncomingMessage } from 'http'; +import * as https from 'https'; +import * as http from 'http'; +import { URL } from 'url'; +import { ConfigurationChangeEvent } from 'vscode'; +import { CICDEvent, CicdManager } from '../src/cicdManager'; +import { DataSource } from '../src/dataSource'; +import { DEFAULT_REPO_STATE, ExtensionState } from '../src/extensionState'; +import { Logger } from '../src/logger'; +import { GitExecutable } from '../src/utils'; +import { EventEmitter } from '../src/utils/event'; +import { RepoManager } from '../src/repoManager'; +import { CICDConfig, CICDProvider } from '../src/types'; +import { waitForExpect } from './helpers/expectations'; + +let onDidChangeConfiguration: EventEmitter; +let onDidChangeGitExecutable: EventEmitter; +let logger: Logger; +let dataSource: DataSource; +let extensionState: ExtensionState; +let repoManager: RepoManager; +let spyOnSaveCicd: jest.SpyInstance, spyOnHttpsGet: jest.SpyInstance, spyOnHttpGet: jest.SpyInstance, spyOnLog: jest.SpyInstance; +let spyOnGetRepos: jest.SpyInstance; +// , spyOnGetKnownRepo: jest.SpyInstance, spyOnRegisterRepo: jest.SpyInstance, spyOnGetCommitSubject: jest.SpyInstance; +// let spyOnGetCodeReviews: jest.SpyInstance; +// , spyOnEndCodeReview: jest.SpyInstance; +const GitHubResponse = JSON.stringify({ + total_count: 1, + check_runs: [{ + id: 2211653232, + head_sha: '149ecc50e5c223251f80a0223cfbbd9822307224', + html_url: 'https://github.com/mhutchie/vscode-git-graph/runs/2211653232', + status: 'completed', + conclusion: 'success', + name: 'build', + app: { name: 'GitHub Actions' } + }] +}); + +const GitLabResponse = JSON.stringify([ + { + id: 2211653232, + sha: '149ecc50e5c223251f80a0223cfbbd9822307224', + ref: 'main', + status: 'success', + name: 'eslint-sast', + target_url: 'https://gitlab.com/mhutchie/vscode-git-graph/runs/2211653232', + allow_failure: false + } +]); + +const JenkinsResponse = JSON.stringify( + { + _class: 'org.jenkinsci.plugins.workflow.job.WorkflowJob', + builds: [ + { + _class: 'org.jenkinsci.plugins.workflow.job.WorkflowRun', + actions: [ + { + _class: 'hudson.plugins.git.util.BuildData', + lastBuiltRevision: { + branch: [ + { + SHA1: '149ecc50e5c223251f80a0223cfbbd9822307224', + name: 'master' + } + ] + } + } + ], + fullDisplayName: 'job01 » MultiBranch » master #3', + id: '3', + result: 'SUCCESS', + timestamp: 1620716982997, + url: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/' + } + ] + } +); + +const GitHubHeader = { + 'content-type': 'application/json; charset=utf-8', + 'link': '; rel="next", ; rel="last"', + 'x-github-media-type': 'unknown, github.v3', + 'x-ratelimit-limit': '60', + 'x-ratelimit-remaining': '57', + 'x-ratelimit-reset': '1618343683' +}; + +const GitLabHeader = { + 'content-type': 'application/json', + 'x-page': '1', + 'x-total': '32500', + 'x-total-pages': '325', + 'ratelimit-limit': '60', + 'ratelimit-observed': '3', + 'ratelimit-remaining': '57', + 'ratelimit-reset': '1618343683' +}; + +const JenkinsHeader = { + 'content-type': 'application/json', + 'x-jenkins': '2.235.1' +}; + +const GitHubCicdEvents = { + 'cicdDataSaves': { + '2211653232': { + name: 'GitHub Actions(build)', + status: 'success', + ref: '', + web_url: 'https://github.com/mhutchie/vscode-git-graph/runs/2211653232', + event: '', + detail: true, + allow_failure: false + } + }, + 'hash': '149ecc50e5c223251f80a0223cfbbd9822307224', + 'repo': '/path/to/repo1' +}; + +const GitLabCicdEvents = { + 'cicdDataSaves': { + '2211653232': { + name: 'eslint-sast', + status: 'success', + ref: 'main', + web_url: 'https://gitlab.com/mhutchie/vscode-git-graph/runs/2211653232', + event: '', + detail: true, + allow_failure: false + } + }, + 'hash': '149ecc50e5c223251f80a0223cfbbd9822307224', + 'repo': '/path/to/repo1' +}; + + +const JenkinsCicdEvents = { + 'cicdDataSaves': { + '3': { + name: 'job01 » MultiBranch » master #3', + status: 'SUCCESS', + ref: 'master', + web_url: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + event: '', + detail: true, + allow_failure: false + } + }, + 'hash': '149ecc50e5c223251f80a0223cfbbd9822307224', + 'repo': '/path/to/repo1' +}; + +const GitHubHttpsGet = { + hostname: 'api.github.com', + path: '/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100', + headers: { + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'vscode-git-graph' + }, + port: '', + agent: false, + timeout: 15000 +}; + +const GitLabHttpsGet = { + hostname: 'gitlab.com', + path: '/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100', + headers: { + 'User-Agent': 'vscode-git-graph' + }, + port: '', + agent: false, + timeout: 15000 +}; + +const JenkinsHttpsGet = { + hostname: 'jenkins.net', + path: '/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]]', + headers: { + 'User-Agent': 'vscode-git-graph' + }, + port: '', + agent: false, + timeout: 15000 +}; + +const GitHubGetRspos = { + '/path/to/repo1': mockRepoState(null, 0, + 'ElZJNHSyT6JDjOGDaVaPiZenu3Xu2MZf', [{ + provider: CICDProvider.GitHubV3, + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + cicdToken: '' + }]), + '/path/to/repo2': mockRepoState('Custom Name', 0, null, null) +}; + +const GitLabGetRspos = { + '/path/to/repo1': mockRepoState(null, 0, + 'ElZJNHSyT6JDjOGDaVaPiZenu3Xu2MZf', [{ + provider: CICDProvider.GitLabV4, + cicdUrl: 'https://gitlab.com/keydepth/vscode-git-graph.git', + cicdToken: '' + }]), + '/path/to/repo2': mockRepoState('Custom Name', 0, null, null) +}; + +const JenkinsGetRspos = { + '/path/to/repo1': mockRepoState(null, 0, + 'ElZJNHSyT6JDjOGDaVaPiZenu3Xu2MZf', [{ + provider: CICDProvider.JenkinsV2, + cicdUrl: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + cicdToken: '' + }]), + '/path/to/repo2': mockRepoState('Custom Name', 0, null, null) +}; + +const GitHubSaveCicd = [ + '/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224', 2211653232, + { + name: 'GitHub Actions(build)', + status: 'success', + ref: '', + web_url: 'https://github.com/mhutchie/vscode-git-graph/runs/2211653232', + event: '', + detail: true, + allow_failure: false + } +]; + +const GitLabSaveCicd = [ + '/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224', 2211653232, + { + name: 'eslint-sast', + status: 'success', + ref: 'main', + web_url: 'https://gitlab.com/mhutchie/vscode-git-graph/runs/2211653232', + event: '', + detail: true, + allow_failure: false + } +]; + +const JenkinsSaveCicd = [ + '/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224', '3', + { + name: 'job01 » MultiBranch » master #3', + status: 'SUCCESS', + ref: 'master', + web_url: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + event: '', + detail: true, + allow_failure: false + } +]; + +beforeAll(() => { + onDidChangeConfiguration = new EventEmitter(); + onDidChangeGitExecutable = new EventEmitter(); + logger = new Logger(); + dataSource = new DataSource(null, onDidChangeConfiguration.subscribe, onDidChangeGitExecutable.subscribe, logger); + extensionState = new ExtensionState(vscode.mocks.extensionContext, onDidChangeGitExecutable.subscribe); + repoManager = new RepoManager(dataSource, extensionState, onDidChangeConfiguration.subscribe, logger); + spyOnGetRepos = jest.spyOn(repoManager, 'getRepos'); + // spyOnGetKnownRepo = jest.spyOn(repoManager, 'getKnownRepo'); + // spyOnRegisterRepo = jest.spyOn(repoManager, 'registerRepo'); + spyOnSaveCicd = jest.spyOn(extensionState, 'saveCICD'); + // spyOnGetCodeReviews = jest.spyOn(extensionState, 'getCodeReviews'); + // spyOnEndCodeReview = jest.spyOn(extensionState, 'endCodeReview'); + // spyOnGetCommitSubject = jest.spyOn(dataSource, 'getCommitSubject'); + spyOnHttpsGet = jest.spyOn(https, 'get'); + spyOnHttpGet = jest.spyOn(http, 'get'); + spyOnLog = jest.spyOn(logger, 'log'); +}); + +afterAll(() => { + extensionState.dispose(); + repoManager.dispose(); + dataSource.dispose(); + logger.dispose(); + onDidChangeConfiguration.dispose(); + onDidChangeGitExecutable.dispose(); +}); + + +describe('CicdManager', () => { + let cicdManager: CicdManager; + // let spyOnGetLastActiveRepo: jest.SpyInstance; + // beforeAll(() => { + // spyOnGetLastActiveRepo = jest.spyOn(extensionState, 'getLastActiveRepo'); + // }); + beforeEach(() => { + jest.spyOn(extensionState, 'getCICDCache').mockReturnValueOnce({ + '/path/to/repo': { + 'hash0': { + 'id0': { + name: 'string', + status: 'string', + ref: 'string', + web_url: 'string', + event: 'string', + detail: true, + allow_failure: false + } + } + } + }); + cicdManager = new CicdManager(extensionState, repoManager, logger); + jest.clearAllTimers(); + jest.useRealTimers(); + }); + afterEach(() => { + cicdManager.dispose(); + }); + + it('Should construct an CicdManager, and be disposed', () => { + // Assert + expect(cicdManager['disposables']).toHaveLength(2); + + // Run + cicdManager.dispose(); + + // Assert + expect(cicdManager['disposables']).toHaveLength(0); + }); + + describe('getCICDDetail', () => { + it('Should trigger the cicd to not be emitted when a known cicd is requested', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo': mockRepoState(null, 0, null, null) + }); + + // Run + let data = await cicdManager.getCICDDetail('/path/to/repo', 'hash0'); + + // Assert + expect.assertions(1); + if (data) { + expect(JSON.parse(data)).toStrictEqual({ + 'id0': { + name: 'string', + status: 'string', + ref: 'string', + web_url: 'string', + event: 'string', + detail: true, + allow_failure: false + }}); + } + }); + + it('Should trigger the cicd to not be emitted when a unknown cicd is requested multi repo', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo1': mockRepoState(null, 0, null, null), + '/path/to/repo2': mockRepoState(null, 0, null, null) + }); + + // Run + let data = await cicdManager.getCICDDetail('/path/to/repo2', 'hash0'); + + // Assert + expect(data).toStrictEqual(null); + }); + + it('Should trigger the cicd to not be emitted when a unknown cicd is requested', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo1': mockRepoState(null, 0, null, null) + }); + + // Run + let data = await cicdManager.getCICDDetail('/path/to/repoX', 'hash0'); + + // Assert + expect(data).toStrictEqual(null); + }); + + + describe('Unknown provider', () => { + it('Should Unknown provider Error', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo1': mockRepoState(null, 0, + 'ElZJNHSyT6JDjOGDaVaPiZenu3Xu2MZf', [{ + provider: -1, + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + cicdToken: '' + }]) + }); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnLog).toHaveBeenCalledWith( 'Unknown provider Error'); + expect(spyOnLog).toHaveBeenCalledTimes(1); + }); + }); + + + describe('GitHub', () => { + it('Should fetch a new cicd from GitHub (HTTPS Remote)', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitHubGetRspos); + mockHttpsResponse(200, GitHubResponse, GitHubHeader); + const cicdEvents = waitForEvents(cicdManager, 1); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(await cicdEvents).toStrictEqual([GitHubCicdEvents]); + expect(spyOnHttpsGet).toHaveBeenCalledWith(GitHubHttpsGet, expect.anything()); + expect(spyOnSaveCicd).toHaveBeenCalledWith(...GitHubSaveCicd); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(1 hour)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitHub'); + expect(spyOnLog).toHaveBeenCalledTimes(4); + }); + + it('Should fetch a new cicd from GitHub (HTTPS Remote) with port', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo1': mockRepoState(null, 0, + 'ElZJNHSyT6JDjOGDaVaPiZenu3Xu2MZf', [{ + provider: CICDProvider.GitHubV3, + cicdUrl: 'https://github.com:80/keydepth/vscode-git-graph.git', + cicdToken: '' + }]), + '/path/to/repo2': mockRepoState('Custom Name', 0, null, null) + }); + mockHttpsResponse(200, GitHubResponse, GitHubHeader); + const cicdEvents = waitForEvents(cicdManager, 1); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(await cicdEvents).toStrictEqual([GitHubCicdEvents]); + expect(spyOnHttpsGet).toHaveBeenCalledWith({ + hostname: 'api.github.com', + path: '/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100', + headers: { + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'vscode-git-graph' + }, + port: '80', + agent: false, + timeout: 15000 + }, expect.anything()); + expect(spyOnSaveCicd).toHaveBeenCalledWith(...GitHubSaveCicd); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://github.com:80/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(1 hour)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitHub'); + expect(spyOnLog).toHaveBeenCalledTimes(4); + }); + + it('Should fetch a new cicd from GitHub (HTTP Remote)', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo1': mockRepoState(null, 0, + 'ElZJNHSyT6JDjOGDaVaPiZenu3Xu2MZf', [{ + provider: CICDProvider.GitHubV3, + cicdUrl: 'http://github.com/keydepth/vscode-git-graph.git', + cicdToken: '' + }]), + '/path/to/repo2': mockRepoState('Custom Name', 0, null, null) + }); + mockHttpResponse(200, JSON.stringify({ + total_count: 1, + check_runs: [{ + id: 2211653232, + head_sha: '149ecc50e5c223251f80a0223cfbbd9822307224', + html_url: 'http://github.com/mhutchie/vscode-git-graph/runs/2211653232', + status: 'completed', + conclusion: 'success', + name: 'build', + app: { name: 'GitHub Actions' } + }] + }), GitHubHeader); + const cicdEvents = waitForEvents(cicdManager, 1); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(await cicdEvents).toStrictEqual([{ + 'cicdDataSaves': { + '2211653232': { + name: 'GitHub Actions(build)', + status: 'success', + ref: '', + web_url: 'http://github.com/mhutchie/vscode-git-graph/runs/2211653232', + event: '', + detail: true, + allow_failure: false + } + }, + 'hash': '149ecc50e5c223251f80a0223cfbbd9822307224', + 'repo': '/path/to/repo1' + }]); + expect(spyOnHttpGet).toHaveBeenCalledWith(GitHubHttpsGet, expect.anything()); + expect(spyOnSaveCicd).toHaveBeenCalledWith( + '/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224', 2211653232, + { + name: 'GitHub Actions(build)', + status: 'success', + ref: '', + web_url: 'http://github.com/mhutchie/vscode-git-graph/runs/2211653232', + event: '', + detail: true, + allow_failure: false + }); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for http://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API - (200)http://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for http://github.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(1 hour)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitHub'); + expect(spyOnLog).toHaveBeenCalledTimes(4); + }); + + it('Should fetch a new cicd from GitHub (SSH Remote)', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo1': mockRepoState(null, 0, + 'ElZJNHSyT6JDjOGDaVaPiZenu3Xu2MZf', [{ + provider: CICDProvider.GitHubV3, + cicdUrl: 'git@github.com:keydepth/vscode-git-graph.git', + cicdToken: '' + }]), + '/path/to/repo2': mockRepoState('Custom Name', 0, null, null) + }); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD is not match URL (git@github.com:keydepth/vscode-git-graph.git) for GitHub'); + expect(spyOnLog).toHaveBeenCalledTimes(1); + }); + + it('Should fetch a new cicd from GitHub (HTTPS Remote) not detail', async () => { + // Setup + jest.useFakeTimers(); + spyOnGetRepos.mockReturnValueOnce(GitHubGetRspos); + mockHttpsResponse(200, JSON.stringify({ + total_count: 324, + workflow_runs: [ + { + id: 740791415, + name: 'Update Milestone on Release', + head_branch: 'v1.31.0-beta.0', + head_sha: 'b9112e60f5fb3d8bc2a387840577b4756a12f357', + event: 'release', + status: 'completed', + conclusion: 'success', + url: 'https://api.github.com/repos/mhutchie/vscode-git-graph/actions/runs/740791415', + html_url: 'https://github.com/mhutchie/vscode-git-graph/actions/runs/740791415', + pull_requests: [], + created_at: '2021-04-12T10:24:48Z', + updated_at: '2021-04-12T10:25:06Z' + } + ] + }), Object.assign({}, GitHubHeader, { + 'link': '; rel="next", ; rel="last"' + })); + mockHttpsResponse(200, GitHubResponse, Object.assign({}, GitHubHeader, { + 'link': '; rel="next", ; rel="last"' + })); + const cicdEvents = waitForEvents(cicdManager, 2, true); + cicdManager['queue']['queue'] = [{ + repo: '/path/to/repo1', + cicdConfig: { + provider: CICDProvider.GitHubV3, + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + cicdToken: '' + }, + page: -1, + checkAfter: 0, + attempts: 0, + detail: false, + hash: 'b9112e60f5fb3d8bc2a387840577b4756a12f357', + maximumStatuses: 1000 + }]; + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', 'b9112e60f5fb3d8bc2a387840577b4756a12f357'); + cicdManager['queue']['itemsAvailableCallback'](); + + // Assert + expect(await cicdEvents).toStrictEqual([{ + 'cicdDataSaves': { + '740791415': { + name: 'Update Milestone on Release', + status: 'success', + ref: 'v1.31.0-beta.0', + web_url: 'https://github.com/mhutchie/vscode-git-graph/actions/runs/740791415', + event: 'release', + detail: false, + allow_failure: false + } + }, + 'hash': 'b9112e60f5fb3d8bc2a387840577b4756a12f357', + 'repo': '/path/to/repo1' + }, GitHubCicdEvents]); + expect(spyOnHttpsGet).toHaveBeenCalledWith({ + hostname: 'api.github.com', + path: '/repos/keydepth/vscode-git-graph/actions/runs?per_page=100', + headers: { + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'vscode-git-graph' + }, + port: '', + agent: false, + timeout: 15000 + }, expect.anything()); + expect(spyOnSaveCicd).toHaveBeenCalledWith( + '/path/to/repo1', 'b9112e60f5fb3d8bc2a387840577b4756a12f357', 740791415, + { + name: 'Update Milestone on Release', + status: 'success', + ref: 'v1.31.0-beta.0', + web_url: 'https://github.com/mhutchie/vscode-git-graph/actions/runs/740791415', + event: 'release', + detail: false, + allow_failure: false + }); + expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/actions/runs?per_page=100 detail=false page=-1 from GitHub'); + expect(spyOnLog).toHaveBeenCalledWith('GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/actions/runs?per_page=100'); + expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitHub'); + expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/b9112e60f5fb3d8bc2a387840577b4756a12f357/check-runs?per_page=100 detail=true page=-1 from GitHub'); + expect(spyOnLog).toHaveBeenCalledWith('GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/commits/b9112e60f5fb3d8bc2a387840577b4756a12f357/check-runs?per_page=100'); + expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitHub'); + expect(spyOnLog).toHaveBeenCalledTimes(6); + jest.useRealTimers(); + }); + + it('Should fetch a new cicd from GitHub (HTTPS Remote) not detail with no conclusion', async () => { + // Setup + jest.useFakeTimers(); + spyOnGetRepos.mockReturnValueOnce(GitHubGetRspos); + mockHttpsResponse(200, JSON.stringify({ + total_count: 324, + workflow_runs: [ + { + id: 740791415, + name: 'Update Milestone on Release', + head_branch: 'v1.31.0-beta.0', + head_sha: 'b9112e60f5fb3d8bc2a387840577b4756a12f357', + event: 'release', + status: 'pending', + conclusion: null, + url: 'https://api.github.com/repos/mhutchie/vscode-git-graph/actions/runs/740791415', + html_url: 'https://github.com/mhutchie/vscode-git-graph/actions/runs/740791415', + pull_requests: [], + created_at: '2021-04-12T10:24:48Z', + updated_at: '2021-04-12T10:25:06Z' + } + ] + }), Object.assign({}, GitHubHeader, { + 'link': '; rel="next", ; rel="last"' + })); + mockHttpsResponse(200, JSON.stringify({ + total_count: 1, + check_runs: [{ + id: 2211653232, + head_sha: '149ecc50e5c223251f80a0223cfbbd9822307224', + html_url: 'https://github.com/mhutchie/vscode-git-graph/runs/2211653232', + status: 'pending', + conclusion: null, + name: 'build', + app: { name: 'GitHub Actions' } + }] + }), Object.assign({}, GitHubHeader, { + 'link': '; rel="next", ; rel="last"' + })); + const cicdEvents = waitForEvents(cicdManager, 2, true); + cicdManager['queue']['queue'] = [{ + repo: '/path/to/repo1', + cicdConfig: { + provider: CICDProvider.GitHubV3, + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + cicdToken: '' + }, + page: -1, + checkAfter: 0, + attempts: 0, + detail: false, + hash: 'b9112e60f5fb3d8bc2a387840577b4756a12f357', + maximumStatuses: 1000 + }]; + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', 'b9112e60f5fb3d8bc2a387840577b4756a12f357'); + cicdManager['queue']['itemsAvailableCallback'](); + + // Assert + expect(await cicdEvents).toStrictEqual([{ + 'cicdDataSaves': { + '740791415': { + name: 'Update Milestone on Release', + status: 'pending', + ref: 'v1.31.0-beta.0', + web_url: 'https://github.com/mhutchie/vscode-git-graph/actions/runs/740791415', + event: 'release', + detail: false, + allow_failure: false + } + }, + 'hash': 'b9112e60f5fb3d8bc2a387840577b4756a12f357', + 'repo': '/path/to/repo1' + }, { + 'cicdDataSaves': { + '2211653232': { + name: 'GitHub Actions(build)', + status: 'pending', + ref: '', + web_url: 'https://github.com/mhutchie/vscode-git-graph/runs/2211653232', + event: '', + detail: true, + allow_failure: false + } + }, + 'hash': '149ecc50e5c223251f80a0223cfbbd9822307224', + 'repo': '/path/to/repo1' + }]); + expect(spyOnHttpsGet).toHaveBeenCalledWith({ + hostname: 'api.github.com', + path: '/repos/keydepth/vscode-git-graph/actions/runs?per_page=100', + headers: { + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'vscode-git-graph' + }, + port: '', + agent: false, + timeout: 15000 + }, expect.anything()); + expect(spyOnSaveCicd).toHaveBeenCalledWith( + '/path/to/repo1', 'b9112e60f5fb3d8bc2a387840577b4756a12f357', 740791415, + { + name: 'Update Milestone on Release', + status: 'pending', + ref: 'v1.31.0-beta.0', + web_url: 'https://github.com/mhutchie/vscode-git-graph/actions/runs/740791415', + event: 'release', + detail: false, + allow_failure: false + }); + expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/actions/runs?per_page=100 detail=false page=-1 from GitHub'); + expect(spyOnLog).toHaveBeenCalledWith('GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/actions/runs?per_page=100'); + expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitHub'); + expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/b9112e60f5fb3d8bc2a387840577b4756a12f357/check-runs?per_page=100 detail=true page=-1 from GitHub'); + expect(spyOnLog).toHaveBeenCalledWith('GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/commits/b9112e60f5fb3d8bc2a387840577b4756a12f357/check-runs?per_page=100'); + expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitHub'); + expect(spyOnLog).toHaveBeenCalledTimes(6); + jest.useRealTimers(); + }); + + it('Should fetch a new cicd from GitHub (HTTPS Remote) with ratelimit header', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitHubGetRspos); + mockHttpsResponse(200, GitHubResponse, Object.assign({}, GitHubHeader, { + 'link': '; rel="next", ; rel"last", ; rel=last', + 'x-ratelimit-limit': '', + 'x-ratelimit-remaining': '', + 'x-ratelimit-reset': '' + })); + const cicdEvents = waitForEvents(cicdManager, 1); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(await cicdEvents).toStrictEqual([GitHubCicdEvents]); + expect(spyOnHttpsGet).toHaveBeenCalledWith(GitHubHttpsGet, expect.anything()); + expect(spyOnSaveCicd).toHaveBeenCalledWith(...GitHubSaveCicd); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=None(1 hour)/Remaining=None) from GitHub'); + expect(spyOnLog).toHaveBeenCalledTimes(3); + }); + + it('Should fetch a new cicd from GitHub (HTTPS Remote) with link error header (number)', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitHubGetRspos); + mockHttpsResponse(200, GitHubResponse, Object.assign({}, GitHubHeader, { + 'link': 0 + })); + const cicdEvents = waitForEvents(cicdManager, 1); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(await cicdEvents).toStrictEqual([GitHubCicdEvents]); + expect(spyOnHttpsGet).toHaveBeenCalledWith(GitHubHttpsGet, expect.anything()); + expect(spyOnSaveCicd).toHaveBeenCalledWith(...GitHubSaveCicd); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitHub'); + expect(spyOnLog).toHaveBeenCalledTimes(3); + }); + + it('Should fetch a new cicd from GitHub (HTTPS Remote) with link error header (string)', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitHubGetRspos); + mockHttpsResponse(200, GitHubResponse, Object.assign({}, GitHubHeader, { + 'link': '' + })); + const cicdEvents = waitForEvents(cicdManager, 1); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(await cicdEvents).toStrictEqual([GitHubCicdEvents]); + expect(spyOnHttpsGet).toHaveBeenCalledWith(GitHubHttpsGet, expect.anything()); + expect(spyOnSaveCicd).toHaveBeenCalledWith(...GitHubSaveCicd); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitHub'); + expect(spyOnLog).toHaveBeenCalledTimes(3); + }); + + it('Should fetch a new cicd from GitHub (HTTPS Remote) with link error header (page)', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitHubGetRspos); + mockHttpsResponse(200, GitHubResponse, Object.assign({}, GitHubHeader, { + 'link': '; rel="next", ; rel="last"' + })); + const cicdEvents = waitForEvents(cicdManager, 1); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(await cicdEvents).toStrictEqual([GitHubCicdEvents]); + expect(spyOnHttpsGet).toHaveBeenCalledWith(GitHubHttpsGet, expect.anything()); + expect(spyOnSaveCicd).toHaveBeenCalledWith(...GitHubSaveCicd); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitHub'); + expect(spyOnLog).toHaveBeenCalledTimes(3); + }); + + it('Should fetch a new cicd from GitHub (HTTPS Remote) no emmit', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitHubGetRspos); + mockHttpsResponse(200, GitHubResponse, GitHubHeader); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnLog).toHaveBeenCalledTimes(4); + }); + + it('Should fetch a new cicd from GitHub (URL is Not Match)', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo1': mockRepoState(null, 0, + 'ElZJNHSyT6JDjOGDaVaPiZenu3Xu2MZf', [{ + provider: CICDProvider.GitHubV3, + cicdUrl: 'ftp://github.com/keydepth/vscode-git-graph.git', + cicdToken: '' + }]), + '/path/to/repo2': mockRepoState('Custom Name', 0, null, null) + }); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD is not match URL (ftp://github.com/keydepth/vscode-git-graph.git) for GitHub'); + expect(spyOnLog).toHaveBeenCalledTimes(1); + }); + + it('Should fetch a new cicd from GitHub (Bad URL)', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo1': mockRepoState(null, 0, + 'ElZJNHSyT6JDjOGDaVaPiZenu3Xu2MZf', [{ + provider: CICDProvider.GitHubV3, + cicdUrl: 'github.com/keydepth/vscode-git-graph.git', + cicdToken: '' + }]), + '/path/to/repo2': mockRepoState('Custom Name', 0, null, null) + }); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD is not match URL (github.com/keydepth/vscode-git-graph.git) for GitHub'); + expect(spyOnLog).toHaveBeenCalledTimes(1); + }); + + it('Should fetch a new multiple cicd from GitHub', async () => { + // Setup + jest.useFakeTimers(); + spyOnGetRepos.mockReturnValueOnce(GitHubGetRspos); + for (let i = 0; i < 10; i++) { + mockHttpsResponse(200, GitHubResponse, GitHubHeader); + } + const cicdEvents = waitForEvents(cicdManager, 1); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(await cicdEvents).toStrictEqual([GitHubCicdEvents]); + expect(cicdManager['queue']['queue'].length).toBe(9); + expect(cicdManager['queue']['queue'][0].attempts).toBe(0); + expect(spyOnHttpsGet).toHaveBeenCalledWith(GitHubHttpsGet, expect.anything()); + jest.runAllTimers(); + for (let i = 1; i < 10; i++) { + // jest.runOnlyPendingTimers(); + expect(spyOnLog).toHaveBeenNthCalledWith(3 + i * 2, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100&page=' + (i + 1) + ' detail=true page=' + (i + 1) + ' from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(4 + i * 2, 'GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100&page=' + (i + 1)); + } + jest.useRealTimers(); + expect(spyOnSaveCicd).toHaveBeenCalledWith(...GitHubSaveCicd); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(1 hour)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitHub'); + expect(spyOnLog).toHaveBeenCalledTimes(22); + }); + + it('Should trigger the cicd to be emitted when a known cicd is requested repo', async () => { + // Setup + const cicdEvents = waitForEvents(cicdManager, 1); + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo': mockRepoState(null, 0, 'hash0', [{ + provider: CICDProvider.GitHubV3, + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + cicdToken: '' + }]) + }); + mockHttpsResponse(200, GitHubResponse, GitHubHeader); + + // Run + let data = await cicdManager.getCICDDetail('/path/to/repo', 'hash0'); + + // Assert + expect.assertions(2); + if (data) { + expect(JSON.parse(data)).toStrictEqual({ + 'id0': { + name: 'string', + status: 'string', + ref: 'string', + web_url: 'string', + event: 'string', + detail: true, + allow_failure: false + } + }); + } + expect(await cicdEvents).toStrictEqual([{ + 'cicdDataSaves': { + '2211653232': { + name: 'GitHub Actions(build)', + status: 'success', + ref: '', + web_url: 'https://github.com/mhutchie/vscode-git-graph/runs/2211653232', + event: '', + detail: true, + allow_failure: false + } + }, + 'hash': '149ecc50e5c223251f80a0223cfbbd9822307224', + 'repo': '/path/to/repo' + }]); + }); + + it('Should trigger the cicd to be emitted when a known cicd is requested repo with cicdToken', async () => { + // Setup + const cicdEvents = waitForEvents(cicdManager, 1); + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo1': mockRepoState(null, 0, 'nonce', [{ + provider: CICDProvider.GitHubV3, + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + cicdToken: 'aaaaaa' + }]) + }); + mockHttpsResponse(200, GitHubResponse, GitHubHeader); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', 'hash0'); + + // Assert + expect(await cicdEvents).toStrictEqual([{ + 'cicdDataSaves': { + '2211653232': { + name: 'GitHub Actions(build)', + status: 'success', + ref: '', + web_url: 'https://github.com/mhutchie/vscode-git-graph/runs/2211653232', + event: '', + detail: true, + allow_failure: false + } + }, + 'hash': '149ecc50e5c223251f80a0223cfbbd9822307224', + 'repo': '/path/to/repo1' + }]); + expect(spyOnHttpsGet).toHaveBeenCalledWith({ + hostname: 'api.github.com', + path: '/repos/keydepth/vscode-git-graph/commits/hash0/check-runs?per_page=100', + headers: { + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'vscode-git-graph', + 'Authorization': 'token aaaaaa' + }, + port: '', + agent: false, + timeout: 15000 + }, expect.anything()); + }); + + it('Should trigger the cicd to be emitted when a known cicd is requested repo with responce is empty JSON', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo': mockRepoState(null, 0, 'hash0', [{ + provider: CICDProvider.GitHubV3, + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + cicdToken: '' + }]) + }); + mockHttpsResponse(200, JSON.stringify({}), GitHubHeader); + + // Run + let data = await cicdManager.getCICDDetail('/path/to/repo', 'hash0'); + + // Assert + expect.assertions(1); + if (data) { + expect(JSON.parse(data)).toStrictEqual({ + 'id0': { + name: 'string', + status: 'string', + ref: 'string', + web_url: 'string', + event: 'string', + detail: true, + allow_failure: false + } + }); + } + }); + + it('Should trigger the cicd to be emitted when a known cicd is requested repo with responce is Not JSON format', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo1': mockRepoState(null, 0, 'hash0', [{ + provider: CICDProvider.GitHubV3, + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + cicdToken: '' + }]) + }); + mockHttpsResponse(200, 'Not JSON format', GitHubHeader); + + // Run + let data = await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect.assertions(5); + expect(data).toStrictEqual(null); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'GitHub API Error - (200)API Result error. : Unexpected token N in JSON at position 0'); + expect(spyOnLog).toHaveBeenCalledTimes(3); + }); + + it('Should halt fetching the cicd when the GitHub cicd url request is unsuccessful', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitHubGetRspos); + mockHttpsResponse(404, '', GitHubHeader); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnHttpsGet).toHaveBeenCalledWith(GitHubHttpsGet, expect.anything()); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API Error - (404)undefined'); + expect(spyOnLog).toHaveBeenCalledTimes(2); + }); + + it('Should halt fetching the cicd when the GitHub cicd url request is unsuccessful with Message Body', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitHubGetRspos); + mockHttpsResponse(404, '{"message":"Error Message Body"}', GitHubHeader); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnHttpsGet).toHaveBeenCalledWith(GitHubHttpsGet, expect.anything()); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API Error - (404)Error Message Body'); + expect(spyOnLog).toHaveBeenCalledTimes(2); + }); + + it('Should halt fetching the cicd when the GitHub cicd url request is unsuccessful with No Message Body', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitHubGetRspos); + mockHttpsResponse(404, '{}', GitHubHeader); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnHttpsGet).toHaveBeenCalledWith(GitHubHttpsGet, expect.anything()); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API Error - (404)undefined'); + expect(spyOnLog).toHaveBeenCalledTimes(2); + }); + + it('Should requeue the request when the GitHub API cannot find the commit', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitHubGetRspos); + mockHttpsResponse(422, '', Object.assign({}, GitHubHeader, { 'x-ratelimit-remaining': '0', 'x-ratelimit-reset': (date.now + 1).toString() })); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + await waitForExpect(() => { + expect(cicdManager['queue']['queue'].length).toBe(1); + expect(cicdManager['queue']['queue'][0].attempts).toBe(1); + }); + expect(cicdManager['queue']['queue']).toStrictEqual([ + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: '', + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + provider: CICDProvider.GitHubV3 + }, + page: -1, + checkAfter: 0, + attempts: 1, + detail: true, + hash: '149ecc50e5c223251f80a0223cfbbd9822307224', + maximumStatuses: 1000 + } + ]); + expect(spyOnHttpsGet).toHaveBeenCalledWith(GitHubHttpsGet, expect.anything()); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API Rate Limit Reached - Paused fetching from GitHub until the Rate Limit is reset (RateLimit=60(1 hour)/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00))'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'GitHub API Rate Limit can upgrade by Access Token.'); + expect(spyOnLog).toHaveBeenCalledTimes(3); + }); + + it('Should set the GitHub API timeout and requeue the request when the rate limit is reached with cicdToken', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo1': mockRepoState(null, 0, + 'ElZJNHSyT6JDjOGDaVaPiZenu3Xu2MZf', [{ + provider: CICDProvider.GitHubV3, + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + cicdToken: 'aaaaaa' + }]), + '/path/to/repo2': mockRepoState('Custom Name', 0, null, null) + }); + mockHttpsResponse(403, '', Object.assign({}, GitHubHeader, { 'x-ratelimit-remaining': '0', 'x-ratelimit-reset': (date.now + 1).toString() })); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + await waitForExpect(() => { + expect(spyOnLog).toHaveBeenCalledWith('GitHub API Rate Limit Reached - Paused fetching from GitHub until the Rate Limit is reset (RateLimit=60(1 hour)/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00))'); + }); + expect(cicdManager['queue']['queue']).toStrictEqual([ + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: 'aaaaaa', + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + provider: CICDProvider.GitHubV3 + }, + page: -1, + checkAfter: (date.now + 1) * 1000, + attempts: 0, + detail: true, + hash: '149ecc50e5c223251f80a0223cfbbd9822307224', + maximumStatuses: 1000 + } + ]); + expect(spyOnHttpsGet).toHaveBeenCalledWith({ + hostname: 'api.github.com', + path: '/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100', + headers: { + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'vscode-git-graph', + 'Authorization': 'token aaaaaa' + }, + port: '', + agent: false, + timeout: 15000 + }, expect.anything()); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API Rate Limit Reached - Paused fetching from GitHub until the Rate Limit is reset (RateLimit=60(1 hour)/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00))'); + expect(spyOnLog).toHaveBeenCalledTimes(2); + expect(cicdManager['gitHubTimeout']).toBe((date.now + 1) * 1000); + }); + + it('Should set the GitHub API timeout and requeue the request when the API returns a 5xx error', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitHubGetRspos); + mockHttpsResponse(500, '', GitHubHeader); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + await waitForExpect(() => { + expect(cicdManager['gitHubTimeout']).toBe(date.now * 1000 + 600000); + }); + expect(cicdManager['queue']['queue']).toStrictEqual([ + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: '', + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + provider: CICDProvider.GitHubV3 + }, + page: -1, + checkAfter: date.now * 1000 + 600000, + attempts: 0, + detail: true, + hash: '149ecc50e5c223251f80a0223cfbbd9822307224', + maximumStatuses: 1000 + } + ]); + }); + + it('Should set the GitHub API timeout and requeue the request when there is an HTTPS Client Request Error', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitHubGetRspos); + mockHttpsClientRequestErrorEvent({ message: 'Error Message' }); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + await waitForExpect(() => { + expect(cicdManager['gitHubTimeout']).toBe(date.now * 1000 + 300000); + }); + expect(spyOnLog).toHaveBeenCalledWith('GitHub API https Error - Error Message'); + expect(cicdManager['queue']['queue']).toStrictEqual([ + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: '', + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + provider: CICDProvider.GitHubV3 + }, + page: -1, + checkAfter: date.now * 1000 + 300000, + attempts: 0, + detail: true, + hash: '149ecc50e5c223251f80a0223cfbbd9822307224', + maximumStatuses: 1000 + } + ]); + }); + + it('Should set the GitHub API timeout and requeue the request when there is an HTTPS Client Request Error', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitHubGetRspos); + mockHttpsClientRequestErrorEvent({ message: 'Error Message' }); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + await waitForExpect(() => { + expect(cicdManager['gitHubTimeout']).toBe(date.now * 1000 + 300000); + }); + expect(spyOnLog).toHaveBeenCalledWith('GitHub API https Error - Error Message'); + expect(cicdManager['queue']['queue']).toStrictEqual([ + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: '', + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + provider: CICDProvider.GitHubV3 + }, + page: -1, + checkAfter: date.now * 1000 + 300000, + attempts: 0, + detail: true, + hash: '149ecc50e5c223251f80a0223cfbbd9822307224', + maximumStatuses: 1000 + } + ]); + }); + + it('Should set the GitHub API timeout and requeue the request when there is an HTTPS Incoming Message Error', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitHubGetRspos); + mockHttpsIncomingMessageErrorEvent(); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + await waitForExpect(() => { + expect(cicdManager['gitHubTimeout']).toBe(date.now * 1000 + 300000); + }); + expect(spyOnLog).toHaveBeenCalledWith('GitHub API https Error - undefined'); + expect(cicdManager['queue']['queue']).toStrictEqual([ + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: '', + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + provider: CICDProvider.GitHubV3 + }, + page: -1, + checkAfter: date.now * 1000 + 300000, + attempts: 0, + detail: true, + hash: '149ecc50e5c223251f80a0223cfbbd9822307224', + maximumStatuses: 1000 + } + ]); + }); + + it('Should set the GitHub API timeout and requeue the request once when there are multiple HTTPS Error Events', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitHubGetRspos); + mockHttpsMultipleErrorEvents(); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + await waitForExpect(() => { + expect(cicdManager['gitHubTimeout']).toBe(date.now * 1000 + 300000); + }); + expect(spyOnLog).toHaveBeenCalledWith('GitHub API https Error - undefined'); + expect(cicdManager['queue']['queue']).toStrictEqual([ + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: '', + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + provider: CICDProvider.GitHubV3 + }, + page: -1, + checkAfter: date.now * 1000 + 300000, + attempts: 0, + detail: true, + hash: '149ecc50e5c223251f80a0223cfbbd9822307224', + maximumStatuses: 1000 + } + ]); + }); + + it('Should requeue the request when it\'s before the GitHub API timeout', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitHubGetRspos); + cicdManager['gitHubTimeout'] = (date.now + 1) * 1000; + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + await waitForExpect(() => { + expect(cicdManager['queue']['queue']).toStrictEqual([ + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: '', + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + provider: CICDProvider.GitHubV3 + }, + page: -1, + checkAfter: (date.now + 1) * 1000, + attempts: 0, + detail: true, + hash: '149ecc50e5c223251f80a0223cfbbd9822307224', + maximumStatuses: 1000 + } + ]); + }); + }); + + it('Should insert requests into the priority queue in the correct order', async () => { + // Setup + spyOnGetRepos.mockReturnValue({ + '/path/to/repo1': mockRepoState(null, 0, + 'ElZJNHSyT6JDjOGDaVaPiZenu3Xu2MZf', [{ + provider: CICDProvider.GitHubV3, + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + cicdToken: '' + }]) + }); + mockHttpsResponse(403, '', Object.assign({}, GitHubHeader, { 'x-ratelimit-remaining': '0', 'x-ratelimit-reset': (date.now + 1).toString() })); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', 'hash0'); + + // Assert + await waitForExpect(() => { + expect(spyOnLog).toHaveBeenCalledWith('GitHub API Rate Limit Reached - Paused fetching from GitHub until the Rate Limit is reset (RateLimit=60(1 hour)/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00))'); + }); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', 'hash1'); + await cicdManager.getCICDDetail('/path/to/repo1', 'hash2'); + await cicdManager.getCICDDetail('/path/to/repo1', 'hash2'); + cicdManager['queue']['add']('/path/to/repo1', { + provider: CICDProvider.GitHubV3, + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + cicdToken: '' + }, -1, false, true, 'hash3'); + + // Assert + expect(cicdManager['queue']['queue']).toStrictEqual([ + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: '', + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + provider: CICDProvider.GitHubV3 + }, + page: -1, + checkAfter: 0, + attempts: 0, + detail: true, + hash: 'hash1', + maximumStatuses: 1000 + }, + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: '', + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + provider: CICDProvider.GitHubV3 + }, + page: -1, + checkAfter: 0, + attempts: 0, + detail: true, + hash: 'hash2', + maximumStatuses: 1000 + }, + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: '', + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + provider: CICDProvider.GitHubV3 + }, + page: -1, + checkAfter: (date.now + 1) * 1000, + attempts: 0, + detail: true, + hash: 'hash0', + maximumStatuses: 1000 + }, + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: '', + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + provider: CICDProvider.GitHubV3 + }, + page: -1, + checkAfter: (date.now + 1) * 1000 + 1, + attempts: 0, + detail: true, + hash: 'hash3', + maximumStatuses: 1000 + } + ]); + }); + + }); + + + describe('GitLab', () => { + it('Should fetch a new cicd from GitLab (HTTPS Remote)', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitLabGetRspos); + mockHttpsResponse(200, GitLabResponse, GitLabHeader); + const cicdEvents = waitForEvents(cicdManager, 1); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(await cicdEvents).toStrictEqual([GitLabCicdEvents]); + expect(spyOnHttpsGet).toHaveBeenCalledWith(GitLabHttpsGet, expect.anything()); + expect(spyOnSaveCicd).toHaveBeenCalledWith(...GitLabSaveCicd); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitLab'); + expect(spyOnLog).toHaveBeenCalledTimes(4); + }); + + it('Should fetch a new cicd from GitLab (HTTPS Remote) with port', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo1': mockRepoState(null, 0, + 'ElZJNHSyT6JDjOGDaVaPiZenu3Xu2MZf', [{ + provider: CICDProvider.GitLabV4, + cicdUrl: 'https://gitlab.com:80/keydepth/vscode-git-graph.git', + cicdToken: '' + }]), + '/path/to/repo2': mockRepoState('Custom Name', 0, null, null) + }); + mockHttpsResponse(200, GitLabResponse, GitLabHeader); + const cicdEvents = waitForEvents(cicdManager, 1); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(await cicdEvents).toStrictEqual([GitLabCicdEvents]); + expect(spyOnHttpsGet).toHaveBeenCalledWith({ + hostname: 'gitlab.com', + path: '/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100', + headers: { + 'User-Agent': 'vscode-git-graph' + }, + port: '80', + agent: false, + timeout: 15000 + }, expect.anything()); + expect(spyOnSaveCicd).toHaveBeenCalledWith(...GitLabSaveCicd); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com:80/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitLab'); + expect(spyOnLog).toHaveBeenCalledTimes(4); + }); + + it('Should fetch a new cicd from GitLab (HTTP Remote)', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo1': mockRepoState(null, 0, + 'ElZJNHSyT6JDjOGDaVaPiZenu3Xu2MZf', [{ + provider: CICDProvider.GitLabV4, + cicdUrl: 'http://gitlab.com/keydepth/vscode-git-graph.git', + cicdToken: '' + }]), + '/path/to/repo2': mockRepoState('Custom Name', 0, null, null) + }); + mockHttpResponse(200, JSON.stringify([ + { + id: 2211653232, + sha: '149ecc50e5c223251f80a0223cfbbd9822307224', + ref: 'main', + status: 'success', + name: 'eslint-sast', + target_url: 'http://gitlab.com/mhutchie/vscode-git-graph/runs/2211653232', + allow_failure: false + } + ]), GitLabHeader); + const cicdEvents = waitForEvents(cicdManager, 1); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(await cicdEvents).toStrictEqual([{ + 'cicdDataSaves': { + '2211653232': { + name: 'eslint-sast', + status: 'success', + ref: 'main', + web_url: 'http://gitlab.com/mhutchie/vscode-git-graph/runs/2211653232', + event: '', + detail: true, + allow_failure: false + } + }, + 'hash': '149ecc50e5c223251f80a0223cfbbd9822307224', + 'repo': '/path/to/repo1' + }]); + expect(spyOnHttpGet).toHaveBeenCalledWith(GitLabHttpsGet, expect.anything()); + expect(spyOnSaveCicd).toHaveBeenCalledWith( + '/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224', 2211653232, + { + name: 'eslint-sast', + status: 'success', + ref: 'main', + web_url: 'http://gitlab.com/mhutchie/vscode-git-graph/runs/2211653232', + event: '', + detail: true, + allow_failure: false + }); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for http://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API - (200)http://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for http://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitLab'); + expect(spyOnLog).toHaveBeenCalledTimes(4); + }); + + it('Should fetch a new cicd from GitLab (SSH Remote)', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo1': mockRepoState(null, 0, + 'ElZJNHSyT6JDjOGDaVaPiZenu3Xu2MZf', [{ + provider: CICDProvider.GitLabV4, + cicdUrl: 'git@gitlab.com:keydepth/vscode-git-graph.git', + cicdToken: '' + }]), + '/path/to/repo2': mockRepoState('Custom Name', 0, null, null) + }); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD is not match URL (git@gitlab.com:keydepth/vscode-git-graph.git) for GitLab'); + expect(spyOnLog).toHaveBeenCalledTimes(1); + }); + + it('Should fetch a new cicd from GitLab (HTTPS Remote) not detail', async () => { + // Setup + jest.useFakeTimers(); + spyOnGetRepos.mockReturnValueOnce(GitLabGetRspos); + mockHttpsResponse(200, JSON.stringify([ + { + id: 740791415, + sha: 'b9112e60f5fb3d8bc2a387840577b4756a12f357', + ref: 'v1.31.0-beta.0', + status: 'success', + created_at: '2021-05-14T11:31:37.881Z', + updated_at: '2021-05-14T11:35:45.904Z', + web_url: 'https://gitlab.com/mhutchie/vscode-git-graph/actions/runs/740791415' + } + ]), Object.assign({}, GitLabHeader, { + 'x-total': '1', + 'x-total-pages': '1' + })); + mockHttpsResponse(200, GitLabResponse, Object.assign({}, GitLabHeader, { + 'x-total': '1', + 'x-total-pages': '1' + })); + const cicdEvents = waitForEvents(cicdManager, 2, true); + cicdManager['queue']['queue'] = [{ + repo: '/path/to/repo1', + cicdConfig: { + provider: CICDProvider.GitLabV4, + cicdUrl: 'https://gitlab.com/keydepth/vscode-git-graph.git', + cicdToken: '' + }, + page: -1, + checkAfter: 0, + attempts: 0, + detail: false, + hash: 'b9112e60f5fb3d8bc2a387840577b4756a12f357', + maximumStatuses: 1000 + }]; + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', 'b9112e60f5fb3d8bc2a387840577b4756a12f357'); + cicdManager['queue']['itemsAvailableCallback'](); + + // Assert + expect(await cicdEvents).toStrictEqual([{ + 'cicdDataSaves': { + '740791415': { + name: '', + status: 'success', + ref: 'v1.31.0-beta.0', + web_url: 'https://gitlab.com/mhutchie/vscode-git-graph/actions/runs/740791415', + event: '', + detail: false, + allow_failure: false + } + }, + 'hash': 'b9112e60f5fb3d8bc2a387840577b4756a12f357', + 'repo': '/path/to/repo1' + }, GitLabCicdEvents]); + expect(spyOnHttpsGet).toHaveBeenCalledWith({ + hostname: 'gitlab.com', + path: '/api/v4/projects/keydepth%2Fvscode-git-graph/pipelines?per_page=100', + headers: { + 'User-Agent': 'vscode-git-graph' + }, + port: '', + agent: false, + timeout: 15000 + }, expect.anything()); + expect(spyOnSaveCicd).toHaveBeenCalledWith( + '/path/to/repo1', 'b9112e60f5fb3d8bc2a387840577b4756a12f357', 740791415, + { + name: '', + status: 'success', + ref: 'v1.31.0-beta.0', + web_url: 'https://gitlab.com/mhutchie/vscode-git-graph/actions/runs/740791415', + event: '', + detail: false, + allow_failure: false + }); + expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/pipelines?per_page=100 detail=false page=-1 from GitLab'); + expect(spyOnLog).toHaveBeenCalledWith('GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/pipelines?per_page=100'); + expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(every minute)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitLab'); + expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/b9112e60f5fb3d8bc2a387840577b4756a12f357/statuses?per_page=100 detail=true page=-1 from GitLab'); + expect(spyOnLog).toHaveBeenCalledWith('GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/b9112e60f5fb3d8bc2a387840577b4756a12f357/statuses?per_page=100'); + expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(every minute)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitLab'); + expect(spyOnLog).toHaveBeenCalledTimes(6); + jest.useRealTimers(); + }); + + it('Should fetch a new cicd from GitLab (HTTPS Remote) not detail with no conclusion', async () => { + // Setup + jest.useFakeTimers(); + spyOnGetRepos.mockReturnValueOnce(GitLabGetRspos); + mockHttpsResponse(200, JSON.stringify([ + { + id: 740791415, + sha: 'b9112e60f5fb3d8bc2a387840577b4756a12f357', + ref: 'v1.31.0-beta.0', + status: 'pending', + created_at: '2021-05-14T11:31:37.881Z', + updated_at: '2021-05-14T11:35:45.904Z', + web_url: 'https://gitlab.com/mhutchie/vscode-git-graph/actions/runs/740791415' + } + ]), Object.assign({}, GitLabHeader, { + 'x-total': '1', + 'x-total-pages': '1' + })); + mockHttpsResponse(200, JSON.stringify([ + { + id: 2211653232, + sha: '149ecc50e5c223251f80a0223cfbbd9822307224', + ref: 'main', + status: 'pending', + name: 'eslint-sast', + target_url: 'https://gitlab.com/mhutchie/vscode-git-graph/runs/2211653232', + allow_failure: false + } + ]), Object.assign({}, GitLabHeader, { + 'x-total': '1', + 'x-total-pages': '1' + })); + const cicdEvents = waitForEvents(cicdManager, 2, true); + cicdManager['queue']['queue'] = [{ + repo: '/path/to/repo1', + cicdConfig: { + provider: CICDProvider.GitLabV4, + cicdUrl: 'https://gitlab.com/keydepth/vscode-git-graph.git', + cicdToken: '' + }, + page: -1, + checkAfter: 0, + attempts: 0, + detail: false, + hash: 'b9112e60f5fb3d8bc2a387840577b4756a12f357', + maximumStatuses: 1000 + }]; + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', 'b9112e60f5fb3d8bc2a387840577b4756a12f357'); + cicdManager['queue']['itemsAvailableCallback'](); + + // Assert + expect(await cicdEvents).toStrictEqual([{ + 'cicdDataSaves': { + '740791415': { + name: '', + status: 'pending', + ref: 'v1.31.0-beta.0', + web_url: 'https://gitlab.com/mhutchie/vscode-git-graph/actions/runs/740791415', + event: '', + detail: false, + allow_failure: false + } + }, + 'hash': 'b9112e60f5fb3d8bc2a387840577b4756a12f357', + 'repo': '/path/to/repo1' + }, { + 'cicdDataSaves': { + '2211653232': { + name: 'eslint-sast', + status: 'pending', + ref: 'main', + web_url: 'https://gitlab.com/mhutchie/vscode-git-graph/runs/2211653232', + event: '', + detail: true, + allow_failure: false + } + }, + 'hash': '149ecc50e5c223251f80a0223cfbbd9822307224', + 'repo': '/path/to/repo1' + }]); + expect(spyOnHttpsGet).toHaveBeenCalledWith({ + hostname: 'gitlab.com', + path: '/api/v4/projects/keydepth%2Fvscode-git-graph/pipelines?per_page=100', + headers: { + 'User-Agent': 'vscode-git-graph' + }, + port: '', + agent: false, + timeout: 15000 + }, expect.anything()); + expect(spyOnSaveCicd).toHaveBeenCalledWith( + '/path/to/repo1', 'b9112e60f5fb3d8bc2a387840577b4756a12f357', 740791415, + { + name: '', + status: 'pending', + ref: 'v1.31.0-beta.0', + web_url: 'https://gitlab.com/mhutchie/vscode-git-graph/actions/runs/740791415', + event: '', + detail: false, + allow_failure: false + }); + expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/pipelines?per_page=100 detail=false page=-1 from GitLab'); + expect(spyOnLog).toHaveBeenCalledWith('GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/pipelines?per_page=100'); + expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(every minute)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitLab'); + expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/b9112e60f5fb3d8bc2a387840577b4756a12f357/statuses?per_page=100 detail=true page=-1 from GitLab'); + expect(spyOnLog).toHaveBeenCalledWith('GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/b9112e60f5fb3d8bc2a387840577b4756a12f357/statuses?per_page=100'); + expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(every minute)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitLab'); + expect(spyOnLog).toHaveBeenCalledTimes(6); + jest.useRealTimers(); + }); + + it('Should fetch a new cicd from GitLab (HTTPS Remote) with ratelimit header', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitLabGetRspos); + mockHttpsResponse(200, GitLabResponse, Object.assign({}, GitLabHeader, { + 'ratelimit-limit': '', + 'ratelimit-remaining': '', + 'ratelimit-reset': '' + })); + const cicdEvents = waitForEvents(cicdManager, 1); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(await cicdEvents).toStrictEqual([GitLabCicdEvents]); + expect(spyOnHttpsGet).toHaveBeenCalledWith(GitLabHttpsGet, expect.anything()); + expect(spyOnSaveCicd).toHaveBeenCalledWith(...GitLabSaveCicd); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=None(every minute)/Remaining=None) from GitLab'); + expect(spyOnLog).toHaveBeenCalledTimes(4); + }); + + it('Should fetch a new cicd from GitLab (HTTPS Remote) with x-page error header (number)', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitLabGetRspos); + mockHttpsResponse(200, GitLabResponse, Object.assign({}, GitLabHeader, { + 'x-page': 1 + })); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnHttpsGet).toHaveBeenCalledWith(GitLabHttpsGet, expect.anything()); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100'); + expect(spyOnLog).toHaveBeenCalledTimes(2); + }); + + it('Should fetch a new cicd from GitLab (HTTPS Remote) with x-total-pages error header (number)', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitLabGetRspos); + mockHttpsResponse(200, GitLabResponse, Object.assign({}, GitLabHeader, { + 'x-total-pages': 1 + })); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnHttpsGet).toHaveBeenCalledWith(GitLabHttpsGet, expect.anything()); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100'); + expect(spyOnLog).toHaveBeenCalledTimes(2); + }); + + it('Should fetch a new cicd from GitLab (HTTPS Remote) with x-total error header (number)', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitLabGetRspos); + mockHttpsResponse(200, GitLabResponse, Object.assign({}, GitLabHeader, { + 'x-total': 1 + })); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnHttpsGet).toHaveBeenCalledWith(GitLabHttpsGet, expect.anything()); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100'); + expect(spyOnLog).toHaveBeenCalledTimes(2); + }); + + it('Should fetch a new cicd from GitLab (HTTPS Remote) no emmit', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitLabGetRspos); + mockHttpsResponse(200, GitLabResponse, GitLabHeader); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnLog).toHaveBeenCalledTimes(4); + }); + + it('Should fetch a new cicd from GitLab (URL is Not Match)', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo1': mockRepoState(null, 0, + 'ElZJNHSyT6JDjOGDaVaPiZenu3Xu2MZf', [{ + provider: CICDProvider.GitLabV4, + cicdUrl: 'ftp://gitlab.com/keydepth/vscode-git-graph.git', + cicdToken: '' + }]), + '/path/to/repo2': mockRepoState('Custom Name', 0, null, null) + }); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD is not match URL (ftp://gitlab.com/keydepth/vscode-git-graph.git) for GitLab'); + expect(spyOnLog).toHaveBeenCalledTimes(1); + }); + + it('Should fetch a new cicd from GitLab (Bad URL)', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo1': mockRepoState(null, 0, + 'ElZJNHSyT6JDjOGDaVaPiZenu3Xu2MZf', [{ + provider: CICDProvider.GitLabV4, + cicdUrl: 'gitlab.com/keydepth/vscode-git-graph.git', + cicdToken: '' + }]), + '/path/to/repo2': mockRepoState('Custom Name', 0, null, null) + }); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD is not match URL (gitlab.com/keydepth/vscode-git-graph.git) for GitLab'); + expect(spyOnLog).toHaveBeenCalledTimes(1); + }); + + it('Should fetch a new cicd from GitLab (No target_url)', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitLabGetRspos); + mockHttpsResponse(200, JSON.stringify([ + { + id: 2211653232, + sha: '149ecc50e5c223251f80a0223cfbbd9822307224', + ref: 'main', + status: 'success', + name: 'eslint-sast', + allow_failure: false + } + ]), GitLabHeader); + const cicdEvents = waitForEvents(cicdManager, 1); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(await cicdEvents).toStrictEqual([{ + 'cicdDataSaves': { + '2211653232': { + name: 'eslint-sast', + status: 'success', + ref: 'main', + web_url: 'https://gitlab.com/keydepth/vscode-git-graph/-/jobs/2211653232', + event: '', + detail: true, + allow_failure: false + } + }, + 'hash': '149ecc50e5c223251f80a0223cfbbd9822307224', + 'repo': '/path/to/repo1' + }]); + expect(spyOnHttpsGet).toHaveBeenCalledWith(GitLabHttpsGet, expect.anything()); + expect(spyOnSaveCicd).toHaveBeenCalledWith( + '/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224', 2211653232, + { + name: 'eslint-sast', + status: 'success', + ref: 'main', + web_url: 'https://gitlab.com/keydepth/vscode-git-graph/-/jobs/2211653232', + event: '', + detail: true, + allow_failure: false + }); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitLab'); + expect(spyOnLog).toHaveBeenCalledTimes(4); + }); + + it('Should fetch a new multiple cicd from GitLab', async () => { + // Setup + jest.useFakeTimers(); + spyOnGetRepos.mockReturnValueOnce(GitLabGetRspos); + for (let i = 0; i < 10; i++) { + mockHttpsResponse(200, GitLabResponse, GitLabHeader); + } + const cicdEvents = waitForEvents(cicdManager, 1); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(await cicdEvents).toStrictEqual([GitLabCicdEvents]); + expect(cicdManager['queue']['queue'].length).toBe(9); + expect(cicdManager['queue']['queue'][0].attempts).toBe(0); + expect(spyOnHttpsGet).toHaveBeenCalledWith(GitLabHttpsGet, expect.anything()); + jest.runAllTimers(); + for (let i = 1; i < 10; i++) { + // jest.runOnlyPendingTimers(); + expect(spyOnLog).toHaveBeenNthCalledWith(3 + i * 2, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100&page=' + (i + 1) + ' detail=true page=' + (i + 1) + ' from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(4 + i * 2, 'GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100&page=' + (i + 1)); + } + jest.useRealTimers(); + expect(spyOnSaveCicd).toHaveBeenCalledWith(...GitLabSaveCicd); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitLab'); + expect(spyOnLog).toHaveBeenCalledTimes(22); + }); + + it('Should trigger the cicd to be emitted when a known cicd is requested repo', async () => { + // Setup + const cicdEvents = waitForEvents(cicdManager, 1); + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo': mockRepoState(null, 0, 'hash0', [{ + provider: CICDProvider.GitLabV4, + cicdUrl: 'https://gitlab.com/keydepth/vscode-git-graph.git', + cicdToken: '' + }]) + }); + mockHttpsResponse(200, GitLabResponse, GitLabHeader); + + // Run + let data = await cicdManager.getCICDDetail('/path/to/repo', 'hash0'); + + // Assert + expect.assertions(2); + if (data) { + expect(JSON.parse(data)).toStrictEqual({ + 'id0': { + name: 'string', + status: 'string', + ref: 'string', + web_url: 'string', + event: 'string', + detail: true, + allow_failure: false + } + }); + } + expect(await cicdEvents).toStrictEqual([{ + 'cicdDataSaves': { + '2211653232': { + name: 'eslint-sast', + status: 'success', + ref: 'main', + web_url: 'https://gitlab.com/mhutchie/vscode-git-graph/runs/2211653232', + event: '', + detail: true, + allow_failure: false + } + }, + 'hash': '149ecc50e5c223251f80a0223cfbbd9822307224', + 'repo': '/path/to/repo' + }]); + }); + + it('Should trigger the cicd to be emitted when a known cicd is requested repo with cicdToken', async () => { + // Setup + const cicdEvents = waitForEvents(cicdManager, 1); + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo1': mockRepoState(null, 0, 'nonce', [{ + provider: CICDProvider.GitLabV4, + cicdUrl: 'https://gitlab.com/keydepth/vscode-git-graph.git', + cicdToken: 'aaaaaa' + }]) + }); + mockHttpsResponse(200, GitLabResponse, GitLabHeader); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', 'hash0'); + + // Assert + expect(await cicdEvents).toStrictEqual([{ + 'cicdDataSaves': { + '2211653232': { + name: 'eslint-sast', + status: 'success', + ref: 'main', + web_url: 'https://gitlab.com/mhutchie/vscode-git-graph/runs/2211653232', + event: '', + detail: true, + allow_failure: false + } + }, + 'hash': '149ecc50e5c223251f80a0223cfbbd9822307224', + 'repo': '/path/to/repo1' + }]); + expect(spyOnHttpsGet).toHaveBeenCalledWith({ + hostname: 'gitlab.com', + path: '/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/hash0/statuses?per_page=100', + headers: { + 'User-Agent': 'vscode-git-graph', + 'PRIVATE-TOKEN': 'aaaaaa' + }, + port: '', + agent: false, + timeout: 15000 + }, expect.anything()); + }); + + it('Should trigger the cicd to be emitted when a known cicd is requested repo with responce is empty JSON', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo': mockRepoState(null, 0, 'hash0', [{ + provider: CICDProvider.GitLabV4, + cicdUrl: 'https://gitlab.com/keydepth/vscode-git-graph.git', + cicdToken: '' + }]) + }); + mockHttpsResponse(200, JSON.stringify({}), GitLabHeader); + + // Run + let data = await cicdManager.getCICDDetail('/path/to/repo', 'hash0'); + + // Assert + expect.assertions(1); + if (data) { + expect(JSON.parse(data)).toStrictEqual({ + 'id0': { + name: 'string', + status: 'string', + ref: 'string', + web_url: 'string', + event: 'string', + detail: true, + allow_failure: false + } + }); + } + }); + + it('Should trigger the cicd to be emitted when a known cicd is requested repo with responce is Not JSON format', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo1': mockRepoState(null, 0, 'hash0', [{ + provider: CICDProvider.GitLabV4, + cicdUrl: 'https://gitlab.com/keydepth/vscode-git-graph.git', + cicdToken: '' + }]) + }); + mockHttpsResponse(200, 'Not JSON format', GitLabHeader); + + // Run + let data = await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect.assertions(5); + expect(data).toStrictEqual(null); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'GitLab API Error - (200)API Result error. : Unexpected token N in JSON at position 0'); + expect(spyOnLog).toHaveBeenCalledTimes(3); + }); + + it('Should halt fetching the cicd when a known cicd is requested repo with responce is No id JSON format', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitLabGetRspos); + mockHttpsResponse(200, JSON.stringify([ + { + sha: '149ecc50e5c223251f80a0223cfbbd9822307224', + ref: 'main', + status: 'success', + name: 'eslint-sast', + target_url: 'https://gitlab.com/mhutchie/vscode-git-graph/runs/2211653232', + allow_failure: false + } + ]), GitLabHeader); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnHttpsGet).toHaveBeenCalledWith(GitLabHttpsGet, expect.anything()); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitLab'); + expect(spyOnLog).toHaveBeenCalledTimes(4); + }); + + it('Should halt fetching the cicd when the GitLab cicd url request is unsuccessful with Message Body', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitLabGetRspos); + mockHttpsResponse(404, '{"message":"Error Message Body"}', GitLabHeader); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnHttpsGet).toHaveBeenCalledWith(GitLabHttpsGet, expect.anything()); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API Error - (404)Error Message Body'); + expect(spyOnLog).toHaveBeenCalledTimes(2); + }); + + it('Should halt fetching the cicd when the GitLab cicd url request is unsuccessful with No Message Body', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitLabGetRspos); + mockHttpsResponse(404, '{}', GitLabHeader); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnHttpsGet).toHaveBeenCalledWith(GitLabHttpsGet, expect.anything()); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API Error - (404)undefined'); + expect(spyOnLog).toHaveBeenCalledTimes(2); + }); + + it('Should requeue the request when the GitLab API cannot find the commit', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitLabGetRspos); + mockHttpsResponse(200, JSON.stringify([]), GitLabHeader); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnHttpsGet).toHaveBeenCalledWith(GitLabHttpsGet, expect.anything()); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitLab'); + expect(spyOnLog).toHaveBeenCalledTimes(4); + + }); + + it('Should set the GitLab API timeout and requeue the request when the rate limit is reached with cicdToken', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo1': mockRepoState(null, 0, + 'ElZJNHSyT6JDjOGDaVaPiZenu3Xu2MZf', [{ + provider: CICDProvider.GitLabV4, + cicdUrl: 'https://gitlab.com/keydepth/vscode-git-graph.git', + cicdToken: 'aaaaaa' + }]), + '/path/to/repo2': mockRepoState('Custom Name', 0, null, null) + }); + mockHttpsResponse(429, '', Object.assign({}, GitLabHeader, { 'ratelimit-remaining': '0', 'ratelimit-reset': (date.now + 1).toString() })); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + await waitForExpect(() => { + expect(spyOnLog).toHaveBeenCalledWith('GitLab API Rate Limit Reached - Paused fetching from GitLab until the Rate Limit is reset (RateLimit=60(every minute)/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00))'); + }); + expect(cicdManager['queue']['queue']).toStrictEqual([ + { + attempts: 0, + checkAfter: 1587559259000, + cicdConfig: { + cicdToken: 'aaaaaa', + cicdUrl: 'https://gitlab.com/keydepth/vscode-git-graph.git', + provider: CICDProvider.GitLabV4 + }, + detail: true, + hash: '149ecc50e5c223251f80a0223cfbbd9822307224', + maximumStatuses: 1000, + page: -1, + repo: '/path/to/repo1' + } + ]); + expect(spyOnHttpsGet).toHaveBeenCalledWith({ + hostname: 'gitlab.com', + path: '/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100', + headers: { + 'User-Agent': 'vscode-git-graph', + 'PRIVATE-TOKEN': 'aaaaaa' + }, + port: '', + agent: false, + timeout: 15000 + }, expect.anything()); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API Rate Limit Reached - Paused fetching from GitLab until the Rate Limit is reset (RateLimit=60(every minute)/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00))'); + expect(spyOnLog).toHaveBeenCalledTimes(2); + expect(cicdManager['gitLabTimeout']).toBe((date.now + 1) * 1000); + }); + + it('Should set the GitLab API timeout and requeue the request when the API returns a 5xx error', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitLabGetRspos); + mockHttpsResponse(500, '', GitLabHeader); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + await waitForExpect(() => { + expect(cicdManager['gitLabTimeout']).toBe(date.now * 1000 + 600000); + }); + expect(cicdManager['queue']['queue']).toStrictEqual([ + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: '', + cicdUrl: 'https://gitlab.com/keydepth/vscode-git-graph.git', + provider: CICDProvider.GitLabV4 + }, + page: -1, + checkAfter: date.now * 1000 + 600000, + attempts: 0, + detail: true, + hash: '149ecc50e5c223251f80a0223cfbbd9822307224', + maximumStatuses: 1000 + } + ]); + }); + + it('Should set the GitLab API timeout and requeue the request when there is an HTTPS Client Request Error', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitLabGetRspos); + mockHttpsClientRequestErrorEvent({ message: 'Error Message' }); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + await waitForExpect(() => { + expect(cicdManager['gitLabTimeout']).toBe(date.now * 1000 + 300000); + }); + expect(spyOnLog).toHaveBeenCalledWith('GitLab API https Error - Error Message'); + expect(cicdManager['queue']['queue']).toStrictEqual([ + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: '', + cicdUrl: 'https://gitlab.com/keydepth/vscode-git-graph.git', + provider: CICDProvider.GitLabV4 + }, + page: -1, + checkAfter: date.now * 1000 + 300000, + attempts: 0, + detail: true, + hash: '149ecc50e5c223251f80a0223cfbbd9822307224', + maximumStatuses: 1000 + } + ]); + }); + + it('Should set the GitLab API timeout and requeue the request when there is an HTTPS Incoming Message Error', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitLabGetRspos); + mockHttpsIncomingMessageErrorEvent(); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + await waitForExpect(() => { + expect(cicdManager['gitLabTimeout']).toBe(date.now * 1000 + 300000); + }); + expect(spyOnLog).toHaveBeenCalledWith('GitLab API https Error - undefined'); + expect(cicdManager['queue']['queue']).toStrictEqual([ + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: '', + cicdUrl: 'https://gitlab.com/keydepth/vscode-git-graph.git', + provider: CICDProvider.GitLabV4 + }, + page: -1, + checkAfter: date.now * 1000 + 300000, + attempts: 0, + detail: true, + hash: '149ecc50e5c223251f80a0223cfbbd9822307224', + maximumStatuses: 1000 + } + ]); + }); + + it('Should set the GitLab API timeout and requeue the request once when there are multiple HTTPS Error Events', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitLabGetRspos); + mockHttpsMultipleErrorEvents(); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + await waitForExpect(() => { + expect(cicdManager['gitLabTimeout']).toBe(date.now * 1000 + 300000); + }); + expect(spyOnLog).toHaveBeenCalledWith('GitLab API https Error - undefined'); + expect(cicdManager['queue']['queue']).toStrictEqual([ + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: '', + cicdUrl: 'https://gitlab.com/keydepth/vscode-git-graph.git', + provider: CICDProvider.GitLabV4 + }, + page: -1, + checkAfter: date.now * 1000 + 300000, + attempts: 0, + detail: true, + hash: '149ecc50e5c223251f80a0223cfbbd9822307224', + maximumStatuses: 1000 + } + ]); + }); + + it('Should requeue the request when it\'s before the GitLab API timeout', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(GitLabGetRspos); + cicdManager['gitLabTimeout'] = (date.now + 1) * 1000; + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + await waitForExpect(() => { + expect(cicdManager['queue']['queue']).toStrictEqual([ + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: '', + cicdUrl: 'https://gitlab.com/keydepth/vscode-git-graph.git', + provider: CICDProvider.GitLabV4 + }, + page: -1, + checkAfter: (date.now + 1) * 1000, + attempts: 0, + detail: true, + hash: '149ecc50e5c223251f80a0223cfbbd9822307224', + maximumStatuses: 1000 + } + ]); + }); + }); + + it('Should insert requests into the priority queue in the correct order', async () => { + // Setup + spyOnGetRepos.mockReturnValue({ + '/path/to/repo1': mockRepoState(null, 0, + 'ElZJNHSyT6JDjOGDaVaPiZenu3Xu2MZf', [{ + provider: CICDProvider.GitLabV4, + cicdUrl: 'https://gitlab.com/keydepth/vscode-git-graph.git', + cicdToken: '' + }]) + }); + mockHttpsResponse(403, '', Object.assign({}, GitLabHeader, { 'x-ratelimit-remaining': '0', 'x-ratelimit-reset': (date.now + 1).toString() })); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', 'hash0'); + + // Assert + await waitForExpect(() => { + expect(spyOnLog).toHaveBeenCalledWith('GitLab API Error - (403)undefined'); + }); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', 'hash1'); + await cicdManager.getCICDDetail('/path/to/repo1', 'hash2'); + await cicdManager.getCICDDetail('/path/to/repo1', 'hash2'); + cicdManager['queue']['add']('/path/to/repo1', { + provider: CICDProvider.GitLabV4, + cicdUrl: 'https://gitlab.com/keydepth/vscode-git-graph.git', + cicdToken: '' + }, -1, false, true, 'hash3'); + + // Assert + expect(cicdManager['queue']['queue']).toStrictEqual([ + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: '', + cicdUrl: 'https://gitlab.com/keydepth/vscode-git-graph.git', + provider: CICDProvider.GitLabV4 + }, + page: -1, + checkAfter: 0, + attempts: 0, + detail: true, + hash: 'hash1', + maximumStatuses: 1000 + }, + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: '', + cicdUrl: 'https://gitlab.com/keydepth/vscode-git-graph.git', + provider: CICDProvider.GitLabV4 + }, + page: -1, + checkAfter: 0, + attempts: 0, + detail: true, + hash: 'hash2', + maximumStatuses: 1000 + }, + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: '', + cicdUrl: 'https://gitlab.com/keydepth/vscode-git-graph.git', + provider: CICDProvider.GitLabV4 + }, + page: -1, + checkAfter: 1, + attempts: 0, + detail: true, + hash: 'hash3', + maximumStatuses: 1000 + } + ]); + }); + }); + + + + describe('Jenkins', () => { + it('Should fetch a new cicd from Jenkins (HTTPS Remote)', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(JenkinsGetRspos); + mockHttpsResponse(200, JenkinsResponse, JenkinsHeader); + const cicdEvents = waitForEvents(cicdManager, 1); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(await cicdEvents).toStrictEqual([JenkinsCicdEvents]); + expect(spyOnHttpsGet).toHaveBeenCalledWith(JenkinsHttpsGet, expect.anything()); + expect(spyOnSaveCicd).toHaveBeenCalledWith(...JenkinsSaveCicd); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]] page=-1 from Jenkins'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'Jenkins API - (200)https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]]'); + expect(spyOnLog).toHaveBeenCalledTimes(2); + }); + + it('Should fetch a new cicd from Jenkins (HTTPS Remote) with port', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo1': mockRepoState(null, 0, + 'ElZJNHSyT6JDjOGDaVaPiZenu3Xu2MZf', [{ + provider: CICDProvider.JenkinsV2, + cicdUrl: 'https://jenkins.net:80/job/job01/job/MultiBranch/job/master/3/', + cicdToken: '' + }]), + '/path/to/repo2': mockRepoState('Custom Name', 0, null, null) + }); + mockHttpsResponse(200, JenkinsResponse, JenkinsHeader); + const cicdEvents = waitForEvents(cicdManager, 1); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(await cicdEvents).toStrictEqual([JenkinsCicdEvents]); + expect(spyOnHttpsGet).toHaveBeenCalledWith({ + hostname: 'jenkins.net', + path: '/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]]', + headers: { + 'User-Agent': 'vscode-git-graph' + }, + port: '80', + agent: false, + timeout: 15000 + }, expect.anything()); + expect(spyOnSaveCicd).toHaveBeenCalledWith(...JenkinsSaveCicd); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]] page=-1 from Jenkins'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'Jenkins API - (200)https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]]'); + expect(spyOnLog).toHaveBeenCalledTimes(2); + }); + + it('Should fetch a new cicd from Jenkins (HTTP Remote)', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo1': mockRepoState(null, 0, + 'ElZJNHSyT6JDjOGDaVaPiZenu3Xu2MZf', [{ + provider: CICDProvider.JenkinsV2, + cicdUrl: 'http://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + cicdToken: '' + }]), + '/path/to/repo2': mockRepoState('Custom Name', 0, null, null) + }); + mockHttpResponse(200, JSON.stringify( + { + _class: 'org.jenkinsci.plugins.workflow.job.WorkflowJob', + builds: [ + { + _class: 'org.jenkinsci.plugins.workflow.job.WorkflowRun', + actions: [ + { + _class: 'hudson.plugins.git.util.BuildData', + lastBuiltRevision: { + branch: [ + { + SHA1: '149ecc50e5c223251f80a0223cfbbd9822307224', + name: 'master' + } + ] + } + } + ], + fullDisplayName: 'job01 » MultiBranch » master #3', + id: '3', + result: 'SUCCESS', + timestamp: 1620716982997, + url: 'http://jenkins.net/job/job01/job/MultiBranch/job/master/3/' + } + ] + } + ), JenkinsHeader); + const cicdEvents = waitForEvents(cicdManager, 1); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(await cicdEvents).toStrictEqual([{ + 'cicdDataSaves': { + '3': { + name: 'job01 » MultiBranch » master #3', + status: 'SUCCESS', + ref: 'master', + web_url: 'http://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + event: '', + detail: true, + allow_failure: false + } + }, + 'hash': '149ecc50e5c223251f80a0223cfbbd9822307224', + 'repo': '/path/to/repo1' + }]); + expect(spyOnHttpGet).toHaveBeenCalledWith(JenkinsHttpsGet, expect.anything()); + expect(spyOnSaveCicd).toHaveBeenCalledWith( + '/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224', '3', + { + name: 'job01 » MultiBranch » master #3', + status: 'SUCCESS', + ref: 'master', + web_url: 'http://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + event: '', + detail: true, + allow_failure: false + }); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for http://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]] page=-1 from Jenkins'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'Jenkins API - (200)http://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]]'); + expect(spyOnLog).toHaveBeenCalledTimes(2); + }); + + it('Should fetch a new cicd from Jenkins (SSH Remote)', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo1': mockRepoState(null, 0, + 'ElZJNHSyT6JDjOGDaVaPiZenu3Xu2MZf', [{ + provider: CICDProvider.JenkinsV2, + cicdUrl: 'git@jenkins.net:keydepth/vscode-git-graph.git', + cicdToken: '' + }]), + '/path/to/repo2': mockRepoState('Custom Name', 0, null, null) + }); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD is not match URL (git@jenkins.net:keydepth/vscode-git-graph.git) for Jenkins'); + expect(spyOnLog).toHaveBeenCalledTimes(1); + }); + + it('Should fetch a new cicd from Jenkins (HTTPS Remote) not detail', async () => { + // Setup + jest.useFakeTimers(); + spyOnGetRepos.mockReturnValueOnce(JenkinsGetRspos); + mockHttpsResponse(200, JSON.stringify( + { + _class: 'org.jenkinsci.plugins.workflow.job.WorkflowJob', + builds: [ + { + _class: 'org.jenkinsci.plugins.workflow.job.WorkflowRun', + actions: [ + { + _class: 'hudson.plugins.git.util.BuildData', + lastBuiltRevision: { + branch: [ + { + SHA1: 'b9112e60f5fb3d8bc2a387840577b4756a12f357', + name: 'master' + } + ] + } + } + ], + fullDisplayName: 'job01 » MultiBranch » master #3', + id: '3', + result: 'SUCCESS', + timestamp: 1620716982997, + url: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/' + } + ] + } + ), Object.assign({}, JenkinsHeader, { + 'x-total': '1', + 'x-total-pages': '1' + })); + mockHttpsResponse(200, JenkinsResponse, Object.assign({}, JenkinsHeader, { + 'x-total': '1', + 'x-total-pages': '1' + })); + const cicdEvents = waitForEvents(cicdManager, 2, true); + cicdManager['queue']['queue'] = [{ + repo: '/path/to/repo1', + cicdConfig: { + provider: CICDProvider.JenkinsV2, + cicdUrl: 'https://jenkins.net/job/job02/job/MultiBranch/job/master/3/', + cicdToken: '' + }, + page: -1, + checkAfter: 0, + attempts: 0, + detail: false, + hash: 'b9112e60f5fb3d8bc2a387840577b4756a12f357', + maximumStatuses: 1000 + }]; + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', 'b9112e60f5fb3d8bc2a387840577b4756a12f357'); + cicdManager['queue']['itemsAvailableCallback'](); + + // Assert + expect(await cicdEvents).toStrictEqual([{ + 'cicdDataSaves': { + '3': { + name: 'job01 » MultiBranch » master #3', + status: 'SUCCESS', + ref: 'master', + web_url: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + event: '', + detail: false, + allow_failure: false + } + }, + 'hash': 'b9112e60f5fb3d8bc2a387840577b4756a12f357', + 'repo': '/path/to/repo1' + }, JenkinsCicdEvents]); + expect(spyOnHttpsGet).toHaveBeenCalledWith({ + hostname: 'jenkins.net', + path: '/job/job02/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]]', + headers: { + 'User-Agent': 'vscode-git-graph' + }, + port: '', + agent: false, + timeout: 15000 + }, expect.anything()); + expect(spyOnSaveCicd).toHaveBeenCalledWith( + '/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224', '3', + { + name: 'job01 » MultiBranch » master #3', + status: 'SUCCESS', + ref: 'master', + web_url: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + event: '', + detail: true, + allow_failure: false + }); + expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]] page=-1 from Jenkins'); + expect(spyOnLog).toHaveBeenCalledWith('Jenkins API - (200)https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]]'); + expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]] page=-1 from Jenkins'); + expect(spyOnLog).toHaveBeenCalledWith('Jenkins API - (200)https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]]'); + expect(spyOnLog).toHaveBeenCalledTimes(4); + jest.useRealTimers(); + }); + + it('Should fetch a new cicd from Jenkins (HTTPS Remote) not detail with no conclusion', async () => { + // Setup + jest.useFakeTimers(); + spyOnGetRepos.mockReturnValueOnce(JenkinsGetRspos); + mockHttpsResponse(200, JSON.stringify( + { + _class: 'org.jenkinsci.plugins.workflow.job.WorkflowJob', + builds: [ + { + _class: 'org.jenkinsci.plugins.workflow.job.WorkflowRun', + actions: [ + { + _class: 'hudson.plugins.git.util.BuildData', + lastBuiltRevision: { + branch: [ + { + SHA1: '149ecc50e5c223251f80a0223cfbbd9822307224', + name: 'master' + } + ] + } + } + ], + fullDisplayName: 'job02 » MultiBranch » master #23', + id: '23', + result: 'pending', + timestamp: 1620716982997, + url: 'https://jenkins.net/job/job02/job/MultiBranch/job/master/23/' + } + ] + } + ), Object.assign({}, JenkinsHeader, { + 'x-total': '1', + 'x-total-pages': '1' + })); + mockHttpsResponse(200, JenkinsResponse, Object.assign({}, JenkinsHeader, { + 'x-total': '1', + 'x-total-pages': '1' + })); + const cicdEvents = waitForEvents(cicdManager, 2, true); + cicdManager['queue']['queue'] = [{ + repo: '/path/to/repo1', + cicdConfig: { + provider: CICDProvider.JenkinsV2, + cicdUrl: 'https://jenkins.net/job/job02/job/MultiBranch/job/master/23/', + cicdToken: '' + }, + page: -1, + checkAfter: 0, + attempts: 0, + detail: false, + hash: 'b9112e60f5fb3d8bc2a387840577b4756a12f357', + maximumStatuses: 1000 + }]; + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', 'b9112e60f5fb3d8bc2a387840577b4756a12f357'); + cicdManager['queue']['itemsAvailableCallback'](); + + // Assert + expect(await cicdEvents).toStrictEqual([{ + 'cicdDataSaves': { + '23': { + name: 'job02 » MultiBranch » master #23', + status: 'pending', + ref: 'master', + web_url: 'https://jenkins.net/job/job02/job/MultiBranch/job/master/23/', + event: '', + detail: false, + allow_failure: false + }, + '3': { + name: 'job01 » MultiBranch » master #3', + status: 'SUCCESS', + ref: 'master', + web_url: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + event: '', + detail: true, + allow_failure: false + } + }, + 'hash': '149ecc50e5c223251f80a0223cfbbd9822307224', + 'repo': '/path/to/repo1' + }, { + 'cicdDataSaves': { + '23': { + name: 'job02 » MultiBranch » master #23', + status: 'pending', + ref: 'master', + web_url: 'https://jenkins.net/job/job02/job/MultiBranch/job/master/23/', + event: '', + detail: false, + allow_failure: false + }, + '3': { + name: 'job01 » MultiBranch » master #3', + status: 'SUCCESS', + ref: 'master', + web_url: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + event: '', + detail: true, + allow_failure: false + } + }, + 'hash': '149ecc50e5c223251f80a0223cfbbd9822307224', + 'repo': '/path/to/repo1' + }]); + expect(spyOnHttpsGet).toHaveBeenCalledWith({ + hostname: 'jenkins.net', + path: '/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]]', + headers: { + 'User-Agent': 'vscode-git-graph' + }, + port: '', + agent: false, + timeout: 15000 + }, expect.anything()); + expect(spyOnSaveCicd).toHaveBeenCalledWith( + '/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224', '23', + { + name: 'job02 » MultiBranch » master #23', + status: 'pending', + ref: 'master', + web_url: 'https://jenkins.net/job/job02/job/MultiBranch/job/master/23/', + event: '', + detail: false, + allow_failure: false + }); + expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]] page=-1 from Jenkins'); + expect(spyOnLog).toHaveBeenCalledWith('Jenkins API - (200)https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]]'); + expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://jenkins.net/job/job02/job/MultiBranch/job/master/23/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]] page=-1 from Jenkins'); + expect(spyOnLog).toHaveBeenCalledWith('Jenkins API - (200)https://jenkins.net/job/job02/job/MultiBranch/job/master/23/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]]'); + expect(spyOnLog).toHaveBeenCalledTimes(4); + jest.useRealTimers(); + }); + + it('Should fetch a new cicd from Jenkins (HTTPS Remote) with x-jenkins error header (number)', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(JenkinsGetRspos); + mockHttpsResponse(200, JenkinsResponse, Object.assign({}, JenkinsHeader, { + 'x-jenkins': '1.1.1' + })); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnHttpsGet).toHaveBeenCalledWith(JenkinsHttpsGet, expect.anything()); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]] page=-1 from Jenkins'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'Jenkins API - (200)https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]]'); + expect(spyOnLog).toHaveBeenCalledTimes(2); + }); + + it('Should fetch a new cicd from Jenkins (HTTPS Remote) with builds format error header (number)', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(JenkinsGetRspos); + mockHttpsResponse(200, JSON.stringify( + { + _class: 'org.jenkinsci.plugins.workflow.job.WorkflowJob', + builds: {} + } + ), JenkinsHeader); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnHttpsGet).toHaveBeenCalledWith(JenkinsHttpsGet, expect.anything()); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]] page=-1 from Jenkins'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'Jenkins API - (200)https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]]'); + expect(spyOnLog).toHaveBeenCalledTimes(2); + }); + + it('Should fetch a new cicd from Jenkins (HTTPS Remote) with lastBuiltRevision format error header (number)', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(JenkinsGetRspos); + mockHttpsResponse(200, JSON.stringify( + { + _class: 'org.jenkinsci.plugins.workflow.job.WorkflowJob', + builds: [ + { + _class: 'org.jenkinsci.plugins.workflow.job.WorkflowRun', + actions: [ + { + _class: 'hudson.plugins.git.util.BuildData' + } + ], + fullDisplayName: 'job01 » MultiBranch » master #3', + id: '3', + result: 'SUCCESS', + timestamp: 1620716982997, + url: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/' + } + ] + } + ), JenkinsHeader); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnHttpsGet).toHaveBeenCalledWith(JenkinsHttpsGet, expect.anything()); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]] page=-1 from Jenkins'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'Jenkins API - (200)https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]]'); + expect(spyOnLog).toHaveBeenCalledTimes(2); + }); + + it('Should fetch a new cicd from Jenkins (HTTPS Remote) no emmit', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(JenkinsGetRspos); + mockHttpsResponse(200, JenkinsResponse, JenkinsHeader); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnLog).toHaveBeenCalledTimes(2); + }); + + it('Should fetch a new cicd from Jenkins (URL is Not Match)', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo1': mockRepoState(null, 0, + 'ElZJNHSyT6JDjOGDaVaPiZenu3Xu2MZf', [{ + provider: CICDProvider.JenkinsV2, + cicdUrl: 'ftp://jenkins.net/keydepth/vscode-git-graph.git', + cicdToken: '' + }]), + '/path/to/repo2': mockRepoState('Custom Name', 0, null, null) + }); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD is not match URL (ftp://jenkins.net/keydepth/vscode-git-graph.git) for Jenkins'); + expect(spyOnLog).toHaveBeenCalledTimes(1); + }); + + it('Should fetch a new cicd from Jenkins (Bad URL)', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo1': mockRepoState(null, 0, + 'ElZJNHSyT6JDjOGDaVaPiZenu3Xu2MZf', [{ + provider: CICDProvider.JenkinsV2, + cicdUrl: 'jenkins.net/keydepth/vscode-git-graph.git', + cicdToken: '' + }]), + '/path/to/repo2': mockRepoState('Custom Name', 0, null, null) + }); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD is not match URL (jenkins.net/keydepth/vscode-git-graph.git) for Jenkins'); + expect(spyOnLog).toHaveBeenCalledTimes(1); + }); + + it('Should fetch a new cicd from Jenkins (No url)', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(JenkinsGetRspos); + mockHttpsResponse(200, JSON.stringify( + { + _class: 'org.jenkinsci.plugins.workflow.job.WorkflowJob', + builds: [ + { + _class: 'org.jenkinsci.plugins.workflow.job.WorkflowRun', + actions: [ + { + _class: 'hudson.plugins.git.util.BuildData', + lastBuiltRevision: { + branch: [ + { + SHA1: '149ecc50e5c223251f80a0223cfbbd9822307224', + name: 'master' + } + ] + } + } + ], + fullDisplayName: 'job01 » MultiBranch » master #3', + id: '3', + result: 'SUCCESS', + timestamp: 1620716982997 + } + ] + } + ), JenkinsHeader); + const cicdEvents = waitForEvents(cicdManager, 1); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(await cicdEvents).toStrictEqual([{ + 'cicdDataSaves': { + '3': { + name: 'job01 » MultiBranch » master #3', + status: 'SUCCESS', + ref: 'master', + web_url: '', + event: '', + detail: true, + allow_failure: false + } + }, + 'hash': '149ecc50e5c223251f80a0223cfbbd9822307224', + 'repo': '/path/to/repo1' + }]); + expect(spyOnHttpsGet).toHaveBeenCalledWith(JenkinsHttpsGet, expect.anything()); + expect(spyOnSaveCicd).toHaveBeenCalledWith( + '/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224', '3', + { + name: 'job01 » MultiBranch » master #3', + status: 'SUCCESS', + ref: 'master', + web_url: '', + event: '', + detail: true, + allow_failure: false + }); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]] page=-1 from Jenkins'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'Jenkins API - (200)https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]]'); + expect(spyOnLog).toHaveBeenCalledTimes(2); + }); + + it('Should trigger the cicd to be emitted when a known cicd is requested repo', async () => { + // Setup + const cicdEvents = waitForEvents(cicdManager, 1); + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo': mockRepoState(null, 0, 'hash0', [{ + provider: CICDProvider.JenkinsV2, + cicdUrl: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + cicdToken: '' + }]) + }); + mockHttpsResponse(200, JenkinsResponse, JenkinsHeader); + + // Run + let data = await cicdManager.getCICDDetail('/path/to/repo', 'hash0'); + + // Assert + expect.assertions(2); + if (data) { + expect(JSON.parse(data)).toStrictEqual({ + 'id0': { + name: 'string', + status: 'string', + ref: 'string', + web_url: 'string', + event: 'string', + detail: true, + allow_failure: false + } + }); + } + expect(await cicdEvents).toStrictEqual([{ + 'cicdDataSaves': { + '3': { + name: 'job01 » MultiBranch » master #3', + status: 'SUCCESS', + ref: 'master', + web_url: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + event: '', + detail: true, + allow_failure: false + } + }, + 'hash': '149ecc50e5c223251f80a0223cfbbd9822307224', + 'repo': '/path/to/repo' + }]); + }); + + it('Should trigger the cicd to be emitted when a known cicd is requested repo with cicdToken', async () => { + // Setup + const cicdEvents = waitForEvents(cicdManager, 1); + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo1': mockRepoState(null, 0, 'nonce', [{ + provider: CICDProvider.JenkinsV2, + cicdUrl: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + cicdToken: 'user:aaaaaa' + }]) + }); + mockHttpsResponse(200, JenkinsResponse, JenkinsHeader); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', 'hash0'); + + // Assert + expect(await cicdEvents).toStrictEqual([{ + 'cicdDataSaves': { + '3': { + name: 'job01 » MultiBranch » master #3', + status: 'SUCCESS', + ref: 'master', + web_url: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + event: '', + detail: true, + allow_failure: false + } + }, + 'hash': '149ecc50e5c223251f80a0223cfbbd9822307224', + 'repo': '/path/to/repo1' + }]); + expect(spyOnHttpsGet).toHaveBeenCalledWith({ + hostname: 'jenkins.net', + path: '/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]]', + headers: { + 'User-Agent': 'vscode-git-graph', + 'Authorization': 'Basic ' + new Buffer('user:aaaaaa').toString('base64') + }, + port: '', + agent: false, + timeout: 15000 + }, expect.anything()); + }); + + it('Should trigger the cicd to be emitted when a known cicd is requested repo with responce is empty JSON', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo': mockRepoState(null, 0, 'hash0', [{ + provider: CICDProvider.JenkinsV2, + cicdUrl: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + cicdToken: '' + }]) + }); + mockHttpsResponse(200, JSON.stringify({}), JenkinsHeader); + + // Run + let data = await cicdManager.getCICDDetail('/path/to/repo', 'hash0'); + + // Assert + expect.assertions(5); + if (data) { + expect(JSON.parse(data)).toStrictEqual({ + 'id0': { + name: 'string', + status: 'string', + ref: 'string', + web_url: 'string', + event: 'string', + detail: true, + allow_failure: false + } + }); + } + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]] page=-1 from Jenkins'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'Jenkins API - (200)https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]]'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'Jenkins API Error - (200)API Result error. : Cannot read property \'length\' of undefined'); + expect(spyOnLog).toHaveBeenCalledTimes(3); + + }); + + it('Should trigger the cicd to be emitted when a known cicd is requested repo with responce is Not JSON format', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo1': mockRepoState(null, 0, 'hash0', [{ + provider: CICDProvider.JenkinsV2, + cicdUrl: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + cicdToken: '' + }]) + }); + mockHttpsResponse(200, 'Not JSON format', JenkinsHeader); + + // Run + let data = await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect.assertions(5); + expect(data).toStrictEqual(null); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]] page=-1 from Jenkins'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'Jenkins API - (200)https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]]'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'Jenkins API Error - (200)API Result error. : Unexpected token N in JSON at position 0'); + expect(spyOnLog).toHaveBeenCalledTimes(3); + }); + + it('Should halt fetching the cicd when a known cicd is requested repo with responce is No id JSON format', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(JenkinsGetRspos); + mockHttpsResponse(200, JSON.stringify([ + { + sha: '149ecc50e5c223251f80a0223cfbbd9822307224', + ref: 'main', + status: 'success', + name: 'eslint-sast', + target_url: 'https://jenkins.net/mhutchie/vscode-git-graph/runs/2211653232', + allow_failure: false + } + ]), JenkinsHeader); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnHttpsGet).toHaveBeenCalledWith(JenkinsHttpsGet, expect.anything()); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]] page=-1 from Jenkins'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'Jenkins API - (200)https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]]'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'Jenkins API Error - (200)API Result error. : Cannot read property \'length\' of undefined'); + expect(spyOnLog).toHaveBeenCalledTimes(3); + }); + + it('Should halt fetching the cicd when the Jenkins cicd url request is unsuccessful with Message Body', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(JenkinsGetRspos); + mockHttpsResponse(404, '{"message":"Error Message Body"}', JenkinsHeader); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnHttpsGet).toHaveBeenCalledWith(JenkinsHttpsGet, expect.anything()); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]] page=-1 from Jenkins'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'Jenkins API Error - (404)Error Message Body'); + expect(spyOnLog).toHaveBeenCalledTimes(2); + }); + + it('Should halt fetching the cicd when the Jenkins cicd url request is unsuccessful with No Message Body', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(JenkinsGetRspos); + mockHttpsResponse(404, '{}', JenkinsHeader); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnHttpsGet).toHaveBeenCalledWith(JenkinsHttpsGet, expect.anything()); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]] page=-1 from Jenkins'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'Jenkins API Error - (404)undefined'); + expect(spyOnLog).toHaveBeenCalledTimes(2); + }); + + it('Should requeue the request when the Jenkins API cannot find the commit', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(JenkinsGetRspos); + mockHttpsResponse(200, JSON.stringify([]), JenkinsHeader); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + expect(spyOnHttpsGet).toHaveBeenCalledWith(JenkinsHttpsGet, expect.anything()); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]] page=-1 from Jenkins'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'Jenkins API - (200)https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]]'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'Jenkins API Error - (200)API Result error. : Cannot read property \'length\' of undefined'); + expect(spyOnLog).toHaveBeenCalledTimes(3); + + }); + + it('Should set the Jenkins API timeout and requeue the request when the rate limit is reached with cicdToken', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce({ + '/path/to/repo1': mockRepoState(null, 0, + 'ElZJNHSyT6JDjOGDaVaPiZenu3Xu2MZf', [{ + provider: CICDProvider.JenkinsV2, + cicdUrl: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + cicdToken: 'user:aaaaaa' + }]), + '/path/to/repo2': mockRepoState('Custom Name', 0, null, null) + }); + mockHttpsResponse(429, '', Object.assign({}, JenkinsHeader, { 'ratelimit-remaining': '0', 'ratelimit-reset': (date.now + 1).toString() })); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + await waitForExpect(() => { + expect(spyOnLog).toHaveBeenCalledWith('Jenkins API Rate Limit Reached - Paused fetching from Jenkins until the Rate Limit is reset (RateLimit=undefined(every minute)/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00))'); + }); + expect(cicdManager['queue']['queue']).toStrictEqual([ + { + attempts: 0, + checkAfter: 1587559259000, + cicdConfig: { + cicdToken: 'user:aaaaaa', + cicdUrl: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + provider: CICDProvider.JenkinsV2 + }, + detail: true, + hash: '149ecc50e5c223251f80a0223cfbbd9822307224', + maximumStatuses: 1000, + page: -1, + repo: '/path/to/repo1' + } + ]); + expect(spyOnHttpsGet).toHaveBeenCalledWith({ + hostname: 'jenkins.net', + path: '/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]]', + headers: { + 'User-Agent': 'vscode-git-graph', + 'Authorization': 'Basic ' + new Buffer('user:aaaaaa').toString('base64') + }, + port: '', + agent: false, + timeout: 15000 + }, expect.anything()); + expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]] page=-1 from Jenkins'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'Jenkins API Rate Limit Reached - Paused fetching from Jenkins until the Rate Limit is reset (RateLimit=undefined(every minute)/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00))'); + expect(spyOnLog).toHaveBeenCalledTimes(2); + expect(cicdManager['jenkinsTimeout']).toBe((date.now + 1) * 1000); + }); + + it('Should set the Jenkins API timeout and requeue the request when the API returns a 5xx error', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(JenkinsGetRspos); + mockHttpsResponse(500, '', JenkinsHeader); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + await waitForExpect(() => { + expect(cicdManager['jenkinsTimeout']).toBe(date.now * 1000 + 600000); + }); + expect(cicdManager['queue']['queue']).toStrictEqual([ + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: '', + cicdUrl: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + provider: CICDProvider.JenkinsV2 + }, + page: -1, + checkAfter: date.now * 1000 + 600000, + attempts: 0, + detail: true, + hash: '149ecc50e5c223251f80a0223cfbbd9822307224', + maximumStatuses: 1000 + } + ]); + }); + + it('Should set the Jenkins API timeout and requeue the request when there is an HTTPS Client Request Error', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(JenkinsGetRspos); + mockHttpsClientRequestErrorEvent({ message: 'Error Message' }); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + await waitForExpect(() => { + expect(cicdManager['jenkinsTimeout']).toBe(date.now * 1000 + 300000); + }); + expect(spyOnLog).toHaveBeenCalledWith('Jenkins API https Error - Error Message'); + expect(cicdManager['queue']['queue']).toStrictEqual([ + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: '', + cicdUrl: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + provider: CICDProvider.JenkinsV2 + }, + page: -1, + checkAfter: date.now * 1000 + 300000, + attempts: 0, + detail: true, + hash: '149ecc50e5c223251f80a0223cfbbd9822307224', + maximumStatuses: 1000 + } + ]); + }); + + it('Should set the Jenkins API timeout and requeue the request when there is an HTTPS Incoming Message Error', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(JenkinsGetRspos); + mockHttpsIncomingMessageErrorEvent(); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + await waitForExpect(() => { + expect(cicdManager['jenkinsTimeout']).toBe(date.now * 1000 + 300000); + }); + expect(spyOnLog).toHaveBeenCalledWith('Jenkins API https Error - undefined'); + expect(cicdManager['queue']['queue']).toStrictEqual([ + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: '', + cicdUrl: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + provider: CICDProvider.JenkinsV2 + }, + page: -1, + checkAfter: date.now * 1000 + 300000, + attempts: 0, + detail: true, + hash: '149ecc50e5c223251f80a0223cfbbd9822307224', + maximumStatuses: 1000 + } + ]); + }); + + it('Should set the Jenkins API timeout and requeue the request once when there are multiple HTTPS Error Events', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(JenkinsGetRspos); + mockHttpsMultipleErrorEvents(); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + await waitForExpect(() => { + expect(cicdManager['jenkinsTimeout']).toBe(date.now * 1000 + 300000); + }); + expect(spyOnLog).toHaveBeenCalledWith('Jenkins API https Error - undefined'); + expect(cicdManager['queue']['queue']).toStrictEqual([ + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: '', + cicdUrl: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + provider: CICDProvider.JenkinsV2 + }, + page: -1, + checkAfter: date.now * 1000 + 300000, + attempts: 0, + detail: true, + hash: '149ecc50e5c223251f80a0223cfbbd9822307224', + maximumStatuses: 1000 + } + ]); + }); + + it('Should requeue the request when it\'s before the Jenkins API timeout', async () => { + // Setup + spyOnGetRepos.mockReturnValueOnce(JenkinsGetRspos); + cicdManager['jenkinsTimeout'] = (date.now + 1) * 1000; + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', '149ecc50e5c223251f80a0223cfbbd9822307224'); + + // Assert + await waitForExpect(() => { + expect(cicdManager['queue']['queue']).toStrictEqual([ + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: '', + cicdUrl: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + provider: CICDProvider.JenkinsV2 + }, + page: -1, + checkAfter: (date.now + 1) * 1000, + attempts: 0, + detail: true, + hash: '149ecc50e5c223251f80a0223cfbbd9822307224', + maximumStatuses: 1000 + } + ]); + }); + }); + + it('Should insert requests into the priority queue in the correct order', async () => { + // Setup + spyOnGetRepos.mockReturnValue({ + '/path/to/repo1': mockRepoState(null, 0, + 'ElZJNHSyT6JDjOGDaVaPiZenu3Xu2MZf', [{ + provider: CICDProvider.JenkinsV2, + cicdUrl: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + cicdToken: '' + }]) + }); + mockHttpsResponse(403, '', Object.assign({}, JenkinsHeader, { 'x-ratelimit-remaining': '0', 'x-ratelimit-reset': (date.now + 1).toString() })); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', 'hash0'); + + // Assert + await waitForExpect(() => { + expect(spyOnLog).toHaveBeenCalledWith('Jenkins API Error - (403)undefined'); + }); + + // Run + await cicdManager.getCICDDetail('/path/to/repo1', 'hash1'); + await cicdManager.getCICDDetail('/path/to/repo1', 'hash2'); + await cicdManager.getCICDDetail('/path/to/repo1', 'hash2'); + cicdManager['queue']['add']('/path/to/repo1', { + provider: CICDProvider.JenkinsV2, + cicdUrl: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + cicdToken: '' + }, -1, false, true, 'hash3'); + + // Assert + expect(cicdManager['queue']['queue']).toStrictEqual([ + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: '', + cicdUrl: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + provider: CICDProvider.JenkinsV2 + }, + page: -1, + checkAfter: 0, + attempts: 0, + detail: true, + hash: 'hash1', + maximumStatuses: 1000 + }, + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: '', + cicdUrl: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + provider: CICDProvider.JenkinsV2 + }, + page: -1, + checkAfter: 0, + attempts: 0, + detail: true, + hash: 'hash2', + maximumStatuses: 1000 + }, + { + repo: '/path/to/repo1', + cicdConfig: { + cicdToken: '', + cicdUrl: 'https://jenkins.net/job/job01/job/MultiBranch/job/master/3/', + provider: CICDProvider.JenkinsV2 + }, + page: -1, + checkAfter: 1, + attempts: 0, + detail: true, + hash: 'hash3', + maximumStatuses: 1000 + } + ]); + }); + }); + + }); + + + describe('clearCache', () => { + let spyOnClearCICDCache: jest.SpyInstance; + beforeAll(() => { + spyOnClearCICDCache = jest.spyOn(extensionState, 'clearCICDCache'); + }); + + it('Should clear the cache of cicd', async () => { + // Setup + spyOnClearCICDCache.mockResolvedValueOnce(null); + + // Run + cicdManager.clearCache(); + + // Assert + expect(cicdManager['cicds']).toStrictEqual({}); + expect(spyOnClearCICDCache).toHaveBeenCalledTimes(1); + }); + + }); +}); + + +function mockHttpsResponse(statusCode: number, resData: string, headers: { [header: string]: string | number } = {}) { + spyOnHttpsGet.mockImplementationOnce((_: string | https.RequestOptions | URL, callback: (res: IncomingMessage) => void): ClientRequest => { + if (callback) { + const callbacks: { [event: string]: (...args: any) => void } = {}; + const message: IncomingMessage = { + statusCode: statusCode, + headers: Object.assign({}, headers), + on: (event: string, listener: () => void) => { + callbacks[event] = listener; + return message; + } + }; + callback(message); + callbacks['data'](Buffer.from(resData)); + callbacks['end'](); + } + return ({ + on: jest.fn() + }) as any as ClientRequest; + }); +} + +function mockHttpResponse(statusCode: number, resData: string, headers: { [header: string]: string } = {}) { + spyOnHttpGet.mockImplementationOnce((_: string | http.RequestOptions | URL, callback: (res: IncomingMessage) => void): ClientRequest => { + if (callback) { + const callbacks: { [event: string]: (...args: any) => void } = {}; + const message: IncomingMessage = { + statusCode: statusCode, + headers: Object.assign({}, headers), + on: (event: string, listener: () => void) => { + callbacks[event] = listener; + return message; + } + }; + callback(message); + callbacks['data'](Buffer.from(resData)); + callbacks['end'](); + } + return ({ + on: jest.fn() + }) as any as ClientRequest; + }); +} + +function mockHttpsClientRequestErrorEvent(err: any) { + spyOnHttpsGet.mockImplementationOnce((_1: string | https.RequestOptions | URL, _2: (res: IncomingMessage) => void): ClientRequest => { + const request: ClientRequest = { + on: (event: string, callback: (err: any) => void) => { + if (event === 'error') { + callback(err); + } + return request; + } + }; + return request; + }); +} + +function mockHttpsIncomingMessageErrorEvent() { + spyOnHttpsGet.mockImplementationOnce((_: string | https.RequestOptions | URL, callback: (res: IncomingMessage) => void): ClientRequest => { + const callbacks: { [event: string]: (...args: any) => void } = {}; + const message: IncomingMessage = { + on: (event: string, listener: () => void) => { + callbacks[event] = listener; + return message; + } + }; + callback(message); + callbacks['error'](); + return ({ + on: jest.fn() + }) as any as ClientRequest; + }); +} + +function mockHttpsMultipleErrorEvents() { + spyOnHttpsGet.mockImplementationOnce((_: string | https.RequestOptions | URL, callback: (res: IncomingMessage) => void): ClientRequest => { + const callbacks: { [event: string]: (...args: any) => void } = {}; + const message: IncomingMessage = { + on: (event: string, listener: () => void) => { + callbacks[event] = listener; + return message; + } + }; + callback(message); + callbacks['error'](); + + const request: ClientRequest = { + on: (event: string, callback: () => void) => { + if (event === 'error') { + callback(); + } + return request; + } + }; + return request; + }); +} + +function waitForEvents(cicdManager: CicdManager, n: number, runPendingTimers = false) { + return new Promise((resolve) => { + const events: CICDEvent[] = []; + cicdManager.onCICD((event) => { + events.push(event); + if (runPendingTimers) { + jest.runOnlyPendingTimers(); + } + if (events.length === n) { + resolve(events); + } + }); + }); +} + +function mockRepoState(name: string | null, workspaceFolderIndex: number | null, cicdNonce: string | null, cicdConfigs: CICDConfig[] | null) { + return Object.assign({}, DEFAULT_REPO_STATE, { name: name, workspaceFolderIndex: workspaceFolderIndex, cicdNonce: cicdNonce, cicdConfigs: cicdConfigs }); +} + diff --git a/tests/commands.test.ts b/tests/commands.test.ts index 06c9b93c..e33f88eb 100644 --- a/tests/commands.test.ts +++ b/tests/commands.test.ts @@ -578,6 +578,26 @@ describe('CommandManager', () => { }); }); + describe('git-graph.clearCICDCache', () => { + let spyOnClearCache: jest.SpyInstance; + beforeAll(() => { + spyOnClearCache = jest.spyOn(cicdManager, 'clearCache'); + }); + + it('Should clear the cicd cache', async () => { + // Setup + spyOnClearCache.mockResolvedValueOnce(null); + + // Run + vscode.commands.executeCommand('git-graph.clearCICDCache'); + + // Assert + await waitForExpect(() => { + expect(spyOnClearCache).toBeCalledTimes(1); + }); + }); + }); + describe('git-graph.fetch', () => { let spyOnGetLastActiveRepo: jest.SpyInstance; beforeAll(() => { diff --git a/tests/extensionState.test.ts b/tests/extensionState.test.ts index b76185e2..b2c39aca 100644 --- a/tests/extensionState.test.ts +++ b/tests/extensionState.test.ts @@ -5,7 +5,9 @@ jest.mock('fs'); import * as fs from 'fs'; import { ExtensionState } from '../src/extensionState'; -import { BooleanOverride, FileViewType, GitGraphViewGlobalState, GitGraphViewWorkspaceState, GitRepoState, RepoCommitOrdering } from '../src/types'; +import { BooleanOverride, CICDDataSave, CICDProvider, FileViewType, GitGraphViewGlobalState, GitGraphViewWorkspaceState, GitRepoState, RepoCommitOrdering } from '../src/types'; +import * as utils from '../src/utils'; +import * as crypto from 'crypto'; import { GitExecutable } from '../src/utils'; import { EventEmitter } from '../src/utils/event'; @@ -369,6 +371,193 @@ describe('ExtensionState', () => { // Assert expect(result).toStrictEqual({}); }); + + it('Should return the stored repositories with decrypt', () => { + // Setup + const cicdConfigsIn = [{ + provider: CICDProvider.GitHubV3, + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + cicdToken: '31613262336334643565366631613262:aedfce60882614d275f01b86aa151bcd' + }]; + const cicdConfigsOut = [{ + provider: CICDProvider.GitHubV3, + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + cicdToken: 'token' + }]; + const repoState: GitRepoState = { + cdvDivider: 0.5, + cdvHeight: 250, + columnWidths: null, + commitOrdering: RepoCommitOrdering.AuthorDate, + fileViewType: FileViewType.List, + hideRemotes: [], + includeCommitsMentionedByReflogs: BooleanOverride.Enabled, + issueLinkingConfig: null, + lastImportAt: 0, + name: 'Custom Name', + onlyFollowFirstParent: BooleanOverride.Disabled, + onRepoLoadShowCheckedOutBranch: BooleanOverride.Enabled, + onRepoLoadShowSpecificBranches: ['master'], + pullRequestConfig: null, + cicdConfigs: cicdConfigsIn, + cicdNonce: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', + showRemoteBranches: true, + showRemoteBranchesV2: BooleanOverride.Enabled, + showStashes: BooleanOverride.Enabled, + showTags: BooleanOverride.Enabled, + workspaceFolderIndex: 0 + }; + extensionContext.workspaceState.get.mockReturnValueOnce({ + '/path/to/repo': repoState + }); + + // Run + const result = extensionState.getRepos(); + + // Assert + expect(result).toStrictEqual({ + '/path/to/repo': Object.assign({}, repoState, { + cicdConfigs: cicdConfigsOut, + cicdNonce: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d' + }) + }); + }); + + it('Should return the stored repositories with decrypt cicdNonce is null', () => { + // Setup + const cicdConfigsIn = [{ + provider: CICDProvider.GitHubV3, + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + cicdToken: '31613262336334643565366631613262:aedfce60882614d275f01b86aa151bcd' + }]; + const cicdConfigsOut: any = []; + const repoState: GitRepoState = { + cdvDivider: 0.5, + cdvHeight: 250, + columnWidths: null, + commitOrdering: RepoCommitOrdering.AuthorDate, + fileViewType: FileViewType.List, + hideRemotes: [], + includeCommitsMentionedByReflogs: BooleanOverride.Enabled, + issueLinkingConfig: null, + lastImportAt: 0, + name: 'Custom Name', + onlyFollowFirstParent: BooleanOverride.Disabled, + onRepoLoadShowCheckedOutBranch: BooleanOverride.Enabled, + onRepoLoadShowSpecificBranches: ['master'], + pullRequestConfig: null, + cicdConfigs: cicdConfigsIn, + cicdNonce: null, + showRemoteBranches: true, + showRemoteBranchesV2: BooleanOverride.Enabled, + showStashes: BooleanOverride.Enabled, + showTags: BooleanOverride.Enabled, + workspaceFolderIndex: 0 + }; + extensionContext.workspaceState.get.mockReturnValueOnce({ + '/path/to/repo': repoState + }); + + // Run + const result = extensionState.getRepos(); + + // Assert + expect(result).toStrictEqual({ + '/path/to/repo': Object.assign({}, repoState, { + cicdConfigs: cicdConfigsOut, + cicdNonce: null + }) + }); + }); + + it('Should return the stored repositories with decrypt cicdConfigs is null', () => { + // Setup + const cicdConfigsIn: any = null; + const cicdConfigsOut: any = null; + const repoState: GitRepoState = { + cdvDivider: 0.5, + cdvHeight: 250, + columnWidths: null, + commitOrdering: RepoCommitOrdering.AuthorDate, + fileViewType: FileViewType.List, + hideRemotes: [], + includeCommitsMentionedByReflogs: BooleanOverride.Enabled, + issueLinkingConfig: null, + lastImportAt: 0, + name: 'Custom Name', + onlyFollowFirstParent: BooleanOverride.Disabled, + onRepoLoadShowCheckedOutBranch: BooleanOverride.Enabled, + onRepoLoadShowSpecificBranches: ['master'], + pullRequestConfig: null, + cicdConfigs: cicdConfigsIn, + cicdNonce: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', + showRemoteBranches: true, + showRemoteBranchesV2: BooleanOverride.Enabled, + showStashes: BooleanOverride.Enabled, + showTags: BooleanOverride.Enabled, + workspaceFolderIndex: 0 + }; + extensionContext.workspaceState.get.mockReturnValueOnce({ + '/path/to/repo': repoState + }); + + // Run + const result = extensionState.getRepos(); + + // Assert + expect(result).toStrictEqual({ + '/path/to/repo': Object.assign({}, repoState, { + cicdConfigs: cicdConfigsOut, + cicdNonce: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d' + }) + }); + }); + + it('Should return the stored repositories with decrypt cicdUrl is undefined', () => { + // Setup + const cicdConfigsIn: any = [{ + provider: CICDProvider.GitHubV3, + cicdToken: '31613262336334643565366631613262:aedfce60882614d275f01b86aa151bcd' + }]; + const cicdConfigsOut: any = []; + const repoState: GitRepoState = { + cdvDivider: 0.5, + cdvHeight: 250, + columnWidths: null, + commitOrdering: RepoCommitOrdering.AuthorDate, + fileViewType: FileViewType.List, + hideRemotes: [], + includeCommitsMentionedByReflogs: BooleanOverride.Enabled, + issueLinkingConfig: null, + lastImportAt: 0, + name: 'Custom Name', + onlyFollowFirstParent: BooleanOverride.Disabled, + onRepoLoadShowCheckedOutBranch: BooleanOverride.Enabled, + onRepoLoadShowSpecificBranches: ['master'], + pullRequestConfig: null, + cicdConfigs: cicdConfigsIn, + cicdNonce: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d', + showRemoteBranches: true, + showRemoteBranchesV2: BooleanOverride.Enabled, + showStashes: BooleanOverride.Enabled, + showTags: BooleanOverride.Enabled, + workspaceFolderIndex: 0 + }; + extensionContext.workspaceState.get.mockReturnValueOnce({ + '/path/to/repo': repoState + }); + + // Run + const result = extensionState.getRepos(); + + // Assert + expect(result).toStrictEqual({ + '/path/to/repo': Object.assign({}, repoState, { + cicdConfigs: cicdConfigsOut, + cicdNonce: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d' + }) + }); + }); }); describe('saveRepos', () => { @@ -383,6 +572,104 @@ describe('ExtensionState', () => { // Assert expect(extensionContext.workspaceState.update).toHaveBeenCalledWith('repoStates', repos); }); + + it('Should store the provided repositories in the workspace state with encrypt', () => { + // Setup + const cicdConfigsIn = [{ + provider: CICDProvider.GitHubV3, + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + cicdToken: 'token' + }]; + const cicdConfigsOut = [{ + provider: CICDProvider.GitHubV3, + cicdUrl: 'https://github.com/keydepth/vscode-git-graph.git', + cicdToken: '31613262336334643565366631613262:aedfce60882614d275f01b86aa151bcd' + }]; + const repoState: GitRepoState = { + cdvDivider: 0.5, + cdvHeight: 250, + columnWidths: null, + commitOrdering: RepoCommitOrdering.AuthorDate, + fileViewType: FileViewType.List, + hideRemotes: [], + includeCommitsMentionedByReflogs: BooleanOverride.Enabled, + issueLinkingConfig: null, + lastImportAt: 0, + name: 'Custom Name', + onlyFollowFirstParent: BooleanOverride.Disabled, + onRepoLoadShowCheckedOutBranch: BooleanOverride.Enabled, + onRepoLoadShowSpecificBranches: ['master'], + pullRequestConfig: null, + cicdConfigs: cicdConfigsIn, + cicdNonce: null, + showRemoteBranches: true, + showRemoteBranchesV2: BooleanOverride.Enabled, + showStashes: BooleanOverride.Enabled, + showTags: BooleanOverride.Enabled, + workspaceFolderIndex: 0 + }; + extensionContext.workspaceState.update.mockResolvedValueOnce(null); + jest.spyOn(utils, 'getNonce').mockReturnValueOnce('1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d'); + const buf16 = Buffer.from('1a2b3c4d5e6f1a2b'); + jest.spyOn(crypto, 'randomBytes').mockImplementation(() => buf16); + + // Run + extensionState.saveRepos({ + '/path/to/repo': repoState + }); + + // Assert + expect(extensionContext.workspaceState.update).toHaveBeenCalledWith('repoStates', { + '/path/to/repo': Object.assign({}, repoState, { + cicdConfigs: cicdConfigsOut, + cicdNonce: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d' + }) + }); + }); + + it('Should store the provided repositories in the workspace state with encrypt cicdConfigs is null', () => { + // Setup + const repoState: GitRepoState = { + cdvDivider: 0.5, + cdvHeight: 250, + columnWidths: null, + commitOrdering: RepoCommitOrdering.AuthorDate, + fileViewType: FileViewType.List, + hideRemotes: [], + includeCommitsMentionedByReflogs: BooleanOverride.Enabled, + issueLinkingConfig: null, + lastImportAt: 0, + name: 'Custom Name', + onlyFollowFirstParent: BooleanOverride.Disabled, + onRepoLoadShowCheckedOutBranch: BooleanOverride.Enabled, + onRepoLoadShowSpecificBranches: ['master'], + pullRequestConfig: null, + cicdConfigs: null, + cicdNonce: null, + showRemoteBranches: true, + showRemoteBranchesV2: BooleanOverride.Enabled, + showStashes: BooleanOverride.Enabled, + showTags: BooleanOverride.Enabled, + workspaceFolderIndex: 0 + }; + extensionContext.workspaceState.update.mockResolvedValueOnce(null); + jest.spyOn(utils, 'getNonce').mockReturnValueOnce('1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d'); + const buf16 = Buffer.from('1a2b3c4d5e6f1a2b'); + jest.spyOn(crypto, 'randomBytes').mockImplementation(() => buf16); + + // Run + extensionState.saveRepos({ + '/path/to/repo': repoState + }); + + // Assert + expect(extensionContext.workspaceState.update).toHaveBeenCalledWith('repoStates', { + '/path/to/repo': Object.assign({}, repoState, { + cicdConfigs: [], + cicdNonce: '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d' + }) + }); + }); }); describe('transferRepo', () => { @@ -935,6 +1222,150 @@ describe('ExtensionState', () => { }); }); + describe('getCICDCache', () => { + it('Should return the stored code reviews', () => { + // Setup + const cicdCache = { + '/path/to/repo': { + 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2': { + 'id000': { + name: 'string', + status: 'string', + ref: 'string', + web_url: 'string', + event: 'string', + detail: 'boolean', + allow_failure: 'boolean' + } + } + } + }; + extensionContext.workspaceState.get.mockReturnValueOnce(cicdCache); + + // Run + const result = extensionState.getCICDCache(); + + // Assert + expect(extensionContext.workspaceState.get).toHaveBeenCalledWith('cicdCache', {}); + expect(result).toBe(cicdCache); + }); + + it('Should return the default value if not defined', () => { + // Setup + extensionContext.workspaceState.get.mockImplementationOnce((_, defaultValue) => defaultValue); + + // Run + const result = extensionState.getCICDCache(); + + // Assert + expect(extensionContext.workspaceState.get).toHaveBeenCalledWith('cicdCache', {}); + expect(result).toStrictEqual({}); + }); + }); + + + describe('saveCICD', () => { + it('Should save the cicd to the cicd unknown cache', () => { + // Setup + const cicdCache = { + '/path/to/repo': { + 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2': { + 'id000': { + name: 'string', + status: 'string', + ref: 'string', + web_url: 'string', + event: 'string', + detail: true, + allow_failure: true + } + } + } + }; + const cicdDataSaveVal: CICDDataSave = { + name: 'string', + status: 'string', + ref: 'string', + web_url: 'string', + event: 'string', + detail: true, + allow_failure: true + }; + extensionContext.workspaceState.get.mockReturnValueOnce({}); + extensionContext.workspaceState.update.mockResolvedValueOnce(null); + + // Run + extensionState.saveCICD('/path/to/repo', 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2', 'id000', cicdDataSaveVal); + + // Assert + expect(extensionContext.workspaceState.update).toHaveBeenCalledWith('cicdCache', cicdCache); + }); + + it('Should save the cicd to the cicd known cache', () => { + // Setup + const cicdCache = { + '/path/to/repo': { + 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2': { + 'id000': { + name: 'string', + status: 'string', + ref: 'string', + web_url: 'string', + event: 'string', + detail: true, + allow_failure: true + } + } + } + }; + const cicdDataSaveVal: CICDDataSave = { + name: 'string1', + status: 'string1', + ref: 'string1', + web_url: 'string1', + event: 'string1', + detail: false, + allow_failure: false + }; + const cicdCacheUpdated = { + '/path/to/repo': { + 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2': { + 'id000': { + name: 'string1', + status: 'string1', + ref: 'string1', + web_url: 'string1', + event: 'string1', + detail: false, + allow_failure: false + } + } + } + }; + extensionContext.workspaceState.get.mockReturnValueOnce(cicdCache); + extensionContext.workspaceState.update.mockResolvedValueOnce(null); + + // Run + extensionState.saveCICD('/path/to/repo', 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2', 'id000', cicdDataSaveVal); + + // Assert + expect(extensionContext.workspaceState.update).toHaveBeenCalledWith('cicdCache', cicdCacheUpdated); + }); + }); + + describe('clearCICDCache', () => { + it('Should clear all cicd from the cache', async () => { + // Setup + extensionContext.workspaceState.update.mockResolvedValueOnce(null); + + // Run + extensionState.clearCICDCache(); + + // Assert + expect(extensionContext.workspaceState.update).toHaveBeenCalledWith('cicdCache', {}); + }); + }); + describe('startCodeReview', () => { it('Should store the code review (in a repository with no prior code reviews)', async () => { // Setup diff --git a/tests/gitGraphView.test.ts b/tests/gitGraphView.test.ts index 59685eaf..b8038ecd 100644 --- a/tests/gitGraphView.test.ts +++ b/tests/gitGraphView.test.ts @@ -9,7 +9,7 @@ jest.mock('../src/repoManager'); import * as path from 'path'; import { ConfigurationChangeEvent } from 'vscode'; import { AvatarEvent, AvatarManager } from '../src/avatarManager'; -import { CicdManager } from '../src/cicdManager'; +import { CICDEvent, CicdManager } from '../src/cicdManager'; import { DataSource } from '../src/dataSource'; import { ExtensionState } from '../src/extensionState'; import { GitGraphView, standardiseCspSource } from '../src/gitGraphView'; @@ -27,6 +27,7 @@ describe('GitGraphView', () => { let onDidChangeGitExecutable: EventEmitter; let onDidChangeRepos: EventEmitter; let onAvatar: EventEmitter; + let onCICD: EventEmitter; let logger: Logger; let dataSource: DataSource; @@ -45,6 +46,7 @@ describe('GitGraphView', () => { onDidChangeGitExecutable = new EventEmitter(); onDidChangeRepos = new EventEmitter(); onAvatar = new EventEmitter(); + onCICD = new EventEmitter(); logger = new Logger(); dataSource = new DataSource({ path: '/path/to/git', version: '2.25.0' }, onDidChangeConfiguration.subscribe, onDidChangeGitExecutable.subscribe, logger); @@ -67,6 +69,9 @@ describe('GitGraphView', () => { Object.defineProperty(avatarManager, 'onAvatar', { get: () => onAvatar.subscribe }); + Object.defineProperty(cicdManager, 'onCICD', { + get: () => onCICD.subscribe + }); jest.spyOn(extensionState, 'getLastActiveRepo').mockReturnValue(null); }); @@ -78,6 +83,7 @@ describe('GitGraphView', () => { dataSource.dispose(); logger.dispose(); onAvatar.dispose(); + onCICD.dispose(); onDidChangeRepos.dispose(); onDidChangeGitExecutable.dispose(); onDidChangeConfiguration.dispose(); @@ -439,6 +445,31 @@ describe('GitGraphView', () => { }); }); + describe('CicdManager.onCICD', () => { + it('Should send the cicd', () => { + // Setup + GitGraphView.createOrShow('/path/to/extension', dataSource, extensionState, avatarManager, cicdManager, repoManager, logger, null); + + // Run + onCICD.emit({ + repo: 'path/to/repo', + hash: 'data:image/png;base64,YmluYXJ5LWltYWdlLWRhdGE=', + cicdDataSaves: {} + }); + + // Assert + const mockedWebviewPanel = vscode.getMockedWebviewPanel(0); + expect(mockedWebviewPanel.mocks.messages).toStrictEqual([ + { + command: 'fetchCICD', + repo: 'path/to/repo', + hash: 'data:image/png;base64,YmluYXJ5LWltYWdlLWRhdGE=', + cicdDataSaves: {} + } + ]); + }); + }); + describe('RepoFileWatcher.repoChangeCallback', () => { it('Should refresh the view when it\'s visible', () => { // Setup From 614b6c918fa840e4fd38a43f4198ce7c78a67857 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sun, 23 May 2021 04:50:50 +0900 Subject: [PATCH 52/54] #462 Removed rejejct for emitCICD --- src/cicdManager.ts | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/cicdManager.ts b/src/cicdManager.ts index af7d5965..ab0beb72 100644 --- a/src/cicdManager.ts +++ b/src/cicdManager.ts @@ -672,18 +672,13 @@ export class CicdManager extends Disposable { * @returns A promise indicating if the event was emitted successfully. */ private emitCICD(repo: string, hash: string, cicdDataSaves: { [id: string]: CICDDataSave }) { - return new Promise((resolve, _reject) => { - if (this.cicdEventEmitter.hasSubscribers()) { - this.cicdEventEmitter.emit({ - repo: repo, - hash: hash, - cicdDataSaves: cicdDataSaves - }); - resolve(true); - } else { - resolve(false); - } - }); + if (this.cicdEventEmitter.hasSubscribers()) { + this.cicdEventEmitter.emit({ + repo: repo, + hash: hash, + cicdDataSaves: cicdDataSaves + }); + } } /** From 22a86df50c08509f65b68886326952474dd66765 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Sun, 23 May 2021 21:28:59 +0900 Subject: [PATCH 53/54] #462 Updated test case of CI/CD date --- tests/cicdManager.test.ts | 60 +++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/tests/cicdManager.test.ts b/tests/cicdManager.test.ts index 66de6519..82040a67 100644 --- a/tests/cicdManager.test.ts +++ b/tests/cicdManager.test.ts @@ -429,7 +429,7 @@ describe('CicdManager', () => { expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100'); expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); - expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(1 hour)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() +') from GitHub'); expect(spyOnLog).toHaveBeenCalledTimes(4); }); @@ -467,7 +467,7 @@ describe('CicdManager', () => { expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100'); expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); - expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://github.com:80/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(1 hour)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://github.com:80/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() +') from GitHub'); expect(spyOnLog).toHaveBeenCalledTimes(4); }); @@ -530,7 +530,7 @@ describe('CicdManager', () => { expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for http://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API - (200)http://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100'); expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); - expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for http://github.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(1 hour)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for http://github.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() +') from GitHub'); expect(spyOnLog).toHaveBeenCalledTimes(4); }); @@ -642,10 +642,10 @@ describe('CicdManager', () => { }); expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/actions/runs?per_page=100 detail=false page=-1 from GitHub'); expect(spyOnLog).toHaveBeenCalledWith('GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/actions/runs?per_page=100'); - expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitHub'); + expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() +') from GitHub'); expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/b9112e60f5fb3d8bc2a387840577b4756a12f357/check-runs?per_page=100 detail=true page=-1 from GitHub'); expect(spyOnLog).toHaveBeenCalledWith('GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/commits/b9112e60f5fb3d8bc2a387840577b4756a12f357/check-runs?per_page=100'); - expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitHub'); + expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() +') from GitHub'); expect(spyOnLog).toHaveBeenCalledTimes(6); jest.useRealTimers(); }); @@ -763,10 +763,10 @@ describe('CicdManager', () => { }); expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/actions/runs?per_page=100 detail=false page=-1 from GitHub'); expect(spyOnLog).toHaveBeenCalledWith('GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/actions/runs?per_page=100'); - expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitHub'); + expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() +') from GitHub'); expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/b9112e60f5fb3d8bc2a387840577b4756a12f357/check-runs?per_page=100 detail=true page=-1 from GitHub'); expect(spyOnLog).toHaveBeenCalledWith('GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/commits/b9112e60f5fb3d8bc2a387840577b4756a12f357/check-runs?per_page=100'); - expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitHub'); + expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() +') from GitHub'); expect(spyOnLog).toHaveBeenCalledTimes(6); jest.useRealTimers(); }); @@ -812,7 +812,7 @@ describe('CicdManager', () => { expect(spyOnSaveCicd).toHaveBeenCalledWith(...GitHubSaveCicd); expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100'); - expect(spyOnLog).toHaveBeenNthCalledWith(3, 'Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() +') from GitHub'); expect(spyOnLog).toHaveBeenCalledTimes(3); }); @@ -833,7 +833,7 @@ describe('CicdManager', () => { expect(spyOnSaveCicd).toHaveBeenCalledWith(...GitHubSaveCicd); expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100'); - expect(spyOnLog).toHaveBeenNthCalledWith(3, 'Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() +') from GitHub'); expect(spyOnLog).toHaveBeenCalledTimes(3); }); @@ -854,7 +854,7 @@ describe('CicdManager', () => { expect(spyOnSaveCicd).toHaveBeenCalledWith(...GitHubSaveCicd); expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100'); - expect(spyOnLog).toHaveBeenNthCalledWith(3, 'Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() +') from GitHub'); expect(spyOnLog).toHaveBeenCalledTimes(3); }); @@ -938,7 +938,7 @@ describe('CicdManager', () => { expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100'); expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); - expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(1 hour)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() +') from GitHub'); expect(spyOnLog).toHaveBeenCalledTimes(22); }); @@ -1164,7 +1164,7 @@ describe('CicdManager', () => { ]); expect(spyOnHttpsGet).toHaveBeenCalledWith(GitHubHttpsGet, expect.anything()); expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); - expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API Rate Limit Reached - Paused fetching from GitHub until the Rate Limit is reset (RateLimit=60(1 hour)/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00))'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API Rate Limit Reached - Paused fetching from GitHub until the Rate Limit is reset (RateLimit=60(1 hour)/' + new Date(1618343683).toString() +')'); expect(spyOnLog).toHaveBeenNthCalledWith(3, 'GitHub API Rate Limit can upgrade by Access Token.'); expect(spyOnLog).toHaveBeenCalledTimes(3); }); @@ -1187,7 +1187,7 @@ describe('CicdManager', () => { // Assert await waitForExpect(() => { - expect(spyOnLog).toHaveBeenCalledWith('GitHub API Rate Limit Reached - Paused fetching from GitHub until the Rate Limit is reset (RateLimit=60(1 hour)/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00))'); + expect(spyOnLog).toHaveBeenCalledWith('GitHub API Rate Limit Reached - Paused fetching from GitHub until the Rate Limit is reset (RateLimit=60(1 hour)/' + new Date(1618343683).toString() +')'); }); expect(cicdManager['queue']['queue']).toStrictEqual([ { @@ -1218,7 +1218,7 @@ describe('CicdManager', () => { timeout: 15000 }, expect.anything()); expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); - expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API Rate Limit Reached - Paused fetching from GitHub until the Rate Limit is reset (RateLimit=60(1 hour)/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00))'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API Rate Limit Reached - Paused fetching from GitHub until the Rate Limit is reset (RateLimit=60(1 hour)/' + new Date(1618343683).toString() +')'); expect(spyOnLog).toHaveBeenCalledTimes(2); expect(cicdManager['gitHubTimeout']).toBe((date.now + 1) * 1000); }); @@ -1423,7 +1423,7 @@ describe('CicdManager', () => { // Assert await waitForExpect(() => { - expect(spyOnLog).toHaveBeenCalledWith('GitHub API Rate Limit Reached - Paused fetching from GitHub until the Rate Limit is reset (RateLimit=60(1 hour)/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00))'); + expect(spyOnLog).toHaveBeenCalledWith('GitHub API Rate Limit Reached - Paused fetching from GitHub until the Rate Limit is reset (RateLimit=60(1 hour)/' + new Date(1618343683).toString() +')'); }); // Run @@ -1517,7 +1517,7 @@ describe('CicdManager', () => { expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100'); expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); - expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() +') from GitLab'); expect(spyOnLog).toHaveBeenCalledTimes(4); }); @@ -1554,7 +1554,7 @@ describe('CicdManager', () => { expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100'); expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); - expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com:80/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com:80/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() +') from GitLab'); expect(spyOnLog).toHaveBeenCalledTimes(4); }); @@ -1616,7 +1616,7 @@ describe('CicdManager', () => { expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for http://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API - (200)http://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100'); expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); - expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for http://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for http://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() +') from GitLab'); expect(spyOnLog).toHaveBeenCalledTimes(4); }); @@ -1721,10 +1721,10 @@ describe('CicdManager', () => { }); expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/pipelines?per_page=100 detail=false page=-1 from GitLab'); expect(spyOnLog).toHaveBeenCalledWith('GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/pipelines?per_page=100'); - expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(every minute)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitLab'); + expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() +') from GitLab'); expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/b9112e60f5fb3d8bc2a387840577b4756a12f357/statuses?per_page=100 detail=true page=-1 from GitLab'); expect(spyOnLog).toHaveBeenCalledWith('GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/b9112e60f5fb3d8bc2a387840577b4756a12f357/statuses?per_page=100'); - expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(every minute)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitLab'); + expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() +') from GitLab'); expect(spyOnLog).toHaveBeenCalledTimes(6); jest.useRealTimers(); }); @@ -1834,10 +1834,10 @@ describe('CicdManager', () => { }); expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/pipelines?per_page=100 detail=false page=-1 from GitLab'); expect(spyOnLog).toHaveBeenCalledWith('GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/pipelines?per_page=100'); - expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(every minute)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitLab'); + expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() +') from GitLab'); expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/b9112e60f5fb3d8bc2a387840577b4756a12f357/statuses?per_page=100 detail=true page=-1 from GitLab'); expect(spyOnLog).toHaveBeenCalledWith('GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/b9112e60f5fb3d8bc2a387840577b4756a12f357/statuses?per_page=100'); - expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(every minute)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitLab'); + expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() +') from GitLab'); expect(spyOnLog).toHaveBeenCalledTimes(6); jest.useRealTimers(); }); @@ -2018,7 +2018,7 @@ describe('CicdManager', () => { expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100'); expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); - expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() +') from GitLab'); expect(spyOnLog).toHaveBeenCalledTimes(4); }); @@ -2050,7 +2050,7 @@ describe('CicdManager', () => { expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100'); expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); - expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() +') from GitLab'); expect(spyOnLog).toHaveBeenCalledTimes(22); }); @@ -2221,7 +2221,7 @@ describe('CicdManager', () => { expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100'); expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); - expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() +') from GitLab'); expect(spyOnLog).toHaveBeenCalledTimes(4); }); @@ -2268,7 +2268,7 @@ describe('CicdManager', () => { expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100'); expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); - expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00)) from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() +') from GitLab'); expect(spyOnLog).toHaveBeenCalledTimes(4); }); @@ -2291,7 +2291,7 @@ describe('CicdManager', () => { // Assert await waitForExpect(() => { - expect(spyOnLog).toHaveBeenCalledWith('GitLab API Rate Limit Reached - Paused fetching from GitLab until the Rate Limit is reset (RateLimit=60(every minute)/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00))'); + expect(spyOnLog).toHaveBeenCalledWith('GitLab API Rate Limit Reached - Paused fetching from GitLab until the Rate Limit is reset (RateLimit=60(every minute)/' + new Date(1618343683).toString() +')'); }); expect(cicdManager['queue']['queue']).toStrictEqual([ { @@ -2321,7 +2321,7 @@ describe('CicdManager', () => { timeout: 15000 }, expect.anything()); expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); - expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API Rate Limit Reached - Paused fetching from GitLab until the Rate Limit is reset (RateLimit=60(every minute)/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00))'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API Rate Limit Reached - Paused fetching from GitLab until the Rate Limit is reset (RateLimit=60(every minute)/' + new Date(1618343683).toString() +')'); expect(spyOnLog).toHaveBeenCalledTimes(2); expect(cicdManager['gitLabTimeout']).toBe((date.now + 1) * 1000); }); @@ -3380,7 +3380,7 @@ describe('CicdManager', () => { // Assert await waitForExpect(() => { - expect(spyOnLog).toHaveBeenCalledWith('Jenkins API Rate Limit Reached - Paused fetching from Jenkins until the Rate Limit is reset (RateLimit=undefined(every minute)/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00))'); + expect(spyOnLog).toHaveBeenCalledWith('Jenkins API Rate Limit Reached - Paused fetching from Jenkins until the Rate Limit is reset (RateLimit=undefined(every minute)/' + new Date(1618343683).toString() +')'); }); expect(cicdManager['queue']['queue']).toStrictEqual([ { @@ -3410,7 +3410,7 @@ describe('CicdManager', () => { timeout: 15000 }, expect.anything()); expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]] page=-1 from Jenkins'); - expect(spyOnLog).toHaveBeenNthCalledWith(2, 'Jenkins API Rate Limit Reached - Paused fetching from Jenkins until the Rate Limit is reset (RateLimit=undefined(every minute)/Wed Apr 22 2020 21:40:58 GMT+0900 (GMT+09:00))'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'Jenkins API Rate Limit Reached - Paused fetching from Jenkins until the Rate Limit is reset (RateLimit=undefined(every minute)/' + new Date(1618343683).toString() +')'); expect(spyOnLog).toHaveBeenCalledTimes(2); expect(cicdManager['jenkinsTimeout']).toBe((date.now + 1) * 1000); }); From 77588d380470f7b12c8978fbe5f49853ec4c6365 Mon Sep 17 00:00:00 2001 From: keydepth <35625044+keydepth@users.noreply.github.com> Date: Mon, 24 May 2021 08:49:45 +0900 Subject: [PATCH 54/54] #462 Updated test case of CI/CD date for LINT --- tests/cicdManager.test.ts | 60 +++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/tests/cicdManager.test.ts b/tests/cicdManager.test.ts index 82040a67..92f623f9 100644 --- a/tests/cicdManager.test.ts +++ b/tests/cicdManager.test.ts @@ -429,7 +429,7 @@ describe('CicdManager', () => { expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100'); expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); - expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() +') from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() + ') from GitHub'); expect(spyOnLog).toHaveBeenCalledTimes(4); }); @@ -467,7 +467,7 @@ describe('CicdManager', () => { expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100'); expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); - expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://github.com:80/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() +') from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://github.com:80/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() + ') from GitHub'); expect(spyOnLog).toHaveBeenCalledTimes(4); }); @@ -530,7 +530,7 @@ describe('CicdManager', () => { expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for http://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API - (200)http://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100'); expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); - expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for http://github.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() +') from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for http://github.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() + ') from GitHub'); expect(spyOnLog).toHaveBeenCalledTimes(4); }); @@ -642,10 +642,10 @@ describe('CicdManager', () => { }); expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/actions/runs?per_page=100 detail=false page=-1 from GitHub'); expect(spyOnLog).toHaveBeenCalledWith('GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/actions/runs?per_page=100'); - expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() +') from GitHub'); + expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() + ') from GitHub'); expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/b9112e60f5fb3d8bc2a387840577b4756a12f357/check-runs?per_page=100 detail=true page=-1 from GitHub'); expect(spyOnLog).toHaveBeenCalledWith('GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/commits/b9112e60f5fb3d8bc2a387840577b4756a12f357/check-runs?per_page=100'); - expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() +') from GitHub'); + expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() + ') from GitHub'); expect(spyOnLog).toHaveBeenCalledTimes(6); jest.useRealTimers(); }); @@ -763,10 +763,10 @@ describe('CicdManager', () => { }); expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/actions/runs?per_page=100 detail=false page=-1 from GitHub'); expect(spyOnLog).toHaveBeenCalledWith('GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/actions/runs?per_page=100'); - expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() +') from GitHub'); + expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() + ') from GitHub'); expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/b9112e60f5fb3d8bc2a387840577b4756a12f357/check-runs?per_page=100 detail=true page=-1 from GitHub'); expect(spyOnLog).toHaveBeenCalledWith('GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/commits/b9112e60f5fb3d8bc2a387840577b4756a12f357/check-runs?per_page=100'); - expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() +') from GitHub'); + expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() + ') from GitHub'); expect(spyOnLog).toHaveBeenCalledTimes(6); jest.useRealTimers(); }); @@ -812,7 +812,7 @@ describe('CicdManager', () => { expect(spyOnSaveCicd).toHaveBeenCalledWith(...GitHubSaveCicd); expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100'); - expect(spyOnLog).toHaveBeenNthCalledWith(3, 'Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() +') from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() + ') from GitHub'); expect(spyOnLog).toHaveBeenCalledTimes(3); }); @@ -833,7 +833,7 @@ describe('CicdManager', () => { expect(spyOnSaveCicd).toHaveBeenCalledWith(...GitHubSaveCicd); expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100'); - expect(spyOnLog).toHaveBeenNthCalledWith(3, 'Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() +') from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() + ') from GitHub'); expect(spyOnLog).toHaveBeenCalledTimes(3); }); @@ -854,7 +854,7 @@ describe('CicdManager', () => { expect(spyOnSaveCicd).toHaveBeenCalledWith(...GitHubSaveCicd); expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100'); - expect(spyOnLog).toHaveBeenNthCalledWith(3, 'Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() +') from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(3, 'Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() + ') from GitHub'); expect(spyOnLog).toHaveBeenCalledTimes(3); }); @@ -938,7 +938,7 @@ describe('CicdManager', () => { expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API - (200)https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100'); expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); - expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() +') from GitHub'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://github.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(1 hour)/Remaining=57/' + new Date(1618343683).toString() + ') from GitHub'); expect(spyOnLog).toHaveBeenCalledTimes(22); }); @@ -1164,7 +1164,7 @@ describe('CicdManager', () => { ]); expect(spyOnHttpsGet).toHaveBeenCalledWith(GitHubHttpsGet, expect.anything()); expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); - expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API Rate Limit Reached - Paused fetching from GitHub until the Rate Limit is reset (RateLimit=60(1 hour)/' + new Date(1618343683).toString() +')'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API Rate Limit Reached - Paused fetching from GitHub until the Rate Limit is reset (RateLimit=60(1 hour)/' + new Date(1618343683).toString() + ')'); expect(spyOnLog).toHaveBeenNthCalledWith(3, 'GitHub API Rate Limit can upgrade by Access Token.'); expect(spyOnLog).toHaveBeenCalledTimes(3); }); @@ -1187,7 +1187,7 @@ describe('CicdManager', () => { // Assert await waitForExpect(() => { - expect(spyOnLog).toHaveBeenCalledWith('GitHub API Rate Limit Reached - Paused fetching from GitHub until the Rate Limit is reset (RateLimit=60(1 hour)/' + new Date(1618343683).toString() +')'); + expect(spyOnLog).toHaveBeenCalledWith('GitHub API Rate Limit Reached - Paused fetching from GitHub until the Rate Limit is reset (RateLimit=60(1 hour)/' + new Date(1618343683).toString() + ')'); }); expect(cicdManager['queue']['queue']).toStrictEqual([ { @@ -1218,7 +1218,7 @@ describe('CicdManager', () => { timeout: 15000 }, expect.anything()); expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://api.github.com/repos/keydepth/vscode-git-graph/commits/149ecc50e5c223251f80a0223cfbbd9822307224/check-runs?per_page=100 detail=true page=-1 from GitHub'); - expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API Rate Limit Reached - Paused fetching from GitHub until the Rate Limit is reset (RateLimit=60(1 hour)/' + new Date(1618343683).toString() +')'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitHub API Rate Limit Reached - Paused fetching from GitHub until the Rate Limit is reset (RateLimit=60(1 hour)/' + new Date(1618343683).toString() + ')'); expect(spyOnLog).toHaveBeenCalledTimes(2); expect(cicdManager['gitHubTimeout']).toBe((date.now + 1) * 1000); }); @@ -1423,7 +1423,7 @@ describe('CicdManager', () => { // Assert await waitForExpect(() => { - expect(spyOnLog).toHaveBeenCalledWith('GitHub API Rate Limit Reached - Paused fetching from GitHub until the Rate Limit is reset (RateLimit=60(1 hour)/' + new Date(1618343683).toString() +')'); + expect(spyOnLog).toHaveBeenCalledWith('GitHub API Rate Limit Reached - Paused fetching from GitHub until the Rate Limit is reset (RateLimit=60(1 hour)/' + new Date(1618343683).toString() + ')'); }); // Run @@ -1517,7 +1517,7 @@ describe('CicdManager', () => { expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100'); expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); - expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() +') from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() + ') from GitLab'); expect(spyOnLog).toHaveBeenCalledTimes(4); }); @@ -1554,7 +1554,7 @@ describe('CicdManager', () => { expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100'); expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); - expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com:80/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() +') from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com:80/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() + ') from GitLab'); expect(spyOnLog).toHaveBeenCalledTimes(4); }); @@ -1616,7 +1616,7 @@ describe('CicdManager', () => { expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for http://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API - (200)http://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100'); expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); - expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for http://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() +') from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for http://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() + ') from GitLab'); expect(spyOnLog).toHaveBeenCalledTimes(4); }); @@ -1721,10 +1721,10 @@ describe('CicdManager', () => { }); expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/pipelines?per_page=100 detail=false page=-1 from GitLab'); expect(spyOnLog).toHaveBeenCalledWith('GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/pipelines?per_page=100'); - expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() +') from GitLab'); + expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() + ') from GitLab'); expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/b9112e60f5fb3d8bc2a387840577b4756a12f357/statuses?per_page=100 detail=true page=-1 from GitLab'); expect(spyOnLog).toHaveBeenCalledWith('GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/b9112e60f5fb3d8bc2a387840577b4756a12f357/statuses?per_page=100'); - expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() +') from GitLab'); + expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() + ') from GitLab'); expect(spyOnLog).toHaveBeenCalledTimes(6); jest.useRealTimers(); }); @@ -1834,10 +1834,10 @@ describe('CicdManager', () => { }); expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/pipelines?per_page=100 detail=false page=-1 from GitLab'); expect(spyOnLog).toHaveBeenCalledWith('GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/pipelines?per_page=100'); - expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() +') from GitLab'); + expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() + ') from GitLab'); expect(spyOnLog).toHaveBeenCalledWith('Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/b9112e60f5fb3d8bc2a387840577b4756a12f357/statuses?per_page=100 detail=true page=-1 from GitLab'); expect(spyOnLog).toHaveBeenCalledWith('GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/b9112e60f5fb3d8bc2a387840577b4756a12f357/statuses?per_page=100'); - expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() +') from GitLab'); + expect(spyOnLog).toHaveBeenCalledWith('Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=1(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() + ') from GitLab'); expect(spyOnLog).toHaveBeenCalledTimes(6); jest.useRealTimers(); }); @@ -2018,7 +2018,7 @@ describe('CicdManager', () => { expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100'); expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); - expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() +') from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() + ') from GitLab'); expect(spyOnLog).toHaveBeenCalledTimes(4); }); @@ -2050,7 +2050,7 @@ describe('CicdManager', () => { expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100'); expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); - expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() +') from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() + ') from GitLab'); expect(spyOnLog).toHaveBeenCalledTimes(22); }); @@ -2221,7 +2221,7 @@ describe('CicdManager', () => { expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100'); expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); - expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() +') from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() + ') from GitLab'); expect(spyOnLog).toHaveBeenCalledTimes(4); }); @@ -2268,7 +2268,7 @@ describe('CicdManager', () => { expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API - (200)https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100'); expect(spyOnLog).toHaveBeenNthCalledWith(3, 'CICD Maximum Statuses(maximumStatuses=1000) reached, if you want to change Maximum page, please configure git-graph.repository.commits.fetchCICDsMaximumStatuses'); - expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() +') from GitLab'); + expect(spyOnLog).toHaveBeenNthCalledWith(4, 'Added CICD for https://gitlab.com/keydepth/vscode-git-graph.git last_page=10(RateLimit=60(every minute)/Remaining=57/' + new Date(1618343683).toString() + ') from GitLab'); expect(spyOnLog).toHaveBeenCalledTimes(4); }); @@ -2291,7 +2291,7 @@ describe('CicdManager', () => { // Assert await waitForExpect(() => { - expect(spyOnLog).toHaveBeenCalledWith('GitLab API Rate Limit Reached - Paused fetching from GitLab until the Rate Limit is reset (RateLimit=60(every minute)/' + new Date(1618343683).toString() +')'); + expect(spyOnLog).toHaveBeenCalledWith('GitLab API Rate Limit Reached - Paused fetching from GitLab until the Rate Limit is reset (RateLimit=60(every minute)/' + new Date(1618343683).toString() + ')'); }); expect(cicdManager['queue']['queue']).toStrictEqual([ { @@ -2321,7 +2321,7 @@ describe('CicdManager', () => { timeout: 15000 }, expect.anything()); expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://gitlab.com/api/v4/projects/keydepth%2Fvscode-git-graph/repository/commits/149ecc50e5c223251f80a0223cfbbd9822307224/statuses?per_page=100 detail=true page=-1 from GitLab'); - expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API Rate Limit Reached - Paused fetching from GitLab until the Rate Limit is reset (RateLimit=60(every minute)/' + new Date(1618343683).toString() +')'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'GitLab API Rate Limit Reached - Paused fetching from GitLab until the Rate Limit is reset (RateLimit=60(every minute)/' + new Date(1618343683).toString() + ')'); expect(spyOnLog).toHaveBeenCalledTimes(2); expect(cicdManager['gitLabTimeout']).toBe((date.now + 1) * 1000); }); @@ -3380,7 +3380,7 @@ describe('CicdManager', () => { // Assert await waitForExpect(() => { - expect(spyOnLog).toHaveBeenCalledWith('Jenkins API Rate Limit Reached - Paused fetching from Jenkins until the Rate Limit is reset (RateLimit=undefined(every minute)/' + new Date(1618343683).toString() +')'); + expect(spyOnLog).toHaveBeenCalledWith('Jenkins API Rate Limit Reached - Paused fetching from Jenkins until the Rate Limit is reset (RateLimit=undefined(every minute)/' + new Date(1618343683).toString() + ')'); }); expect(cicdManager['queue']['queue']).toStrictEqual([ { @@ -3410,7 +3410,7 @@ describe('CicdManager', () => { timeout: 15000 }, expect.anything()); expect(spyOnLog).toHaveBeenNthCalledWith(1, 'Requesting CICD for https://jenkins.net/job/job01/job/MultiBranch/job/master/3/api/json?tree=builds[id,timestamp,fullDisplayName,result,url,actions[lastBuiltRevision[branch[*]]]] page=-1 from Jenkins'); - expect(spyOnLog).toHaveBeenNthCalledWith(2, 'Jenkins API Rate Limit Reached - Paused fetching from Jenkins until the Rate Limit is reset (RateLimit=undefined(every minute)/' + new Date(1618343683).toString() +')'); + expect(spyOnLog).toHaveBeenNthCalledWith(2, 'Jenkins API Rate Limit Reached - Paused fetching from Jenkins until the Rate Limit is reset (RateLimit=undefined(every minute)/' + new Date(1618343683).toString() + ')'); expect(spyOnLog).toHaveBeenCalledTimes(2); expect(cicdManager['jenkinsTimeout']).toBe((date.now + 1) * 1000); });