Skip to content

Commit

Permalink
Fixes #708 - Prompt for file path when missing from revision
Browse files Browse the repository at this point in the history
Improves the `openFileAtRevision()` function by:
- Asking user to enter another path when the requested file doesn't exist in the
  selected revision.
- Showing an error message on subsequent failure (or if an error is thrown).

The above function is used by a number of commands:
- `gitlens.openFileRevision`
- `gitlens.openFileRevisionFrom`
- `gitlens.openRevisionFile`
  • Loading branch information
eamodio committed Dec 6, 2023
1 parent 7981bd5 commit 1d371b3
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 7 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p

## [Unreleased]

### Added

- Adds support for opening renamed/deleted files using the _Open File at Revision..._ & _Open File at Revision from..._ commands by showing a quick pick menu if the requested file doesn't exist in the selected revision — closes [#708](https://github.com/gitkraken/vscode-gitlens/issues/708) thanks to [PR #2825](https://github.com/gitkraken/vscode-gitlens/pull/2825) by Victor Hallberg ([@mogelbrod](https://github.com/mogelbrod))

### Changed

- Changes blame to show the last modified time of the file for uncommitted changes
Expand Down
34 changes: 27 additions & 7 deletions src/git/actions/commit.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { TextDocumentShowOptions } from 'vscode';
import type { TextDocumentShowOptions, TextEditor } from 'vscode';
import { env, Range, Uri, window, workspace } from 'vscode';
import type { DiffWithCommandArgs } from '../../commands/diffWith';
import type { DiffWithPreviousCommandArgs } from '../../commands/diffWithPrevious';
Expand All @@ -12,6 +12,7 @@ import type { FileAnnotationType } from '../../config';
import { Commands, GlyphChars } from '../../constants';
import { Container } from '../../container';
import type { ShowInCommitGraphCommandArgs } from '../../plus/webviews/graph/protocol';
import { showRevisionPicker } from '../../quickpicks/revisionPicker';
import { executeCommand, executeEditorCommand } from '../../system/command';
import { configuration } from '../../system/configuration';
import { findOrOpenEditor, findOrOpenEditors, openChangesEditor } from '../../system/utils';
Expand Down Expand Up @@ -463,7 +464,7 @@ export async function openFileAtRevision(
commitOrOptions?: GitCommit | TextDocumentShowOptions,
options?: TextDocumentShowOptions & { annotationType?: FileAnnotationType; line?: number },
): Promise<void> {
let uri;
let uri: Uri;
if (fileOrRevisionUri instanceof Uri) {
if (isCommit(commitOrOptions)) throw new Error('Invalid arguments');

Expand Down Expand Up @@ -501,11 +502,30 @@ export async function openFileAtRevision(
opts.selection = new Range(line, 0, line, 0);
}

const editor = await findOrOpenEditor(uri, opts);
if (annotationType != null && editor != null) {
void (await Container.instance.fileAnnotations.show(editor, annotationType, {
selection: { line: line },
}));
const gitUri = await GitUri.fromUri(uri);

let editor: TextEditor | undefined;
try {
editor = await findOrOpenEditor(uri, { throwOnError: true, ...opts }).catch(error => {
if (error?.message?.includes('Unable to resolve nonexistent file')) {
return showRevisionPicker(gitUri, {
title: 'File not found in revision - pick another file to open instead',
}).then(pickedUri => {
return pickedUri ? findOrOpenEditor(pickedUri, opts) : undefined;
});
}
throw error;
});

if (annotationType != null && editor != null) {
void (await Container.instance.fileAnnotations.show(editor, annotationType, {
selection: { line: line },
}));
}
} catch (error) {
await window.showErrorMessage(
`Unable to open '${gitUri.relativePath}' - file doesn't exist in selected revision`,
);
}
}

Expand Down
58 changes: 58 additions & 0 deletions src/quickpicks/revisionPicker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// import path from "path";
import type { Disposable, Uri } from "vscode";
import { window } from "vscode";
import { Container } from "../container";
import type { GitUri } from "../git/gitUri";
import { filterMap } from "../system/iterable";
import { getQuickPickIgnoreFocusOut } from "../system/utils";

export async function showRevisionPicker(
uri: GitUri,
options: {
title: string;
initialPath?: string;
},
): Promise<Uri | undefined> {
const disposables: Disposable[] = [];
try {
const picker = window.createQuickPick();
picker.title = options.title;
picker.value = options.initialPath ?? uri.relativePath;
picker.placeholder = 'Enter path to file...';
picker.matchOnDescription = true;
picker.busy = true;
picker.ignoreFocusOut = getQuickPickIgnoreFocusOut();

picker.show();

const tree = await Container.instance.git.getTreeForRevision(uri.repoPath, uri.sha!);
picker.items = Array.from(filterMap(tree, file => {
// Exclude directories
if (file.type !== 'blob') { return null }
return { label: file.path }
// FIXME: Remove this unless we opt to show the directory in the description
// const parsed = path.parse(file.path)
// return { label: parsed.base, description: parsed.dir }
}))
picker.busy = false;

const pick = await new Promise<string | undefined>(resolve => {
disposables.push(
picker,
picker.onDidHide(() => resolve(undefined)),
picker.onDidAccept(() => {
if (picker.activeItems.length === 0) return;
resolve(picker.activeItems[0].label);
}),
);
});

return pick
? Container.instance.git.getRevisionUri(uri.sha!, `${uri.repoPath}/${pick}`, uri.repoPath!)
: undefined;
} finally {
disposables.forEach(d => {
d.dispose();
});
}
}

0 comments on commit 1d371b3

Please sign in to comment.