Browse Source

Fixes various failures when not on a branch

main
Eric Amodio 6 years ago
parent
commit
f8a1183039
4 changed files with 633 additions and 625 deletions
  1. +1
    -0
      CHANGELOG.md
  2. +147
    -144
      src/commands/showQuickCommitDetails.ts
  3. +325
    -323
      src/quickPicks/commitFileQuickPick.ts
  4. +160
    -158
      src/quickPicks/fileHistoryQuickPick.ts

+ 1
- 0
CHANGELOG.md View File

@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
### Fixed
- Fixes issue where comparing previous revision during a merge/rebase conflict failed to show the correct contents
- Fixes various issues when not on a branch
## [8.2.4] - 2018-04-22
### Added

+ 147
- 144
src/commands/showQuickCommitDetails.ts View File

@ -1,145 +1,148 @@
'use strict';
import { Strings } from '../system';
import { commands, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCachedCommand, CommandContext, Commands, getCommandUri, isCommandViewContextWithCommit } from './common';
import { GlyphChars } from '../constants';
import { GitCommit, GitLog, GitLogCommit, GitUri } from '../gitService';
import { Logger } from '../logger';
import { CommandQuickPickItem, CommitQuickPick, CommitWithFileStatusQuickPickItem } from '../quickPicks/quickPicks';
import { ShowQuickCommitFileDetailsCommandArgs } from './showQuickCommitFileDetails';
import { Messages } from '../messages';
import * as path from 'path';
import { Container } from '../container';
export interface ShowQuickCommitDetailsCommandArgs {
sha?: string;
commit?: GitCommit | GitLogCommit;
repoLog?: GitLog;
goBackCommand?: CommandQuickPickItem;
}
export class ShowQuickCommitDetailsCommand extends ActiveEditorCachedCommand {
static getMarkdownCommandArgs(sha: string): string;
static getMarkdownCommandArgs(args: ShowQuickCommitDetailsCommandArgs): string;
static getMarkdownCommandArgs(argsOrSha: ShowQuickCommitDetailsCommandArgs | string): string {
const args = typeof argsOrSha === 'string'
? { sha: argsOrSha }
: argsOrSha;
return super.getMarkdownCommandArgsCore<ShowQuickCommitDetailsCommandArgs>(Commands.ShowQuickCommitDetails, args);
}
constructor() {
super(Commands.ShowQuickCommitDetails);
}
protected async preExecute(context: CommandContext, args: ShowQuickCommitDetailsCommandArgs = {}): Promise<any> {
if (context.type === 'view') {
args = { ...args };
args.sha = context.node.uri.sha;
if (isCommandViewContextWithCommit(context)) {
args.commit = context.node.commit;
}
}
return this.execute(context.editor, context.uri, args);
}
async execute(editor?: TextEditor, uri?: Uri, args: ShowQuickCommitDetailsCommandArgs = {}) {
uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined;
const gitUri = await GitUri.fromUri(uri);
let repoPath = gitUri.repoPath;
let workingFileName = path.relative(repoPath || '', gitUri.fsPath);
args = { ...args };
if (args.sha === undefined) {
if (editor === undefined) return undefined;
const blameline = editor.selection.active.line;
if (blameline < 0) return undefined;
try {
const blame = await Container.git.getBlameForLine(gitUri, blameline);
if (blame === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to show commit details');
// Because the previous sha of an uncommitted file isn't trust worthy we just have to kick out
if (blame.commit.isUncommitted) return Messages.showLineUncommittedWarningMessage('Unable to show commit details');
args.sha = blame.commit.sha;
repoPath = blame.commit.repoPath;
workingFileName = blame.commit.fileName;
args.commit = blame.commit;
}
catch (ex) {
Logger.error(ex, 'ShowQuickCommitDetailsCommand', `getBlameForLine(${blameline})`);
return window.showErrorMessage(`Unable to show commit details. See output channel for more details`);
}
}
try {
if (args.commit === undefined || args.commit.isFile) {
if (args.repoLog !== undefined) {
args.commit = args.repoLog.commits.get(args.sha!);
// If we can't find the commit, kill the repoLog
if (args.commit === undefined) {
args.repoLog = undefined;
}
}
if (args.repoLog === undefined) {
const log = await Container.git.getLog(repoPath!, { maxCount: 2, ref: args.sha });
if (log === undefined) return Messages.showCommitNotFoundWarningMessage(`Unable to show commit details`);
args.commit = log.commits.get(args.sha!);
}
}
if (args.commit === undefined) return Messages.showCommitNotFoundWarningMessage(`Unable to show commit details`);
if (args.commit.workingFileName === undefined) {
args.commit.workingFileName = workingFileName;
}
if (args.goBackCommand === undefined) {
// Create a command to get back to the branch history
args.goBackCommand = new CommandQuickPickItem({
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to branch history`
}, Commands.ShowQuickCurrentBranchHistory, [
args.commit.toGitUri()
]);
}
// Create a command to get back to where we are right now
const currentCommand = new CommandQuickPickItem({
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to details of ${GlyphChars.Space}$(git-commit) ${args.commit.shortSha}`
}, Commands.ShowQuickCommitDetails, [
args.commit.toGitUri(),
args
]);
const pick = await CommitQuickPick.show(args.commit as GitLogCommit, uri, args.goBackCommand, currentCommand, args.repoLog);
if (pick === undefined) return undefined;
if (!(pick instanceof CommitWithFileStatusQuickPickItem)) return pick.execute();
return commands.executeCommand(Commands.ShowQuickCommitFileDetails,
pick.commit.toGitUri(),
{
commit: pick.commit,
sha: pick.sha,
goBackCommand: currentCommand
} as ShowQuickCommitFileDetailsCommandArgs);
}
catch (ex) {
Logger.error(ex, 'ShowQuickCommitDetailsCommand');
return window.showErrorMessage(`Unable to show commit details. See output channel for more details`);
}
}
'use strict';
import { Strings } from '../system';
import { commands, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCachedCommand, CommandContext, Commands, getCommandUri, isCommandViewContextWithCommit } from './common';
import { GlyphChars } from '../constants';
import { GitCommit, GitLog, GitLogCommit, GitUri } from '../gitService';
import { Logger } from '../logger';
import { CommandQuickPickItem, CommitQuickPick, CommitWithFileStatusQuickPickItem } from '../quickPicks/quickPicks';
import { ShowQuickCommitFileDetailsCommandArgs } from './showQuickCommitFileDetails';
import { Messages } from '../messages';
import * as path from 'path';
import { Container } from '../container';
export interface ShowQuickCommitDetailsCommandArgs {
sha?: string;
commit?: GitCommit | GitLogCommit;
repoLog?: GitLog;
goBackCommand?: CommandQuickPickItem;
}
export class ShowQuickCommitDetailsCommand extends ActiveEditorCachedCommand {
static getMarkdownCommandArgs(sha: string): string;
static getMarkdownCommandArgs(args: ShowQuickCommitDetailsCommandArgs): string;
static getMarkdownCommandArgs(argsOrSha: ShowQuickCommitDetailsCommandArgs | string): string {
const args = typeof argsOrSha === 'string'
? { sha: argsOrSha }
: argsOrSha;
return super.getMarkdownCommandArgsCore<ShowQuickCommitDetailsCommandArgs>(Commands.ShowQuickCommitDetails, args);
}
constructor() {
super(Commands.ShowQuickCommitDetails);
}
protected async preExecute(context: CommandContext, args: ShowQuickCommitDetailsCommandArgs = {}): Promise<any> {
if (context.type === 'view') {
args = { ...args };
args.sha = context.node.uri.sha;
if (isCommandViewContextWithCommit(context)) {
args.commit = context.node.commit;
}
}
return this.execute(context.editor, context.uri, args);
}
async execute(editor?: TextEditor, uri?: Uri, args: ShowQuickCommitDetailsCommandArgs = {}) {
uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined;
const gitUri = await GitUri.fromUri(uri);
let repoPath = gitUri.repoPath;
let workingFileName = path.relative(repoPath || '', gitUri.fsPath);
args = { ...args };
if (args.sha === undefined) {
if (editor === undefined) return undefined;
const blameline = editor.selection.active.line;
if (blameline < 0) return undefined;
try {
const blame = await Container.git.getBlameForLine(gitUri, blameline);
if (blame === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to show commit details');
// Because the previous sha of an uncommitted file isn't trust worthy we just have to kick out
if (blame.commit.isUncommitted) return Messages.showLineUncommittedWarningMessage('Unable to show commit details');
args.sha = blame.commit.sha;
repoPath = blame.commit.repoPath;
workingFileName = blame.commit.fileName;
args.commit = blame.commit;
}
catch (ex) {
Logger.error(ex, 'ShowQuickCommitDetailsCommand', `getBlameForLine(${blameline})`);
return window.showErrorMessage(`Unable to show commit details. See output channel for more details`);
}
}
try {
if (args.commit === undefined || args.commit.isFile) {
if (args.repoLog !== undefined) {
args.commit = args.repoLog.commits.get(args.sha!);
// If we can't find the commit, kill the repoLog
if (args.commit === undefined) {
args.repoLog = undefined;
}
}
if (args.repoLog === undefined) {
const log = await Container.git.getLog(repoPath!, { maxCount: 2, ref: args.sha });
if (log === undefined) return Messages.showCommitNotFoundWarningMessage(`Unable to show commit details`);
args.commit = log.commits.get(args.sha!);
}
}
if (args.commit === undefined) return Messages.showCommitNotFoundWarningMessage(`Unable to show commit details`);
if (args.commit.workingFileName === undefined) {
args.commit.workingFileName = workingFileName;
}
if (args.goBackCommand === undefined) {
const branch = await Container.git.getBranch(args.commit.repoPath);
if (branch !== undefined) {
// Create a command to get back to the branch history
args.goBackCommand = new CommandQuickPickItem({
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to ${branch.name} history`
}, Commands.ShowQuickCurrentBranchHistory, [
args.commit.toGitUri()
]);
}
}
// Create a command to get back to where we are right now
const currentCommand = new CommandQuickPickItem({
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to details of ${GlyphChars.Space}$(git-commit) ${args.commit.shortSha}`
}, Commands.ShowQuickCommitDetails, [
args.commit.toGitUri(),
args
]);
const pick = await CommitQuickPick.show(args.commit as GitLogCommit, uri, args.goBackCommand, currentCommand, args.repoLog);
if (pick === undefined) return undefined;
if (!(pick instanceof CommitWithFileStatusQuickPickItem)) return pick.execute();
return commands.executeCommand(Commands.ShowQuickCommitFileDetails,
pick.commit.toGitUri(),
{
commit: pick.commit,
sha: pick.sha,
goBackCommand: currentCommand
} as ShowQuickCommitFileDetailsCommandArgs);
}
catch (ex) {
Logger.error(ex, 'ShowQuickCommitDetailsCommand');
return window.showErrorMessage(`Unable to show commit details. See output channel for more details`);
}
}
}

+ 325
- 323
src/quickPicks/commitFileQuickPick.ts View File

@ -1,324 +1,326 @@
'use strict';
import { Iterables, Strings } from '../system';
import { QuickPickItem, QuickPickOptions, Uri, window } from 'vscode';
import { Commands, CopyMessageToClipboardCommandArgs, CopyShaToClipboardCommandArgs, DiffWithPreviousCommandArgs, DiffWithWorkingCommandArgs, openEditor, ShowQuickCommitDetailsCommandArgs, ShowQuickCommitFileDetailsCommandArgs, ShowQuickFileHistoryCommandArgs } from '../commands';
import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, KeyCommandQuickPickItem, OpenFileCommandQuickPickItem } from './commonQuickPicks';
import { GlyphChars } from '../constants';
import { Container } from '../container';
import { GitLog, GitLogCommit, GitUri, RemoteResource } from '../gitService';
import { KeyCommand, KeyNoopCommand } from '../keyboard';
import { OpenRemotesCommandQuickPickItem } from './remotesQuickPick';
import * as path from 'path';
export class ApplyCommitFileChangesCommandQuickPickItem extends CommandQuickPickItem {
constructor(
private readonly commit: GitLogCommit,
item?: QuickPickItem
) {
super(item || {
label: `$(git-pull-request) Apply Changes`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} $(file-text) ${path.basename(commit.fileName)} in ${GlyphChars.Space}$(git-commit) ${commit.shortSha}`
}, undefined, undefined);
}
async execute(): Promise<{} | undefined> {
const uri = this.commit.toGitUri();
await Container.git.checkoutFile(uri);
return openEditor(uri, { preserveFocus: true, preview: false });
}
}
export class OpenCommitFileCommandQuickPickItem extends OpenFileCommandQuickPickItem {
constructor(
commit: GitLogCommit,
item?: QuickPickItem
) {
const uri = Uri.file(path.resolve(commit.repoPath, commit.fileName));
super(uri, item || {
label: `$(file-symlink-file) Open File`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} ${path.basename(commit.fileName)}`
});
}
}
export class OpenCommitFileRevisionCommandQuickPickItem extends OpenFileCommandQuickPickItem {
constructor(
commit: GitLogCommit,
item?: QuickPickItem
) {
let description: string;
let uri: Uri;
if (commit.status === 'D') {
uri = GitUri.toRevisionUri(commit.previousFileSha, commit.previousUri.fsPath, commit.repoPath);
description = `${Strings.pad(GlyphChars.Dash, 2, 3)} ${path.basename(commit.fileName)} in ${GlyphChars.Space}$(git-commit) ${commit.previousShortSha} (deleted in ${GlyphChars.Space}$(git-commit) ${commit.shortSha})`;
}
else {
uri = GitUri.toRevisionUri(commit.sha, commit.uri.fsPath, commit.repoPath);
description = `${Strings.pad(GlyphChars.Dash, 2, 3)} ${path.basename(commit.fileName)} in ${GlyphChars.Space}$(git-commit) ${commit.shortSha}`;
}
super(uri, item || {
label: `$(file-symlink-file) Open Revision`,
description: description
});
}
}
export class CommitFileQuickPick {
static async show(commit: GitLogCommit, uri: Uri, goBackCommand?: CommandQuickPickItem, currentCommand?: CommandQuickPickItem, fileLog?: GitLog): Promise<CommandQuickPickItem | undefined> {
const items: CommandQuickPickItem[] = [];
const stash = commit.isStash;
const workingName = (commit.workingFileName && path.basename(commit.workingFileName)) || path.basename(commit.fileName);
const isUncommitted = commit.isUncommitted;
if (isUncommitted) {
// Since we can't trust the previous sha on an uncommitted commit, find the last commit for this file
const c = await Container.git.getRecentLogCommitForFile(undefined, commit.uri.fsPath);
if (c === undefined) return undefined;
commit = c;
}
await commit.resolvePreviousFileSha();
if (stash) {
items.push(new ApplyCommitFileChangesCommandQuickPickItem(commit));
}
if (commit.previousFileShortSha) {
items.push(new CommandQuickPickItem({
label: `$(git-compare) Open Changes`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} $(git-commit) ${commit.previousFileShortSha} ${GlyphChars.Space} $(git-compare) ${GlyphChars.Space} $(git-commit) ${commit.shortSha}`
}, Commands.DiffWithPrevious, [
commit.uri,
{
commit
} as DiffWithPreviousCommandArgs
])
);
}
if (commit.workingFileName) {
items.push(new CommandQuickPickItem({
label: `$(git-compare) Open Changes with Working Tree`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} $(git-commit) ${commit.shortSha} ${GlyphChars.Space} $(git-compare) ${GlyphChars.Space} $(file-text) ${workingName}`
}, Commands.DiffWithWorking, [
Uri.file(path.resolve(commit.repoPath, commit.workingFileName)),
{
commit
} as DiffWithWorkingCommandArgs
])
);
}
if (commit.workingFileName && commit.status !== 'D') {
items.push(new OpenCommitFileCommandQuickPickItem(commit));
}
items.push(new OpenCommitFileRevisionCommandQuickPickItem(commit));
const remotes = await Container.git.getRemotes(commit.repoPath);
if (remotes.length) {
if (commit.workingFileName && commit.status !== 'D') {
const branch = await Container.git.getBranch(commit.repoPath);
items.push(new OpenRemotesCommandQuickPickItem(remotes, {
type: 'file',
fileName: commit.workingFileName,
branch: branch!.name
} as RemoteResource, currentCommand));
}
if (!stash) {
items.push(new OpenRemotesCommandQuickPickItem(remotes, {
type: 'revision',
fileName: commit.fileName,
commit
} as RemoteResource, currentCommand));
}
}
if (!stash) {
items.push(new ApplyCommitFileChangesCommandQuickPickItem(commit));
items.push(new CommandQuickPickItem({
label: `$(clippy) Copy Commit ID to Clipboard`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} ${commit.shortSha}`
}, Commands.CopyShaToClipboard, [
uri,
{
sha: commit.sha
} as CopyShaToClipboardCommandArgs
])
);
items.push(new CommandQuickPickItem({
label: `$(clippy) Copy Commit Message to Clipboard`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} ${commit.getShortMessage(`${GlyphChars.Space}$(ellipsis)`)}`
}, Commands.CopyMessageToClipboard, [
uri,
{
message: commit.message,
sha: commit.sha
} as CopyMessageToClipboardCommandArgs
]));
}
if (commit.workingFileName) {
items.push(new CommandQuickPickItem({
label: `$(history) Show File History`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} of ${path.basename(commit.fileName)}`
}, Commands.ShowQuickFileHistory, [
Uri.file(path.resolve(commit.repoPath, commit.workingFileName)),
{
fileLog,
goBackCommand: currentCommand
} as ShowQuickFileHistoryCommandArgs
]));
}
if (!stash) {
items.push(new CommandQuickPickItem({
label: `$(history) Show ${commit.workingFileName ? 'Previous ' : ''}File History`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} of ${path.basename(commit.fileName)} ${Strings.pad(GlyphChars.Dot, 1, 1)} from ${GlyphChars.Space}$(git-commit) ${commit.shortSha}`
}, Commands.ShowQuickFileHistory, [
commit.toGitUri(),
{
goBackCommand: currentCommand
} as ShowQuickFileHistoryCommandArgs
]));
items.push(new CommandQuickPickItem({
label: `$(git-commit) Show Commit Details`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} $(git-commit) ${commit.shortSha}`
}, Commands.ShowQuickCommitDetails, [
commit.toGitUri(),
{
commit,
sha: commit.sha,
goBackCommand: currentCommand
} as ShowQuickCommitDetailsCommandArgs
])
);
}
if (goBackCommand) {
items.splice(0, 0, goBackCommand);
}
let previousCommand: KeyCommand | (() => Promise<KeyCommand>) | undefined = undefined;
let nextCommand: KeyCommand | (() => Promise<KeyCommand>) | undefined = undefined;
if (!stash) {
// If we have the full history, we are good
if (fileLog !== undefined && !fileLog.truncated && fileLog.sha === undefined) {
previousCommand = commit.previousSha === undefined
? undefined
: new KeyCommandQuickPickItem(Commands.ShowQuickCommitFileDetails, [
commit.previousUri,
{
fileLog,
sha: commit.previousSha,
goBackCommand
} as ShowQuickCommitFileDetailsCommandArgs
]);
nextCommand = commit.nextSha === undefined
? undefined
: new KeyCommandQuickPickItem(Commands.ShowQuickCommitFileDetails, [
commit.nextUri,
{
fileLog,
sha: commit.nextSha,
goBackCommand
} as ShowQuickCommitFileDetailsCommandArgs
]);
}
else {
previousCommand = async () => {
let log = fileLog;
let c = log && log.commits.get(commit.sha);
// If we can't find the commit or the previous commit isn't available (since it isn't trustworthy)
if (c === undefined || c.previousSha === undefined) {
log = await Container.git.getLogForFile(commit.repoPath, uri.fsPath, { maxCount: Container.config.advanced.maxListItems, ref: commit.sha, renames: true });
if (log === undefined) return KeyNoopCommand;
c = log && log.commits.get(commit.sha);
// Since we exclude merge commits in file log, just grab the first returned commit
if (c === undefined && commit.isMerge) {
c = Iterables.first(log.commits.values());
}
if (c) {
// Copy over next info, since it is trustworthy at this point
c.nextSha = commit.nextSha;
c.nextFileName = commit.nextFileName;
}
}
if (c === undefined || c.previousSha === undefined) return KeyNoopCommand;
return new KeyCommandQuickPickItem(Commands.ShowQuickCommitFileDetails, [
c.previousUri,
{
fileLog: log,
sha: c.previousSha,
goBackCommand
} as ShowQuickCommitFileDetailsCommandArgs
]);
};
nextCommand = async () => {
let log = fileLog;
let c = log && log.commits.get(commit.sha);
// If we can't find the commit or the next commit isn't available (since it isn't trustworthy)
if (c === undefined || c.nextSha === undefined) {
log = undefined;
c = undefined;
// Try to find the next commit
const next = await Container.git.findNextCommit(commit.repoPath, uri.fsPath, commit.sha);
if (next !== undefined && next.sha !== commit.sha) {
c = commit;
c.nextSha = next.sha;
c.nextFileName = next.originalFileName || next.fileName;
}
}
if (c === undefined || c.nextSha === undefined) return KeyNoopCommand;
return new KeyCommandQuickPickItem(Commands.ShowQuickCommitFileDetails, [
c.nextUri,
{
fileLog: log,
sha: c.nextSha,
goBackCommand
} as ShowQuickCommitFileDetailsCommandArgs
]);
};
}
}
const scope = await Container.keyboard.beginScope({
left: goBackCommand,
',': previousCommand,
'.': nextCommand
});
const pick = await window.showQuickPick(items, {
matchOnDescription: true,
placeHolder: `${commit.getFormattedPath()} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${isUncommitted ? `Uncommitted ${GlyphChars.ArrowRightHollow} ` : ''}${commit.shortSha} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.author}, ${commit.formattedDate} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.getShortMessage(`${GlyphChars.Space}$(ellipsis)`)}`,
ignoreFocusOut: getQuickPickIgnoreFocusOut(),
onDidSelectItem: (item: QuickPickItem) => {
scope.setKeyCommand('right', item as KeyCommand);
}
} as QuickPickOptions);
await scope.dispose();
return pick;
}
'use strict';
import { Iterables, Strings } from '../system';
import { QuickPickItem, QuickPickOptions, Uri, window } from 'vscode';
import { Commands, CopyMessageToClipboardCommandArgs, CopyShaToClipboardCommandArgs, DiffWithPreviousCommandArgs, DiffWithWorkingCommandArgs, openEditor, ShowQuickCommitDetailsCommandArgs, ShowQuickCommitFileDetailsCommandArgs, ShowQuickFileHistoryCommandArgs } from '../commands';
import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, KeyCommandQuickPickItem, OpenFileCommandQuickPickItem } from './commonQuickPicks';
import { GlyphChars } from '../constants';
import { Container } from '../container';
import { GitLog, GitLogCommit, GitUri, RemoteResource } from '../gitService';
import { KeyCommand, KeyNoopCommand } from '../keyboard';
import { OpenRemotesCommandQuickPickItem } from './remotesQuickPick';
import * as path from 'path';
export class ApplyCommitFileChangesCommandQuickPickItem extends CommandQuickPickItem {
constructor(
private readonly commit: GitLogCommit,
item?: QuickPickItem
) {
super(item || {
label: `$(git-pull-request) Apply Changes`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} $(file-text) ${path.basename(commit.fileName)} in ${GlyphChars.Space}$(git-commit) ${commit.shortSha}`
}, undefined, undefined);
}
async execute(): Promise<{} | undefined> {
const uri = this.commit.toGitUri();
await Container.git.checkoutFile(uri);
return openEditor(uri, { preserveFocus: true, preview: false });
}
}
export class OpenCommitFileCommandQuickPickItem extends OpenFileCommandQuickPickItem {
constructor(
commit: GitLogCommit,
item?: QuickPickItem
) {
const uri = Uri.file(path.resolve(commit.repoPath, commit.fileName));
super(uri, item || {
label: `$(file-symlink-file) Open File`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} ${path.basename(commit.fileName)}`
});
}
}
export class OpenCommitFileRevisionCommandQuickPickItem extends OpenFileCommandQuickPickItem {
constructor(
commit: GitLogCommit,
item?: QuickPickItem
) {
let description: string;
let uri: Uri;
if (commit.status === 'D') {
uri = GitUri.toRevisionUri(commit.previousFileSha, commit.previousUri.fsPath, commit.repoPath);
description = `${Strings.pad(GlyphChars.Dash, 2, 3)} ${path.basename(commit.fileName)} in ${GlyphChars.Space}$(git-commit) ${commit.previousShortSha} (deleted in ${GlyphChars.Space}$(git-commit) ${commit.shortSha})`;
}
else {
uri = GitUri.toRevisionUri(commit.sha, commit.uri.fsPath, commit.repoPath);
description = `${Strings.pad(GlyphChars.Dash, 2, 3)} ${path.basename(commit.fileName)} in ${GlyphChars.Space}$(git-commit) ${commit.shortSha}`;
}
super(uri, item || {
label: `$(file-symlink-file) Open Revision`,
description: description
});
}
}
export class CommitFileQuickPick {
static async show(commit: GitLogCommit, uri: Uri, goBackCommand?: CommandQuickPickItem, currentCommand?: CommandQuickPickItem, fileLog?: GitLog): Promise<CommandQuickPickItem | undefined> {
const items: CommandQuickPickItem[] = [];
const stash = commit.isStash;
const workingName = (commit.workingFileName && path.basename(commit.workingFileName)) || path.basename(commit.fileName);
const isUncommitted = commit.isUncommitted;
if (isUncommitted) {
// Since we can't trust the previous sha on an uncommitted commit, find the last commit for this file
const c = await Container.git.getRecentLogCommitForFile(undefined, commit.uri.fsPath);
if (c === undefined) return undefined;
commit = c;
}
await commit.resolvePreviousFileSha();
if (stash) {
items.push(new ApplyCommitFileChangesCommandQuickPickItem(commit));
}
if (commit.previousFileShortSha) {
items.push(new CommandQuickPickItem({
label: `$(git-compare) Open Changes`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} $(git-commit) ${commit.previousFileShortSha} ${GlyphChars.Space} $(git-compare) ${GlyphChars.Space} $(git-commit) ${commit.shortSha}`
}, Commands.DiffWithPrevious, [
commit.uri,
{
commit
} as DiffWithPreviousCommandArgs
])
);
}
if (commit.workingFileName) {
items.push(new CommandQuickPickItem({
label: `$(git-compare) Open Changes with Working Tree`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} $(git-commit) ${commit.shortSha} ${GlyphChars.Space} $(git-compare) ${GlyphChars.Space} $(file-text) ${workingName}`
}, Commands.DiffWithWorking, [
Uri.file(path.resolve(commit.repoPath, commit.workingFileName)),
{
commit
} as DiffWithWorkingCommandArgs
])
);
}
if (commit.workingFileName && commit.status !== 'D') {
items.push(new OpenCommitFileCommandQuickPickItem(commit));
}
items.push(new OpenCommitFileRevisionCommandQuickPickItem(commit));
const remotes = await Container.git.getRemotes(commit.repoPath);
if (remotes.length) {
if (commit.workingFileName && commit.status !== 'D') {
const branch = await Container.git.getBranch(commit.repoPath);
if (branch !== undefined) {
items.push(new OpenRemotesCommandQuickPickItem(remotes, {
type: 'file',
fileName: commit.workingFileName,
branch: branch.name
} as RemoteResource, currentCommand));
}
}
if (!stash) {
items.push(new OpenRemotesCommandQuickPickItem(remotes, {
type: 'revision',
fileName: commit.fileName,
commit
} as RemoteResource, currentCommand));
}
}
if (!stash) {
items.push(new ApplyCommitFileChangesCommandQuickPickItem(commit));
items.push(new CommandQuickPickItem({
label: `$(clippy) Copy Commit ID to Clipboard`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} ${commit.shortSha}`
}, Commands.CopyShaToClipboard, [
uri,
{
sha: commit.sha
} as CopyShaToClipboardCommandArgs
])
);
items.push(new CommandQuickPickItem({
label: `$(clippy) Copy Commit Message to Clipboard`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} ${commit.getShortMessage(`${GlyphChars.Space}$(ellipsis)`)}`
}, Commands.CopyMessageToClipboard, [
uri,
{
message: commit.message,
sha: commit.sha
} as CopyMessageToClipboardCommandArgs
]));
}
if (commit.workingFileName) {
items.push(new CommandQuickPickItem({
label: `$(history) Show File History`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} of ${path.basename(commit.fileName)}`
}, Commands.ShowQuickFileHistory, [
Uri.file(path.resolve(commit.repoPath, commit.workingFileName)),
{
fileLog,
goBackCommand: currentCommand
} as ShowQuickFileHistoryCommandArgs
]));
}
if (!stash) {
items.push(new CommandQuickPickItem({
label: `$(history) Show ${commit.workingFileName ? 'Previous ' : ''}File History`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} of ${path.basename(commit.fileName)} ${Strings.pad(GlyphChars.Dot, 1, 1)} from ${GlyphChars.Space}$(git-commit) ${commit.shortSha}`
}, Commands.ShowQuickFileHistory, [
commit.toGitUri(),
{
goBackCommand: currentCommand
} as ShowQuickFileHistoryCommandArgs
]));
items.push(new CommandQuickPickItem({
label: `$(git-commit) Show Commit Details`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} $(git-commit) ${commit.shortSha}`
}, Commands.ShowQuickCommitDetails, [
commit.toGitUri(),
{
commit,
sha: commit.sha,
goBackCommand: currentCommand
} as ShowQuickCommitDetailsCommandArgs
])
);
}
if (goBackCommand) {
items.splice(0, 0, goBackCommand);
}
let previousCommand: KeyCommand | (() => Promise<KeyCommand>) | undefined = undefined;
let nextCommand: KeyCommand | (() => Promise<KeyCommand>) | undefined = undefined;
if (!stash) {
// If we have the full history, we are good
if (fileLog !== undefined && !fileLog.truncated && fileLog.sha === undefined) {
previousCommand = commit.previousSha === undefined
? undefined
: new KeyCommandQuickPickItem(Commands.ShowQuickCommitFileDetails, [
commit.previousUri,
{
fileLog,
sha: commit.previousSha,
goBackCommand
} as ShowQuickCommitFileDetailsCommandArgs
]);
nextCommand = commit.nextSha === undefined
? undefined
: new KeyCommandQuickPickItem(Commands.ShowQuickCommitFileDetails, [
commit.nextUri,
{
fileLog,
sha: commit.nextSha,
goBackCommand
} as ShowQuickCommitFileDetailsCommandArgs
]);
}
else {
previousCommand = async () => {
let log = fileLog;
let c = log && log.commits.get(commit.sha);
// If we can't find the commit or the previous commit isn't available (since it isn't trustworthy)
if (c === undefined || c.previousSha === undefined) {
log = await Container.git.getLogForFile(commit.repoPath, uri.fsPath, { maxCount: Container.config.advanced.maxListItems, ref: commit.sha, renames: true });
if (log === undefined) return KeyNoopCommand;
c = log && log.commits.get(commit.sha);
// Since we exclude merge commits in file log, just grab the first returned commit
if (c === undefined && commit.isMerge) {
c = Iterables.first(log.commits.values());
}
if (c) {
// Copy over next info, since it is trustworthy at this point
c.nextSha = commit.nextSha;
c.nextFileName = commit.nextFileName;
}
}
if (c === undefined || c.previousSha === undefined) return KeyNoopCommand;
return new KeyCommandQuickPickItem(Commands.ShowQuickCommitFileDetails, [
c.previousUri,
{
fileLog: log,
sha: c.previousSha,
goBackCommand
} as ShowQuickCommitFileDetailsCommandArgs
]);
};
nextCommand = async () => {
let log = fileLog;
let c = log && log.commits.get(commit.sha);
// If we can't find the commit or the next commit isn't available (since it isn't trustworthy)
if (c === undefined || c.nextSha === undefined) {
log = undefined;
c = undefined;
// Try to find the next commit
const next = await Container.git.findNextCommit(commit.repoPath, uri.fsPath, commit.sha);
if (next !== undefined && next.sha !== commit.sha) {
c = commit;
c.nextSha = next.sha;
c.nextFileName = next.originalFileName || next.fileName;
}
}
if (c === undefined || c.nextSha === undefined) return KeyNoopCommand;
return new KeyCommandQuickPickItem(Commands.ShowQuickCommitFileDetails, [
c.nextUri,
{
fileLog: log,
sha: c.nextSha,
goBackCommand
} as ShowQuickCommitFileDetailsCommandArgs
]);
};
}
}
const scope = await Container.keyboard.beginScope({
left: goBackCommand,
',': previousCommand,
'.': nextCommand
});
const pick = await window.showQuickPick(items, {
matchOnDescription: true,
placeHolder: `${commit.getFormattedPath()} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${isUncommitted ? `Uncommitted ${GlyphChars.ArrowRightHollow} ` : ''}${commit.shortSha} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.author}, ${commit.formattedDate} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.getShortMessage(`${GlyphChars.Space}$(ellipsis)`)}`,
ignoreFocusOut: getQuickPickIgnoreFocusOut(),
onDidSelectItem: (item: QuickPickItem) => {
scope.setKeyCommand('right', item as KeyCommand);
}
} as QuickPickOptions);
await scope.dispose();
return pick;
}
}

+ 160
- 158
src/quickPicks/fileHistoryQuickPick.ts View File

@ -1,159 +1,161 @@
'use strict';
import { Iterables, Strings } from '../system';
import { CancellationTokenSource, QuickPickOptions, Uri, window } from 'vscode';
import { Commands, ShowQuickCurrentBranchHistoryCommandArgs, ShowQuickFileHistoryCommandArgs } from '../commands';
import { CommandQuickPickItem, CommitQuickPickItem, getQuickPickIgnoreFocusOut, ShowBranchesAndTagsQuickPickItem, showQuickPickProgress } from './commonQuickPicks';
import { GlyphChars } from '../constants';
import { Container } from '../container';
import { GitLog, GitUri, RemoteResource } from '../gitService';
import { KeyNoopCommand } from '../keyboard';
import { OpenRemotesCommandQuickPickItem } from './remotesQuickPick';
import * as path from 'path';
export class FileHistoryQuickPick {
static showProgress(placeHolder: string) {
return showQuickPickProgress(placeHolder,
{
left: KeyNoopCommand,
',': KeyNoopCommand,
'.': KeyNoopCommand
});
}
static async show(log: GitLog, uri: GitUri, placeHolder: string, options: { currentCommand?: CommandQuickPickItem, goBackCommand?: CommandQuickPickItem, nextPageCommand?: CommandQuickPickItem, previousPageCommand?: CommandQuickPickItem, pickerOnly?: boolean, progressCancellation?: CancellationTokenSource, showAllCommand?: CommandQuickPickItem, showInResultsExplorerCommand?: CommandQuickPickItem } = {}): Promise<CommitQuickPickItem | CommandQuickPickItem | undefined> {
options = { pickerOnly: false, ...options };
const items = Array.from(Iterables.map(log.commits.values(), c => new CommitQuickPickItem(c))) as (CommitQuickPickItem | CommandQuickPickItem)[];
let index = 0;
if (options.pickerOnly) {
index++;
items.splice(0, 0, new ShowBranchesAndTagsQuickPickItem(log.repoPath, placeHolder, options.currentCommand));
}
if (options.showInResultsExplorerCommand !== undefined) {
index++;
items.splice(0, 0, options.showInResultsExplorerCommand);
}
if (log.truncated || log.sha) {
if (options.showAllCommand !== undefined) {
index++;
items.splice(0, 0, options.showAllCommand);
}
else if (!options.pickerOnly) {
const [workingFileName] = await Container.git.findWorkingFileName(path.relative(log.repoPath, uri.fsPath), log.repoPath);
if (workingFileName) {
index++;
items.splice(0, 0, new CommandQuickPickItem({
label: `$(history) Show File History`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} of ${path.basename(workingFileName)}`
}, Commands.ShowQuickFileHistory, [
Uri.file(path.resolve(log.repoPath, workingFileName)),
{
goBackCommand: new CommandQuickPickItem({
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to history of ${GlyphChars.Space}$(file-text) ${path.basename(uri.fsPath)}${uri.sha ? ` from ${GlyphChars.Space}$(git-commit) ${uri.shortSha}` : ''}`
}, Commands.ShowQuickFileHistory, [
uri,
{
log: log,
maxCount: log.maxCount,
range: log.range,
goBackCommand: options.goBackCommand
} as ShowQuickFileHistoryCommandArgs
])
} as ShowQuickFileHistoryCommandArgs
]));
}
}
if (options.nextPageCommand !== undefined) {
index++;
items.splice(0, 0, options.nextPageCommand);
}
if (options.previousPageCommand !== undefined) {
index++;
items.splice(0, 0, options.previousPageCommand);
}
}
if (!options.pickerOnly) {
const branch = await Container.git.getBranch(uri.repoPath!);
const currentCommand = new CommandQuickPickItem({
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to history of ${GlyphChars.Space}$(file-text) ${path.basename(uri.fsPath)}${uri.sha ? ` from ${GlyphChars.Space}$(git-commit) ${uri.shortSha}` : ''}`
}, Commands.ShowQuickFileHistory, [
uri,
{
log,
maxCount: log.maxCount,
range: log.range
} as ShowQuickFileHistoryCommandArgs
]);
// Only show the full repo option if we are the root
if (options.goBackCommand === undefined) {
items.splice(index++, 0, new CommandQuickPickItem({
label: `$(history) Show Branch History`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} shows ${GlyphChars.Space}$(git-branch) ${branch!.name} history`
}, Commands.ShowQuickCurrentBranchHistory,
[
undefined,
{
goBackCommand: currentCommand
} as ShowQuickCurrentBranchHistoryCommandArgs
]));
}
const remotes = await Container.git.getRemotes(uri.repoPath!);
if (remotes.length) {
const resource = uri.sha !== undefined
? {
type: 'revision',
branch: branch!.name,
fileName: uri.getRelativePath(),
sha: uri.sha
} as RemoteResource
: {
type: 'file',
branch: branch!.name,
fileName: uri.getRelativePath()
} as RemoteResource;
items.splice(index++, 0, new OpenRemotesCommandQuickPickItem(remotes, resource, currentCommand));
}
if (options.goBackCommand) {
items.splice(0, 0, options.goBackCommand);
}
}
if (options.progressCancellation !== undefined && options.progressCancellation.token.isCancellationRequested) return undefined;
const scope = await Container.keyboard.beginScope({
left: options.goBackCommand,
',': options.previousPageCommand,
'.': options.nextPageCommand
});
options.progressCancellation && options.progressCancellation.cancel();
const pick = await window.showQuickPick(items, {
matchOnDescription: true,
matchOnDetail: true,
placeHolder: placeHolder,
ignoreFocusOut: getQuickPickIgnoreFocusOut()
// onDidSelectItem: (item: QuickPickItem) => {
// scope.setKeyCommand('right', item);
// }
} as QuickPickOptions);
await scope.dispose();
return pick;
}
'use strict';
import { Iterables, Strings } from '../system';
import { CancellationTokenSource, QuickPickOptions, Uri, window } from 'vscode';
import { Commands, ShowQuickCurrentBranchHistoryCommandArgs, ShowQuickFileHistoryCommandArgs } from '../commands';
import { CommandQuickPickItem, CommitQuickPickItem, getQuickPickIgnoreFocusOut, ShowBranchesAndTagsQuickPickItem, showQuickPickProgress } from './commonQuickPicks';
import { GlyphChars } from '../constants';
import { Container } from '../container';
import { GitLog, GitUri, RemoteResource } from '../gitService';
import { KeyNoopCommand } from '../keyboard';
import { OpenRemotesCommandQuickPickItem } from './remotesQuickPick';
import * as path from 'path';
export class FileHistoryQuickPick {
static showProgress(placeHolder: string) {
return showQuickPickProgress(placeHolder,
{
left: KeyNoopCommand,
',': KeyNoopCommand,
'.': KeyNoopCommand
});
}
static async show(log: GitLog, uri: GitUri, placeHolder: string, options: { currentCommand?: CommandQuickPickItem, goBackCommand?: CommandQuickPickItem, nextPageCommand?: CommandQuickPickItem, previousPageCommand?: CommandQuickPickItem, pickerOnly?: boolean, progressCancellation?: CancellationTokenSource, showAllCommand?: CommandQuickPickItem, showInResultsExplorerCommand?: CommandQuickPickItem } = {}): Promise<CommitQuickPickItem | CommandQuickPickItem | undefined> {
options = { pickerOnly: false, ...options };
const items = Array.from(Iterables.map(log.commits.values(), c => new CommitQuickPickItem(c))) as (CommitQuickPickItem | CommandQuickPickItem)[];
let index = 0;
if (options.pickerOnly) {
index++;
items.splice(0, 0, new ShowBranchesAndTagsQuickPickItem(log.repoPath, placeHolder, options.currentCommand));
}
if (options.showInResultsExplorerCommand !== undefined) {
index++;
items.splice(0, 0, options.showInResultsExplorerCommand);
}
if (log.truncated || log.sha) {
if (options.showAllCommand !== undefined) {
index++;
items.splice(0, 0, options.showAllCommand);
}
else if (!options.pickerOnly) {
const [workingFileName] = await Container.git.findWorkingFileName(path.relative(log.repoPath, uri.fsPath), log.repoPath);
if (workingFileName) {
index++;
items.splice(0, 0, new CommandQuickPickItem({
label: `$(history) Show File History`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} of ${path.basename(workingFileName)}`
}, Commands.ShowQuickFileHistory, [
Uri.file(path.resolve(log.repoPath, workingFileName)),
{
goBackCommand: new CommandQuickPickItem({
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to history of ${GlyphChars.Space}$(file-text) ${path.basename(uri.fsPath)}${uri.sha ? ` from ${GlyphChars.Space}$(git-commit) ${uri.shortSha}` : ''}`
}, Commands.ShowQuickFileHistory, [
uri,
{
log: log,
maxCount: log.maxCount,
range: log.range,
goBackCommand: options.goBackCommand
} as ShowQuickFileHistoryCommandArgs
])
} as ShowQuickFileHistoryCommandArgs
]));
}
}
if (options.nextPageCommand !== undefined) {
index++;
items.splice(0, 0, options.nextPageCommand);
}
if (options.previousPageCommand !== undefined) {
index++;
items.splice(0, 0, options.previousPageCommand);
}
}
if (!options.pickerOnly) {
const branch = await Container.git.getBranch(uri.repoPath!);
if (branch !== undefined) {
const currentCommand = new CommandQuickPickItem({
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to history of ${GlyphChars.Space}$(file-text) ${path.basename(uri.fsPath)}${uri.sha ? ` from ${GlyphChars.Space}$(git-commit) ${uri.shortSha}` : ''}`
}, Commands.ShowQuickFileHistory, [
uri,
{
log,
maxCount: log.maxCount,
range: log.range
} as ShowQuickFileHistoryCommandArgs
]);
// Only show the full repo option if we are the root
if (options.goBackCommand === undefined) {
items.splice(index++, 0, new CommandQuickPickItem({
label: `$(history) Show Branch History`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} shows ${GlyphChars.Space}$(git-branch) ${branch.name} history`
}, Commands.ShowQuickCurrentBranchHistory,
[
undefined,
{
goBackCommand: currentCommand
} as ShowQuickCurrentBranchHistoryCommandArgs
]));
}
const remotes = await Container.git.getRemotes(uri.repoPath!);
if (remotes.length) {
const resource = uri.sha !== undefined
? {
type: 'revision',
branch: branch.name,
fileName: uri.getRelativePath(),
sha: uri.sha
} as RemoteResource
: {
type: 'file',
branch: branch.name,
fileName: uri.getRelativePath()
} as RemoteResource;
items.splice(index++, 0, new OpenRemotesCommandQuickPickItem(remotes, resource, currentCommand));
}
}
if (options.goBackCommand) {
items.splice(0, 0, options.goBackCommand);
}
}
if (options.progressCancellation !== undefined && options.progressCancellation.token.isCancellationRequested) return undefined;
const scope = await Container.keyboard.beginScope({
left: options.goBackCommand,
',': options.previousPageCommand,
'.': options.nextPageCommand
});
options.progressCancellation && options.progressCancellation.cancel();
const pick = await window.showQuickPick(items, {
matchOnDescription: true,
matchOnDetail: true,
placeHolder: placeHolder,
ignoreFocusOut: getQuickPickIgnoreFocusOut()
// onDidSelectItem: (item: QuickPickItem) => {
// scope.setKeyCommand('right', item);
// }
} as QuickPickOptions);
await scope.dispose();
return pick;
}
}

Loading…
Cancel
Save