mirror of
https://github.com/microsoft/vscode.git
synced 2026-10-05 15:36:22 +01:00
SCM - history view (#216629)
* Hacked up the starting point * More progress * More progress * Simplified algo, basic support for colors * Add the concept of secondary colors for merge commits * Further optimizations of the algo * Bug fixes to clean-up algo * Add support for curved branches * Keep track of nodes that are moved so that the second parent for the merge commit is rendered in the correct swimlane * Account for offset when drawing a base commit * Algo rework completed * Revert some of the hacks that were put in place to quickly get going * Add caching to the history items used in the graph Remove more hacks that were in place * Added initial tests * More work so that incoming/outgoing works along history * Uncomment more code * Bug fixes to edge cases * Experiment with a new rendering for curved branches * Handle repository with a single commit * Maintain swimlanes * Fix condition * Saving my changes * More polish and clean-up * Remove code that is not needed * Revert change * Revert more changes * More fixes * Rename interface * One last minor change * Pull request feedback * More refactoring * More pull request feedback * Fix layering issues
This commit is contained in:
1 parent
1330e6d2b8
commit
484cd68eff
13 files changed
+1180
-36
No files matched your search
Vendored
+1
@@ -145,6 +145,7 @@ export interface LogOptions {
|
||||
readonly sortByAuthorDate?: boolean;
|
||||
readonly shortStats?: boolean;
|
||||
readonly author?: string;
|
||||
readonly refNames?: string[];
|
||||
}
|
||||
|
||||
export interface CommitOptions {
|
||||
|
||||
@@ -1165,6 +1165,11 @@ export class Repository {
|
||||
args.push(`--author="${options.author}"`);
|
||||
}
|
||||
|
||||
if (options?.refNames) {
|
||||
args.push('--topo-order');
|
||||
args.push(...options.refNames);
|
||||
}
|
||||
|
||||
if (options?.path) {
|
||||
args.push('--', options.path);
|
||||
}
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
import { Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryItemGroup, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, Uri, window, LogOutputChannel } from 'vscode';
|
||||
import { Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryItemGroup, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, Uri, window, LogOutputChannel, SourceControlHistoryItemLabel } from 'vscode';
|
||||
import { Repository, Resource } from './repository';
|
||||
import { IDisposable, dispose, filterEvent } from './util';
|
||||
import { toGitUri } from './uri';
|
||||
import { Branch, RefType, UpstreamRef } from './api/git';
|
||||
import { emojify, ensureEmojis } from './emoji';
|
||||
import { Operation } from './operation';
|
||||
import { Commit } from './git';
|
||||
|
||||
export class GitHistoryProvider implements SourceControlHistoryProvider, FileDecorationProvider, IDisposable {
|
||||
|
||||
@@ -112,6 +113,47 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
|
||||
return historyItems;
|
||||
}
|
||||
|
||||
async provideHistoryItems2(options: SourceControlHistoryOptions): Promise<SourceControlHistoryItem[]> {
|
||||
if (!this.currentHistoryItemGroup || !options.historyItemGroupIds) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Deduplicate refNames
|
||||
const refNames = new Set<string>(options.historyItemGroupIds);
|
||||
|
||||
// Get the merge base of the refNames
|
||||
const refsMergeBase = await this.resolveHistoryItemGroupsMergeBase(refNames);
|
||||
if (!refsMergeBase) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Get the commits
|
||||
const commits = await this.repository.log({ range: `${refsMergeBase}^..`, refNames: Array.from(refNames) });
|
||||
|
||||
await ensureEmojis();
|
||||
|
||||
const historyItems: SourceControlHistoryItem[] = [];
|
||||
historyItems.push(...commits.map(commit => {
|
||||
const newLineIndex = commit.message.indexOf('\n');
|
||||
const subject = newLineIndex !== -1 ? commit.message.substring(0, newLineIndex) : commit.message;
|
||||
|
||||
const labels = this.resolveHistoryItemLabels(commit, refNames);
|
||||
|
||||
return {
|
||||
id: commit.hash,
|
||||
parentIds: commit.parents,
|
||||
message: emojify(subject),
|
||||
author: commit.authorName,
|
||||
icon: new ThemeIcon('git-commit'),
|
||||
timestamp: commit.authorDate?.getTime(),
|
||||
statistics: commit.shortStat ?? { files: 0, insertions: 0, deletions: 0 },
|
||||
labels: labels.length !== 0 ? labels : undefined
|
||||
};
|
||||
}));
|
||||
|
||||
return historyItems;
|
||||
}
|
||||
|
||||
async provideHistoryItemSummary(historyItemId: string, historyItemParentId: string | undefined): Promise<SourceControlHistoryItem> {
|
||||
if (!historyItemParentId) {
|
||||
const commit = await this.repository.getCommit(historyItemId);
|
||||
@@ -159,9 +201,23 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
|
||||
return historyItemChanges;
|
||||
}
|
||||
|
||||
async resolveHistoryItemGroupBase(historyItemGroupId: string): Promise<SourceControlHistoryItemGroup | undefined> {
|
||||
// Base (config -> reflog -> default)
|
||||
const remoteBranch = await this.repository.getBranchBase(historyItemGroupId);
|
||||
if (!remoteBranch?.remote || !remoteBranch?.name || !remoteBranch?.commit || remoteBranch?.type !== RefType.RemoteHead) {
|
||||
this.logger.info(`GitHistoryProvider:resolveHistoryItemGroupBase - Failed to resolve history item group base for '${historyItemGroupId}'`);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
id: `refs/remotes/${remoteBranch.remote}/${remoteBranch.name}`,
|
||||
name: `${remoteBranch.remote}/${remoteBranch.name}`,
|
||||
};
|
||||
}
|
||||
|
||||
async resolveHistoryItemGroupCommonAncestor(historyItemId1: string, historyItemId2: string | undefined): Promise<{ id: string; ahead: number; behind: number } | undefined> {
|
||||
if (!historyItemId2) {
|
||||
const upstreamRef = await this.resolveHistoryItemGroupBase(historyItemId1);
|
||||
const upstreamRef = await this.resolveHistoryItemGroupUpstreamOrBase(historyItemId1);
|
||||
if (!upstreamRef) {
|
||||
this.logger.info(`GitHistoryProvider:resolveHistoryItemGroupCommonAncestor - Failed to resolve history item group base for '${historyItemId1}'`);
|
||||
return undefined;
|
||||
@@ -191,7 +247,65 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
|
||||
return this.historyItemDecorations.get(uri.toString());
|
||||
}
|
||||
|
||||
private async resolveHistoryItemGroupBase(historyItemId: string): Promise<UpstreamRef | undefined> {
|
||||
private async resolveHistoryItemGroupsMergeBase(refNames: Set<string>): Promise<string | undefined> {
|
||||
let refsMergeBase: string | undefined = undefined;
|
||||
|
||||
for (const refName of refNames) {
|
||||
if (refsMergeBase === undefined) {
|
||||
const commit = await this.repository.revParse(refName);
|
||||
refsMergeBase = commit ?? refName;
|
||||
continue;
|
||||
}
|
||||
|
||||
const newMergeBase = await this.repository.getMergeBase(refsMergeBase, refName);
|
||||
refsMergeBase = newMergeBase ?? refsMergeBase;
|
||||
}
|
||||
|
||||
return refsMergeBase;
|
||||
}
|
||||
|
||||
private resolveHistoryItemLabels(commit: Commit, refNames: Set<string>): SourceControlHistoryItemLabel[] {
|
||||
const labels: SourceControlHistoryItemLabel[] = [];
|
||||
|
||||
for (const label of commit.refNames) {
|
||||
if (label === 'origin/HEAD' || label === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (label.startsWith('HEAD -> ')) {
|
||||
labels.push(
|
||||
{
|
||||
title: label.substring(8),
|
||||
icon: new ThemeIcon('git-branch')
|
||||
}
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (refNames.has(label)) {
|
||||
if (label.startsWith('tag: ')) {
|
||||
labels.push({
|
||||
title: label.substring(5),
|
||||
icon: new ThemeIcon('tag')
|
||||
});
|
||||
} else if (label.startsWith('origin/')) {
|
||||
labels.push({
|
||||
title: label,
|
||||
icon: new ThemeIcon('cloud')
|
||||
});
|
||||
} else {
|
||||
labels.push({
|
||||
title: label,
|
||||
icon: new ThemeIcon('git-branch')
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return labels;
|
||||
}
|
||||
|
||||
private async resolveHistoryItemGroupUpstreamOrBase(historyItemId: string): Promise<UpstreamRef | undefined> {
|
||||
try {
|
||||
// Upstream
|
||||
const branch = await this.repository.getBranch(historyItemId);
|
||||
@@ -202,7 +316,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
|
||||
// Base (config -> reflog -> default)
|
||||
const remoteBranch = await this.repository.getBranchBase(historyItemId);
|
||||
if (!remoteBranch?.remote || !remoteBranch?.name || !remoteBranch?.commit || remoteBranch?.type !== RefType.RemoteHead) {
|
||||
this.logger.info(`GitHistoryProvider:resolveHistoryItemGroupBase - Failed to resolve history item group base for '${historyItemId}'`);
|
||||
this.logger.info(`GitHistoryProvider:resolveHistoryItemGroupUpstreamOrBase - Failed to resolve history item group base for '${historyItemId}'`);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -213,7 +327,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
|
||||
};
|
||||
}
|
||||
catch (err) {
|
||||
this.logger.error(`GitHistoryProvider:resolveHistoryItemGroupBase - Failed to get branch base for '${historyItemId}': ${err.message}`);
|
||||
this.logger.error(`GitHistoryProvider:resolveHistoryItemGroupUpstreamOrBase - Failed to get branch base for '${historyItemId}': ${err.message}`);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { observableValue } from 'vs/base/common/observable';
|
||||
import { IDisposable, DisposableStore, combinedDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ISCMService, ISCMRepository, ISCMProvider, ISCMResource, ISCMResourceGroup, ISCMResourceDecorations, IInputValidation, ISCMViewService, InputValidationType, ISCMActionButtonDescriptor } from 'vs/workbench/contrib/scm/common/scm';
|
||||
import { ExtHostContext, MainThreadSCMShape, ExtHostSCMShape, SCMProviderFeatures, SCMRawResourceSplices, SCMGroupFeatures, MainContext, SCMHistoryItemGroupDto } from '../common/extHost.protocol';
|
||||
import { ExtHostContext, MainThreadSCMShape, ExtHostSCMShape, SCMProviderFeatures, SCMRawResourceSplices, SCMGroupFeatures, MainContext, SCMHistoryItemGroupDto, SCMHistoryItemDto } from '../common/extHost.protocol';
|
||||
import { Command } from 'vs/editor/common/languages';
|
||||
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
@@ -42,6 +42,13 @@ function getIconFromIconDto(iconDto?: UriComponents | { light: UriComponents; da
|
||||
}
|
||||
}
|
||||
|
||||
function toISCMHistoryItem(historyItemDto: SCMHistoryItemDto): ISCMHistoryItem {
|
||||
const icon = getIconFromIconDto(historyItemDto.icon);
|
||||
const labels = historyItemDto.labels?.map(l => ({ title: l.title, icon: getIconFromIconDto(l.icon) }));
|
||||
|
||||
return { ...historyItemDto, icon, labels };
|
||||
}
|
||||
|
||||
class SCMInputBoxContentProvider extends Disposable implements ITextModelContentProvider {
|
||||
constructor(
|
||||
textModelService: ITextModelService,
|
||||
@@ -170,18 +177,27 @@ class MainThreadSCMHistoryProvider implements ISCMHistoryProvider {
|
||||
|
||||
constructor(private readonly proxy: ExtHostSCMShape, private readonly handle: number) { }
|
||||
|
||||
async resolveHistoryItemGroupBase(historyItemGroupId: string): Promise<ISCMHistoryItemGroup | undefined> {
|
||||
return this.proxy.$resolveHistoryItemGroupBase(this.handle, historyItemGroupId, CancellationToken.None);
|
||||
}
|
||||
|
||||
async resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string | undefined): Promise<{ id: string; ahead: number; behind: number } | undefined> {
|
||||
return this.proxy.$resolveHistoryItemGroupCommonAncestor(this.handle, historyItemGroupId1, historyItemGroupId2, CancellationToken.None);
|
||||
}
|
||||
|
||||
async provideHistoryItems(historyItemGroupId: string, options: ISCMHistoryOptions): Promise<ISCMHistoryItem[] | undefined> {
|
||||
const historyItems = await this.proxy.$provideHistoryItems(this.handle, historyItemGroupId, options, CancellationToken.None);
|
||||
return historyItems?.map(historyItem => ({ ...historyItem, icon: getIconFromIconDto(historyItem.icon) }));
|
||||
return historyItems?.map(historyItem => toISCMHistoryItem(historyItem));
|
||||
}
|
||||
|
||||
async provideHistoryItems2(options: ISCMHistoryOptions): Promise<ISCMHistoryItem[] | undefined> {
|
||||
const historyItems = await this.proxy.$provideHistoryItems2(this.handle, options, CancellationToken.None);
|
||||
return historyItems?.map(historyItem => toISCMHistoryItem(historyItem));
|
||||
}
|
||||
|
||||
async provideHistoryItemSummary(historyItemId: string, historyItemParentId: string | undefined): Promise<ISCMHistoryItem | undefined> {
|
||||
const historyItem = await this.proxy.$provideHistoryItemSummary(this.handle, historyItemId, historyItemParentId, CancellationToken.None);
|
||||
return historyItem ? { ...historyItem, icon: getIconFromIconDto(historyItem.icon) } : undefined;
|
||||
return historyItem ? toISCMHistoryItem(historyItem) : undefined;
|
||||
}
|
||||
|
||||
async provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined): Promise<ISCMHistoryItemChange[] | undefined> {
|
||||
|
||||
@@ -1525,6 +1525,15 @@ export interface SCMHistoryItemDto {
|
||||
readonly author?: string;
|
||||
readonly icon?: UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon;
|
||||
readonly timestamp?: number;
|
||||
readonly statistics?: {
|
||||
readonly files: number;
|
||||
readonly insertions: number;
|
||||
readonly deletions: number;
|
||||
};
|
||||
readonly labels?: {
|
||||
readonly title: string;
|
||||
readonly icon?: UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface SCMHistoryItemChangeDto {
|
||||
@@ -2317,8 +2326,10 @@ export interface ExtHostSCMShape {
|
||||
$validateInput(sourceControlHandle: number, value: string, cursorPosition: number): Promise<[string | IMarkdownString, number] | undefined>;
|
||||
$setSelectedSourceControl(selectedSourceControlHandle: number | undefined): Promise<void>;
|
||||
$provideHistoryItems(sourceControlHandle: number, historyItemGroupId: string, options: any, token: CancellationToken): Promise<SCMHistoryItemDto[] | undefined>;
|
||||
$provideHistoryItems2(sourceControlHandle: number, options: any, token: CancellationToken): Promise<SCMHistoryItemDto[] | undefined>;
|
||||
$provideHistoryItemSummary(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise<SCMHistoryItemDto | undefined>;
|
||||
$provideHistoryItemChanges(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise<SCMHistoryItemChangeDto[] | undefined>;
|
||||
$resolveHistoryItemGroupBase(sourceControlHandle: number, historyItemGroupId: string, token: CancellationToken): Promise<SCMHistoryItemGroupDto | undefined>;
|
||||
$resolveHistoryItemGroupCommonAncestor(sourceControlHandle: number, historyItemGroupId1: string, historyItemGroupId2: string | undefined, token: CancellationToken): Promise<{ id: string; ahead: number; behind: number } | undefined>;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { debounce } from 'vs/base/common/decorators';
|
||||
import { DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { asPromise } from 'vs/base/common/async';
|
||||
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
|
||||
import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, ICommandDto, MainThreadTelemetryShape, SCMGroupFeatures, SCMHistoryItemDto, SCMHistoryItemChangeDto } from './extHost.protocol';
|
||||
import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, ICommandDto, MainThreadTelemetryShape, SCMGroupFeatures, SCMHistoryItemDto, SCMHistoryItemChangeDto, SCMHistoryItemGroupDto } from './extHost.protocol';
|
||||
import { sortedDiff, equals } from 'vs/base/common/arrays';
|
||||
import { comparePaths } from 'vs/base/common/comparers';
|
||||
import type * as vscode from 'vscode';
|
||||
@@ -58,19 +58,26 @@ function getIconResource(decorations?: vscode.SourceControlResourceThemableDecor
|
||||
}
|
||||
}
|
||||
|
||||
function getHistoryItemIconDto(historyItem: vscode.SourceControlHistoryItem): UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon | undefined {
|
||||
if (!historyItem.icon) {
|
||||
function getHistoryItemIconDto(icon: vscode.Uri | { light: vscode.Uri; dark: vscode.Uri } | vscode.ThemeIcon | undefined): UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon | undefined {
|
||||
if (!icon) {
|
||||
return undefined;
|
||||
} else if (URI.isUri(historyItem.icon)) {
|
||||
return historyItem.icon;
|
||||
} else if (ThemeIcon.isThemeIcon(historyItem.icon)) {
|
||||
return historyItem.icon;
|
||||
} else if (URI.isUri(icon)) {
|
||||
return icon;
|
||||
} else if (ThemeIcon.isThemeIcon(icon)) {
|
||||
return icon;
|
||||
} else {
|
||||
const icon = historyItem.icon as { light: URI; dark: URI };
|
||||
return { light: icon.light, dark: icon.dark };
|
||||
const iconDto = icon as { light: URI; dark: URI };
|
||||
return { light: iconDto.light, dark: iconDto.dark };
|
||||
}
|
||||
}
|
||||
|
||||
function toSCMHistoryItemDto(historyItem: vscode.SourceControlHistoryItem): SCMHistoryItemDto {
|
||||
const icon = getHistoryItemIconDto(historyItem.icon);
|
||||
const labels = historyItem.labels?.map(l => ({ title: l.title, icon: getHistoryItemIconDto(l.icon) }));
|
||||
|
||||
return { ...historyItem, icon, labels };
|
||||
}
|
||||
|
||||
function compareResourceThemableDecorations(a: vscode.SourceControlResourceThemableDecorations, b: vscode.SourceControlResourceThemableDecorations): number {
|
||||
if (!a.iconPath && !b.iconPath) {
|
||||
return 0;
|
||||
@@ -963,6 +970,11 @@ export class ExtHostSCM implements ExtHostSCMShape {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
async $resolveHistoryItemGroupBase(sourceControlHandle: number, historyItemGroupId: string, token: CancellationToken): Promise<SCMHistoryItemGroupDto | undefined> {
|
||||
const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider;
|
||||
return await historyProvider?.resolveHistoryItemGroupBase(historyItemGroupId, token) ?? undefined;
|
||||
}
|
||||
|
||||
async $resolveHistoryItemGroupCommonAncestor(sourceControlHandle: number, historyItemGroupId1: string, historyItemGroupId2: string | undefined, token: CancellationToken): Promise<{ id: string; ahead: number; behind: number } | undefined> {
|
||||
const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider;
|
||||
return await historyProvider?.resolveHistoryItemGroupCommonAncestor(historyItemGroupId1, historyItemGroupId2, token) ?? undefined;
|
||||
@@ -972,7 +984,14 @@ export class ExtHostSCM implements ExtHostSCMShape {
|
||||
const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider;
|
||||
const historyItems = await historyProvider?.provideHistoryItems(historyItemGroupId, options, token);
|
||||
|
||||
return historyItems?.map(item => ({ ...item, icon: getHistoryItemIconDto(item) })) ?? undefined;
|
||||
return historyItems?.map(item => toSCMHistoryItemDto(item)) ?? undefined;
|
||||
}
|
||||
|
||||
async $provideHistoryItems2(sourceControlHandle: number, options: any, token: CancellationToken): Promise<SCMHistoryItemDto[] | undefined> {
|
||||
const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider;
|
||||
const historyItems = await historyProvider?.provideHistoryItems2(options, token);
|
||||
|
||||
return historyItems?.map(item => toSCMHistoryItemDto(item)) ?? undefined;
|
||||
}
|
||||
|
||||
async $provideHistoryItemSummary(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise<SCMHistoryItemDto | undefined> {
|
||||
@@ -982,7 +1001,7 @@ export class ExtHostSCM implements ExtHostSCMShape {
|
||||
}
|
||||
|
||||
const historyItem = await historyProvider.provideHistoryItemSummary(historyItemId, historyItemParentId, token);
|
||||
return historyItem ? { ...historyItem, icon: getHistoryItemIconDto(historyItem) } : undefined;
|
||||
return historyItem ? toSCMHistoryItemDto(historyItem) : undefined;
|
||||
}
|
||||
|
||||
async $provideHistoryItemChanges(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise<SCMHistoryItemChangeDto[] | undefined> {
|
||||
|
||||
@@ -129,6 +129,31 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.scm-view .monaco-list-row .history-item > .graph-container {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.scm-view .monaco-list-row .history-item > .graph-container > .graph > circle {
|
||||
stroke: var(--vscode-sideBar-background);
|
||||
}
|
||||
|
||||
.scm-view .monaco-list-row .history-item > .label-container {
|
||||
display: flex;
|
||||
opacity: 0.75;
|
||||
flex-shrink: 0;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.scm-view .monaco-list-row .history-item > .label-container > .codicon {
|
||||
font-size: 14px;
|
||||
border: 1px solid var(--vscode-scm-historyItemStatisticsBorder);
|
||||
border-radius: 2px;
|
||||
margin: 1px 0;
|
||||
padding: 2px
|
||||
}
|
||||
|
||||
.scm-view .monaco-list-row .history-item .stats-container {
|
||||
display: flex;
|
||||
font-size: 11px;
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { lastOrDefault } from 'vs/base/common/arrays';
|
||||
import { deepClone } from 'vs/base/common/objects';
|
||||
import { ISCMHistoryItem, ISCMHistoryItemGraphNode, ISCMHistoryItemViewModel } from 'vs/workbench/contrib/scm/common/history';
|
||||
|
||||
const SWIMLANE_HEIGHT = 22;
|
||||
const SWIMLANE_WIDTH = 11;
|
||||
const CIRCLE_RADIUS = 4;
|
||||
const SWIMLANE_CURVE_RADIUS = 5;
|
||||
|
||||
const graphColors = ['#007ACC', '#BC3FBC', '#BF8803', '#CC6633', '#F14C4C', '#16825D'];
|
||||
|
||||
function createPath(stroke: string): SVGPathElement {
|
||||
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
path.setAttribute('fill', 'none');
|
||||
path.setAttribute('stroke', stroke);
|
||||
path.setAttribute('stroke-width', '1px');
|
||||
path.setAttribute('stroke-linecap', 'round');
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
function drawCircle(index: number, radius: number, fill: string): SVGCircleElement {
|
||||
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
||||
circle.setAttribute('cx', `${SWIMLANE_WIDTH * (index + 1)}`);
|
||||
circle.setAttribute('cy', `${SWIMLANE_WIDTH}`);
|
||||
circle.setAttribute('r', `${radius}`);
|
||||
circle.setAttribute('fill', fill);
|
||||
|
||||
return circle;
|
||||
}
|
||||
|
||||
function drawVerticalLine(x1: number, y1: number, y2: number, color: string): SVGPathElement {
|
||||
const path = createPath(color);
|
||||
path.setAttribute('d', `M ${x1} ${y1} V ${y2}`);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
function findLastIndex(nodes: ISCMHistoryItemGraphNode[], id: string): number {
|
||||
for (let i = nodes.length - 1; i >= 0; i--) {
|
||||
if (nodes[i].id === id) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
export function renderSCMHistoryItemGraph(historyItemViewModel: ISCMHistoryItemViewModel): SVGElement {
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.classList.add('graph');
|
||||
|
||||
const historyItem = historyItemViewModel.historyItem;
|
||||
const inputSwimlanes = historyItemViewModel.inputSwimlanes;
|
||||
const outputSwimlanes = historyItemViewModel.outputSwimlanes;
|
||||
|
||||
const inputIndex = inputSwimlanes.findIndex(node => node.id === historyItem.id);
|
||||
const outputIndex = historyItem.parentIds.length === 0 ? -1 : outputSwimlanes.findIndex(node => node.id === historyItem.parentIds[0]);
|
||||
|
||||
const circleIndex = inputIndex !== -1 ? inputIndex : inputSwimlanes.length;
|
||||
const circleColorIndex = inputIndex !== -1 ? inputSwimlanes[inputIndex].color : outputSwimlanes[circleIndex]?.color ?? 0;
|
||||
|
||||
for (let index = 0; index < inputSwimlanes.length; index++) {
|
||||
const node = inputSwimlanes[index];
|
||||
const color = graphColors[inputSwimlanes[index].color];
|
||||
|
||||
// Not the current commit
|
||||
if (node.id !== historyItem.id) {
|
||||
if (index < outputSwimlanes.length && node.id === outputSwimlanes[index].id) {
|
||||
// Draw |
|
||||
const path = drawVerticalLine(SWIMLANE_WIDTH * (index + 1), 0, SWIMLANE_HEIGHT, color);
|
||||
svg.append(path);
|
||||
} else {
|
||||
const d: string[] = [];
|
||||
const path = createPath(color);
|
||||
|
||||
// Draw |
|
||||
d.push(`M ${SWIMLANE_WIDTH * (index + 1)} 0`);
|
||||
d.push(`V 6`);
|
||||
|
||||
// Draw /
|
||||
d.push(`A ${SWIMLANE_CURVE_RADIUS} ${SWIMLANE_CURVE_RADIUS} 0 0 1 ${(SWIMLANE_WIDTH * (index + 1)) - SWIMLANE_CURVE_RADIUS} ${SWIMLANE_HEIGHT / 2}`);
|
||||
|
||||
// Start walking backwards from the current index and
|
||||
// find the first occurrence in the output swimlanes
|
||||
// array
|
||||
let nodeOutputIndex = -1;
|
||||
for (let j = Math.min(index, outputSwimlanes.length) - 1; j >= 0; j--) {
|
||||
if (outputSwimlanes[j].id === node.id) {
|
||||
nodeOutputIndex = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw -
|
||||
d.push(`H ${(SWIMLANE_WIDTH * (nodeOutputIndex + 1)) + SWIMLANE_CURVE_RADIUS}`);
|
||||
|
||||
// Draw /
|
||||
d.push(`A ${SWIMLANE_CURVE_RADIUS} ${SWIMLANE_CURVE_RADIUS} 0 0 0 ${SWIMLANE_WIDTH * (nodeOutputIndex + 1)} ${(SWIMLANE_HEIGHT / 2) + SWIMLANE_CURVE_RADIUS}`);
|
||||
|
||||
// Draw |
|
||||
d.push(`V ${SWIMLANE_HEIGHT}`);
|
||||
|
||||
path.setAttribute('d', d.join(' '));
|
||||
svg.append(path);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Base commit
|
||||
if (index !== circleIndex) {
|
||||
const d: string[] = [];
|
||||
const path = createPath(color);
|
||||
|
||||
// Draw /
|
||||
d.push(`M ${SWIMLANE_WIDTH * (index + 1)} 0`);
|
||||
d.push(`A ${SWIMLANE_WIDTH} ${SWIMLANE_WIDTH} 0 0 1 ${SWIMLANE_WIDTH * (index)} ${SWIMLANE_WIDTH}`);
|
||||
|
||||
// Draw -
|
||||
d.push(`H ${SWIMLANE_WIDTH * (circleIndex + 1)}`);
|
||||
|
||||
path.setAttribute('d', d.join(' '));
|
||||
svg.append(path);
|
||||
}
|
||||
}
|
||||
|
||||
// Add remaining parent(s)
|
||||
for (let i = 1; i < historyItem.parentIds.length; i++) {
|
||||
const parentOutputIndex = findLastIndex(outputSwimlanes, historyItem.parentIds[i]);
|
||||
if (parentOutputIndex === -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Draw -\
|
||||
const d: string[] = [];
|
||||
const path = createPath(graphColors[outputSwimlanes[parentOutputIndex].color]);
|
||||
|
||||
// Draw \
|
||||
d.push(`M ${SWIMLANE_WIDTH * parentOutputIndex} ${SWIMLANE_HEIGHT / 2}`);
|
||||
d.push(`A ${SWIMLANE_WIDTH} ${SWIMLANE_WIDTH} 0 0 1 ${SWIMLANE_WIDTH * (parentOutputIndex + 1)} ${SWIMLANE_HEIGHT}`);
|
||||
|
||||
// Draw -
|
||||
d.push(`M ${SWIMLANE_WIDTH * parentOutputIndex} ${SWIMLANE_HEIGHT / 2}`);
|
||||
d.push(`H ${SWIMLANE_WIDTH * (circleIndex + 1)} `);
|
||||
|
||||
path.setAttribute('d', d.join(' '));
|
||||
svg.append(path);
|
||||
}
|
||||
|
||||
// Draw | to circle
|
||||
if (inputIndex !== -1) {
|
||||
const path = drawVerticalLine(SWIMLANE_WIDTH * (circleIndex + 1), 0, SWIMLANE_HEIGHT / 2, graphColors[circleColorIndex]);
|
||||
svg.append(path);
|
||||
}
|
||||
|
||||
// Draw | from circle
|
||||
if (outputIndex !== -1) {
|
||||
const path = drawVerticalLine(SWIMLANE_WIDTH * (circleIndex + 1), SWIMLANE_HEIGHT / 2, SWIMLANE_HEIGHT, graphColors[circleColorIndex]);
|
||||
svg.append(path);
|
||||
}
|
||||
|
||||
// Draw *
|
||||
if (historyItem.parentIds.length > 1) {
|
||||
// Multi-parent node
|
||||
const circleOuter = drawCircle(circleIndex, CIRCLE_RADIUS + 1, graphColors[circleColorIndex]);
|
||||
svg.append(circleOuter);
|
||||
|
||||
const circleInner = drawCircle(circleIndex, CIRCLE_RADIUS - 1, graphColors[circleColorIndex]);
|
||||
svg.append(circleInner);
|
||||
} else {
|
||||
// Node
|
||||
const circle = drawCircle(circleIndex, CIRCLE_RADIUS, graphColors[circleColorIndex]);
|
||||
svg.append(circle);
|
||||
}
|
||||
|
||||
// Set dimensions
|
||||
svg.style.height = `${SWIMLANE_HEIGHT}px`;
|
||||
svg.style.width = `${SWIMLANE_WIDTH * (Math.max(inputSwimlanes.length, outputSwimlanes.length, 1) + 1)}px`;
|
||||
|
||||
return svg;
|
||||
}
|
||||
|
||||
export function toISCMHistoryItemViewModelArray(historyItems: ISCMHistoryItem[]): ISCMHistoryItemViewModel[] {
|
||||
let colorIndex = -1;
|
||||
const viewModels: ISCMHistoryItemViewModel[] = [];
|
||||
|
||||
for (let index = 0; index < historyItems.length; index++) {
|
||||
const historyItem = historyItems[index];
|
||||
|
||||
const outputSwimlanesFromPreviousItem = lastOrDefault(viewModels)?.outputSwimlanes ?? [];
|
||||
const inputSwimlanes = outputSwimlanesFromPreviousItem.map(i => deepClone(i));
|
||||
const outputSwimlanes: ISCMHistoryItemGraphNode[] = [];
|
||||
|
||||
if (historyItem.parentIds.length > 0) {
|
||||
let firstParentAdded = false;
|
||||
|
||||
// Add first parent to the output
|
||||
for (const node of inputSwimlanes) {
|
||||
if (node.id === historyItem.id) {
|
||||
if (!firstParentAdded) {
|
||||
outputSwimlanes.push({
|
||||
...deepClone(node),
|
||||
id: historyItem.parentIds[0]
|
||||
});
|
||||
firstParentAdded = true;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
outputSwimlanes.push(deepClone(node));
|
||||
}
|
||||
|
||||
// Add unprocessed parent(s) to the output
|
||||
for (let i = firstParentAdded ? 1 : 0; i < historyItem.parentIds.length; i++) {
|
||||
colorIndex = colorIndex < graphColors.length - 1 ? colorIndex + 1 : 1;
|
||||
outputSwimlanes.push({
|
||||
id: historyItem.parentIds[i],
|
||||
color: colorIndex
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
viewModels.push({
|
||||
historyItem,
|
||||
inputSwimlanes,
|
||||
outputSwimlanes,
|
||||
});
|
||||
}
|
||||
|
||||
return viewModels;
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import { IDisposable, Disposable, DisposableStore, combinedDisposable, dispose,
|
||||
import { ViewPane, IViewPaneOptions, ViewAction } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { append, $, Dimension, asCSSUrl, trackFocus, clearNode, prepend, isPointerEvent, isActiveElement } from 'vs/base/browser/dom';
|
||||
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryProviderCacheEntry, SCMHistoryItemChangeTreeElement, SCMHistoryItemGroupTreeElement, SCMHistoryItemTreeElement, SCMViewSeparatorElement } from 'vs/workbench/contrib/scm/common/history';
|
||||
import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryItemViewModel, SCMHistoryItemViewModelTreeElement, ISCMHistoryProviderCacheEntry, SCMHistoryItemChangeTreeElement, SCMHistoryItemGroupTreeElement, SCMHistoryItemTreeElement, SCMViewSeparatorElement } from 'vs/workbench/contrib/scm/common/history';
|
||||
import { ISCMResourceGroup, ISCMResource, InputValidationType, ISCMRepository, ISCMInput, IInputValidation, ISCMViewService, ISCMViewVisibleRepositoryChangeEvent, ISCMService, SCMInputChangeReason, VIEW_PANE_ID, ISCMActionButton, ISCMActionButtonDescriptor, ISCMRepositorySortKey, ISCMInputValueProviderContext, ISCMProvider } from 'vs/workbench/contrib/scm/common/scm';
|
||||
import { ResourceLabels, IResourceLabel, IFileLabelOptions } from 'vs/workbench/browser/labels';
|
||||
import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge';
|
||||
@@ -24,7 +24,7 @@ import { MenuItemAction, IMenuService, registerAction2, MenuId, IAction2Options,
|
||||
import { IAction, ActionRunner, Action, Separator, IActionRunner } from 'vs/base/common/actions';
|
||||
import { ActionBar, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IThemeService, IFileIconTheme } from 'vs/platform/theme/common/themeService';
|
||||
import { isSCMResource, isSCMResourceGroup, connectPrimaryMenuToInlineActionBar, isSCMRepository, isSCMInput, collectContextMenuActions, getActionViewItemProvider, isSCMActionButton, isSCMViewService, isSCMHistoryItemGroupTreeElement, isSCMHistoryItemTreeElement, isSCMHistoryItemChangeTreeElement, toDiffEditorArguments, isSCMResourceNode, isSCMHistoryItemChangeNode, isSCMViewSeparator, connectPrimaryMenu } from './util';
|
||||
import { isSCMResource, isSCMResourceGroup, connectPrimaryMenuToInlineActionBar, isSCMRepository, isSCMInput, collectContextMenuActions, getActionViewItemProvider, isSCMActionButton, isSCMViewService, isSCMHistoryItemGroupTreeElement, isSCMHistoryItemTreeElement, isSCMHistoryItemChangeTreeElement, toDiffEditorArguments, isSCMResourceNode, isSCMHistoryItemChangeNode, isSCMViewSeparator, connectPrimaryMenu, isSCMHistoryItemViewModelTreeElement } from './util';
|
||||
import { WorkbenchCompressibleAsyncDataTree, IOpenEvent } from 'vs/platform/list/browser/listService';
|
||||
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { disposableTimeout, Sequencer, ThrottledDelayer, Throttler } from 'vs/base/common/async';
|
||||
@@ -102,7 +102,6 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { DropdownWithPrimaryActionViewItem } from 'vs/platform/actions/browser/dropdownWithPrimaryActionViewItem';
|
||||
import { clamp, rot } from 'vs/base/common/numbers';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory';
|
||||
import { MarkdownString } from 'vs/base/common/htmlContent';
|
||||
import type { IManagedHover, IManagedHoverTooltipMarkdownString } from 'vs/base/browser/ui/hover/hover';
|
||||
import { IHoverService } from 'vs/platform/hover/browser/hover';
|
||||
@@ -110,6 +109,8 @@ import { OpenScmGroupAction } from 'vs/workbench/contrib/multiDiffEditor/browser
|
||||
import { HoverController } from 'vs/editor/contrib/hover/browser/hoverController';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { autorun } from 'vs/base/common/observable';
|
||||
import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory';
|
||||
import { renderSCMHistoryItemGraph, toISCMHistoryItemViewModelArray } from 'vs/workbench/contrib/scm/browser/scmHistory';
|
||||
|
||||
// type SCMResourceTreeNode = IResourceNode<ISCMResource, ISCMResourceGroup>;
|
||||
// type SCMHistoryItemChangeResourceTreeNode = IResourceNode<SCMHistoryItemChangeTreeElement, SCMHistoryItemTreeElement>;
|
||||
@@ -122,6 +123,7 @@ type TreeElement =
|
||||
IResourceNode<ISCMResource, ISCMResourceGroup> |
|
||||
SCMHistoryItemGroupTreeElement |
|
||||
SCMHistoryItemTreeElement |
|
||||
SCMHistoryItemViewModelTreeElement |
|
||||
SCMHistoryItemChangeTreeElement |
|
||||
IResourceNode<SCMHistoryItemChangeTreeElement, SCMHistoryItemTreeElement> |
|
||||
SCMViewSeparatorElement;
|
||||
@@ -1003,6 +1005,104 @@ class HistoryItemRenderer implements ICompressibleTreeRenderer<SCMHistoryItemTre
|
||||
}
|
||||
}
|
||||
|
||||
interface HistoryItem2Template {
|
||||
readonly label: IconLabel;
|
||||
readonly graphContainer: HTMLElement;
|
||||
readonly labelContainer: HTMLElement;
|
||||
readonly elementDisposables: DisposableStore;
|
||||
readonly disposables: IDisposable;
|
||||
}
|
||||
|
||||
class HistoryItem2Renderer implements ICompressibleTreeRenderer<SCMHistoryItemViewModelTreeElement, LabelFuzzyScore, HistoryItem2Template> {
|
||||
|
||||
static readonly TEMPLATE_ID = 'history-item-2';
|
||||
get templateId(): string { return HistoryItem2Renderer.TEMPLATE_ID; }
|
||||
|
||||
constructor(
|
||||
@IHoverService private readonly hoverService: IHoverService
|
||||
) { }
|
||||
|
||||
renderTemplate(container: HTMLElement): HistoryItem2Template {
|
||||
// hack
|
||||
(container.parentElement!.parentElement!.querySelector('.monaco-tl-twistie')! as HTMLElement).classList.add('force-no-twistie');
|
||||
|
||||
const element = append(container, $('.history-item'));
|
||||
const graphContainer = append(element, $('.graph-container'));
|
||||
|
||||
const iconLabel = new IconLabel(element, { supportIcons: true, supportHighlights: true, supportDescriptionHighlights: true });
|
||||
|
||||
const labelContainer = append(element, $('.label-container'));
|
||||
element.appendChild(labelContainer);
|
||||
|
||||
return { graphContainer, label: iconLabel, labelContainer, elementDisposables: new DisposableStore(), disposables: new DisposableStore() };
|
||||
}
|
||||
|
||||
renderElement(node: ITreeNode<SCMHistoryItemViewModelTreeElement, LabelFuzzyScore>, index: number, templateData: HistoryItem2Template, height: number | undefined): void {
|
||||
const historyItemViewModel = node.element.historyItemViewModel;
|
||||
const historyItem = historyItemViewModel.historyItem;
|
||||
|
||||
templateData.graphContainer.textContent = '';
|
||||
templateData.graphContainer.appendChild(renderSCMHistoryItemGraph(historyItemViewModel));
|
||||
|
||||
const title = this.getTooltip(historyItemViewModel);
|
||||
const [matches, descriptionMatches] = this.processMatches(historyItemViewModel, node.filterData);
|
||||
templateData.label.setLabel(historyItem.message, undefined, { title, matches, descriptionMatches });
|
||||
|
||||
templateData.labelContainer.textContent = '';
|
||||
if (historyItem.labels) {
|
||||
for (const label of historyItem.labels) {
|
||||
if (label.icon && ThemeIcon.isThemeIcon(label.icon)) {
|
||||
const icon = append(templateData.labelContainer, $('div.label'));
|
||||
icon.classList.add(...ThemeIcon.asClassNameArray(label.icon));
|
||||
|
||||
templateData.elementDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('element'), icon, label.title));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<SCMHistoryItemViewModelTreeElement>, LabelFuzzyScore>, index: number, templateData: HistoryItem2Template, height: number | undefined): void {
|
||||
throw new Error('Should never happen since node is incompressible');
|
||||
}
|
||||
|
||||
private getTooltip(historyItemViewModel: ISCMHistoryItemViewModel): IManagedHoverTooltipMarkdownString {
|
||||
const historyItem = historyItemViewModel.historyItem;
|
||||
const markdown = new MarkdownString('', { isTrusted: true, supportThemeIcons: true });
|
||||
|
||||
if (historyItem.author) {
|
||||
markdown.appendMarkdown(`$(account) **${historyItem.author}**\n\n`);
|
||||
}
|
||||
|
||||
if (historyItem.timestamp) {
|
||||
const dateFormatter = new Intl.DateTimeFormat(platform.language, { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' });
|
||||
markdown.appendMarkdown(`$(history) ${dateFormatter.format(historyItem.timestamp)}\n\n`);
|
||||
}
|
||||
|
||||
markdown.appendMarkdown(historyItem.message);
|
||||
|
||||
return { markdown, markdownNotSupportedFallback: historyItem.message };
|
||||
}
|
||||
|
||||
private processMatches(historyItemViewModel: ISCMHistoryItemViewModel, filterData: LabelFuzzyScore | undefined): [IMatch[] | undefined, IMatch[] | undefined] {
|
||||
if (!filterData) {
|
||||
return [undefined, undefined];
|
||||
}
|
||||
|
||||
return [
|
||||
historyItemViewModel.historyItem.message === filterData.label ? createMatches(filterData.score) : undefined,
|
||||
historyItemViewModel.historyItem.author === filterData.label ? createMatches(filterData.score) : undefined
|
||||
];
|
||||
}
|
||||
|
||||
disposeElement(element: ITreeNode<SCMHistoryItemViewModelTreeElement, LabelFuzzyScore>, index: number, templateData: HistoryItem2Template, height: number | undefined): void {
|
||||
templateData.elementDisposables.clear();
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: HistoryItem2Template): void {
|
||||
templateData.disposables.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
interface HistoryItemChangeTemplate {
|
||||
readonly element: HTMLElement;
|
||||
readonly name: HTMLElement;
|
||||
@@ -1084,6 +1184,7 @@ class SeparatorRenderer implements ICompressibleTreeRenderer<SCMViewSeparatorEle
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IContextMenuService private readonly contextMenuService: IContextMenuService,
|
||||
@IKeybindingService private readonly keybindingService: IKeybindingService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@IMenuService private readonly menuService: IMenuService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService
|
||||
@@ -1102,8 +1203,10 @@ class SeparatorRenderer implements ICompressibleTreeRenderer<SCMViewSeparatorEle
|
||||
append(element, $('.separator'));
|
||||
disposables.add(label);
|
||||
|
||||
const toolBar = new MenuWorkbenchToolBar(append(element, $('.actions')), MenuId.SCMChangesSeparator, { moreIcon: Codicon.gear }, this.menuService, this.contextKeyService, this.contextMenuService, this.keybindingService, this.commandService, this.telemetryService);
|
||||
disposables.add(toolBar);
|
||||
if (this.configurationService.getValue<boolean>('scm.showHistoryGraph') !== true) {
|
||||
const toolBar = new MenuWorkbenchToolBar(append(element, $('.actions')), MenuId.SCMChangesSeparator, { moreIcon: Codicon.gear }, this.menuService, this.contextKeyService, this.contextMenuService, this.keybindingService, this.commandService, this.telemetryService);
|
||||
disposables.add(toolBar);
|
||||
}
|
||||
|
||||
return { label, disposables };
|
||||
}
|
||||
@@ -1150,6 +1253,8 @@ class ListDelegate implements IListVirtualDelegate<TreeElement> {
|
||||
return HistoryItemGroupRenderer.TEMPLATE_ID;
|
||||
} else if (isSCMHistoryItemTreeElement(element)) {
|
||||
return HistoryItemRenderer.TEMPLATE_ID;
|
||||
} else if (isSCMHistoryItemViewModelTreeElement(element)) {
|
||||
return HistoryItem2Renderer.TEMPLATE_ID;
|
||||
} else if (isSCMHistoryItemChangeTreeElement(element) || isSCMHistoryItemChangeNode(element)) {
|
||||
return HistoryItemChangeRenderer.TEMPLATE_ID;
|
||||
} else if (isSCMViewSeparator(element)) {
|
||||
@@ -1230,6 +1335,10 @@ export class SCMTreeSorter implements ITreeSorter<TreeElement> {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (isSCMHistoryItemViewModelTreeElement(one)) {
|
||||
return isSCMHistoryItemViewModelTreeElement(other) ? 0 : 1;
|
||||
}
|
||||
|
||||
if (isSCMHistoryItemChangeTreeElement(one) || isSCMHistoryItemChangeNode(one)) {
|
||||
// List
|
||||
if (this.viewMode() === ViewMode.List) {
|
||||
@@ -1314,6 +1423,11 @@ export class SCMTreeKeyboardNavigationLabelProvider implements ICompressibleKeyb
|
||||
// the author. A match in the message takes precedence over
|
||||
// a match in the author.
|
||||
return [element.message, element.author];
|
||||
} else if (isSCMHistoryItemViewModelTreeElement(element)) {
|
||||
// For a history item we want to match both the message and
|
||||
// the author. A match in the message takes precedence over
|
||||
// a match in the author.
|
||||
return [element.historyItemViewModel.historyItem.message, element.historyItemViewModel.historyItem.author];
|
||||
} else if (isSCMViewSeparator(element)) {
|
||||
return element.label;
|
||||
} else {
|
||||
@@ -1364,6 +1478,10 @@ function getSCMResourceId(element: TreeElement): string {
|
||||
const historyItemGroup = element.historyItemGroup;
|
||||
const provider = historyItemGroup.repository.provider;
|
||||
return `historyItem:${provider.id}/${historyItemGroup.id}/${element.id}/${element.parentIds.join(',')}`;
|
||||
} else if (isSCMHistoryItemViewModelTreeElement(element)) {
|
||||
const provider = element.repository.provider;
|
||||
const historyItem = element.historyItemViewModel.historyItem;
|
||||
return `historyItem2:${provider.id}/${historyItem.id}/${historyItem.parentIds.join(',')}`;
|
||||
} else if (isSCMHistoryItemChangeTreeElement(element)) {
|
||||
const historyItem = element.historyItem;
|
||||
const historyItemGroup = historyItem.historyItemGroup;
|
||||
@@ -1414,6 +1532,9 @@ export class SCMAccessibilityProvider implements IListAccessibilityProvider<Tree
|
||||
return element.ariaLabel ?? `${element.label.trim()}${element.description ? `, ${element.description}` : ''}`;
|
||||
} else if (isSCMHistoryItemTreeElement(element)) {
|
||||
return `${stripIcons(element.message).trim()}${element.author ? `, ${element.author}` : ''}`;
|
||||
} else if (isSCMHistoryItemViewModelTreeElement(element)) {
|
||||
const historyItem = element.historyItemViewModel.historyItem;
|
||||
return `${stripIcons(historyItem.message).trim()}${historyItem.author ? `, ${historyItem.author}` : ''}`;
|
||||
} else if (isSCMHistoryItemChangeTreeElement(element)) {
|
||||
const result = [basename(element.uri)];
|
||||
const path = this.labelService.getUriLabel(dirname(element.uri), { relative: true, noPrefix: true });
|
||||
@@ -1488,7 +1609,7 @@ MenuRegistry.appendMenuItem(MenuId.SCMTitle, {
|
||||
MenuRegistry.appendMenuItem(MenuId.SCMTitle, {
|
||||
title: localize('scmChanges', "Incoming & Outgoing"),
|
||||
submenu: Menus.ChangesSettings,
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_PANE_ID), ContextKeys.RepositoryCount.notEqualsTo(0)),
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_PANE_ID), ContextKeys.RepositoryCount.notEqualsTo(0), ContextKeyExpr.equals('config.scm.showHistoryGraph', true).negate()),
|
||||
group: '0_view&sort',
|
||||
order: 2
|
||||
});
|
||||
@@ -2895,7 +3016,8 @@ export class SCMViewPane extends ViewPane {
|
||||
e.affectsConfiguration('scm.showActionButton') ||
|
||||
e.affectsConfiguration('scm.showChangesSummary') ||
|
||||
e.affectsConfiguration('scm.showIncomingChanges') ||
|
||||
e.affectsConfiguration('scm.showOutgoingChanges'),
|
||||
e.affectsConfiguration('scm.showOutgoingChanges') ||
|
||||
e.affectsConfiguration('scm.showHistoryGraph'),
|
||||
this.visibilityDisposables)
|
||||
(() => this.updateChildren(), this, this.visibilityDisposables);
|
||||
|
||||
@@ -2973,6 +3095,7 @@ export class SCMViewPane extends ViewPane {
|
||||
this.instantiationService.createInstance(ResourceRenderer, () => this.viewMode, this.listLabels, getActionViewItemProvider(this.instantiationService), resourceActionRunner),
|
||||
this.instantiationService.createInstance(HistoryItemGroupRenderer, historyItemGroupActionRunner),
|
||||
this.instantiationService.createInstance(HistoryItemRenderer, historyItemActionRunner, getActionViewItemProvider(this.instantiationService)),
|
||||
this.instantiationService.createInstance(HistoryItem2Renderer),
|
||||
this.instantiationService.createInstance(HistoryItemChangeRenderer, () => this.viewMode, this.listLabels),
|
||||
this.instantiationService.createInstance(SeparatorRenderer)
|
||||
],
|
||||
@@ -3601,6 +3724,8 @@ class SCMTreeDataSource implements IAsyncDataSource<ISCMViewService, TreeElement
|
||||
return true;
|
||||
} else if (isSCMHistoryItemTreeElement(inputOrElement)) {
|
||||
return true;
|
||||
} else if (isSCMHistoryItemViewModelTreeElement(inputOrElement)) {
|
||||
return false;
|
||||
} else if (isSCMHistoryItemChangeTreeElement(inputOrElement)) {
|
||||
return false;
|
||||
} else if (isSCMViewSeparator(inputOrElement)) {
|
||||
@@ -3667,6 +3792,17 @@ class SCMTreeDataSource implements IAsyncDataSource<ISCMViewService, TreeElement
|
||||
|
||||
children.push(...historyItemGroups);
|
||||
|
||||
// History items
|
||||
const historyItems = await this.getHistoryItems2(inputOrElement);
|
||||
if (historyItems.length > 0) {
|
||||
const label = localize('historySeparatorHeader', "History");
|
||||
const ariaLabel = localize('historySeparatorHeaderAriaLabel', "History");
|
||||
|
||||
children.push({ label, ariaLabel, repository: inputOrElement, type: 'separator' } satisfies SCMViewSeparatorElement);
|
||||
}
|
||||
|
||||
children.push(...historyItems);
|
||||
|
||||
return children;
|
||||
} else if (isSCMResourceGroup(inputOrElement)) {
|
||||
if (this.viewMode() === ViewMode.List) {
|
||||
@@ -3701,13 +3837,13 @@ class SCMTreeDataSource implements IAsyncDataSource<ISCMViewService, TreeElement
|
||||
}
|
||||
|
||||
private async getHistoryItemGroups(element: ISCMRepository): Promise<SCMHistoryItemGroupTreeElement[]> {
|
||||
const { showIncomingChanges, showOutgoingChanges } = this.getConfiguration();
|
||||
const { showIncomingChanges, showOutgoingChanges, showHistoryGraph } = this.getConfiguration();
|
||||
|
||||
const scmProvider = element.provider;
|
||||
const historyProvider = scmProvider.historyProvider;
|
||||
const currentHistoryItemGroup = historyProvider?.currentHistoryItemGroup;
|
||||
|
||||
if (!historyProvider || !currentHistoryItemGroup || (showIncomingChanges === 'never' && showOutgoingChanges === 'never')) {
|
||||
if (!historyProvider || !currentHistoryItemGroup || (showIncomingChanges === 'never' && showOutgoingChanges === 'never') || showHistoryGraph) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -3822,6 +3958,44 @@ class SCMTreeDataSource implements IAsyncDataSource<ISCMViewService, TreeElement
|
||||
return children;
|
||||
}
|
||||
|
||||
private async getHistoryItems2(element: ISCMRepository): Promise<SCMHistoryItemViewModelTreeElement[]> {
|
||||
const { showHistoryGraph } = this.getConfiguration();
|
||||
|
||||
const historyProvider = element.provider.historyProvider;
|
||||
const currentHistoryItemGroup = historyProvider?.currentHistoryItemGroup;
|
||||
|
||||
if (!currentHistoryItemGroup || !showHistoryGraph) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const historyProviderCacheEntry = this.getHistoryProviderCacheEntry(element);
|
||||
let historyItemsElement = historyProviderCacheEntry.historyItems2.get(element.id);
|
||||
const historyItemsMap = historyProviderCacheEntry.historyItems2;
|
||||
|
||||
if (!historyItemsElement) {
|
||||
const historyItemGroupBase = await historyProvider.resolveHistoryItemGroupBase(currentHistoryItemGroup.id);
|
||||
const historyItemGroupIds = [
|
||||
currentHistoryItemGroup.id,
|
||||
...currentHistoryItemGroup.base ? [currentHistoryItemGroup.base.id] : [],
|
||||
...historyItemGroupBase ? [historyItemGroupBase.id] : []
|
||||
];
|
||||
|
||||
historyItemsElement = await historyProvider.provideHistoryItems2({ historyItemGroupIds }) ?? [];
|
||||
|
||||
this.historyProviderCache.set(element, {
|
||||
...historyProviderCacheEntry,
|
||||
historyItems2: historyItemsMap.set(element.id, historyItemsElement)
|
||||
});
|
||||
}
|
||||
|
||||
return toISCMHistoryItemViewModelArray(historyItemsElement)
|
||||
.map(v => ({
|
||||
repository: element,
|
||||
historyItemViewModel: v,
|
||||
type: 'historyItem2'
|
||||
}) satisfies SCMHistoryItemViewModelTreeElement);
|
||||
}
|
||||
|
||||
private async getHistoryItemChanges(element: SCMHistoryItemTreeElement): Promise<(SCMHistoryItemChangeTreeElement | IResourceNode<SCMHistoryItemChangeTreeElement, SCMHistoryItemTreeElement>)[]> {
|
||||
const repository = element.historyItemGroup.repository;
|
||||
const historyProvider = repository.provider.historyProvider;
|
||||
@@ -3919,13 +4093,15 @@ class SCMTreeDataSource implements IAsyncDataSource<ISCMViewService, TreeElement
|
||||
showChangesSummary: boolean;
|
||||
showIncomingChanges: ShowChangesSetting;
|
||||
showOutgoingChanges: ShowChangesSetting;
|
||||
showHistoryGraph: boolean;
|
||||
} {
|
||||
return {
|
||||
alwaysShowRepositories: this.configurationService.getValue<boolean>('scm.alwaysShowRepositories'),
|
||||
showActionButton: this.configurationService.getValue<boolean>('scm.showActionButton'),
|
||||
showChangesSummary: this.configurationService.getValue<boolean>('scm.showChangesSummary'),
|
||||
showIncomingChanges: this.configurationService.getValue<ShowChangesSetting>('scm.showIncomingChanges'),
|
||||
showOutgoingChanges: this.configurationService.getValue<ShowChangesSetting>('scm.showOutgoingChanges')
|
||||
showOutgoingChanges: this.configurationService.getValue<ShowChangesSetting>('scm.showOutgoingChanges'),
|
||||
showHistoryGraph: this.configurationService.getValue<boolean>('scm.showHistoryGraph')
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3963,6 +4139,7 @@ class SCMTreeDataSource implements IAsyncDataSource<ISCMViewService, TreeElement
|
||||
incomingHistoryItemGroup: undefined,
|
||||
outgoingHistoryItemGroup: undefined,
|
||||
historyItems: new Map<string, [ISCMHistoryItem | undefined, ISCMHistoryItem[]]>(),
|
||||
historyItems2: new Map<string, ISCMHistoryItem[]>(),
|
||||
historyItemChanges: new Map<string, ISCMHistoryItemChange[]>()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as path from 'vs/base/common/path';
|
||||
import { SCMHistoryItemChangeTreeElement, SCMHistoryItemGroupTreeElement, SCMHistoryItemTreeElement, SCMViewSeparatorElement } from 'vs/workbench/contrib/scm/common/history';
|
||||
import { SCMHistoryItemChangeTreeElement, SCMHistoryItemGroupTreeElement, SCMHistoryItemTreeElement, SCMHistoryItemViewModelTreeElement, SCMViewSeparatorElement } from 'vs/workbench/contrib/scm/common/history';
|
||||
import { ISCMResource, ISCMRepository, ISCMResourceGroup, ISCMInput, ISCMActionButton, ISCMViewService, ISCMProvider } from 'vs/workbench/contrib/scm/common/scm';
|
||||
import { IMenu, MenuItemAction } from 'vs/platform/actions/common/actions';
|
||||
import { ActionBar, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
@@ -62,6 +62,10 @@ export function isSCMHistoryItemTreeElement(element: any): element is SCMHistory
|
||||
(element as SCMHistoryItemTreeElement).type === 'historyItem';
|
||||
}
|
||||
|
||||
export function isSCMHistoryItemViewModelTreeElement(element: any): element is SCMHistoryItemViewModelTreeElement {
|
||||
return (element as SCMHistoryItemViewModelTreeElement).type === 'historyItem2';
|
||||
}
|
||||
|
||||
export function isSCMHistoryItemChangeTreeElement(element: any): element is SCMHistoryItemChangeTreeElement {
|
||||
return (element as SCMHistoryItemChangeTreeElement).type === 'historyItemChange';
|
||||
}
|
||||
|
||||
@@ -26,8 +26,10 @@ export interface ISCMHistoryProvider {
|
||||
readonly currentHistoryItemGroupObs: IObservable<ISCMHistoryItemGroup | undefined>;
|
||||
|
||||
provideHistoryItems(historyItemGroupId: string, options: ISCMHistoryOptions): Promise<ISCMHistoryItem[] | undefined>;
|
||||
provideHistoryItems2(options: ISCMHistoryOptions): Promise<ISCMHistoryItem[] | undefined>;
|
||||
provideHistoryItemSummary(historyItemId: string, historyItemParentId: string | undefined): Promise<ISCMHistoryItem | undefined>;
|
||||
provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined): Promise<ISCMHistoryItemChange[] | undefined>;
|
||||
resolveHistoryItemGroupBase(historyItemGroupId: string): Promise<ISCMHistoryItemGroup | undefined>;
|
||||
resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string | undefined): Promise<{ id: string; ahead: number; behind: number } | undefined>;
|
||||
}
|
||||
|
||||
@@ -35,12 +37,14 @@ export interface ISCMHistoryProviderCacheEntry {
|
||||
readonly incomingHistoryItemGroup: SCMHistoryItemGroupTreeElement | undefined;
|
||||
readonly outgoingHistoryItemGroup: SCMHistoryItemGroupTreeElement | undefined;
|
||||
readonly historyItems: Map<string, [ISCMHistoryItem | undefined, ISCMHistoryItem[]]>;
|
||||
readonly historyItems2: Map<string, ISCMHistoryItem[]>;
|
||||
readonly historyItemChanges: Map<string, ISCMHistoryItemChange[]>;
|
||||
}
|
||||
|
||||
export interface ISCMHistoryOptions {
|
||||
readonly cursor?: string;
|
||||
readonly limit?: number | { id?: string };
|
||||
readonly historyItemGroupIds?: readonly string[];
|
||||
}
|
||||
|
||||
export interface ISCMHistoryItemGroup {
|
||||
@@ -68,6 +72,11 @@ export interface ISCMHistoryItemStatistics {
|
||||
readonly deletions: number;
|
||||
}
|
||||
|
||||
export interface ISCMHistoryItemLabel {
|
||||
readonly title: string;
|
||||
readonly icon?: URI | { light: URI; dark: URI } | ThemeIcon;
|
||||
}
|
||||
|
||||
export interface ISCMHistoryItem {
|
||||
readonly id: string;
|
||||
readonly parentIds: string[];
|
||||
@@ -76,6 +85,24 @@ export interface ISCMHistoryItem {
|
||||
readonly icon?: URI | { light: URI; dark: URI } | ThemeIcon;
|
||||
readonly timestamp?: number;
|
||||
readonly statistics?: ISCMHistoryItemStatistics;
|
||||
readonly labels?: ISCMHistoryItemLabel[];
|
||||
}
|
||||
|
||||
export interface ISCMHistoryItemGraphNode {
|
||||
readonly id: string;
|
||||
readonly color: number;
|
||||
}
|
||||
|
||||
export interface ISCMHistoryItemViewModel {
|
||||
readonly historyItem: ISCMHistoryItem;
|
||||
readonly inputSwimlanes: ISCMHistoryItemGraphNode[];
|
||||
readonly outputSwimlanes: ISCMHistoryItemGraphNode[];
|
||||
}
|
||||
|
||||
export interface SCMHistoryItemViewModelTreeElement {
|
||||
readonly repository: ISCMRepository;
|
||||
readonly historyItemViewModel: ISCMHistoryItemViewModel;
|
||||
readonly type: 'historyItem2';
|
||||
}
|
||||
|
||||
export interface SCMHistoryItemTreeElement extends ISCMHistoryItem {
|
||||
|
||||
@@ -0,0 +1,503 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
|
||||
import { toISCMHistoryItemViewModelArray } from 'vs/workbench/contrib/scm/browser/scmHistory';
|
||||
import { ISCMHistoryItem } from 'vs/workbench/contrib/scm/common/history';
|
||||
|
||||
suite('toISCMHistoryItemViewModelArray', () => {
|
||||
|
||||
ensureNoDisposablesAreLeakedInTestSuite();
|
||||
|
||||
test('empty graph', () => {
|
||||
const viewModels = toISCMHistoryItemViewModelArray([]);
|
||||
|
||||
assert.strictEqual(viewModels.length, 0);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* * a
|
||||
*/
|
||||
|
||||
test('single commit', () => {
|
||||
const models = [
|
||||
{ id: 'a', parentIds: [], message: '' },
|
||||
] as ISCMHistoryItem[];
|
||||
|
||||
const viewModels = toISCMHistoryItemViewModelArray(models);
|
||||
|
||||
assert.strictEqual(viewModels.length, 1);
|
||||
|
||||
assert.strictEqual(viewModels[0].inputSwimlanes.length, 0);
|
||||
assert.strictEqual(viewModels[0].outputSwimlanes.length, 0);
|
||||
});
|
||||
|
||||
/**
|
||||
* * a(b)
|
||||
* * b(c)
|
||||
* * c(d)
|
||||
* * d(e)
|
||||
* * e
|
||||
*/
|
||||
test('linear graph', () => {
|
||||
const models = [
|
||||
{ id: 'a', parentIds: ['b'] },
|
||||
{ id: 'b', parentIds: ['c'] },
|
||||
{ id: 'c', parentIds: ['d'] },
|
||||
{ id: 'd', parentIds: ['e'] },
|
||||
{ id: 'e', parentIds: [] },
|
||||
] as ISCMHistoryItem[];
|
||||
|
||||
const viewModels = toISCMHistoryItemViewModelArray(models);
|
||||
|
||||
assert.strictEqual(viewModels.length, 5);
|
||||
|
||||
// node a
|
||||
assert.strictEqual(viewModels[0].inputSwimlanes.length, 0);
|
||||
|
||||
assert.strictEqual(viewModels[0].outputSwimlanes.length, 1);
|
||||
assert.strictEqual(viewModels[0].outputSwimlanes[0].id, 'b');
|
||||
assert.strictEqual(viewModels[0].outputSwimlanes[0].color, 0);
|
||||
|
||||
// node b
|
||||
assert.strictEqual(viewModels[1].inputSwimlanes.length, 1);
|
||||
assert.strictEqual(viewModels[1].inputSwimlanes[0].id, 'b');
|
||||
assert.strictEqual(viewModels[1].inputSwimlanes[0].color, 0);
|
||||
|
||||
assert.strictEqual(viewModels[1].outputSwimlanes.length, 1);
|
||||
assert.strictEqual(viewModels[1].outputSwimlanes[0].id, 'c');
|
||||
assert.strictEqual(viewModels[1].outputSwimlanes[0].color, 0);
|
||||
|
||||
// node c
|
||||
assert.strictEqual(viewModels[2].inputSwimlanes.length, 1);
|
||||
assert.strictEqual(viewModels[2].inputSwimlanes[0].id, 'c');
|
||||
assert.strictEqual(viewModels[2].inputSwimlanes[0].color, 0);
|
||||
|
||||
assert.strictEqual(viewModels[2].outputSwimlanes.length, 1);
|
||||
assert.strictEqual(viewModels[2].outputSwimlanes[0].id, 'd');
|
||||
assert.strictEqual(viewModels[2].outputSwimlanes[0].color, 0);
|
||||
|
||||
// node d
|
||||
assert.strictEqual(viewModels[3].inputSwimlanes.length, 1);
|
||||
assert.strictEqual(viewModels[3].inputSwimlanes[0].id, 'd');
|
||||
assert.strictEqual(viewModels[3].inputSwimlanes[0].color, 0);
|
||||
|
||||
assert.strictEqual(viewModels[3].outputSwimlanes.length, 1);
|
||||
assert.strictEqual(viewModels[3].outputSwimlanes[0].id, 'e');
|
||||
assert.strictEqual(viewModels[3].outputSwimlanes[0].color, 0);
|
||||
|
||||
// node e
|
||||
assert.strictEqual(viewModels[4].inputSwimlanes.length, 1);
|
||||
assert.strictEqual(viewModels[4].inputSwimlanes[0].id, 'e');
|
||||
assert.strictEqual(viewModels[4].inputSwimlanes[0].color, 0);
|
||||
|
||||
assert.strictEqual(viewModels[4].outputSwimlanes.length, 0);
|
||||
});
|
||||
|
||||
/**
|
||||
* * a(b)
|
||||
* * b(c,d)
|
||||
* |\
|
||||
* | * d(c)
|
||||
* |/
|
||||
* * c(e)
|
||||
* * e(f)
|
||||
*/
|
||||
test('merge commit (single commit in topic branch)', () => {
|
||||
const models = [
|
||||
{ id: 'a', parentIds: ['b'] },
|
||||
{ id: 'b', parentIds: ['c', 'd'] },
|
||||
{ id: 'd', parentIds: ['c'] },
|
||||
{ id: 'c', parentIds: ['e'] },
|
||||
{ id: 'e', parentIds: ['f'] },
|
||||
] as ISCMHistoryItem[];
|
||||
|
||||
const viewModels = toISCMHistoryItemViewModelArray(models);
|
||||
|
||||
assert.strictEqual(viewModels.length, 5);
|
||||
|
||||
// node a
|
||||
assert.strictEqual(viewModels[0].inputSwimlanes.length, 0);
|
||||
|
||||
assert.strictEqual(viewModels[0].outputSwimlanes.length, 1);
|
||||
assert.strictEqual(viewModels[0].outputSwimlanes[0].id, 'b');
|
||||
assert.strictEqual(viewModels[0].outputSwimlanes[0].color, 0);
|
||||
|
||||
// node b
|
||||
assert.strictEqual(viewModels[1].inputSwimlanes.length, 1);
|
||||
assert.strictEqual(viewModels[1].inputSwimlanes[0].id, 'b');
|
||||
assert.strictEqual(viewModels[1].inputSwimlanes[0].color, 0);
|
||||
|
||||
assert.strictEqual(viewModels[1].outputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[1].outputSwimlanes[0].id, 'c');
|
||||
assert.strictEqual(viewModels[1].outputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[1].outputSwimlanes[1].id, 'd');
|
||||
assert.strictEqual(viewModels[1].outputSwimlanes[1].color, 1);
|
||||
|
||||
// node d
|
||||
assert.strictEqual(viewModels[2].inputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[2].inputSwimlanes[0].id, 'c');
|
||||
assert.strictEqual(viewModels[2].inputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[2].inputSwimlanes[1].id, 'd');
|
||||
assert.strictEqual(viewModels[2].inputSwimlanes[1].color, 1);
|
||||
|
||||
assert.strictEqual(viewModels[2].outputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[2].outputSwimlanes[0].id, 'c');
|
||||
assert.strictEqual(viewModels[2].outputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[2].outputSwimlanes[1].id, 'c');
|
||||
assert.strictEqual(viewModels[2].outputSwimlanes[1].color, 1);
|
||||
|
||||
// node c
|
||||
assert.strictEqual(viewModels[3].inputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[3].inputSwimlanes[0].id, 'c');
|
||||
assert.strictEqual(viewModels[3].inputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[3].inputSwimlanes[1].id, 'c');
|
||||
assert.strictEqual(viewModels[3].inputSwimlanes[1].color, 1);
|
||||
|
||||
assert.strictEqual(viewModels[3].outputSwimlanes.length, 1);
|
||||
assert.strictEqual(viewModels[3].outputSwimlanes[0].id, 'e');
|
||||
assert.strictEqual(viewModels[3].outputSwimlanes[0].color, 0);
|
||||
|
||||
// node e
|
||||
assert.strictEqual(viewModels[4].inputSwimlanes.length, 1);
|
||||
assert.strictEqual(viewModels[4].inputSwimlanes[0].id, 'e');
|
||||
assert.strictEqual(viewModels[4].inputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[4].outputSwimlanes.length, 1);
|
||||
assert.strictEqual(viewModels[4].outputSwimlanes[0].id, 'f');
|
||||
assert.strictEqual(viewModels[4].outputSwimlanes[0].color, 0);
|
||||
});
|
||||
|
||||
/**
|
||||
* * a(b,c)
|
||||
* |\
|
||||
* | * c(d)
|
||||
* * | b(e)
|
||||
* * | e(f)
|
||||
* * | f(d)
|
||||
* |/
|
||||
* * d(g)
|
||||
*/
|
||||
test('merge commit (multiple commits in topic branch)', () => {
|
||||
const models = [
|
||||
{ id: 'a', parentIds: ['b', 'c'] },
|
||||
{ id: 'c', parentIds: ['d'] },
|
||||
{ id: 'b', parentIds: ['e'] },
|
||||
{ id: 'e', parentIds: ['f'] },
|
||||
{ id: 'f', parentIds: ['d'] },
|
||||
{ id: 'd', parentIds: ['g'] },
|
||||
] as ISCMHistoryItem[];
|
||||
|
||||
const viewModels = toISCMHistoryItemViewModelArray(models);
|
||||
|
||||
assert.strictEqual(viewModels.length, 6);
|
||||
|
||||
// node a
|
||||
assert.strictEqual(viewModels[0].inputSwimlanes.length, 0);
|
||||
|
||||
assert.strictEqual(viewModels[0].outputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[0].outputSwimlanes[0].id, 'b');
|
||||
assert.strictEqual(viewModels[0].outputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[0].outputSwimlanes[1].id, 'c');
|
||||
assert.strictEqual(viewModels[0].outputSwimlanes[1].color, 1);
|
||||
|
||||
// node c
|
||||
assert.strictEqual(viewModels[1].inputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[1].inputSwimlanes[0].id, 'b');
|
||||
assert.strictEqual(viewModels[1].inputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[1].inputSwimlanes[1].id, 'c');
|
||||
assert.strictEqual(viewModels[1].inputSwimlanes[1].color, 1);
|
||||
|
||||
assert.strictEqual(viewModels[1].outputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[1].outputSwimlanes[0].id, 'b');
|
||||
assert.strictEqual(viewModels[1].outputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[1].outputSwimlanes[1].id, 'd');
|
||||
assert.strictEqual(viewModels[1].outputSwimlanes[1].color, 1);
|
||||
|
||||
// node b
|
||||
assert.strictEqual(viewModels[2].inputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[2].inputSwimlanes[0].id, 'b');
|
||||
assert.strictEqual(viewModels[2].inputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[2].inputSwimlanes[1].id, 'd');
|
||||
assert.strictEqual(viewModels[2].inputSwimlanes[1].color, 1);
|
||||
|
||||
assert.strictEqual(viewModels[2].outputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[2].outputSwimlanes[0].id, 'e');
|
||||
assert.strictEqual(viewModels[2].outputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[2].outputSwimlanes[1].id, 'd');
|
||||
assert.strictEqual(viewModels[2].outputSwimlanes[1].color, 1);
|
||||
|
||||
// node e
|
||||
assert.strictEqual(viewModels[3].inputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[3].inputSwimlanes[0].id, 'e');
|
||||
assert.strictEqual(viewModels[3].inputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[3].inputSwimlanes[1].id, 'd');
|
||||
assert.strictEqual(viewModels[3].inputSwimlanes[1].color, 1);
|
||||
|
||||
assert.strictEqual(viewModels[3].outputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[3].outputSwimlanes[0].id, 'f');
|
||||
assert.strictEqual(viewModels[3].outputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[3].outputSwimlanes[1].id, 'd');
|
||||
assert.strictEqual(viewModels[3].outputSwimlanes[1].color, 1);
|
||||
|
||||
// node f
|
||||
assert.strictEqual(viewModels[4].inputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[4].inputSwimlanes[0].id, 'f');
|
||||
assert.strictEqual(viewModels[4].inputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[4].inputSwimlanes[1].id, 'd');
|
||||
assert.strictEqual(viewModels[4].inputSwimlanes[1].color, 1);
|
||||
|
||||
assert.strictEqual(viewModels[4].outputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[4].outputSwimlanes[0].id, 'd');
|
||||
assert.strictEqual(viewModels[4].outputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[4].outputSwimlanes[1].id, 'd');
|
||||
assert.strictEqual(viewModels[4].outputSwimlanes[1].color, 1);
|
||||
|
||||
// node d
|
||||
assert.strictEqual(viewModels[5].inputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[5].inputSwimlanes[0].id, 'd');
|
||||
assert.strictEqual(viewModels[5].inputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[5].inputSwimlanes[1].id, 'd');
|
||||
assert.strictEqual(viewModels[5].inputSwimlanes[1].color, 1);
|
||||
|
||||
assert.strictEqual(viewModels[5].outputSwimlanes.length, 1);
|
||||
assert.strictEqual(viewModels[5].outputSwimlanes[0].id, 'g');
|
||||
assert.strictEqual(viewModels[5].outputSwimlanes[0].color, 0);
|
||||
});
|
||||
|
||||
/**
|
||||
* * a(b,c)
|
||||
* |\
|
||||
* | * c(b)
|
||||
* |/
|
||||
* * b(d,e)
|
||||
* |\
|
||||
* | * e(f)
|
||||
* | * f(g)
|
||||
* * | d(h)
|
||||
*/
|
||||
test('create brach from merge commit', () => {
|
||||
const models = [
|
||||
{ id: 'a', parentIds: ['b', 'c'] },
|
||||
{ id: 'c', parentIds: ['b'] },
|
||||
{ id: 'b', parentIds: ['d', 'e'] },
|
||||
{ id: 'e', parentIds: ['f'] },
|
||||
{ id: 'f', parentIds: ['g'] },
|
||||
{ id: 'd', parentIds: ['h'] },
|
||||
] as ISCMHistoryItem[];
|
||||
|
||||
const viewModels = toISCMHistoryItemViewModelArray(models);
|
||||
|
||||
assert.strictEqual(viewModels.length, 6);
|
||||
|
||||
// node a
|
||||
assert.strictEqual(viewModels[0].inputSwimlanes.length, 0);
|
||||
|
||||
assert.strictEqual(viewModels[0].outputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[0].outputSwimlanes[0].id, 'b');
|
||||
assert.strictEqual(viewModels[0].outputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[0].outputSwimlanes[1].id, 'c');
|
||||
assert.strictEqual(viewModels[0].outputSwimlanes[1].color, 1);
|
||||
|
||||
// node c
|
||||
assert.strictEqual(viewModels[1].inputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[1].inputSwimlanes[0].id, 'b');
|
||||
assert.strictEqual(viewModels[1].inputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[1].inputSwimlanes[1].id, 'c');
|
||||
assert.strictEqual(viewModels[1].inputSwimlanes[1].color, 1);
|
||||
|
||||
assert.strictEqual(viewModels[1].outputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[1].outputSwimlanes[0].id, 'b');
|
||||
assert.strictEqual(viewModels[1].outputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[1].outputSwimlanes[1].id, 'b');
|
||||
assert.strictEqual(viewModels[1].outputSwimlanes[1].color, 1);
|
||||
|
||||
// node b
|
||||
assert.strictEqual(viewModels[2].inputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[2].inputSwimlanes[0].id, 'b');
|
||||
assert.strictEqual(viewModels[2].inputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[2].inputSwimlanes[1].id, 'b');
|
||||
assert.strictEqual(viewModels[2].inputSwimlanes[1].color, 1);
|
||||
|
||||
assert.strictEqual(viewModels[2].outputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[2].outputSwimlanes[0].id, 'd');
|
||||
assert.strictEqual(viewModels[2].outputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[2].outputSwimlanes[1].id, 'e');
|
||||
assert.strictEqual(viewModels[2].outputSwimlanes[1].color, 2);
|
||||
|
||||
// node e
|
||||
assert.strictEqual(viewModels[3].inputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[3].inputSwimlanes[0].id, 'd');
|
||||
assert.strictEqual(viewModels[3].inputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[3].inputSwimlanes[1].id, 'e');
|
||||
assert.strictEqual(viewModels[3].inputSwimlanes[1].color, 2);
|
||||
|
||||
assert.strictEqual(viewModels[3].outputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[3].outputSwimlanes[0].id, 'd');
|
||||
assert.strictEqual(viewModels[3].outputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[3].outputSwimlanes[1].id, 'f');
|
||||
assert.strictEqual(viewModels[3].outputSwimlanes[1].color, 2);
|
||||
|
||||
// node f
|
||||
assert.strictEqual(viewModels[4].inputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[4].inputSwimlanes[0].id, 'd');
|
||||
assert.strictEqual(viewModels[4].inputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[4].inputSwimlanes[1].id, 'f');
|
||||
assert.strictEqual(viewModels[4].inputSwimlanes[1].color, 2);
|
||||
|
||||
assert.strictEqual(viewModels[4].outputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[4].outputSwimlanes[0].id, 'd');
|
||||
assert.strictEqual(viewModels[4].outputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[4].outputSwimlanes[1].id, 'g');
|
||||
assert.strictEqual(viewModels[4].outputSwimlanes[1].color, 2);
|
||||
|
||||
// node d
|
||||
assert.strictEqual(viewModels[5].inputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[5].inputSwimlanes[0].id, 'd');
|
||||
assert.strictEqual(viewModels[5].inputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[5].inputSwimlanes[1].id, 'g');
|
||||
assert.strictEqual(viewModels[5].inputSwimlanes[1].color, 2);
|
||||
|
||||
assert.strictEqual(viewModels[5].outputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[5].outputSwimlanes[0].id, 'h');
|
||||
assert.strictEqual(viewModels[5].outputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[5].outputSwimlanes[1].id, 'g');
|
||||
assert.strictEqual(viewModels[5].outputSwimlanes[1].color, 2);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* * a(b,c)
|
||||
* |\
|
||||
* | * c(d)
|
||||
* * | b(e,f)
|
||||
* |\|
|
||||
* | |\
|
||||
* | | * f(g)
|
||||
* * | | e(g)
|
||||
* | * | d(g)
|
||||
* |/ /
|
||||
* | /
|
||||
* |/
|
||||
* * g(h)
|
||||
*/
|
||||
test('create multiple branches from a commit', () => {
|
||||
const models = [
|
||||
{ id: 'a', parentIds: ['b', 'c'] },
|
||||
{ id: 'c', parentIds: ['d'] },
|
||||
{ id: 'b', parentIds: ['e', 'f'] },
|
||||
{ id: 'f', parentIds: ['g'] },
|
||||
{ id: 'e', parentIds: ['g'] },
|
||||
{ id: 'd', parentIds: ['g'] },
|
||||
{ id: 'g', parentIds: ['h'] },
|
||||
] as ISCMHistoryItem[];
|
||||
|
||||
const viewModels = toISCMHistoryItemViewModelArray(models);
|
||||
|
||||
assert.strictEqual(viewModels.length, 7);
|
||||
|
||||
// node a
|
||||
assert.strictEqual(viewModels[0].inputSwimlanes.length, 0);
|
||||
|
||||
assert.strictEqual(viewModels[0].outputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[0].outputSwimlanes[0].id, 'b');
|
||||
assert.strictEqual(viewModels[0].outputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[0].outputSwimlanes[1].id, 'c');
|
||||
assert.strictEqual(viewModels[0].outputSwimlanes[1].color, 1);
|
||||
|
||||
// node c
|
||||
assert.strictEqual(viewModels[1].inputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[1].inputSwimlanes[0].id, 'b');
|
||||
assert.strictEqual(viewModels[1].inputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[1].inputSwimlanes[1].id, 'c');
|
||||
assert.strictEqual(viewModels[1].inputSwimlanes[1].color, 1);
|
||||
|
||||
assert.strictEqual(viewModels[1].outputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[1].outputSwimlanes[0].id, 'b');
|
||||
assert.strictEqual(viewModels[1].outputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[1].outputSwimlanes[1].id, 'd');
|
||||
assert.strictEqual(viewModels[1].outputSwimlanes[1].color, 1);
|
||||
|
||||
// node b
|
||||
assert.strictEqual(viewModels[2].inputSwimlanes.length, 2);
|
||||
assert.strictEqual(viewModels[2].inputSwimlanes[0].id, 'b');
|
||||
assert.strictEqual(viewModels[2].inputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[2].inputSwimlanes[1].id, 'd');
|
||||
assert.strictEqual(viewModels[2].inputSwimlanes[1].color, 1);
|
||||
|
||||
assert.strictEqual(viewModels[2].outputSwimlanes.length, 3);
|
||||
assert.strictEqual(viewModels[2].outputSwimlanes[0].id, 'e');
|
||||
assert.strictEqual(viewModels[2].outputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[2].outputSwimlanes[1].id, 'd');
|
||||
assert.strictEqual(viewModels[2].outputSwimlanes[1].color, 1);
|
||||
assert.strictEqual(viewModels[2].outputSwimlanes[2].id, 'f');
|
||||
assert.strictEqual(viewModels[2].outputSwimlanes[2].color, 2);
|
||||
|
||||
// node f
|
||||
assert.strictEqual(viewModels[3].inputSwimlanes.length, 3);
|
||||
assert.strictEqual(viewModels[3].inputSwimlanes[0].id, 'e');
|
||||
assert.strictEqual(viewModels[3].inputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[3].inputSwimlanes[1].id, 'd');
|
||||
assert.strictEqual(viewModels[3].inputSwimlanes[1].color, 1);
|
||||
assert.strictEqual(viewModels[3].inputSwimlanes[2].id, 'f');
|
||||
assert.strictEqual(viewModels[3].inputSwimlanes[2].color, 2);
|
||||
|
||||
assert.strictEqual(viewModels[3].outputSwimlanes.length, 3);
|
||||
assert.strictEqual(viewModels[3].outputSwimlanes[0].id, 'e');
|
||||
assert.strictEqual(viewModels[3].outputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[3].outputSwimlanes[1].id, 'd');
|
||||
assert.strictEqual(viewModels[3].outputSwimlanes[1].color, 1);
|
||||
assert.strictEqual(viewModels[3].outputSwimlanes[2].id, 'g');
|
||||
assert.strictEqual(viewModels[3].outputSwimlanes[2].color, 2);
|
||||
|
||||
// node e
|
||||
assert.strictEqual(viewModels[4].inputSwimlanes.length, 3);
|
||||
assert.strictEqual(viewModels[4].inputSwimlanes[0].id, 'e');
|
||||
assert.strictEqual(viewModels[4].inputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[4].inputSwimlanes[1].id, 'd');
|
||||
assert.strictEqual(viewModels[4].inputSwimlanes[1].color, 1);
|
||||
assert.strictEqual(viewModels[4].inputSwimlanes[2].id, 'g');
|
||||
assert.strictEqual(viewModels[4].inputSwimlanes[2].color, 2);
|
||||
|
||||
assert.strictEqual(viewModels[4].outputSwimlanes.length, 3);
|
||||
assert.strictEqual(viewModels[4].outputSwimlanes[0].id, 'g');
|
||||
assert.strictEqual(viewModels[4].outputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[4].outputSwimlanes[1].id, 'd');
|
||||
assert.strictEqual(viewModels[4].outputSwimlanes[1].color, 1);
|
||||
assert.strictEqual(viewModels[4].outputSwimlanes[2].id, 'g');
|
||||
assert.strictEqual(viewModels[4].outputSwimlanes[2].color, 2);
|
||||
|
||||
// node d
|
||||
assert.strictEqual(viewModels[5].inputSwimlanes.length, 3);
|
||||
assert.strictEqual(viewModels[5].inputSwimlanes[0].id, 'g');
|
||||
assert.strictEqual(viewModels[5].inputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[5].inputSwimlanes[1].id, 'd');
|
||||
assert.strictEqual(viewModels[5].inputSwimlanes[1].color, 1);
|
||||
assert.strictEqual(viewModels[5].inputSwimlanes[2].id, 'g');
|
||||
assert.strictEqual(viewModels[5].inputSwimlanes[2].color, 2);
|
||||
|
||||
assert.strictEqual(viewModels[5].outputSwimlanes.length, 3);
|
||||
assert.strictEqual(viewModels[5].outputSwimlanes[0].id, 'g');
|
||||
assert.strictEqual(viewModels[5].outputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[5].outputSwimlanes[1].id, 'g');
|
||||
assert.strictEqual(viewModels[5].outputSwimlanes[1].color, 1);
|
||||
assert.strictEqual(viewModels[5].outputSwimlanes[2].id, 'g');
|
||||
assert.strictEqual(viewModels[5].outputSwimlanes[2].color, 2);
|
||||
|
||||
// node g
|
||||
assert.strictEqual(viewModels[6].inputSwimlanes.length, 3);
|
||||
assert.strictEqual(viewModels[6].inputSwimlanes[0].id, 'g');
|
||||
assert.strictEqual(viewModels[6].inputSwimlanes[0].color, 0);
|
||||
assert.strictEqual(viewModels[6].inputSwimlanes[1].id, 'g');
|
||||
assert.strictEqual(viewModels[6].inputSwimlanes[1].color, 1);
|
||||
assert.strictEqual(viewModels[6].inputSwimlanes[2].id, 'g');
|
||||
assert.strictEqual(viewModels[6].inputSwimlanes[2].color, 2);
|
||||
|
||||
assert.strictEqual(viewModels[6].outputSwimlanes.length, 1);
|
||||
assert.strictEqual(viewModels[6].outputSwimlanes[0].id, 'h');
|
||||
assert.strictEqual(viewModels[6].outputSwimlanes[0].color, 0);
|
||||
});
|
||||
});
|
||||
+10
-6
@@ -25,26 +25,24 @@ declare module 'vscode' {
|
||||
// onDidChangeHistoryItemGroups: Event<SourceControlHistoryChangeEvent>;
|
||||
|
||||
provideHistoryItems(historyItemGroupId: string, options: SourceControlHistoryOptions, token: CancellationToken): ProviderResult<SourceControlHistoryItem[]>;
|
||||
provideHistoryItems2(options: SourceControlHistoryOptions, token: CancellationToken): ProviderResult<SourceControlHistoryItem[]>;
|
||||
provideHistoryItemSummary?(historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): ProviderResult<SourceControlHistoryItem>;
|
||||
provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): ProviderResult<SourceControlHistoryItemChange[]>;
|
||||
|
||||
resolveHistoryItemGroupBase(historyItemGroupId: string, token: CancellationToken): ProviderResult<SourceControlHistoryItemGroup>;
|
||||
resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string | undefined, token: CancellationToken): ProviderResult<{ id: string; ahead: number; behind: number }>;
|
||||
}
|
||||
|
||||
export interface SourceControlHistoryOptions {
|
||||
readonly cursor?: string;
|
||||
readonly limit?: number | { id?: string };
|
||||
readonly historyItemGroupIds?: readonly string[];
|
||||
}
|
||||
|
||||
export interface SourceControlHistoryItemGroup {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly base?: Omit<SourceControlRemoteHistoryItemGroup, 'base'>;
|
||||
}
|
||||
|
||||
export interface SourceControlRemoteHistoryItemGroup {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly base?: Omit<SourceControlHistoryItemGroup, 'base'>;
|
||||
}
|
||||
|
||||
export interface SourceControlHistoryItemStatistics {
|
||||
@@ -53,6 +51,11 @@ declare module 'vscode' {
|
||||
readonly deletions: number;
|
||||
}
|
||||
|
||||
export interface SourceControlHistoryItemLabel {
|
||||
readonly title: string;
|
||||
readonly icon?: Uri | { light: Uri; dark: Uri } | ThemeIcon;
|
||||
}
|
||||
|
||||
export interface SourceControlHistoryItem {
|
||||
readonly id: string;
|
||||
readonly parentIds: string[];
|
||||
@@ -61,6 +64,7 @@ declare module 'vscode' {
|
||||
readonly icon?: Uri | { light: Uri; dark: Uri } | ThemeIcon;
|
||||
readonly timestamp?: number;
|
||||
readonly statistics?: SourceControlHistoryItemStatistics;
|
||||
readonly labels?: SourceControlHistoryItemLabel[];
|
||||
}
|
||||
|
||||
export interface SourceControlHistoryItemChange {
|
||||
|
||||
Reference in new issue
Block a user