Skip to content

Commit

Permalink
Reworks diff with prev (line) for better accuracy
Browse files Browse the repository at this point in the history
Adds getDiffWithPreviousForFile & getPreviousRevisionUri
  • Loading branch information
eamodio committed Apr 28, 2019
1 parent 38bf21c commit 4658b80
Show file tree
Hide file tree
Showing 7 changed files with 251 additions and 193 deletions.
139 changes: 83 additions & 56 deletions src/commands/diffLineWithPrevious.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
'use strict';
import { commands, Range, TextDocumentShowOptions, TextEditor, Uri } from 'vscode';
import { commands, TextDocumentShowOptions, TextEditor, Uri } from 'vscode';
import { Container } from '../container';
import { GitCommit, GitService, GitUri } from '../git/gitService';
import { Logger } from '../logger';
import { Messages } from '../messages';
import { ActiveEditorCommand, command, Commands, getCommandUri } from './common';
import { DiffWithCommandArgs } from './diffWith';
import { Iterables } from '../system';

export interface DiffLineWithPreviousCommandArgs {
commit?: GitCommit;
Expand All @@ -25,73 +24,101 @@ export class DiffLineWithPreviousCommand extends ActiveEditorCommand {
uri = getCommandUri(uri, editor);
if (uri == null) return undefined;

const gitUri = await GitUri.fromUri(uri);

args = { ...args };
if (args.line === undefined) {
args.line = editor == null ? 0 : editor.selection.active.line;
}

if (args.commit === undefined || GitService.isUncommitted(args.commit.sha)) {
if (args.line < 0) return undefined;
const gitUri = args.commit !== undefined ? GitUri.fromCommit(args.commit) : await GitUri.fromUri(uri);

try {
if (!GitService.isStagedUncommitted(gitUri.sha)) {
const blame =
editor && editor.document && editor.document.isDirty
? await Container.git.getBlameForLineContents(gitUri, args.line, editor.document.getText())
: await Container.git.getBlameForLine(gitUri, args.line);
if (blame === undefined) {
return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
}
if (gitUri.sha === undefined || GitService.isUncommitted(gitUri.sha)) {
const blame =
editor && editor.document.isDirty
? await Container.git.getBlameForLineContents(gitUri, args.line, editor.document.getText())
: await Container.git.getBlameForLine(gitUri, args.line);
if (blame === undefined) {
return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
}

// If the line is uncommitted, change the previous commit
if (blame.commit.isUncommitted) {
const status = await Container.git.getStatusForFile(gitUri.repoPath!, gitUri.fsPath);
if (status !== undefined && status.indexStatus !== undefined) {
args.commit = blame.commit.with({
sha: GitService.stagedUncommittedSha
});
}
}
}
// Since there could be a change in the line number, update it
args.line = blame.line.originalLine - 1;

// If the line is uncommitted, change the previous commit
if (blame.commit.isUncommitted) {
try {
const previous = await Container.git.getPreviousRevisionUri(
gitUri.repoPath!,
gitUri,
gitUri.sha,
0,
args.line
);

if (args.commit === undefined) {
const log = await Container.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, {
maxCount: 2,
range: new Range(args.line, 0, args.line, 0),
ref:
gitUri.sha === undefined || GitService.isStagedUncommitted(gitUri.sha)
? undefined
: `${gitUri.sha}^`,
renames: true
});
if (log === undefined) {
return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
if (previous === undefined) {
return Messages.showCommitHasNoPreviousCommitWarningMessage();
}

args.commit = (gitUri.sha && log.commits.get(gitUri.sha)) || Iterables.first(log.commits.values());
const diffArgs: DiffWithCommandArgs = {
repoPath: gitUri.repoPath!,
lhs: {
sha: previous.sha || '',
uri: previous.documentUri()
},
rhs: {
sha: gitUri.sha || '',
uri: gitUri.documentUri()
},
line: args.line,
showOptions: args.showOptions
};
return commands.executeCommand(Commands.DiffWith, diffArgs);
}
catch (ex) {
Logger.error(
ex,
'DiffLineWithPreviousCommand',
`getPreviousRevisionUri(${gitUri.repoPath}, ${gitUri.fsPath}, ${gitUri.sha})`
);
return Messages.showGenericErrorMessage('Unable to open compare');
}
}
catch (ex) {
Logger.error(ex, 'DiffLineWithPreviousCommand', `getLogForFile(${args.line})`);
return Messages.showGenericErrorMessage('Unable to open compare');
}
}

const diffArgs: DiffWithCommandArgs = {
repoPath: args.commit.repoPath,
lhs: {
sha: args.commit.sha,
uri: args.commit.uri
},
rhs: {
sha: gitUri.sha || '',
uri: gitUri
},
line: args.line,
showOptions: args.showOptions
};
return commands.executeCommand(Commands.DiffWith, diffArgs);
try {
const diffWith = await Container.git.getDiffWithPreviousForFile(
gitUri.repoPath!,
gitUri,
gitUri.sha,
0,
args.line
);

if (diffWith === undefined || diffWith.previous === undefined) {
return Messages.showCommitHasNoPreviousCommitWarningMessage();
}

const diffArgs: DiffWithCommandArgs = {
repoPath: diffWith.current.repoPath,
lhs: {
sha: diffWith.previous.sha || '',
uri: diffWith.previous.documentUri()
},
rhs: {
sha: diffWith.current.sha || '',
uri: diffWith.current.documentUri()
},
line: args.line,
showOptions: args.showOptions
};
return commands.executeCommand(Commands.DiffWith, diffArgs);
}
catch (ex) {
Logger.error(
ex,
'DiffLineWithPreviousCommand',
`getDiffWithPreviousForFile(${gitUri.repoPath}, ${gitUri.fsPath}, ${gitUri.sha})`
);
return Messages.showGenericErrorMessage('Unable to open compare');
}
}
}
169 changes: 36 additions & 133 deletions src/commands/diffWithPrevious.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
'use strict';
import { commands, TextDocumentShowOptions, TextEditor, Uri } from 'vscode';
import { Container } from '../container';
import { GitCommit, GitService, GitUri } from '../git/gitService';
import { GitCommit, GitUri } from '../git/gitService';
import { Logger } from '../logger';
import { Messages } from '../messages';
import { Iterables } from '../system';
import { ActiveEditorCommand, command, CommandContext, Commands, getCommandUri } from './common';
import { DiffWithCommandArgs } from './diffWith';
import { DiffWithWorkingCommandArgs } from './diffWithWorking';
import { UriComparer } from '../comparers';

export interface DiffWithPreviousCommandArgs {
Expand Down Expand Up @@ -52,137 +50,42 @@ export class DiffWithPreviousCommand extends ActiveEditorCommand {
args.line = editor == null ? 0 : editor.selection.active.line;
}

if (args.commit === undefined || !args.commit.isFile) {
const gitUri = await GitUri.fromUri(uri);

try {
let sha = args.commit === undefined ? gitUri.sha : args.commit.sha;
if (sha === GitService.deletedOrMissingSha) {
return Messages.showCommitHasNoPreviousCommitWarningMessage();
}

// If we are a fake "staged" sha, remove it
let isStagedUncommitted = false;
if (GitService.isStagedUncommitted(sha!)) {
gitUri.sha = sha = undefined;
isStagedUncommitted = true;
}

// If we are in a diff editor, assume we are on the right side, and need to move back 2 revisions
const originalSha = sha;
if (args.inDiffEditor && sha !== undefined) {
sha = `${sha}^`;
}

args.commit = undefined;

let log = await Container.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, {
maxCount: 2,
ref: sha,
renames: true
});

if (log !== undefined) {
args.commit = (sha && log.commits.get(sha)) || Iterables.first(log.commits.values());
}
else {
// Only kick out if we aren't looking for the previous sha -- since renames won't return a log above
if (sha === undefined || !sha.endsWith('^')) {
return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
}

// Check for renames
log = await Container.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, {
maxCount: 3,
ref: originalSha,
renames: true
});

if (log === undefined) {
return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
}

args.commit =
Iterables.next(Iterables.skip(log.commits.values(), 1)) ||
Iterables.first(log.commits.values());

if (args.commit.sha === originalSha) {
return Messages.showCommitHasNoPreviousCommitWarningMessage();
}
}

// If the sha is missing (i.e. working tree), check the file status
// If file is uncommitted, then treat it as a DiffWithWorking
if (gitUri.sha === undefined) {
const status = await Container.git.getStatusForFile(gitUri.repoPath!, gitUri.fsPath);
if (status !== undefined) {
if (isStagedUncommitted) {
const diffArgs: DiffWithCommandArgs = {
repoPath: args.commit.repoPath,
lhs: {
sha: args.inDiffEditor
? args.commit.previousSha || GitService.deletedOrMissingSha
: args.commit.sha,
uri: args.inDiffEditor ? args.commit.previousUri : args.commit.uri
},
rhs: {
sha: args.inDiffEditor ? args.commit.sha : GitService.stagedUncommittedSha,
uri: args.commit.uri
},
line: args.line,
showOptions: args.showOptions
};
return commands.executeCommand(Commands.DiffWith, diffArgs);
}

// Check if the file is staged
if (status.indexStatus !== undefined) {
const diffArgs: DiffWithCommandArgs = {
repoPath: args.commit.repoPath,
lhs: {
sha: args.inDiffEditor ? args.commit.sha : GitService.stagedUncommittedSha,
uri: args.commit.uri
},
rhs: {
sha: args.inDiffEditor ? GitService.stagedUncommittedSha : '',
uri: args.commit.uri
},
line: args.line,
showOptions: args.showOptions
};

return commands.executeCommand(Commands.DiffWith, diffArgs);
}

if (!args.inDiffEditor) {
const commandArgs: DiffWithWorkingCommandArgs = {
commit: args.commit,
showOptions: args.showOptions
};
return commands.executeCommand(Commands.DiffWithWorking, uri, commandArgs);
}
}
}
}
catch (ex) {
Logger.error(ex, 'DiffWithPreviousCommand', `getLogForFile(${gitUri.repoPath}, ${gitUri.fsPath})`);
return Messages.showGenericErrorMessage('Unable to open compare');
const gitUri = args.commit !== undefined ? GitUri.fromCommit(args.commit) : await GitUri.fromUri(uri);
try {
const diffWith = await Container.git.getDiffWithPreviousForFile(
gitUri.repoPath!,
gitUri,
gitUri.sha,
// If we are in a diff editor, assume we are on the right side, and need to skip back 1 more revisions
args.inDiffEditor ? 1 : 0
);

if (diffWith === undefined || diffWith.previous === undefined) {
return Messages.showCommitHasNoPreviousCommitWarningMessage();
}
}

const diffArgs: DiffWithCommandArgs = {
repoPath: args.commit.repoPath,
lhs: {
sha: args.commit.previousSha !== undefined ? args.commit.previousSha : GitService.deletedOrMissingSha,
uri: args.commit.previousUri
},
rhs: {
sha: args.commit.sha,
uri: args.commit.uri
},
line: args.line,
showOptions: args.showOptions
};
return commands.executeCommand(Commands.DiffWith, diffArgs);
const diffArgs: DiffWithCommandArgs = {
repoPath: diffWith.current.repoPath,
lhs: {
sha: diffWith.previous.sha || '',
uri: diffWith.previous.documentUri()
},
rhs: {
sha: diffWith.current.sha || '',
uri: diffWith.current.documentUri()
},
line: args.line,
showOptions: args.showOptions
};
return commands.executeCommand(Commands.DiffWith, diffArgs);
}
catch (ex) {
Logger.error(
ex,
'DiffWithPreviousCommand',
`getDiffWithPreviousForFile(${gitUri.repoPath}, ${gitUri.fsPath}, ${gitUri.sha})`
);
return Messages.showGenericErrorMessage('Unable to open compare');
}
}
}
21 changes: 21 additions & 0 deletions src/git/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ const logFormat = [
`${lb}f${rb}`
].join('%n');

const logSimpleFormat = `${lb}r${rb}${sp}%H`;

const defaultLogParams = ['log', '--name-status', `--format=${logFormat}`];

const stashFormat = [
Expand Down Expand Up @@ -682,6 +684,25 @@ export class Git {
return git<string>({ cwd: root }, ...params, '--', file);
}

static log_file_simple(repoPath: string, fileName: string, ref?: string, count: number = 2, line?: number) {
const [file, root] = Git.splitPath(fileName, repoPath);

const params = ['log', `--format=${logSimpleFormat}`, `-n${count}`, '--follow'];
if (ref && !Git.isStagedUncommitted(ref)) {
params.push(ref);
}

if (line != null) {
// Don't include --name-status or -s because Git won't honor it
params.push(/*'-s',*/ `-L ${line},${line}:${file}`);
}
else {
params.push('--name-status');
}

return git<string>({ cwd: root }, ...params, '--', file);
}

static async log_recent(repoPath: string, fileName: string) {
const data = await git<string>(
{ cwd: repoPath, errors: GitErrorHandling.Ignore },
Expand Down

0 comments on commit 4658b80

Please sign in to comment.