From e41d489d21171802a009a3173ccac2560c977146 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Thu, 13 Oct 2022 02:21:28 -0400 Subject: [PATCH] Refines graph ref hiding actions - Avoids polluting GitReference with graph specific data - Avoids Git tips ordering for proper group context - Reworks group context to merge with base context --- package.json | 36 +++-- src/commands/gitCommands.actions.ts | 7 +- src/env/node/git/localGitProvider.ts | 249 +++++++++++++++++++------------- src/git/models/graph.ts | 2 + src/git/models/reference.ts | 54 +++---- src/git/models/tag.ts | 2 +- src/plus/github/githubGitProvider.ts | 4 + src/plus/webviews/graph/graphWebview.ts | 133 +++++++++++------ src/plus/webviews/graph/protocol.ts | 8 +- src/storage.ts | 4 +- src/system/webview.ts | 14 +- 11 files changed, 316 insertions(+), 197 deletions(-) diff --git a/package.json b/package.json index 9edf02c..3f05be6 100644 --- a/package.json +++ b/package.json @@ -6349,18 +6349,23 @@ "icon": "$(gitlens-switch)" }, { - "command": "gitlens.graph.hideBranch", - "title": "Hide Branch...", + "command": "gitlens.graph.hideLocalBranch", + "title": "Hide Local Branch", + "category": "GitLens" + }, + { + "command": "gitlens.graph.hideRemoteBranch", + "title": "Hide Remote Branch", "category": "GitLens" }, { "command": "gitlens.graph.hideTag", - "title": "Hide Tag...", + "title": "Hide Tag", "category": "GitLens" }, { - "command": "gitlens.graph.hideGroupedBranch", - "title": "Hide from Graph...", + "command": "gitlens.graph.hideRefGroup", + "title": "Hide", "category": "GitLens" }, { @@ -8387,7 +8392,11 @@ "when": "false" }, { - "command": "gitlens.graph.hideBranch", + "command": "gitlens.graph.hideLocalBranch", + "when": "false" + }, + { + "command": "gitlens.graph.hideRemoteBranch", "when": "false" }, { @@ -8395,7 +8404,7 @@ "when": "false" }, { - "command": "gitlens.graph.hideGroupedBranch", + "command": "gitlens.graph.hideRefGroup", "when": "false" }, { @@ -11028,13 +11037,18 @@ "group": "1_gitlens_actions_@10" }, { - "command": "gitlens.graph.hideBranch", - "when": "webviewItem =~ /gitlens:branch\\b(?!.*?\\b\\+(grouped|current)\\b)/", + "command": "gitlens.graph.hideLocalBranch", + "when": "webviewItem =~ /gitlens:branch\\b(?!.*?\\b\\+(current|remote)\\b)/", + "group": "8_gitlens_actions@10" + }, + { + "command": "gitlens.graph.hideRemoteBranch", + "when": "webviewItem =~ /gitlens:branch\\b(?=.*?\\b\\+remote\\b)(?!.*?\\b\\+current\\b)/", "group": "8_gitlens_actions@10" }, { - "command": "gitlens.graph.hideGroupedBranch", - "when": "webviewItem =~ /gitlens:branch\\b(?!.*?\\b\\+current\\b)/ && webviewItem =~ /gitlens:branch\\+grouped\\b/", + "command": "gitlens.graph.hideRefGroup", + "when": "webviewItemGroup =~ /gitlens:refGroup\\b(?!.*?\\b\\+current\\b)/", "group": "8_gitlens_actions@11" }, { diff --git a/src/commands/gitCommands.actions.ts b/src/commands/gitCommands.actions.ts index cdf14b8..757a776 100644 --- a/src/commands/gitCommands.actions.ts +++ b/src/commands/gitCommands.actions.ts @@ -109,10 +109,15 @@ export namespace GitActions { }); } - export function switchTo(repos?: string | string[] | Repository | Repository[], ref?: GitReference) { + export function switchTo( + repos?: string | string[] | Repository | Repository[], + ref?: GitReference, + confirm?: boolean, + ) { return executeGitCommand({ command: 'switch', state: { repos: repos, reference: ref }, + confirm: confirm, }); } diff --git a/src/env/node/git/localGitProvider.ts b/src/env/node/git/localGitProvider.ts index 901d727..834efef 100644 --- a/src/env/node/git/localGitProvider.ts +++ b/src/env/node/git/localGitProvider.ts @@ -122,7 +122,13 @@ import { showGitMissingErrorMessage, showGitVersionUnsupportedErrorMessage, } from '../../../messages'; -import type { GraphItemContext, GraphItemRefContext } from '../../../plus/webviews/graph/graphWebview'; +import type { + GraphBranchContextValue, + GraphItemContext, + GraphItemRefContext, + GraphItemRefGroupContext, + GraphTagContextValue, +} from '../../../plus/webviews/graph/graphWebview'; import { countStringLength, filterMap } from '../../../system/array'; import { TimedCancellationSource } from '../../../system/cancellation'; import { gate } from '../../../system/decorators/gate'; @@ -142,7 +148,14 @@ import { } from '../../../system/path'; import type { PromiseOrValue } from '../../../system/promise'; import { any, fastestSettled, getSettledValue } from '../../../system/promise'; -import { equalsIgnoreCase, getDurationMilliseconds, interpolate, md5, splitSingle } from '../../../system/string'; +import { + equalsIgnoreCase, + getDurationMilliseconds, + interpolate, + md5, + sortCompare, + splitSingle, +} from '../../../system/string'; import { PathTrie } from '../../../system/trie'; import { compare, fromString } from '../../../system/version'; import { serializeWebviewItemContext } from '../../../system/webview'; @@ -1648,10 +1661,7 @@ export class LocalGitProvider implements GitProvider, Disposable { const currentUser = getSettledValue(currentUserResult); const remotes = getSettledValue(remotesResult); - const remoteMap = - remotes != null - ? new Map(remotes.map(r => [r.name, r])) - : new Map>(); + const remoteMap = remotes != null ? new Map(remotes.map(r => [r.name, r])) : new Map(); const selectSha = first(refParser.parse(getSettledValue(refResult) ?? '')); let stdin: string | undefined; @@ -1712,7 +1722,7 @@ export class LocalGitProvider implements GitProvider, Disposable { if (cursorIndex === -1) { // If we didn't find any new commits, we must have them all so return that we have everything if (size === data.length) { - return { repoPath: repoPath, avatars: avatars, ids: ids, rows: [] }; + return { repoPath: repoPath, avatars: avatars, ids: ids, remotes: remoteMap, rows: [] }; } size = data.length; @@ -1738,7 +1748,7 @@ export class LocalGitProvider implements GitProvider, Disposable { } } - if (!data) return { repoPath: repoPath, avatars: avatars, ids: ids, rows: [] }; + if (!data) return { repoPath: repoPath, avatars: avatars, ids: ids, remotes: remoteMap, rows: [] }; log = data; if (limit !== 0) { @@ -1750,20 +1760,38 @@ export class LocalGitProvider implements GitProvider, Disposable { const rows: GitGraphRow[] = []; + let avatarUri: Uri | undefined; + let avatarUrl: string | undefined; let branch: GitBranch | undefined; + let branchId: string; let branchName: string; + let context: + | GraphItemRefContext + | GraphItemRefContext + | undefined; + let contexts: GitGraphRowContexts | undefined; + let group; + const groupedRefs = new Map< + string, + { head?: boolean; local?: GitBranchReference; remotes?: GitBranchReference[] } + >(); let head = false; let isCurrentUser = false; + let refHead: GitGraphRowHead; let refHeads: GitGraphRowHead[]; + let refRemoteHead: GitGraphRowRemoteHead; let refRemoteHeads: GitGraphRowRemoteHead[]; + let refTag: GitGraphRowTag; let refTags: GitGraphRowTag[]; let parent: string; let parents: string[]; let remote: GitRemote | undefined; + let remoteBranchId: string; let remoteName: string; let stashCommit: GitStashCommit | undefined; - let tag: GitGraphRowTag; - let contexts: GitGraphRowContexts | undefined; + let tagId: string; + let tagName: string; + let tip: string; let count = 0; @@ -1780,34 +1808,38 @@ export class LocalGitProvider implements GitProvider, Disposable { refHeads = []; refRemoteHeads = []; refTags = []; - contexts = undefined; + contexts = {}; head = false; if (commit.tips) { - for (let tip of commit.tips.split(', ')) { + groupedRefs.clear(); + + for (tip of commit.tips.split(', ')) { if (tip === 'refs/stash') continue; if (tip.startsWith('tag: ')) { - const tagName = tip.substring(5); - const tagId = getTagId(repoPath, tagName); - tag = { - id: tagId, - name: tagName, - // Not currently used, so don't bother looking it up - annotated: true, - }; - tag.context = serializeWebviewItemContext({ + tagName = tip.substring(5); + tagId = getTagId(repoPath, tagName); + context = { webviewItem: 'gitlens:tag', webviewItemValue: { type: 'tag', - ref: GitReference.create(tag.name, repoPath, { + ref: GitReference.create(tagName, repoPath, { id: tagId, refType: 'tag', - name: tag.name, + name: tagName, }), }, - }); - refTags.push(tag); + }; + + refTag = { + id: tagId, + name: tagName, + // Not currently used, so don't bother looking it up + annotated: true, + context: serializeWebviewItemContext(context), + }; + refTags.push(refTag); continue; } @@ -1828,94 +1860,115 @@ export class LocalGitProvider implements GitProvider, Disposable { branchName = getBranchNameWithoutRemote(tip); if (branchName === 'HEAD') continue; - const remoteBranchId = getBranchId(repoPath, true, tip); - const avatarUrl = ( + remoteBranchId = getBranchId(repoPath, true, tip); + avatarUrl = ( (useAvatars ? remote.provider?.avatarUri : undefined) ?? getRemoteIconUri(this.container, remote, asWebviewUri) )?.toString(true); + context = { + webviewItem: 'gitlens:branch+remote', + webviewItemValue: { + type: 'branch', + ref: GitReference.create(tip, repoPath, { + id: remoteBranchId, + refType: 'branch', + name: tip, + remote: true, + upstream: { name: remote.name, missing: false }, + }), + }, + }; - refRemoteHeads.push({ + refRemoteHead = { id: remoteBranchId, name: branchName, owner: remote.name, url: remote.url, avatarUrl: avatarUrl, - context: serializeWebviewItemContext({ - webviewItem: 'gitlens:branch+remote', - webviewItemValue: { - type: 'branch', - ref: GitReference.create(tip, repoPath, { - id: remoteBranchId, - refType: 'branch', - name: tip, - remote: true, - upstream: { name: remote.name, missing: false }, - avatarUrl: avatarUrl, - }), - }, - }), - }); + context: serializeWebviewItemContext(context), + }; + refRemoteHeads.push(refRemoteHead); + + group = groupedRefs.get(branchName); + if (group == null) { + group = { remotes: [] }; + groupedRefs.set(branchName, group); + } + if (group.remotes == null) { + group.remotes = []; + } + group.remotes.push(context.webviewItemValue.ref); continue; } } branch = branchMap.get(tip); - const branchId = branch?.id ?? getBranchId(repoPath, false, tip); - - const groupedRefs: GitReference[] = []; - for (const refRemoteHead of refRemoteHeads) { - if (refRemoteHead.name == tip && refRemoteHead.context) { - const refContext = typeof refRemoteHead.context === 'string' - ? JSON.parse(refRemoteHead.context) - : refRemoteHead.context; - const ref = refContext?.webviewItemValue?.ref; - if (ref) { - groupedRefs.push(ref); - } - } - } + branchId = branch?.id ?? getBranchId(repoPath, false, tip); + context = { + webviewItem: `gitlens:branch${head ? '+current' : ''}${ + branch?.upstream != null ? '+tracking' : '' + }`, + webviewItemValue: { + type: 'branch', + ref: GitReference.create(tip, repoPath, { + id: branchId, + refType: 'branch', + name: tip, + remote: false, + upstream: branch?.upstream, + }), + }, + }; - refHeads.push({ + refHead = { id: branchId, name: tip, isCurrentHead: head, - context: serializeWebviewItemContext({ - webviewItem: `gitlens:branch${head ? '+current' : ''}${ - branch?.upstream != null ? '+tracking' : '' - }`, - webviewItemValue: { - type: 'branch', - ref: GitReference.create(tip, repoPath, { - id: branchId, - refType: 'branch', - name: tip, - remote: false, - upstream: branch?.upstream, - groupedRefs: groupedRefs, - }), - }, - }), - contextGroup: - groupedRefs.length > 0 - ? serializeWebviewItemContext({ - webviewItem: `gitlens:branch+grouped${head ? '+current' : ''}${ - branch?.upstream != null ? '+tracking' : '' - }`, - webviewItemValue: { - type: 'branch', - ref: GitReference.create(tip, repoPath, { - id: branchId, - refType: 'branch', - name: tip, - remote: false, - upstream: branch?.upstream, - groupedRefs: groupedRefs, - }), - }, - }) - : undefined, - }); + context: serializeWebviewItemContext(context), + }; + refHeads.push(refHead); + + group = groupedRefs.get(tip); + if (group == null) { + group = {}; + groupedRefs.set(tip, group); + } + + if (head) { + group.head = true; + } + group.local = context.webviewItemValue.ref; + } + + for (group of groupedRefs.values()) { + if ( + group.remotes != null && + ((group.local != null && group.remotes.length > 0) || group.remotes.length > 1) + ) { + if (group.remotes != null && group.remotes.length > 1) { + group.remotes.sort( + (a, b) => + (a.upstream!.name === 'origin' ? -1 : 1) - + (b.upstream!.name === 'origin' ? -1 : 1) || + (a.upstream!.name === 'upstream' ? -1 : 1) - + (b.upstream!.name === 'upstream' ? -1 : 1) || + sortCompare(a.upstream!.name, b.upstream!.name), + ); + } + + if (contexts.refGroups == null) { + contexts.refGroups = {}; + } + contexts.refGroups[group.local?.name ?? group.remotes[0].name] = + serializeWebviewItemContext({ + webviewItemGroup: `gitlens:refGroup${group.head ? '+current' : ''}`, + webviewItemGroupValue: { + type: 'refGroup', + refs: group.local != null ? [group.local, ...group.remotes] : group.remotes, + }, + }); + } } } @@ -1938,15 +1991,14 @@ export class LocalGitProvider implements GitProvider, Disposable { } if (stashCommit == null && !avatars.has(commit.authorEmail)) { - const uri = getCachedAvatarUri(commit.authorEmail); - if (uri != null) { - avatars.set(commit.authorEmail, uri.toString(true)); + avatarUri = getCachedAvatarUri(commit.authorEmail); + if (avatarUri != null) { + avatars.set(commit.authorEmail, avatarUri.toString(true)); } } isCurrentUser = isUserMatch(currentUser, commit.author, commit.authorEmail); - contexts = {}; if (stashCommit != null) { contexts.row = serializeWebviewItemContext({ webviewItem: 'gitlens:stash', @@ -2021,6 +2073,7 @@ export class LocalGitProvider implements GitProvider, Disposable { avatars: avatars, ids: ids, skippedIds: skippedIds, + remotes: remoteMap, rows: rows, id: sha, diff --git a/src/git/models/graph.ts b/src/git/models/graph.ts index c7b365a..4495fb6 100644 --- a/src/git/models/graph.ts +++ b/src/git/models/graph.ts @@ -1,4 +1,5 @@ import type { GraphRow, Head, Remote, RowContexts, Tag } from '@gitkraken/gitkraken-components'; +import type { GitRemote } from './remote'; export type GitGraphRowHead = Head; export type GitGraphRowRemoteHead = Remote; @@ -29,6 +30,7 @@ export interface GitGraph { readonly ids: Set; /** A set of all skipped commit ids -- typically for stash index/untracked commits */ readonly skippedIds?: Set; + readonly remotes: Map; /** The rows for the set of commits requested */ readonly rows: GitGraphRow[]; readonly id?: string; diff --git a/src/git/models/reference.ts b/src/git/models/reference.ts index cc86daf..50e995a 100644 --- a/src/git/models/reference.ts +++ b/src/git/models/reference.ts @@ -109,11 +109,6 @@ export interface GitBranchReference { repoPath: string; } -export interface GraphGitBranchReference extends GitBranchReference { - avatarUrl?: string; - groupedRefs?: GitReference[]; -} - export interface GitRevisionReference { readonly refType: 'revision' | 'stash'; id?: undefined; @@ -144,27 +139,20 @@ export interface GitTagReference { repoPath: string; } -export type GitReference = - | GitBranchReference - | GraphGitBranchReference - | GitRevisionReference - | GitStashReference - | GitTagReference; +export type GitReference = GitBranchReference | GitRevisionReference | GitStashReference | GitTagReference; export namespace GitReference { export function create( ref: string, repoPath: string, options: { - id?: string; refType: 'branch'; name: string; + id?: string; remote: boolean; upstream?: { name: string; missing: boolean }; - avatarUrl?: string; - groupedRefs?: GitReference[]; }, - ): GraphGitBranchReference | GitBranchReference; + ): GitBranchReference; export function create( ref: string, repoPath: string, @@ -178,7 +166,7 @@ export namespace GitReference { export function create( ref: string, repoPath: string, - options: { id?: string; refType: 'tag'; name: string }, + options: { refType: 'tag'; name: string; id?: string }, ): GitTagReference; export function create( ref: string, @@ -189,8 +177,7 @@ export namespace GitReference { refType: 'branch'; name: string; remote: boolean; - avatarUrl?: string; - groupedRefs?: GitReference[]; + upstream?: { name: string; missing: boolean }; } | { refType?: 'revision'; name?: string; message?: string } | { refType: 'stash'; name: string; number: string | undefined; message?: string } @@ -199,53 +186,50 @@ export namespace GitReference { switch (options.refType) { case 'branch': return { - id: options.id, - name: options.name, - ref: ref, refType: 'branch', - remote: options.remote, repoPath: repoPath, - avatarUrl: options.avatarUrl, - groupedRefs: options.groupedRefs, + ref: ref, + name: options.name, + id: options.id, + remote: options.remote, + upstream: options.upstream, }; case 'stash': return { - name: options.name, - ref: ref, refType: 'stash', repoPath: repoPath, + ref: ref, + name: options.name, number: options.number, message: options.message, }; case 'tag': return { - id: options.id, - name: options.name, - ref: ref, refType: 'tag', repoPath: repoPath, + ref: ref, + name: options.name, + id: options.id, }; default: return { - name: - options.name ?? GitRevision.shorten(ref, { force: true, strings: { working: 'Working Tree' } }), - ref: ref, refType: 'revision', repoPath: repoPath, + ref: ref, + name: + options.name ?? GitRevision.shorten(ref, { force: true, strings: { working: 'Working Tree' } }), message: options.message, }; } } - export function fromBranch(branch: GraphGitBranchReference) { + export function fromBranch(branch: GitBranchReference) { return create(branch.ref, branch.repoPath, { id: branch.id, refType: branch.refType, name: branch.name, remote: branch.remote, upstream: branch.upstream, - avatarUrl: branch.avatarUrl, - groupedRefs: branch.groupedRefs, }); } diff --git a/src/git/models/tag.ts b/src/git/models/tag.ts index 3def871..7be143c 100644 --- a/src/git/models/tag.ts +++ b/src/git/models/tag.ts @@ -11,7 +11,7 @@ export interface TagSortOptions { } export function getTagId(repoPath: string, name: string): string { - return `${repoPath}/tag/${name}`; + return `${repoPath}|tag/${name}`; } export class GitTag implements GitTagReference { diff --git a/src/plus/github/githubGitProvider.ts b/src/plus/github/githubGitProvider.ts index 230fc72..a561d8c 100644 --- a/src/plus/github/githubGitProvider.ts +++ b/src/plus/github/githubGitProvider.ts @@ -1123,11 +1123,13 @@ export class GitHubGitProvider implements GitProvider, Disposable { useAvatars?: boolean; }, ): Promise { + const remoteMap = remote != null ? new Map([[remote.name, remote]]) : new Map(); if (log == null) { return { repoPath: repoPath, avatars: avatars, ids: ids, + remotes: remoteMap, rows: [], }; } @@ -1138,6 +1140,7 @@ export class GitHubGitProvider implements GitProvider, Disposable { repoPath: repoPath, avatars: avatars, ids: ids, + remotes: remoteMap, rows: [], }; } @@ -1293,6 +1296,7 @@ export class GitHubGitProvider implements GitProvider, Disposable { repoPath: repoPath, avatars: avatars, ids: ids, + remotes: remoteMap, rows: rows, id: options?.ref, diff --git a/src/plus/webviews/graph/graphWebview.ts b/src/plus/webviews/graph/graphWebview.ts index 99b626f..fafa8ed 100644 --- a/src/plus/webviews/graph/graphWebview.ts +++ b/src/plus/webviews/graph/graphWebview.ts @@ -1,4 +1,3 @@ -import type { GraphRefType } from '@gitkraken/gitkraken-components'; import type { ColorTheme, ConfigurationChangeEvent, @@ -36,7 +35,7 @@ import type { Container } from '../../../container'; import { getContext, onDidChangeContext, setContext } from '../../../context'; import { PlusFeatures } from '../../../features'; import { GitSearchError } from '../../../git/errors'; -import { getBranchNameWithoutRemote } from '../../../git/models/branch'; +import { getBranchNameWithoutRemote, getRemoteNameFromBranchName } from '../../../git/models/branch'; import type { GitCommit } from '../../../git/models/commit'; import { GitContributor } from '../../../git/models/contributor'; import { GitGraphRowType } from '../../../git/models/graph'; @@ -48,6 +47,7 @@ import type { GitTagReference, } from '../../../git/models/reference'; import { GitReference, GitRevision } from '../../../git/models/reference'; +import { getRemoteIconUri } from '../../../git/models/remote'; import type { Repository, RepositoryChangeEvent, @@ -56,6 +56,7 @@ import type { import { RepositoryChange, RepositoryChangeComparisonMode } from '../../../git/models/repository'; import type { GitSearch } from '../../../git/search'; import { getSearchQueryComparisonKey } from '../../../git/search'; +import type { StoredGraphHiddenRef } from '../../../storage'; import { executeActionCommand, executeCommand, executeCoreGitCommand, registerCommand } from '../../../system/command'; import { gate } from '../../../system/decorators/gate'; import { debug } from '../../../system/decorators/log'; @@ -65,8 +66,8 @@ import { last } from '../../../system/iterable'; import { updateRecordValue } from '../../../system/object'; import { getSettledValue } from '../../../system/promise'; import { isDarkTheme, isLightTheme } from '../../../system/utils'; -import type { WebviewItemContext } from '../../../system/webview'; -import { isWebviewItemContext, serializeWebviewItemContext } from '../../../system/webview'; +import type { WebviewItemContext, WebviewItemGroupContext } from '../../../system/webview'; +import { isWebviewItemContext, isWebviewItemGroupContext, serializeWebviewItemContext } from '../../../system/webview'; import type { BranchNode } from '../../../views/nodes/branchNode'; import type { CommitFileNode } from '../../../views/nodes/commitFileNode'; import type { CommitNode } from '../../../views/nodes/commitNode'; @@ -323,9 +324,10 @@ export class GraphWebview extends WebviewBase { registerCommand('gitlens.graph.switchToAnotherBranch', this.switchToAnother, this), registerCommand('gitlens.graph.switchToBranch', this.switchTo, this), - registerCommand('gitlens.graph.hideBranch', this.hideRef, this), + registerCommand('gitlens.graph.hideLocalBranch', this.hideRef, this), + registerCommand('gitlens.graph.hideRemoteBranch', this.hideRef, this), registerCommand('gitlens.graph.hideTag', this.hideRef, this), - registerCommand('gitlens.graph.hideGroupedBranch', this.hideRef, this), + registerCommand('gitlens.graph.hideRefGroup', item => this.hideRef(item, true), this), registerCommand('gitlens.graph.cherryPick', this.cherryPick, this), registerCommand('gitlens.graph.copyRemoteCommitUrl', item => this.openCommitOnRemote(item, true), this), @@ -946,7 +948,7 @@ export class GraphWebview extends WebviewBase { } return this.notify(DidChangeRefsVisibilityNotificationType, { - hiddenRefs: this.getHiddenRefs(), + hiddenRefs: this.getHiddenRefs(this._graph), }); } @@ -1119,18 +1121,33 @@ export class GraphWebview extends WebviewBase { return this.container.storage.getWorkspace('graph:columns'); } - private getHiddenRefs(): Record | undefined { - return this.filterHiddenRefs(this.container.storage.getWorkspace('graph:hiddenRefs')); + private getHiddenRefs(graph: GitGraph | undefined): Record | undefined { + return this.filterHiddenRefs(this.container.storage.getWorkspace('graph:hiddenRefs'), graph); } - private filterHiddenRefs(hiddenRefs: Record | undefined): GraphHiddenRefs | undefined { - if (hiddenRefs == null || this.repository == null) return undefined; + private filterHiddenRefs( + hiddenRefs: Record | undefined, + graph: GitGraph | undefined, + ): GraphHiddenRefs | undefined { + if (hiddenRefs == null || graph == null) return undefined; + + const useAvatars = configuration.get('graph.avatars', undefined, true); const filteredRefs: GraphHiddenRefs = {}; for (const id in hiddenRefs) { - if (getRepoPathFromBranchOrTagId(id) === this.repository.path) { - filteredRefs[id] = hiddenRefs[id]; + if (getRepoPathFromBranchOrTagId(id) === graph.repoPath) { + const ref: GraphHiddenRef = { ...hiddenRefs[id] }; + if (ref.type === 'remote' && ref.owner) { + const remote = graph.remotes.get(ref.owner); + if (remote != null) { + ref.avatarUrl = ( + (useAvatars ? remote.provider?.avatarUri : undefined) ?? + getRemoteIconUri(this.container, remote, this._panel!.webview.asWebviewUri.bind(this)) + )?.toString(true); + } + } + filteredRefs[id] = ref; } } @@ -1296,6 +1313,7 @@ export class GraphWebview extends WebviewBase { this.setGraph(data); this.setSelectedRows(data.id); + void this.notifyDidChangeRefsVisibility(); void this.notifyDidChangeRows(true); }); } else { @@ -1331,7 +1349,7 @@ export class GraphWebview extends WebviewBase { context: { header: this.getColumnHeaderContext(columns), }, - hiddenRefs: this.getHiddenRefs(), + hiddenRefs: data != null ? this.getHiddenRefs(data) : undefined, nonce: this.cspNonce, workingTreeStats: getSettledValue(workingStatsResult) ?? { added: 0, deleted: 0, modified: 0 }, }; @@ -1345,11 +1363,16 @@ export class GraphWebview extends WebviewBase { } private updateHiddenRefs(refs: GraphHiddenRef[], visible: boolean) { - let hiddenRefs = this.container.storage.getWorkspace('graph:hiddenRefs'); + let storedHiddenRefs = this.container.storage.getWorkspace('graph:hiddenRefs'); for (const ref of refs) { - hiddenRefs = updateRecordValue(hiddenRefs, ref.id, visible ? undefined : ref); + storedHiddenRefs = updateRecordValue( + storedHiddenRefs, + ref.id, + visible ? undefined : { id: ref.id, type: ref.type, name: ref.name, owner: ref.owner }, + ); } - void this.container.storage.storeWorkspace('graph:hiddenRefs', hiddenRefs); + + void this.container.storage.storeWorkspace('graph:hiddenRefs', storedHiddenRefs); void this.notifyDidChangeRefsVisibility(); } @@ -1631,35 +1654,30 @@ export class GraphWebview extends WebviewBase { } @debug() - private hideRef(item: GraphItemContext) { - if (isGraphItemRefContext(item)) { + private hideRef(item: GraphItemContext, group?: boolean) { + let refs; + if (group && isGraphItemRefGroupContext(item)) { + ({ refs } = item.webviewItemGroupValue); + } else if (!group && isGraphItemRefContext(item)) { const { ref } = item.webviewItemValue; - const groupedRefs: GitReference[] = (ref as any).groupedRefs ?? []; - const refsToHide: GitReference[] = [...groupedRefs, ref]; - const graphHiddenRefs: GraphHiddenRef[] = []; - - for (const refToHide of refsToHide) { - if (refToHide?.id) { - let isRemoteBranch = false; - let graphRefType: GraphRefType = 'tag'; - - if (refToHide.refType === 'branch') { - isRemoteBranch = refToHide.remote; - graphRefType = isRemoteBranch ? 'remote' : 'head'; - } - - graphHiddenRefs.push({ - id: refToHide.id, - name: isRemoteBranch ? getBranchNameWithoutRemote(refToHide.name) : refToHide.name, - type: graphRefType, - avatarUrl: (refToHide as any).avatarUrl, - }); - } + if (ref.id != null) { + refs = [ref]; } + } - if (graphHiddenRefs.length > 0) { - this.updateHiddenRefs(graphHiddenRefs, false); - } + if (refs != null) { + this.updateHiddenRefs( + refs.map(r => { + const remoteBranch = r.refType === 'branch' && r.remote; + return { + id: r.id!, + name: remoteBranch ? getBranchNameWithoutRemote(r.name) : r.name, + owner: remoteBranch ? getRemoteNameFromBranchName(r.name) : undefined, + type: r.refType === 'branch' ? (r.remote ? 'remote' : 'head') : 'tag', + }; + }), + false, + ); } return Promise.resolve(); @@ -1898,6 +1916,11 @@ function formatRepositories(repositories: Repository[]): GraphRepository[] { } export type GraphItemContext = WebviewItemContext; +export type GraphItemContextValue = GraphColumnsContextValue | GraphItemTypedContextValue | GraphItemRefContextValue; + +export type GraphItemGroupContext = WebviewItemGroupContext; +export type GraphItemGroupContextValue = GraphItemRefGroupContextValue; + export type GraphItemRefContext = WebviewItemContext; export type GraphItemRefContextValue = | GraphBranchContextValue @@ -1905,11 +1928,15 @@ export type GraphItemRefContextValue = | GraphStashContextValue | GraphTagContextValue; +export type GraphItemRefGroupContext = WebviewItemGroupContext; +export interface GraphItemRefGroupContextValue { + type: 'refGroup'; + refs: (GitBranchReference | GitTagReference)[]; +} + export type GraphItemTypedContext = WebviewItemContext; export type GraphItemTypedContextValue = GraphContributorContextValue | GraphPullRequestContextValue; -export type GraphItemContextValue = GraphColumnsContextValue | GraphItemTypedContextValue | GraphItemRefContextValue; - export type GraphColumnsContextValue = string; export interface GraphContributorContextValue { @@ -1952,6 +1979,12 @@ function isGraphItemContext(item: unknown): item is GraphItemContext { return isWebviewItemContext(item) && item.webview === 'gitlens.graph'; } +function isGraphItemGroupContext(item: unknown): item is GraphItemGroupContext { + if (item == null) return false; + + return isWebviewItemGroupContext(item) && item.webview === 'gitlens.graph'; +} + function isGraphItemTypedContext( item: unknown, type: 'contributor', @@ -1969,6 +2002,16 @@ function isGraphItemTypedContext( return isGraphItemContext(item) && typeof item.webviewItemValue === 'object' && item.webviewItemValue.type === type; } +function isGraphItemRefGroupContext(item: unknown): item is GraphItemRefGroupContext { + if (item == null) return false; + + return ( + isGraphItemGroupContext(item) && + typeof item.webviewItemGroupValue === 'object' && + item.webviewItemGroupValue.type === 'refGroup' + ); +} + function isGraphItemRefContext(item: unknown): item is GraphItemRefContext; function isGraphItemRefContext(item: unknown, refType: 'branch'): item is GraphItemRefContext; function isGraphItemRefContext( diff --git a/src/plus/webviews/graph/protocol.ts b/src/plus/webviews/graph/protocol.ts index 7d7d2e9..ed6e482 100644 --- a/src/plus/webviews/graph/protocol.ts +++ b/src/plus/webviews/graph/protocol.ts @@ -5,12 +5,14 @@ import type { GraphRefOptData, GraphRow, GraphZoneType, + Head, HiddenRefsById, HostingServiceType, PullRequestMetadata, RefMetadata, RefMetadataType, Remote, + Tag, WorkDirStats, } from '@gitkraken/gitkraken-components'; import type { DateStyle } from '../../../config'; @@ -91,8 +93,8 @@ export interface GraphCommit { avatarUrl: string | undefined; } export type GraphRemote = Remote; -export type GraphTag = Record; -export type GraphBranch = Record; +export type GraphTag = Tag; +export type GraphBranch = Head; export interface GraphComponentConfig { avatars?: boolean; @@ -237,7 +239,7 @@ export const DidChangeColumnsNotificationType = new IpcNotificationType; + hiddenRefs?: GraphHiddenRefs; } export const DidChangeRefsVisibilityNotificationType = new IpcNotificationType( 'graph/refs/didChangeVisibility', diff --git a/src/storage.ts b/src/storage.ts index 8a3ed3f..c34495d 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -238,9 +238,9 @@ export type StoredGraphRefType = 'head' | 'remote' | 'tag'; export interface StoredGraphHiddenRef { id: string; - name: string; type: StoredGraphRefType; - avatarUrl?: string; + name: string; + owner?: string; } export interface StoredNamedRef { diff --git a/src/system/webview.ts b/src/system/webview.ts index 99b402c..6fbbca8 100644 --- a/src/system/webview.ts +++ b/src/system/webview.ts @@ -10,6 +10,18 @@ export function isWebviewItemContext(item: unknown): item is W return 'webview' in item && 'webviewItem' in item; } -export function serializeWebviewItemContext(context: T): string { +export interface WebviewItemGroupContext { + webview?: string; + webviewItemGroup: string; + webviewItemGroupValue: TValue; +} + +export function isWebviewItemGroupContext(item: unknown): item is WebviewItemGroupContext { + if (item == null) return false; + + return 'webview' in item && 'webviewItemGroup' in item; +} + +export function serializeWebviewItemContext(context: T): string { return JSON.stringify(context); }