Ver código fonte

Adds author filtering in Commits view

main
Eric Amodio 1 ano atrás
pai
commit
c1210801f9
10 arquivos alterados com 225 adições e 51 exclusões
  1. +33
    -18
      package.json
  2. +5
    -0
      src/commands/quickCommand.buttons.ts
  3. +7
    -4
      src/commands/quickCommand.steps.ts
  4. +3
    -3
      src/constants.ts
  5. +5
    -0
      src/git/models/contributor.ts
  6. +93
    -0
      src/quickpicks/contributorsPicker.ts
  7. +4
    -3
      src/quickpicks/items/gitCommands.ts
  8. +65
    -17
      src/views/commitsView.ts
  9. +6
    -2
      src/views/nodes/viewNode.ts
  10. +4
    -4
      yarn.lock

+ 33
- 18
package.json Ver arquivo

@ -4,7 +4,7 @@
"description": "Supercharge Git within VS Code — Visualize code authorship at a glance via Git blame annotations and CodeLens, seamlessly navigate and explore Git repositories, gain valuable insights via rich visualizations and powerful comparison commands, and so much more",
"version": "14.4.0",
"engines": {
"vscode": "^1.80.0"
"vscode": "^1.81.0"
},
"license": "SEE LICENSE IN LICENSE",
"publisher": "eamodio",
@ -6680,14 +6680,14 @@
"icon": "$(list-flat)"
},
{
"command": "gitlens.views.commits.setMyCommitsOnlyOn",
"title": "View Only My Commits",
"command": "gitlens.views.commits.setCommitsFilterAuthors",
"title": "Filter Commits by Author...",
"category": "GitLens",
"icon": "$(filter)"
},
{
"command": "gitlens.views.commits.setMyCommitsOnlyOff",
"title": "View All Commits",
"command": "gitlens.views.commits.setCommitsFilterOff",
"title": "Clear Filter",
"category": "GitLens",
"icon": "$(filter-filled)"
},
@ -9502,11 +9502,11 @@
"when": "false"
},
{
"command": "gitlens.views.commits.setMyCommitsOnlyOn",
"command": "gitlens.views.commits.setCommitsFilterAuthors",
"when": "false"
},
{
"command": "gitlens.views.commits.setMyCommitsOnlyOff",
"command": "gitlens.views.commits.setCommitsFilterOff",
"when": "false"
},
{
@ -11016,8 +11016,8 @@
"group": "navigation@99"
},
{
"command": "gitlens.views.commits.setMyCommitsOnlyOff",
"when": "view =~ /^gitlens\\.views\\.commits/ && gitlens:views:commits:myCommitsOnly",
"command": "gitlens.views.commits.setCommitsFilterOff",
"when": "view =~ /^gitlens\\.views\\.commits/ && gitlens:views:commits:filtered",
"group": "navigation@50"
},
{
@ -11026,29 +11026,29 @@
"group": "navigation@99"
},
{
"command": "gitlens.views.commits.setMyCommitsOnlyOn",
"when": "view =~ /^gitlens\\.views\\.commits/ && !gitlens:views:commits:myCommitsOnly",
"command": "gitlens.views.commits.setCommitsFilterOff",
"when": "view =~ /^gitlens\\.views\\.commits/ && gitlens:views:commits:filtered",
"group": "3_gitlens@0"
},
{
"command": "gitlens.views.commits.setMyCommitsOnlyOff",
"when": "view =~ /^gitlens\\.views\\.commits/ && gitlens:views:commits:myCommitsOnly",
"group": "3_gitlens@0"
"command": "gitlens.views.commits.setCommitsFilterAuthors",
"when": "view =~ /^gitlens\\.views\\.commits/",
"group": "3_gitlens@1"
},
{
"command": "gitlens.views.commits.setFilesLayoutToAuto",
"when": "view =~ /^gitlens\\.views\\.commits/ && config.gitlens.views.commits.files.layout == tree",
"group": "3_gitlens@1"
"group": "3_gitlens@2"
},
{
"command": "gitlens.views.commits.setFilesLayoutToList",
"when": "view =~ /^gitlens\\.views\\.commits/ && config.gitlens.views.commits.files.layout == auto",
"group": "3_gitlens@1"
"group": "3_gitlens@2"
},
{
"command": "gitlens.views.commits.setFilesLayoutToTree",
"when": "view =~ /^gitlens\\.views\\.commits/ && config.gitlens.views.commits.files.layout == list",
"group": "3_gitlens@1"
"group": "3_gitlens@2"
},
{
"command": "gitlens.views.commits.setShowAvatarsOn",
@ -12652,6 +12652,11 @@
"group": "inline@100"
},
{
"command": "gitlens.views.commits.setCommitsFilterOff",
"when": "view =~ /^gitlens\\.views\\.commits/ && viewItem =~ /gitlens:repo-folder\\b(?=.*?\\b\\+filtered\\b)/ && gitlens:views:commits:filtered",
"group": "inline@101"
},
{
"command": "gitlens.views.fetch",
"when": "gitlens:hasRemotes && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders && viewItem =~ /gitlens:repo-folder\\b/",
"group": "1_gitlens_actions@1"
@ -12718,6 +12723,16 @@
"group": "8_gitlens_actions_@2"
},
{
"command": "gitlens.views.commits.setCommitsFilterOff",
"when": "view =~ /^gitlens\\.views\\.commits/ && viewItem =~ /gitlens:repo-folder\\b(?=.*?\\b\\+filtered\\b)/ && gitlens:views:commits:filtered",
"group": "8_gitlens_filter_@1"
},
{
"command": "gitlens.views.commits.setCommitsFilterAuthors",
"when": "view =~ /^gitlens\\.views\\.commits/ && viewItem =~ /gitlens:repo-folder\\b/",
"group": "8_gitlens_filter_@2"
},
{
"command": "gitlens.views.publishRepository",
"when": "!gitlens:hasRemotes && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders && viewItem =~ /gitlens:status(\\-branch)?:upstream:none/",
"group": "inline@1"
@ -15196,7 +15211,7 @@
"@types/react": "17.0.69",
"@types/react-dom": "17.0.21",
"@types/sortablejs": "1.15.4",
"@types/vscode": "1.80.0",
"@types/vscode": "1.81.0",
"@typescript-eslint/eslint-plugin": "6.8.0",
"@typescript-eslint/parser": "6.8.0",
"@vscode/test-electron": "2.3.5",

+ 5
- 0
src/commands/quickCommand.buttons.ts Ver arquivo

@ -57,6 +57,11 @@ export class SelectableQuickInputButton extends ToggleQuickInputButton {
}
}
export const ClearQuickInputButton: QuickInputButton = {
iconPath: new ThemeIcon('clear-all'),
tooltip: 'Clear',
};
export const FetchQuickInputButton: QuickInputButton = {
iconPath: new ThemeIcon('sync'),
tooltip: 'Fetch',

+ 7
- 4
src/commands/quickCommand.steps.ts Ver arquivo

@ -1285,16 +1285,19 @@ export async function* pickContributorsStep<
): AsyncStepResultGenerator<GitContributor[]> {
const message = (await Container.instance.git.getOrOpenScmRepository(state.repo.path))?.inputBox.value;
const contributors = await Container.instance.git.getContributors(state.repo.path);
const step = createPickStep<ContributorQuickPickItem>({
title: appendReposToTitle(context.title, state, context),
allowEmpty: true,
multiselect: true,
placeholder: placeholder,
matchOnDescription: true,
items: (await Container.instance.git.getContributors(state.repo.path)).map(c =>
createContributorQuickPickItem(c, message?.includes(c.getCoauthor()), {
buttons: [RevealInSideBarQuickInputButton],
}),
items: await Promise.all(
contributors.map(c =>
createContributorQuickPickItem(c, message?.includes(c.getCoauthor()), {
buttons: [RevealInSideBarQuickInputButton],
}),
),
),
onDidClickItemButton: (quickpick, button, { item }) => {
if (button === RevealInSideBarQuickInputButton) {

+ 3
- 3
src/constants.ts Ver arquivo

@ -334,7 +334,7 @@ export type TreeViewCommands = `gitlens.views.${
| 'copy'
| 'refresh'
| `setFilesLayoutTo${'Auto' | 'List' | 'Tree'}`
| `setMyCommitsOnly${'On' | 'Off'}`
| `setCommitsFilter${'Authors' | 'Off'}`
| `setShowAvatars${'On' | 'Off'}`
| `setShowBranchComparison${'On' | 'Off'}`
| `setShowBranchPullRequest${'On' | 'Off'}`}`
@ -527,7 +527,7 @@ export type TreeViewSubscribableNodeTypes =
| 'line-history-tracker'
| 'repositories'
| 'repository'
| 'repository-folder'
| 'repo-folder'
| 'search-results'
| 'workspace';
export type TreeViewNodeTypes =
@ -587,7 +587,7 @@ export type ContextKeys =
| `${typeof extensionPrefix}:untrusted`
| `${typeof extensionPrefix}:views:canCompare`
| `${typeof extensionPrefix}:views:canCompare:file`
| `${typeof extensionPrefix}:views:commits:myCommitsOnly`
| `${typeof extensionPrefix}:views:commits:filtered`
| `${typeof extensionPrefix}:views:fileHistory:canPin`
| `${typeof extensionPrefix}:views:fileHistory:cursorFollowing`
| `${typeof extensionPrefix}:views:fileHistory:editorFollowing`

+ 5
- 0
src/git/models/contributor.ts Ver arquivo

@ -5,6 +5,7 @@ import { configuration } from '../../system/configuration';
import { formatDate, fromNow } from '../../system/date';
import { memoize } from '../../system/decorators/memoize';
import { sortCompare } from '../../system/string';
import type { GitUser } from './user';
export interface ContributorSortOptions {
current?: true;
@ -104,3 +105,7 @@ export class GitContributor {
return `${this.name}${this.email ? ` <${this.email}>` : ''}`;
}
}
export function matchContributor(c: GitContributor, user: GitUser): boolean {
return c.name === user.name && c.email === user.email && c.username === user.username;
}

+ 93
- 0
src/quickpicks/contributorsPicker.ts Ver arquivo

@ -0,0 +1,93 @@
import type { Disposable } from 'vscode';
import { window } from 'vscode';
import { ClearQuickInputButton } from '../commands/quickCommand.buttons';
import { GlyphChars, quickPickTitleMaxChars } from '../constants';
import type { Container } from '../container';
import type { GitContributor } from '../git/models/contributor';
import type { Repository } from '../git/models/repository';
import { defer } from '../system/promise';
import { pad, truncate } from '../system/string';
import { getQuickPickIgnoreFocusOut } from '../system/utils';
import type { ContributorQuickPickItem } from './items/gitCommands';
import { createContributorQuickPickItem } from './items/gitCommands';
export async function showContributorsPicker(
container: Container,
repository: Repository,
title: string,
placeholder: string,
options?: {
appendReposToTitle?: boolean;
clearButton?: boolean;
multiselect?: boolean;
picked?: (contributor: GitContributor) => boolean;
},
): Promise<GitContributor[] | undefined> {
const deferred = defer<GitContributor[] | undefined>();
const disposables: Disposable[] = [];
try {
const quickpick = window.createQuickPick<ContributorQuickPickItem>();
disposables.push(
quickpick,
quickpick.onDidHide(() => deferred.fulfill(undefined)),
quickpick.onDidAccept(() =>
!quickpick.busy ? deferred.fulfill(quickpick.selectedItems.map(c => c.item)) : undefined,
),
quickpick.onDidTriggerButton(e => {
if (e === ClearQuickInputButton) {
if (quickpick.canSelectMany) {
quickpick.selectedItems = [];
} else {
deferred.fulfill([]);
}
}
}),
);
quickpick.ignoreFocusOut = getQuickPickIgnoreFocusOut();
quickpick.title = options?.appendReposToTitle ? appendRepoToTitle(container, title, repository) : title;
quickpick.placeholder = placeholder;
quickpick.matchOnDescription = true;
quickpick.matchOnDetail = true;
quickpick.canSelectMany = options?.multiselect ?? true;
quickpick.buttons = options?.clearButton ? [ClearQuickInputButton] : [];
quickpick.busy = true;
quickpick.show();
const contributors = await repository.getContributors();
if (!deferred.pending) return;
const items = await Promise.all(
contributors.map(c => createContributorQuickPickItem(c, options?.picked?.(c) ?? false)),
);
if (!deferred.pending) return;
quickpick.items = items;
if (quickpick.canSelectMany) {
quickpick.selectedItems = items.filter(i => i.picked);
} else {
quickpick.activeItems = items.filter(i => i.picked);
}
quickpick.busy = false;
const picks = await deferred.promise;
return picks;
} finally {
disposables.forEach(d => void d.dispose());
}
}
function appendRepoToTitle(container: Container, title: string, repo: Repository) {
return container.git.openRepositoryCount <= 1
? title
: `${title}${truncate(
`${pad(GlyphChars.Dot, 2, 2)}${repo.formattedName}`,
quickPickTitleMaxChars - title.length,
)}`;
}

+ 4
- 3
src/quickpicks/items/gitCommands.ts Ver arquivo

@ -219,18 +219,19 @@ export function createCommitQuickPickItem(
export type ContributorQuickPickItem = QuickPickItemOfT<GitContributor>;
export function createContributorQuickPickItem(
export async function createContributorQuickPickItem(
contributor: GitContributor,
picked?: boolean,
options?: { alwaysShow?: boolean; buttons?: QuickInputButton[] },
): ContributorQuickPickItem {
): Promise<ContributorQuickPickItem> {
const item: ContributorQuickPickItem = {
label: contributor.label,
description: contributor.email,
description: contributor.current ? 'you' : contributor.email,
alwaysShow: options?.alwaysShow,
buttons: options?.buttons,
picked: picked,
item: contributor,
iconPath: await contributor.getAvatarUri(),
};
return item;
}

+ 65
- 17
src/views/commitsView.ts Ver arquivo

@ -6,10 +6,14 @@ import type { Container } from '../container';
import { GitUri } from '../git/gitUri';
import type { GitCommit } from '../git/models/commit';
import { isCommit } from '../git/models/commit';
import { matchContributor } from '../git/models/contributor';
import type { GitRevisionReference } from '../git/models/reference';
import { getReferenceLabel } from '../git/models/reference';
import type { RepositoryChangeEvent } from '../git/models/repository';
import { Repository, RepositoryChange, RepositoryChangeComparisonMode } from '../git/models/repository';
import type { GitUser } from '../git/models/user';
import { showContributorsPicker } from '../quickpicks/contributorsPicker';
import { getRepositoryOrShowPicker } from '../quickpicks/repositoryPicker';
import { createCommand, executeCommand } from '../system/command';
import { configuration } from '../system/configuration';
import { setContext } from '../system/context';
@ -37,14 +41,7 @@ export class CommitsRepositoryNode extends RepositoryFolderNode
this.view.message = undefined;
let authors;
if (this.view.state.myCommitsOnly) {
const user = await this.view.container.git.getCurrentUser(this.repo.path);
if (user != null) {
authors = [{ name: user.name, email: user.email, username: user.username, id: user.id }];
}
}
const authors = this.view.state.filterCommits.get(this.repo.id);
this.child = new BranchNode(
this.uri,
this.view,
@ -180,7 +177,7 @@ export class CommitsViewNode extends RepositoriesSubscribeableNode
}
interface CommitsViewState {
myCommitsOnly?: boolean;
filterCommits: Map<string, GitUser[] | undefined>;
}
export class CommitsView extends ViewBase<'commits', CommitsViewNode, CommitsViewConfig> {
@ -202,7 +199,7 @@ export class CommitsView extends ViewBase<'commits', CommitsViewNode, CommitsVie
return this.config.reveal || !configuration.get('views.repositories.showCommits');
}
private readonly _state: CommitsViewState = {};
private readonly _state: CommitsViewState = { filterCommits: new Map<string, GitUser[] | undefined>() };
get state(): CommitsViewState {
return this._state;
}
@ -244,13 +241,13 @@ export class CommitsView extends ViewBase<'commits', CommitsViewNode, CommitsVie
this,
),
registerViewCommand(
this.getQualifiedCommand('setMyCommitsOnlyOn'),
() => this.setMyCommitsOnly(true),
this.getQualifiedCommand('setCommitsFilterAuthors'),
n => this.setCommitsFilter(n, true),
this,
),
registerViewCommand(
this.getQualifiedCommand('setMyCommitsOnlyOff'),
() => this.setMyCommitsOnly(false),
this.getQualifiedCommand('setCommitsFilterOff'),
n => this.setCommitsFilter(n, false),
this,
),
registerViewCommand(this.getQualifiedCommand('setShowAvatarsOn'), () => this.setShowAvatars(true), this),
@ -395,9 +392,60 @@ export class CommitsView extends ViewBase<'commits', CommitsViewNode, CommitsVie
return configuration.updateEffective(`views.${this.configKey}.files.layout` as const, layout);
}
private setMyCommitsOnly(enabled: boolean) {
void setContext('gitlens:views:commits:myCommitsOnly', enabled);
this.state.myCommitsOnly = enabled;
private async setCommitsFilter(node: ViewNode, filter: boolean) {
let repo;
if (node != null) {
if (node.is('repo-folder')) {
repo = node.repo;
} else {
let parent: ViewNode | undefined = node;
do {
parent = parent.getParent();
if (parent?.is('repo-folder')) {
repo = parent.repo;
break;
}
} while (parent != null);
}
}
if (filter) {
repo ??= await getRepositoryOrShowPicker('Filter Commits', 'Choose a repository');
if (repo == null) return;
let authors = this.state.filterCommits.get(repo.id);
if (authors == null) {
const current = await this.container.git.getCurrentUser(repo.uri);
authors = current != null ? [current] : undefined;
}
const result = await showContributorsPicker(
this.container,
repo,
'Filter Commits',
repo.virtual ? 'Choose a contributor to show commits from' : 'Choose contributors to show commits from',
{
appendReposToTitle: true,
clearButton: true,
multiselect: !repo.virtual,
picked: c => authors?.some(u => matchContributor(c, u)) ?? false,
},
);
if (result == null) return;
if (result.length === 0) {
filter = false;
this.state.filterCommits.delete(repo.id);
} else {
this.state.filterCommits.set(repo.id, result);
}
} else if (repo != null) {
this.state.filterCommits.delete(repo.id);
} else {
this.state.filterCommits.clear();
}
void setContext('gitlens:views:commits:filtered', this.state.filterCommits.size !== 0);
void this.refresh(true);
}

+ 6
- 2
src/views/nodes/viewNode.ts Ver arquivo

@ -555,7 +555,7 @@ export abstract class SubscribeableViewNode<
export abstract class RepositoryFolderNode<
TView extends View = View,
TChild extends ViewNode = ViewNode,
> extends SubscribeableViewNode<'repository-folder', TView> {
> extends SubscribeableViewNode<'repo-folder', TView> {
protected override splatted = true;
protected child: TChild | undefined;
@ -567,7 +567,7 @@ export abstract class RepositoryFolderNode<
splatted: boolean,
private readonly options?: { showBranchAndLastFetched?: boolean },
) {
super('repository-folder', uri, view, parent);
super('repo-folder', uri, view, parent);
this.updateContext({ repository: this.repo });
this._uniqueId = getViewNodeId(this.type, this.context);
@ -607,6 +607,9 @@ export abstract class RepositoryFolderNode<
if (behind) {
item.contextValue += '+behind';
}
if (this.view.type === 'commits' && this.view.state.filterCommits.get(this.repo.id)?.length) {
item.contextValue += '+filtered';
}
if (branch != null && this.options?.showBranchAndLastFetched) {
const lastFetched = (await this.repo.getLastFetched()) ?? 0;
@ -838,6 +841,7 @@ type TreeViewNodesByType = {
['folder']: FolderNode;
['line-history-tracker']: LineHistoryTrackerNode;
['repository']: RepositoryNode;
['repo-folder']: RepositoryFolderNode;
['results-file']: ResultsFileNode;
['stash']: StashNode;
['stash-file']: StashFileNode;

+ 4
- 4
yarn.lock Ver arquivo

@ -756,10 +756,10 @@
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.5.tgz#5cac7e7df3275bb95f79594f192d97da3b4fd5fe"
integrity sha512-I3pkr8j/6tmQtKV/ZzHtuaqYSQvyjGRKH4go60Rr0IDLlFxuRT5V32uvB1mecM5G1EVAUyF/4r4QZ1GHgz+mxA==
"@types/vscode@1.80.0":
version "1.80.0"
resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.80.0.tgz#e004dd6cde74dafdb7fab64a6e1754bf8165b981"
integrity sha512-qK/CmOdS2o7ry3k6YqU4zD3R2AYlJfbwBoSbKpBoP+GpXNE+0NEgJOli4n0bm0diK5kfBnchgCEj4igQz/44Hg==
"@types/vscode@1.81.0":
version "1.81.0"
resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.81.0.tgz#c27228dd063002e0e00611be70b0497beaa24d39"
integrity sha512-YIaCwpT+O2E7WOMq0eCgBEABE++SX3Yl/O02GoMIF2DO3qAtvw7m6BXFYsxnc6XyzwZgh6/s/UG78LSSombl2w==
"@types/yargs-parser@*":
version "21.0.2"

Carregando…
Cancelar
Salvar