Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 52 additions & 41 deletions extensions/git/src/historyProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,50 +4,16 @@
*--------------------------------------------------------------------------------------------*/


import { Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, Uri, window, LogOutputChannel, SourceControlHistoryItemRef, l10n, SourceControlHistoryItemRefsChangeEvent } from 'vscode';
import { Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, Uri, window, LogOutputChannel, SourceControlHistoryItemRef, l10n, SourceControlHistoryItemRefsChangeEvent, workspace, ConfigurationChangeEvent } from 'vscode';
import { Repository, Resource } from './repository';
import { IDisposable, deltaHistoryItemRefs, dispose, filterEvent, getCommitShortHash, truncate } from './util';
import { IDisposable, deltaHistoryItemRefs, dispose, filterEvent, truncate } from './util';
import { toMultiFileDiffEditorUris } from './uri';
import { AvatarQuery, AvatarQueryCommit, Branch, LogOptions, Ref, RefType } from './api/git';
import { emojify, ensureEmojis } from './emoji';
import { Commit } from './git';
import { OperationKind, OperationResult } from './operation';
import { ISourceControlHistoryItemDetailsProviderRegistry, provideSourceControlHistoryItemAvatar, provideSourceControlHistoryItemMessageLinks } from './historyItemDetailsProvider';

function toSourceControlHistoryItemRef(repository: Repository, ref: Ref): SourceControlHistoryItemRef {
const rootUri = Uri.file(repository.root);

switch (ref.type) {
case RefType.RemoteHead:
return {
id: `refs/remotes/${ref.name}`,
name: ref.name ?? '',
description: ref.commit ? l10n.t('Remote branch at {0}', getCommitShortHash(rootUri, ref.commit)) : undefined,
revision: ref.commit,
icon: new ThemeIcon('cloud'),
category: l10n.t('remote branches')
};
case RefType.Tag:
return {
id: `refs/tags/${ref.name}`,
name: ref.name ?? '',
description: ref.commit ? l10n.t('Tag at {0}', getCommitShortHash(rootUri, ref.commit)) : undefined,
revision: ref.commit,
icon: new ThemeIcon('tag'),
category: l10n.t('tags')
};
default:
return {
id: `refs/heads/${ref.name}`,
name: ref.name ?? '',
description: ref.commit ? getCommitShortHash(rootUri, ref.commit) : undefined,
revision: ref.commit,
icon: new ThemeIcon('git-branch'),
category: l10n.t('branches')
};
}
}

function compareSourceControlHistoryItemRef(ref1: SourceControlHistoryItemRef, ref2: SourceControlHistoryItemRef): number {
const getOrder = (ref: SourceControlHistoryItemRef): number => {
if (ref.id.startsWith('refs/heads/')) {
Expand Down Expand Up @@ -93,6 +59,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
private _HEAD: Branch | undefined;
private _historyItemRefs: SourceControlHistoryItemRef[] = [];

private commitShortHashLength = 7;
private historyItemDecorations = new Map<string, FileDecoration>();

private disposables: Disposable[] = [];
Expand All @@ -102,12 +69,24 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
private readonly repository: Repository,
private readonly logger: LogOutputChannel
) {
this.disposables.push(workspace.onDidChangeConfiguration(this.onDidChangeConfiguration));
this.onDidChangeConfiguration();

const onDidRunWriteOperation = filterEvent(repository.onDidRunOperation, e => !e.operation.readOnly);
this.disposables.push(onDidRunWriteOperation(this.onDidRunWriteOperation, this));

this.disposables.push(window.registerFileDecorationProvider(this));
}

private onDidChangeConfiguration(e?: ConfigurationChangeEvent): void {
if (e && !e.affectsConfiguration('git.commitShortHashLength')) {
return;
}

const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
this.commitShortHashLength = config.get<number>('commitShortHashLength', 7);
}

private async onDidRunWriteOperation(result: OperationResult): Promise<void> {
if (!this.repository.HEAD) {
this.logger.trace('[GitHistoryProvider][onDidRunWriteOperation] repository.HEAD is undefined');
Expand All @@ -119,7 +98,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec

// Refs (alphabetically)
const historyItemRefs = this.repository.refs
.map(ref => toSourceControlHistoryItemRef(this.repository, ref))
.map(ref => this.toSourceControlHistoryItemRef(ref))
.sort((a, b) => a.id.localeCompare(b.id));

const delta = deltaHistoryItemRefs(this._historyItemRefs, historyItemRefs);
Expand Down Expand Up @@ -241,13 +220,13 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
for (const ref of refs) {
switch (ref.type) {
case RefType.RemoteHead:
remoteBranches.push(toSourceControlHistoryItemRef(this.repository, ref));
remoteBranches.push(this.toSourceControlHistoryItemRef(ref));
break;
case RefType.Tag:
tags.push(toSourceControlHistoryItemRef(this.repository, ref));
tags.push(this.toSourceControlHistoryItemRef(ref));
break;
default:
branches.push(toSourceControlHistoryItemRef(this.repository, ref));
branches.push(this.toSourceControlHistoryItemRef(ref));
break;
}
}
Expand Down Expand Up @@ -319,7 +298,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
author: commit.authorName,
authorEmail: commit.authorEmail,
authorIcon: avatarUrl ? Uri.parse(avatarUrl) : new ThemeIcon('account'),
displayId: getCommitShortHash(Uri.file(this.repository.root), commit.hash),
displayId: truncate(commit.hash, this.commitShortHashLength, false),
timestamp: commit.authorDate?.getTime(),
statistics: commit.shortStat ?? { files: 0, insertions: 0, deletions: 0 },
references: references.length !== 0 ? references : undefined
Expand Down Expand Up @@ -469,6 +448,38 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
}
}

private toSourceControlHistoryItemRef(ref: Ref): SourceControlHistoryItemRef {
switch (ref.type) {
case RefType.RemoteHead:
return {
id: `refs/remotes/${ref.name}`,
name: ref.name ?? '',
description: ref.commit ? l10n.t('Remote branch at {0}', truncate(ref.commit, this.commitShortHashLength, false)) : undefined,
revision: ref.commit,
icon: new ThemeIcon('cloud'),
category: l10n.t('remote branches')
};
case RefType.Tag:
return {
id: `refs/tags/${ref.name}`,
name: ref.name ?? '',
description: ref.commit ? l10n.t('Tag at {0}', truncate(ref.commit, this.commitShortHashLength, false)) : undefined,
revision: ref.commit,
icon: new ThemeIcon('tag'),
category: l10n.t('tags')
};
default:
return {
id: `refs/heads/${ref.name}`,
name: ref.name ?? '',
description: ref.commit ? truncate(ref.commit, this.commitShortHashLength, false) : undefined,
revision: ref.commit,
icon: new ThemeIcon('git-branch'),
category: l10n.t('branches')
};
}
}

dispose(): void {
dispose(this.disposables);
}
Expand Down
24 changes: 12 additions & 12 deletions extensions/git/src/timelineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { debounce } from './decorators';
import { emojify, ensureEmojis } from './emoji';
import { CommandCenter } from './commands';
import { OperationKind, OperationResult } from './operation';
import { getCommitShortHash, truncate } from './util';
import { truncate } from './util';
import { CommitShortStat } from './git';
import { provideSourceControlHistoryItemAvatar, provideSourceControlHistoryItemHoverCommands, provideSourceControlHistoryItemMessageLinks } from './historyItemDetailsProvider';
import { AvatarQuery, AvatarQueryCommit } from './api/git';
Expand Down Expand Up @@ -54,7 +54,7 @@ export class GitTimelineItem extends TimelineItem {
return this.shortenRef(this.previousRef);
}

setItemDetails(uri: Uri, hash: string | undefined, avatar: string | undefined, author: string, email: string | undefined, date: string, message: string, shortStat?: CommitShortStat, remoteSourceCommands: Command[] = []): void {
setItemDetails(uri: Uri, hash: string | undefined, shortHash: string | undefined, avatar: string | undefined, author: string, email: string | undefined, date: string, message: string, shortStat?: CommitShortStat, remoteSourceCommands: Command[] = []): void {
this.tooltip = new MarkdownString('', true);
this.tooltip.isTrusted = true;

Expand Down Expand Up @@ -91,10 +91,10 @@ export class GitTimelineItem extends TimelineItem {
this.tooltip.appendMarkdown(`${labels.join(', ')}\n\n`);
}

if (hash) {
if (hash && shortHash) {
this.tooltip.appendMarkdown(`---\n\n`);

this.tooltip.appendMarkdown(`[\`$(git-commit) ${getCommitShortHash(uri, hash)} \`](command:git.viewCommit?${encodeURIComponent(JSON.stringify([uri, hash]))} "${l10n.t('Open Commit')}")`);
this.tooltip.appendMarkdown(`[\`$(git-commit) ${shortHash} \`](command:git.viewCommit?${encodeURIComponent(JSON.stringify([uri, hash]))} "${l10n.t('Open Commit')}")`);
this.tooltip.appendMarkdown('&nbsp;');
this.tooltip.appendMarkdown(`[$(copy)](command:git.copyContentToClipboard?${encodeURIComponent(JSON.stringify(hash))} "${l10n.t('Copy Commit Hash')}")`);

Expand Down Expand Up @@ -173,8 +173,6 @@ export class GitTimelineProvider implements TimelineProvider {
);
}

const config = workspace.getConfiguration('git.timeline');

// TODO@eamodio: Ensure that the uri is a file -- if not we could get the history of the repo?

let limit: number | undefined;
Expand Down Expand Up @@ -215,9 +213,11 @@ export class GitTimelineProvider implements TimelineProvider {

const dateFormatter = new Intl.DateTimeFormat(env.language, { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' });

const dateType = config.get<'committed' | 'authored'>('date');
const showAuthor = config.get<boolean>('showAuthor');
const showUncommitted = config.get<boolean>('showUncommitted');
const config = workspace.getConfiguration('git', Uri.file(repo.root));
const dateType = config.get<'committed' | 'authored'>('timeline.date');
const showAuthor = config.get<boolean>('timeline.showAuthor');
const showUncommitted = config.get<boolean>('timeline.showUncommitted');
const commitShortHashLength = config.get<number>('commitShortHashLength') ?? 7;

const openComparison = l10n.t('Open Comparison');

Expand Down Expand Up @@ -253,7 +253,7 @@ export class GitTimelineProvider implements TimelineProvider {
const commitRemoteSourceCommands = !unpublishedCommits.has(c.hash) ? remoteHoverCommands : [];
const messageWithLinks = await provideSourceControlHistoryItemMessageLinks(this.model, repo, message) ?? message;

item.setItemDetails(uri, c.hash, avatars?.get(c.hash), c.authorName!, c.authorEmail, dateFormatter.format(date), messageWithLinks, c.shortStat, commitRemoteSourceCommands);
item.setItemDetails(uri, c.hash, truncate(c.hash, commitShortHashLength, false), avatars?.get(c.hash), c.authorName!, c.authorEmail, dateFormatter.format(date), messageWithLinks, c.shortStat, commitRemoteSourceCommands);

const cmd = this.commands.resolveTimelineOpenDiffCommand(item, uri);
if (cmd) {
Expand All @@ -278,7 +278,7 @@ export class GitTimelineProvider implements TimelineProvider {
// TODO@eamodio: Replace with a better icon -- reflecting its status maybe?
item.iconPath = new ThemeIcon('git-commit');
item.description = '';
item.setItemDetails(uri, undefined, undefined, you, undefined, dateFormatter.format(date), Resource.getStatusText(index.type));
item.setItemDetails(uri, undefined, undefined, undefined, you, undefined, dateFormatter.format(date), Resource.getStatusText(index.type));

const cmd = this.commands.resolveTimelineOpenDiffCommand(item, uri);
if (cmd) {
Expand All @@ -300,7 +300,7 @@ export class GitTimelineProvider implements TimelineProvider {
const item = new GitTimelineItem('', index ? '~' : 'HEAD', l10n.t('Uncommitted Changes'), date.getTime(), 'working', 'git:file:working');
item.iconPath = new ThemeIcon('circle-outline');
item.description = '';
item.setItemDetails(uri, undefined, undefined, you, undefined, dateFormatter.format(date), Resource.getStatusText(working.type));
item.setItemDetails(uri, undefined, undefined, undefined, you, undefined, dateFormatter.format(date), Resource.getStatusText(working.type));

const cmd = this.commands.resolveTimelineOpenDiffCommand(item, uri);
if (cmd) {
Expand Down
4 changes: 2 additions & 2 deletions extensions/git/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,8 @@ export function detectUnicodeEncoding(buffer: Buffer): Encoding | null {
return null;
}

export function truncate(value: string, maxLength = 20): string {
return value.length <= maxLength ? value : `${value.substring(0, maxLength)}\u2026`;
export function truncate(value: string, maxLength = 20, ellipsis = true): string {
return value.length <= maxLength ? value : `${value.substring(0, maxLength)}${ellipsis ? '\u2026' : ''}`;
}

function normalizePath(path: string): string {
Expand Down
Loading