diff --git a/src/commands/quickPickItems.ts b/src/commands/quickPickItems.ts index 4bd8c65..8705b05 100644 --- a/src/commands/quickPickItems.ts +++ b/src/commands/quickPickItems.ts @@ -2,10 +2,23 @@ import { commands, QuickPickItem, TextEditor, Uri, window, workspace } from 'vscode'; import { Commands } from '../commands'; import { BuiltInCommands } from '../constants'; -import { GitCommit, GitFileStatusItem, GitUri } from '../gitProvider'; +import { GitCommit, GitFileStatus, GitFileStatusItem, GitLogCommit, GitUri } from '../gitProvider'; import * as moment from 'moment'; import * as path from 'path'; +const statusOcticonsMap = { + '?': '$(diff-ignored)', + A: '$(diff-added)', + C: '$(diff-added)', + D: '$(diff-removed)', + M: '$(diff-modified)', + R: '$(diff-renamed)', + U: '$(question)' +}; +function getStatusIcon(status: GitFileStatus, missing: string = '\u00a0\u00a0\u00a0\u00a0'): string { + return statusOcticonsMap[status] || missing; +} + export interface PartialQuickPickItem { label?: string; description?: string; @@ -58,13 +71,9 @@ export class OpenFilesCommandQuickPickItem extends CommandQuickPickItem { export class OpenCommitFilesCommandQuickPickItem extends OpenFilesCommandQuickPickItem { - constructor(commit: GitCommit, fileNames?: string[], item?: PartialQuickPickItem) { + constructor(commit: GitLogCommit, item?: PartialQuickPickItem) { const repoPath = commit.repoPath; - if (!fileNames) { - fileNames = commit.fileName.split(', ').filter(_ => !!_); - } - item = { ...{ label: `$(file-symlink-file) Open Files`, @@ -74,7 +83,7 @@ export class OpenCommitFilesCommandQuickPickItem extends OpenFilesCommandQuickPi ...item }; - super(fileNames, repoPath, item as QuickPickItem); + super(commit.fileStatuses.map(_ => _.fileName), repoPath, item as QuickPickItem); } } @@ -133,15 +142,6 @@ export class OpenCommitFileCommandQuickPickItem extends OpenFileCommandQuickPick } } -const statusOcticons = [ - '\u00a0$(question)', - '\u00a0$(diff-ignored)', - '\u00a0$(diff-added)', - '\u00a0$(diff-modified)', - '\u00a0$(diff-removed)', - '\u00a0$(diff-renamed)' -]; - export class OpenStatusFileCommandQuickPickItem extends OpenFileCommandQuickPickItem { constructor(status: GitFileStatusItem, item?: PartialQuickPickItem) { @@ -150,9 +150,10 @@ export class OpenStatusFileCommandQuickPickItem extends OpenFileCommandQuickPick directory = undefined; } + const icon = getStatusIcon(status.status); item = { ...{ - label: `${status.staged ? '$(check)' : '\u00a0\u00a0\u00a0'}\u00a0${statusOcticons[status.status]}\u00a0\u00a0\u00a0${path.basename(status.fileName)}`, + label: `${status.staged ? '$(check)' : '\u00a0\u00a0\u00a0'}\u00a0\u00a0${icon}\u00a0\u00a0\u00a0${path.basename(status.fileName)}`, description: directory }, ...item @@ -184,8 +185,9 @@ export class FileQuickPickItem implements QuickPickItem { sha: string; uri: GitUri; - constructor(commit: GitCommit, public fileName: string) { - this.label = `$(info) ${path.basename(fileName)}`; + constructor(commit: GitCommit, public fileName: string, public status: GitFileStatus) { + const icon = getStatusIcon(status); + this.label = `${icon} ${path.basename(fileName)}`; let directory = path.dirname(fileName); if (!directory || directory === '.') { diff --git a/src/commands/quickPicks.ts b/src/commands/quickPicks.ts index a6badc7..5485493 100644 --- a/src/commands/quickPicks.ts +++ b/src/commands/quickPicks.ts @@ -3,7 +3,7 @@ import { Iterables } from '../system'; import { QuickPickOptions, Uri, window, workspace } from 'vscode'; import { IAdvancedConfig } from '../configuration'; import { Commands } from '../commands'; -import GitProvider, { GitCommit, GitFileStatus, GitFileStatusItem, GitUri, IGitLog } from '../gitProvider'; +import GitProvider, { GitCommit, GitFileStatusItem, GitLogCommit, GitUri, IGitLog } from '../gitProvider'; import { CommandQuickPickItem, CommitQuickPickItem, FileQuickPickItem, OpenCommitFileCommandQuickPickItem, OpenStatusFileCommandQuickPickItem, OpenCommitFilesCommandQuickPickItem, OpenStatusFilesCommandQuickPickItem } from './quickPickItems'; import * as moment from 'moment'; import * as path from 'path'; @@ -88,11 +88,10 @@ export class CommitQuickPick { export class CommitFilesQuickPick { - static async show(commit: GitCommit, uri: Uri, goBackCommand?: CommandQuickPickItem): Promise<FileQuickPickItem | CommandQuickPickItem | undefined> { - const fileNames = commit.fileName.split(', ').filter(_ => !!_); - const items: (FileQuickPickItem | CommandQuickPickItem)[] = fileNames.map(f => new FileQuickPickItem(commit, f)); + static async show(commit: GitLogCommit, uri: Uri, goBackCommand?: CommandQuickPickItem): Promise<FileQuickPickItem | CommandQuickPickItem | undefined> { + const items: (FileQuickPickItem | CommandQuickPickItem)[] = commit.fileStatuses.map(fs => new FileQuickPickItem(commit, fs.fileName, fs.status)); - items.splice(0, 0, new OpenCommitFilesCommandQuickPickItem(commit, fileNames)); + items.splice(0, 0, new OpenCommitFilesCommandQuickPickItem(commit)); items.splice(1, 0, new CommandQuickPickItem({ label: `$(clippy) Copy Commit Sha to Clipboard`, @@ -201,13 +200,13 @@ export class RepoStatusesQuickPick { if (statuses.some(_ => _.staged)) { const index = statuses.findIndex(_ => !_.staged); if (index > -1) { - items.splice(index, 0, new OpenStatusFilesCommandQuickPickItem(statuses.filter(_ => _.status !== GitFileStatus.Deleted && !_.staged), { + items.splice(index, 0, new OpenStatusFilesCommandQuickPickItem(statuses.filter(_ => _.status !== 'D' && !_.staged), { label: `$(file-symlink-file) Open Unstaged Files`, description: undefined, detail: `Opens all of the unstaged files in the repository` })); - items.splice(0, 0, new OpenStatusFilesCommandQuickPickItem(statuses.filter(_ => _.status !== GitFileStatus.Deleted && _.staged), { + items.splice(0, 0, new OpenStatusFilesCommandQuickPickItem(statuses.filter(_ => _.status !== 'D' && _.staged), { label: `$(file-symlink-file) Open Staged Files`, description: undefined, detail: `Opens all of the staged files in the repository` @@ -216,7 +215,7 @@ export class RepoStatusesQuickPick { } if (statuses.length) { - items.splice(0, 0, new OpenStatusFilesCommandQuickPickItem(statuses.filter(_ => _.status !== GitFileStatus.Deleted))); + items.splice(0, 0, new OpenStatusFilesCommandQuickPickItem(statuses.filter(_ => _.status !== 'D'))); } if (goBackCommand) { diff --git a/src/commands/showQuickCommitDetails.ts b/src/commands/showQuickCommitDetails.ts index d1c06fa..8ee5356 100644 --- a/src/commands/showQuickCommitDetails.ts +++ b/src/commands/showQuickCommitDetails.ts @@ -2,7 +2,7 @@ import { Iterables } from '../system'; import { commands, TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands } from '../commands'; -import GitProvider, { GitCommit, GitUri } from '../gitProvider'; +import GitProvider, { GitCommit, GitLogCommit, GitUri } from '../gitProvider'; import { Logger } from '../logger'; import { CommandQuickPickItem, FileQuickPickItem } from './quickPickItems'; import { CommitQuickPick, CommitFilesQuickPick } from './quickPicks'; @@ -54,7 +54,7 @@ export default class ShowQuickCommitDetailsCommand extends ActiveEditorCommand { commit = Iterables.first(log.commits.values()); - pick = await CommitFilesQuickPick.show(commit, uri, goBackCommand); + pick = await CommitFilesQuickPick.show(commit as GitLogCommit, uri, goBackCommand); if (!pick) return undefined; if (pick instanceof CommandQuickPickItem) { diff --git a/src/git/enrichers/logParserEnricher.ts b/src/git/enrichers/logParserEnricher.ts index e2b685f..03758db 100644 --- a/src/git/enrichers/logParserEnricher.ts +++ b/src/git/enrichers/logParserEnricher.ts @@ -1,5 +1,5 @@ 'use strict'; -import Git, { GitCommit, IGitAuthor, IGitEnricher, IGitLog } from './../git'; +import Git, { GitFileStatus, GitLogCommit, IGitAuthor, IGitEnricher, IGitLog } from './../git'; import * as moment from 'moment'; import * as path from 'path'; @@ -13,7 +13,9 @@ interface ILogEntry { committerDate?: string; fileName?: string; - fileNames?: string[]; + fileStatuses?: { status: GitFileStatus, fileName: string }[]; + + status?: GitFileStatus; summary?: string; } @@ -78,22 +80,25 @@ export class GitLogParserEnricher implements IGitEnricher<IGitLog> { break; } - if (entry.fileNames == null) { - entry.fileNames = [lineParts[0]]; - } - else { - entry.fileNames.push(lineParts[0]); + if (entry.fileStatuses == null) { + entry.fileStatuses = []; } + entry.fileStatuses.push({ + status: lineParts[0][0] as GitFileStatus, + fileName: lineParts[0].substring(2) + }); } - entry.fileName = entry.fileNames.join(', '); + entry.fileName = entry.fileStatuses.filter(_ => !!_.fileName).map(_ => _.fileName).join(', '); } else { position += 2; lineParts = lines[position].split(' '); if (lineParts.length === 1) { - entry.fileName = lineParts[0]; + entry.status = lineParts[0][0] as GitFileStatus; + entry.fileName = lineParts[0].substring(2); } else { + entry.status = lineParts[3][0] as GitFileStatus; entry.fileName = lineParts[3].substring(2); position += 4; } @@ -116,11 +121,11 @@ export class GitLogParserEnricher implements IGitEnricher<IGitLog> { if (!entries) return undefined; const authors: Map<string, IGitAuthor> = new Map(); - const commits: Map<string, GitCommit> = new Map(); + const commits: Map<string, GitLogCommit> = new Map(); let repoPath: string; let relativeFileName: string; - let recentCommit: GitCommit; + let recentCommit: GitLogCommit; if (isRepoPath) { repoPath = fileNameOrRepoPath; @@ -151,7 +156,7 @@ export class GitLogParserEnricher implements IGitEnricher<IGitLog> { authors.set(entry.author, author); } - commit = new GitCommit(repoPath, entry.sha, relativeFileName, entry.author, moment(entry.authorDate).toDate(), entry.summary); + commit = new GitLogCommit(repoPath, entry.sha, relativeFileName, entry.author, moment(entry.authorDate).toDate(), entry.summary, entry.status, entry.fileStatuses); if (relativeFileName !== entry.fileName) { commit.originalFileName = entry.fileName; diff --git a/src/git/git.ts b/src/git/git.ts index 06625cf..3d67dc0 100644 --- a/src/git/git.ts +++ b/src/git/git.ts @@ -13,7 +13,7 @@ export * from './enrichers/logParserEnricher'; let git: IGit; const UncommittedRegex = /^[0]+$/; -const DefaultLogParams = [`log`, `--name-only`, `--full-history`, `-m`, `--date=iso8601-strict`, `--format=%H -%nauthor %an%nauthor-date %ai%ncommitter %cn%ncommitter-date %ci%nsummary %s%nfilename ?`]; +const DefaultLogParams = [`log`, `--name-status`, `--full-history`, `-m`, `--date=iso8601-strict`, `--format=%H -%nauthor %an%nauthor-date %ai%ncommitter %cn%ncommitter-date %ci%nsummary %s%nfilename ?`]; async function gitCommand(cwd: string, ...args: any[]) { try { diff --git a/src/git/gitEnrichment.ts b/src/git/gitEnrichment.ts index 65c5a8b..bf87ee9 100644 --- a/src/git/gitEnrichment.ts +++ b/src/git/gitEnrichment.ts @@ -72,6 +72,8 @@ export class GitCommit implements IGitCommit { previousSha?: string, previousFileName?: string ) { + this.fileName = this.fileName.replace(/, ?$/, ''); + this.lines = lines || []; this.originalFileName = originalFileName; this.previousSha = previousSha; @@ -101,6 +103,37 @@ export class GitCommit implements IGitCommit { } } +export class GitLogCommit extends GitCommit { + + fileStatuses: { status: GitFileStatus, fileName: string }[]; + status: GitFileStatus; + + constructor( + repoPath: string, + sha: string, + fileName: string, + author: string, + date: Date, + message: string, + status?: GitFileStatus, + fileStatuses?: { status: GitFileStatus, fileName: string }[], + lines?: IGitCommitLine[], + originalFileName?: string, + previousSha?: string, + previousFileName?: string + ) { + super(repoPath, sha, fileName, author, date, message, lines, originalFileName, previousSha, previousFileName); + this.status = status; + + if (fileStatuses) { + this.fileStatuses = fileStatuses.filter(_ => !!_.fileName); + } + else { + this.fileStatuses = [{ status: status, fileName: fileName }]; + } + } +} + export interface IGitCommitLine { sha: string; previousSha?: string; @@ -115,14 +148,7 @@ export interface IGitLog { commits: Map<string, GitCommit>; } -export enum GitFileStatus { - Unknown, - Untracked, - Added, - Modified, - Deleted, - Renamed -} +export declare type GitFileStatus = '?' | 'A' | 'C' | 'D' | 'M' | 'R' | 'U'; export class GitFileStatusItem { @@ -136,36 +162,10 @@ export class GitFileStatusItem { } private parseStatus(status: string) { - const indexStatus = status[0]; - const workTreeStatus = status[1]; - - this.staged = workTreeStatus === ' '; - - if (indexStatus === '?' && workTreeStatus === '?') { - this.status = GitFileStatus.Untracked; - return; - } - - if (indexStatus === 'A') { - this.status = GitFileStatus.Added; - return; - } - - if (indexStatus === 'M' || workTreeStatus === 'M') { - this.status = GitFileStatus.Modified; - return; - } - - if (indexStatus === 'D' || workTreeStatus === 'D') { - this.status = GitFileStatus.Deleted; - return; - } - - if (indexStatus === 'R') { - this.status = GitFileStatus.Renamed; - return; - } + const indexStatus = status[0].trim(); + const workTreeStatus = status[1].trim(); - this.status = GitFileStatus.Unknown; + this.staged = !!indexStatus; + this.status = (indexStatus || workTreeStatus || 'U') as GitFileStatus; } } \ No newline at end of file