'use strict'; import { Uri } from 'vscode'; import { Git } from './git'; import * as path from 'path'; export interface IGitEnricher { enrich(data: string, ...args: any[]): T; } export interface IGitBlame { repoPath: string; authors: Map; commits: Map; lines: IGitCommitLine[]; } export interface IGitBlameLine { author: IGitAuthor; commit: GitCommit; line: IGitCommitLine; } export interface IGitBlameLines extends IGitBlame { allLines: IGitCommitLine[]; } export interface IGitBlameCommitLines { author: IGitAuthor; commit: GitCommit; lines: IGitCommitLine[]; } export interface IGitAuthor { name: string; lineCount: number; } interface IGitCommit { repoPath: string; sha: string; fileName: string; author: string; date: Date; message: string; lines: IGitCommitLine[]; originalFileName?: string; previousSha?: string; previousFileName?: string; readonly isUncommitted: boolean; previousUri: Uri; uri: Uri; } export class GitCommit implements IGitCommit { lines: IGitCommitLine[]; originalFileName?: string; previousSha?: string; previousFileName?: string; private _isUncommitted: boolean | undefined; constructor( public repoPath: string, public sha: string, public fileName: string, public author: string, public date: Date, public message: string, lines?: IGitCommitLine[], originalFileName?: string, previousSha?: string, previousFileName?: string ) { this.fileName = this.fileName.replace(/, ?$/, ''); this.lines = lines || []; this.originalFileName = originalFileName; this.previousSha = previousSha; this.previousFileName = previousFileName; } get shortSha() { return this.sha.substring(0, 8); } get isUncommitted(): boolean { if (this._isUncommitted === undefined) { this._isUncommitted = Git.isUncommitted(this.sha); } return this._isUncommitted; } get previousShortSha() { return this.previousSha && this.previousSha.substring(0, 8); } get previousUri(): Uri { return this.previousFileName ? Uri.file(path.resolve(this.repoPath, this.previousFileName)) : this.uri; } get uri(): Uri { return Uri.file(path.resolve(this.repoPath, this.originalFileName || this.fileName)); } getFormattedPath(separator: string = ' \u00a0\u2022\u00a0 '): string { const directory = path.dirname(this.fileName); return (!directory || directory === '.') ? path.basename(this.fileName) : `${path.basename(this.fileName)}${separator}${directory}`; } } export type GitLogType = 'file' | 'repo'; export class GitLogCommit extends GitCommit { fileNames: string; fileStatuses: { status: GitFileStatus, fileName: string }[]; nextSha?: string; nextFileName?: string; status: GitFileStatus; constructor( public type: GitLogType, 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; this.fileNames = this.fileName; if (fileStatuses) { this.fileStatuses = fileStatuses.filter(_ => !!_.fileName); this.fileName = this.fileStatuses[0].fileName; } else { this.fileStatuses = [{ status: status, fileName: fileName }]; } } get nextShortSha() { return this.nextSha && this.nextSha.substring(0, 8); } get nextUri(): Uri { return this.nextFileName ? Uri.file(path.resolve(this.repoPath, this.nextFileName)) : this.uri; } } export interface IGitCommitLine { sha: string; previousSha?: string; line: number; originalLine: number; code?: string; } export interface IGitLog { repoPath: string; authors: Map; commits: Map; maxCount: number | undefined; truncated: boolean; } export declare type GitFileStatus = '?' | 'A' | 'C' | 'D' | 'M' | 'R' | 'U'; export class GitFileStatusItem { staged: boolean; status: GitFileStatus; fileName: string; constructor(public repoPath: string, status: string) { this.fileName = status.substring(3); this.parseStatus(status); } private parseStatus(status: string) { const indexStatus = status[0].trim(); const workTreeStatus = status[1].trim(); this.staged = !!indexStatus; this.status = (indexStatus || workTreeStatus || 'U') as GitFileStatus; } } const statusOcticonsMap = { '?': '$(diff-ignored)', A: '$(diff-added)', C: '$(diff-added)', D: '$(diff-removed)', M: '$(diff-modified)', R: '$(diff-renamed)', U: '$(question)' }; export function getGitStatusIcon(status: GitFileStatus, missing: string = '\u00a0\u00a0\u00a0\u00a0'): string { return statusOcticonsMap[status] || missing; } export class GitBranch { current: boolean; name: string; remote: boolean; constructor(branch: string) { branch = branch.trim(); if (branch.startsWith('* ')) { branch = branch.substring(2); this.current = true; } if (branch.startsWith('remotes/')) { branch = branch.substring(8); this.remote = true; } const index = branch.indexOf(' '); if (index !== -1) { branch = branch.substring(0, index); } this.name = branch; } }