You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

167 lines
7.3 KiB

  1. 'use strict';
  2. import { commands, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode';
  3. import { Container } from '../container';
  4. import { GitCommit, GitService, GitUri } from '../git/gitService';
  5. import { Logger } from '../logger';
  6. import { Messages } from '../messages';
  7. import { Iterables } from '../system';
  8. import { ActiveEditorCommand, CommandContext, Commands, getCommandUri } from './common';
  9. import { DiffWithCommandArgs } from './diffWith';
  10. import { DiffWithWorkingCommandArgs } from './diffWithWorking';
  11. export interface DiffWithPreviousCommandArgs {
  12. commit?: GitCommit;
  13. inDiffEditor?: boolean;
  14. line?: number;
  15. showOptions?: TextDocumentShowOptions;
  16. }
  17. export class DiffWithPreviousCommand extends ActiveEditorCommand {
  18. constructor() {
  19. super([Commands.DiffWithPrevious, Commands.DiffWithPreviousInDiff]);
  20. }
  21. protected async preExecute(context: CommandContext, args: DiffWithPreviousCommandArgs = {}): Promise<any> {
  22. if (context.command === Commands.DiffWithPreviousInDiff) {
  23. args.inDiffEditor = true;
  24. }
  25. return this.execute(context.editor, context.uri, args);
  26. }
  27. async execute(editor?: TextEditor, uri?: Uri, args: DiffWithPreviousCommandArgs = {}): Promise<any> {
  28. uri = getCommandUri(uri, editor);
  29. if (uri == null) return undefined;
  30. args = { ...args };
  31. if (args.line === undefined) {
  32. args.line = editor == null ? 0 : editor.selection.active.line;
  33. }
  34. if (args.commit === undefined || !args.commit.isFile) {
  35. const gitUri = await GitUri.fromUri(uri);
  36. try {
  37. let sha = args.commit === undefined ? gitUri.sha : args.commit.sha;
  38. if (sha === GitService.deletedOrMissingSha) return Messages.showCommitHasNoPreviousCommitWarningMessage();
  39. // If we are a fake "staged" sha, remove it
  40. let isStagedUncommitted = false;
  41. if (GitService.isStagedUncommitted(sha!)) {
  42. gitUri.sha = sha = undefined;
  43. isStagedUncommitted = true;
  44. }
  45. // If we are in a diff editor, assume we are on the right side, and need to move back 2 revisions
  46. if (args.inDiffEditor && sha !== undefined) {
  47. sha = sha + '^';
  48. }
  49. args.commit = undefined;
  50. let log = await Container.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, {
  51. maxCount: 2,
  52. ref: sha,
  53. renames: true
  54. });
  55. if (log !== undefined) {
  56. args.commit = (sha && log.commits.get(sha)) || Iterables.first(log.commits.values());
  57. }
  58. else {
  59. // Only kick out if we aren't looking for the previous sha -- since renames won't return a log above
  60. if (sha === undefined || !sha.endsWith('^')) {
  61. return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
  62. }
  63. // Check for renames
  64. log = await Container.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, {
  65. maxCount: 3,
  66. ref: sha.substring(0, sha.length - 1),
  67. renames: true
  68. });
  69. if (log === undefined) {
  70. return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
  71. }
  72. args.commit =
  73. Iterables.next(Iterables.skip(log.commits.values(), 1)) ||
  74. Iterables.first(log.commits.values());
  75. }
  76. // If the sha is missing (i.e. working tree), check the file status
  77. // If file is uncommitted, then treat it as a DiffWithWorking
  78. if (gitUri.sha === undefined) {
  79. const status = await Container.git.getStatusForFile(gitUri.repoPath!, gitUri.fsPath);
  80. if (status !== undefined) {
  81. if (isStagedUncommitted) {
  82. const diffArgs: DiffWithCommandArgs = {
  83. repoPath: args.commit.repoPath,
  84. lhs: {
  85. sha: args.inDiffEditor
  86. ? args.commit.previousSha || GitService.deletedOrMissingSha
  87. : args.commit.sha,
  88. uri: args.inDiffEditor ? args.commit.previousUri : args.commit.uri
  89. },
  90. rhs: {
  91. sha: args.inDiffEditor ? args.commit.sha : GitService.stagedUncommittedSha,
  92. uri: args.commit.uri
  93. },
  94. line: args.line,
  95. showOptions: args.showOptions
  96. };
  97. return commands.executeCommand(Commands.DiffWith, diffArgs);
  98. }
  99. // Check if the file is staged
  100. if (status.indexStatus !== undefined) {
  101. const diffArgs: DiffWithCommandArgs = {
  102. repoPath: args.commit.repoPath,
  103. lhs: {
  104. sha: args.inDiffEditor ? args.commit.sha : GitService.stagedUncommittedSha,
  105. uri: args.commit.uri
  106. },
  107. rhs: {
  108. sha: args.inDiffEditor ? GitService.stagedUncommittedSha : '',
  109. uri: args.commit.uri
  110. },
  111. line: args.line,
  112. showOptions: args.showOptions
  113. };
  114. return commands.executeCommand(Commands.DiffWith, diffArgs);
  115. }
  116. if (!args.inDiffEditor) {
  117. return commands.executeCommand(Commands.DiffWithWorking, uri, {
  118. commit: args.commit,
  119. showOptions: args.showOptions
  120. } as DiffWithWorkingCommandArgs);
  121. }
  122. }
  123. }
  124. }
  125. catch (ex) {
  126. Logger.error(ex, 'DiffWithPreviousCommand', `getLogForFile(${gitUri.repoPath}, ${gitUri.fsPath})`);
  127. return Messages.showGenericErrorMessage('Unable to open compare');
  128. }
  129. }
  130. const diffArgs: DiffWithCommandArgs = {
  131. repoPath: args.commit.repoPath,
  132. lhs: {
  133. sha: args.commit.previousSha !== undefined ? args.commit.previousSha : GitService.deletedOrMissingSha,
  134. uri: args.commit.previousUri
  135. },
  136. rhs: {
  137. sha: args.commit.sha,
  138. uri: args.commit.uri
  139. },
  140. line: args.line,
  141. showOptions: args.showOptions
  142. };
  143. return commands.executeCommand(Commands.DiffWith, diffArgs);
  144. }
  145. }