mirror of
https://github.com/ckaczor/vscode-gitlens.git
synced 2026-02-17 02:51:47 -05:00
Adds diff info to the active line hover for uncommitted changes
This commit is contained in:
@@ -297,6 +297,32 @@ export class BlameActiveLineController extends Disposable {
|
|||||||
// If we don't have a possible dupe or we aren't showing annotations get the hover message
|
// If we don't have a possible dupe or we aren't showing annotations get the hover message
|
||||||
if (!commit.isUncommitted && (!possibleDuplicate || !this.annotationController.isAnnotating(editor))) {
|
if (!commit.isUncommitted && (!possibleDuplicate || !this.annotationController.isAnnotating(editor))) {
|
||||||
hoverMessage = BlameAnnotationFormatter.getAnnotationHover(cfg, blameLine, logCommit || commit);
|
hoverMessage = BlameAnnotationFormatter.getAnnotationHover(cfg, blameLine, logCommit || commit);
|
||||||
|
|
||||||
|
// if (commit.previousSha !== undefined) {
|
||||||
|
// const changes = await this.git.getDiffForLine(this._uri.repoPath, this._uri.fsPath, blameLine.line + offset, commit.previousSha);
|
||||||
|
// if (changes !== undefined) {
|
||||||
|
// const previous = changes[0];
|
||||||
|
// if (previous !== undefined) {
|
||||||
|
// hoverMessage += `\n\n\`Before ${commit.shortSha}\`\n\`\`\`\n${previous.trim().replace(/\n/g, '\`\n>\n> \`')}\n\`\`\``;
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// hoverMessage += `\n\n\`Added in ${commit.shortSha}\``;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
else if (commit.isUncommitted) {
|
||||||
|
const changes = await this.git.getDiffForLine(this._uri.repoPath, this._uri.fsPath, blameLine.line + offset);
|
||||||
|
if (changes !== undefined) {
|
||||||
|
let original = changes[0];
|
||||||
|
if (original !== undefined) {
|
||||||
|
original = original.replace(/\n/g, '\`\n>\n> \`').trim();
|
||||||
|
hoverMessage = `\`${'0'.repeat(8)}\` __Uncommitted change__\n\n\---\n\`\`\`\n${original}\n\`\`\``;
|
||||||
|
}
|
||||||
|
// else {
|
||||||
|
// hoverMessage = `\`${'0'.repeat(8)}\` __Uncommitted change__\n\n\`Added\``;
|
||||||
|
// }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import * as tmp from 'tmp';
|
|||||||
export { IGit };
|
export { IGit };
|
||||||
export * from './models/models';
|
export * from './models/models';
|
||||||
export * from './parsers/blameParser';
|
export * from './parsers/blameParser';
|
||||||
|
export * from './parsers/diffParser';
|
||||||
export * from './parsers/logParser';
|
export * from './parsers/logParser';
|
||||||
export * from './parsers/stashParser';
|
export * from './parsers/stashParser';
|
||||||
export * from './parsers/statusParser';
|
export * from './parsers/statusParser';
|
||||||
@@ -160,6 +161,18 @@ export class Git {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static diff(repoPath: string, fileName: string, sha1?: string, sha2?: string) {
|
||||||
|
const params = [`diff`, `--diff-filter=M`, `-M`];
|
||||||
|
if (sha1) {
|
||||||
|
params.push(sha1);
|
||||||
|
}
|
||||||
|
if (sha2) {
|
||||||
|
params.push(sha2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return gitCommand(repoPath, ...params, '--', fileName);
|
||||||
|
}
|
||||||
|
|
||||||
static diff_nameStatus(repoPath: string, sha1?: string, sha2?: string) {
|
static diff_nameStatus(repoPath: string, sha1?: string, sha2?: string) {
|
||||||
const params = [`diff`, `--name-status`, `-M`];
|
const params = [`diff`, `--name-status`, `-M`];
|
||||||
if (sha1) {
|
if (sha1) {
|
||||||
|
|||||||
18
src/git/models/diff.ts
Normal file
18
src/git/models/diff.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
export interface IGitDiffChunk {
|
||||||
|
chunk?: string;
|
||||||
|
|
||||||
|
original: (string | undefined)[];
|
||||||
|
originalStart: number;
|
||||||
|
originalEnd: number;
|
||||||
|
|
||||||
|
changes: (string | undefined)[];
|
||||||
|
changesStart: number;
|
||||||
|
changesEnd: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGitDiff {
|
||||||
|
diff?: string;
|
||||||
|
chunks: IGitDiffChunk[];
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
export * from './blame';
|
export * from './blame';
|
||||||
export * from './branch';
|
export * from './branch';
|
||||||
export * from './commit';
|
export * from './commit';
|
||||||
|
export * from './diff';
|
||||||
export * from './log';
|
export * from './log';
|
||||||
export * from './logCommit';
|
export * from './logCommit';
|
||||||
export * from './remote';
|
export * from './remote';
|
||||||
|
|||||||
45
src/git/parsers/diffParser.ts
Normal file
45
src/git/parsers/diffParser.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
'use strict';
|
||||||
|
import { IGitDiff, IGitDiffChunk } from './../git';
|
||||||
|
|
||||||
|
const unifiedDiffRegex = /^@@ -([\d]+),([\d]+) [+]([\d]+),([\d]+) @@([\s\S]*?)(?=^@@)/gm;
|
||||||
|
|
||||||
|
export class GitDiffParser {
|
||||||
|
|
||||||
|
static parse(data: string, debug: boolean = false): IGitDiff | undefined {
|
||||||
|
if (!data) return undefined;
|
||||||
|
|
||||||
|
const chunks: IGitDiffChunk[] = [];
|
||||||
|
|
||||||
|
let match: RegExpExecArray | null = null;
|
||||||
|
do {
|
||||||
|
match = unifiedDiffRegex.exec(`${data}\n@@`);
|
||||||
|
if (match == null) break;
|
||||||
|
|
||||||
|
const originalStart = +match[1];
|
||||||
|
const changedStart = +match[3];
|
||||||
|
|
||||||
|
const chunk = match[5];
|
||||||
|
const lines = chunk.split('\n').slice(1);
|
||||||
|
const original = lines.filter(l => l[0] !== '+').map(l => (l[0] === '-') ? l.substring(1) : undefined);
|
||||||
|
const changed = lines.filter(l => l[0] !== '-').map(l => (l[0] === '+') ? l.substring(1) : undefined);
|
||||||
|
|
||||||
|
chunks.push({
|
||||||
|
chunk: debug ? chunk : undefined,
|
||||||
|
original: original,
|
||||||
|
originalStart: originalStart,
|
||||||
|
originalEnd: originalStart + +match[2],
|
||||||
|
changes: changed,
|
||||||
|
changesStart: changedStart,
|
||||||
|
changesEnd: changedStart + +match[4]
|
||||||
|
});
|
||||||
|
} while (match != null);
|
||||||
|
|
||||||
|
if (!chunks.length) return undefined;
|
||||||
|
|
||||||
|
const diff = {
|
||||||
|
diff: debug ? data : undefined,
|
||||||
|
chunks: chunks
|
||||||
|
} as IGitDiff;
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import { Disposable, Event, EventEmitter, ExtensionContext, FileSystemWatcher, l
|
|||||||
import { CommandContext, setCommandContext } from './commands';
|
import { CommandContext, setCommandContext } from './commands';
|
||||||
import { CodeLensVisibility, IConfig } from './configuration';
|
import { CodeLensVisibility, IConfig } from './configuration';
|
||||||
import { DocumentSchemes, ExtensionKey } from './constants';
|
import { DocumentSchemes, ExtensionKey } from './constants';
|
||||||
import { Git, GitBlameParser, GitBranch, GitCommit, GitLogCommit, GitLogParser, GitRemote, GitStashParser, GitStatusFile, GitStatusParser, IGit, IGitAuthor, IGitBlame, IGitBlameLine, IGitBlameLines, IGitLog, IGitStash, IGitStatus } from './git/git';
|
import { Git, GitBlameParser, GitBranch, GitCommit, GitDiffParser, GitLogCommit, GitLogParser, GitRemote, GitStashParser, GitStatusFile, GitStatusParser, IGit, IGitAuthor, IGitBlame, IGitBlameLine, IGitBlameLines, IGitDiff, IGitLog, IGitStash, IGitStatus } from './git/git';
|
||||||
import { GitUri, IGitCommitInfo, IGitUriData } from './git/gitUri';
|
import { GitUri, IGitCommitInfo, IGitUriData } from './git/gitUri';
|
||||||
import { GitCodeLensProvider } from './gitCodeLensProvider';
|
import { GitCodeLensProvider } from './gitCodeLensProvider';
|
||||||
import { Logger } from './logger';
|
import { Logger } from './logger';
|
||||||
@@ -25,7 +25,7 @@ class UriCacheEntry {
|
|||||||
|
|
||||||
class GitCacheEntry {
|
class GitCacheEntry {
|
||||||
|
|
||||||
private cache: Map<string, ICachedBlame | ICachedLog> = new Map();
|
private cache: Map<string, ICachedBlame | ICachedDiff | ICachedLog> = new Map();
|
||||||
|
|
||||||
constructor(public key: string) { }
|
constructor(public key: string) { }
|
||||||
|
|
||||||
@@ -33,11 +33,11 @@ class GitCacheEntry {
|
|||||||
return Iterables.every(this.cache.values(), _ => _.errorMessage !== undefined);
|
return Iterables.every(this.cache.values(), _ => _.errorMessage !== undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
get<T extends ICachedBlame | ICachedLog > (key: string): T | undefined {
|
get<T extends ICachedBlame | ICachedDiff | ICachedLog > (key: string): T | undefined {
|
||||||
return this.cache.get(key) as T;
|
return this.cache.get(key) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
set<T extends ICachedBlame | ICachedLog > (key: string, value: T) {
|
set<T extends ICachedBlame | ICachedDiff | ICachedLog > (key: string, value: T) {
|
||||||
this.cache.set(key, value);
|
this.cache.set(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,6 +49,7 @@ interface ICachedItem<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ICachedBlame extends ICachedItem<IGitBlame> { }
|
interface ICachedBlame extends ICachedItem<IGitBlame> { }
|
||||||
|
interface ICachedDiff extends ICachedItem<IGitDiff> { }
|
||||||
interface ICachedLog extends ICachedItem<IGitLog> { }
|
interface ICachedLog extends ICachedItem<IGitLog> { }
|
||||||
|
|
||||||
enum RemoveCacheReason {
|
enum RemoveCacheReason {
|
||||||
@@ -88,7 +89,7 @@ export class GitService extends Disposable {
|
|||||||
private _fsWatcher: FileSystemWatcher | undefined;
|
private _fsWatcher: FileSystemWatcher | undefined;
|
||||||
private _gitignore: Promise<ignore.Ignore>;
|
private _gitignore: Promise<ignore.Ignore>;
|
||||||
|
|
||||||
static EmptyPromise: Promise<IGitBlame | IGitLog | undefined> = Promise.resolve(undefined);
|
static EmptyPromise: Promise<IGitBlame | IGitDiff | IGitLog | undefined> = Promise.resolve(undefined);
|
||||||
|
|
||||||
constructor(private context: ExtensionContext, public repoPath: string) {
|
constructor(private context: ExtensionContext, public repoPath: string) {
|
||||||
super(() => this.dispose());
|
super(() => this.dispose());
|
||||||
@@ -565,6 +566,97 @@ export class GitService extends Disposable {
|
|||||||
return entry && entry.uri;
|
return entry && entry.uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getDiffForFile(repoPath: string | undefined, fileName: string, sha1?: string, sha2?: string): Promise<IGitDiff | undefined> {
|
||||||
|
let key: string = 'diff';
|
||||||
|
if (sha1 !== undefined) {
|
||||||
|
key += `:${sha1}`;
|
||||||
|
}
|
||||||
|
if (sha2 !== undefined) {
|
||||||
|
key += `:${sha2}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let entry: GitCacheEntry | undefined;
|
||||||
|
if (this.UseCaching) {
|
||||||
|
const cacheKey = this.getCacheEntryKey(fileName);
|
||||||
|
entry = this._gitCache.get(cacheKey);
|
||||||
|
|
||||||
|
if (entry !== undefined) {
|
||||||
|
const cachedDiff = entry.get<ICachedDiff>(key);
|
||||||
|
if (cachedDiff !== undefined) {
|
||||||
|
Logger.log(`Cached(${key}): getDiffForFile('${repoPath}', '${fileName}', ${sha1}, ${sha2})`);
|
||||||
|
return cachedDiff.item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.log(`Not Cached(${key}): getDiffForFile('${repoPath}', '${fileName}', ${sha1}, ${sha2})`);
|
||||||
|
|
||||||
|
if (entry === undefined) {
|
||||||
|
entry = new GitCacheEntry(cacheKey);
|
||||||
|
this._gitCache.set(entry.key, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Logger.log(`getDiffForFile('${repoPath}', '${fileName}', ${sha1}, ${sha2})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const promise = this._getDiffForFile(repoPath, fileName, sha1, sha2, entry, key);
|
||||||
|
|
||||||
|
if (entry) {
|
||||||
|
Logger.log(`Add log cache for '${entry.key}:${key}'`);
|
||||||
|
|
||||||
|
entry.set<ICachedDiff>(key, {
|
||||||
|
//date: new Date(),
|
||||||
|
item: promise
|
||||||
|
} as ICachedDiff);
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _getDiffForFile(repoPath: string | undefined, fileName: string, sha1: string | undefined, sha2: string | undefined, entry: GitCacheEntry | undefined, key: string): Promise<IGitDiff | undefined> {
|
||||||
|
const [file, root] = Git.splitPath(fileName, repoPath, false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await Git.diff(root, file, sha1, sha2);
|
||||||
|
return GitDiffParser.parse(data, this.config.debug);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
// Trap and cache expected diff errors
|
||||||
|
if (entry) {
|
||||||
|
const msg = ex && ex.toString();
|
||||||
|
Logger.log(`Replace diff cache with empty promise for '${entry.key}:${key}'`);
|
||||||
|
|
||||||
|
entry.set<ICachedDiff>(key, {
|
||||||
|
//date: new Date(),
|
||||||
|
item: GitService.EmptyPromise,
|
||||||
|
errorMessage: msg
|
||||||
|
} as ICachedDiff);
|
||||||
|
|
||||||
|
return await GitService.EmptyPromise as IGitDiff;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDiffForLine(repoPath: string | undefined, fileName: string, line: number, sha1?: string, sha2?: string): Promise<[string | undefined, string | undefined] | undefined> {
|
||||||
|
try {
|
||||||
|
const diff = await this.getDiffForFile(repoPath, fileName, sha1, sha2);
|
||||||
|
if (diff === undefined) return undefined;
|
||||||
|
|
||||||
|
const chunk = diff.chunks.find(_ => Math.min(_.originalStart, _.changesStart) <= line && Math.max(_.originalEnd, _.changesEnd) >= line);
|
||||||
|
if (chunk === undefined) return undefined;
|
||||||
|
|
||||||
|
return [
|
||||||
|
chunk.original[line - chunk.originalStart + 1],
|
||||||
|
chunk.changes[line - chunk.changesStart + 1]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async getLogCommit(repoPath: string | undefined, fileName: string, options?: { firstIfMissing?: boolean, previous?: boolean }): Promise<GitLogCommit | undefined>;
|
async getLogCommit(repoPath: string | undefined, fileName: string, options?: { firstIfMissing?: boolean, previous?: boolean }): Promise<GitLogCommit | undefined>;
|
||||||
async getLogCommit(repoPath: string | undefined, fileName: string, sha: string | undefined, options?: { firstIfMissing?: boolean, previous?: boolean }): Promise<GitLogCommit | undefined>;
|
async getLogCommit(repoPath: string | undefined, fileName: string, sha: string | undefined, options?: { firstIfMissing?: boolean, previous?: boolean }): Promise<GitLogCommit | undefined>;
|
||||||
async getLogCommit(repoPath: string | undefined, fileName: string, shaOrOptions?: string | undefined | { firstIfMissing?: boolean, previous?: boolean }, options?: { firstIfMissing?: boolean, previous?: boolean }): Promise<GitLogCommit | undefined> {
|
async getLogCommit(repoPath: string | undefined, fileName: string, shaOrOptions?: string | undefined | { firstIfMissing?: boolean, previous?: boolean }, options?: { firstIfMissing?: boolean, previous?: boolean }): Promise<GitLogCommit | undefined> {
|
||||||
|
|||||||
Reference in New Issue
Block a user