'use strict';
|
|
import * as paths from 'path';
|
|
import {
|
|
Disposable,
|
|
Event,
|
|
EventEmitter,
|
|
FileChangeEvent,
|
|
FileStat,
|
|
FileSystemError,
|
|
FileSystemProvider,
|
|
FileType,
|
|
Uri,
|
|
workspace
|
|
} from 'vscode';
|
|
import { DocumentSchemes } from '../constants';
|
|
import { Container } from '../container';
|
|
import { GitService, GitTree, GitUri } from '../git/gitService';
|
|
import { Iterables, Strings, TernarySearchTree } from '../system';
|
|
|
|
export function fromGitLensFSUri(uri: Uri): { path: string; ref: string; repoPath: string } {
|
|
const gitUri = uri instanceof GitUri ? uri : GitUri.fromRevisionUri(uri);
|
|
return { path: gitUri.getRelativePath(), ref: gitUri.sha!, repoPath: gitUri.repoPath! };
|
|
}
|
|
|
|
export function toGitLensFSUri(ref: string, repoPath: string): Uri {
|
|
return GitUri.toRevisionUri(ref, repoPath, repoPath);
|
|
}
|
|
|
|
const emptyArray = new Uint8Array(0);
|
|
|
|
export class GitFileSystemProvider implements FileSystemProvider, Disposable {
|
|
private readonly _disposable: Disposable;
|
|
private readonly _searchTreeMap = new Map<string, Promise<TernarySearchTree<GitTree>>>();
|
|
|
|
constructor() {
|
|
this._disposable = Disposable.from(
|
|
workspace.registerFileSystemProvider(DocumentSchemes.GitLens, this, {
|
|
isCaseSensitive: true,
|
|
isReadonly: true
|
|
})
|
|
);
|
|
}
|
|
|
|
dispose() {
|
|
this._disposable && this._disposable.dispose();
|
|
}
|
|
|
|
private _onDidChangeFile = new EventEmitter<FileChangeEvent[]>();
|
|
get onDidChangeFile(): Event<FileChangeEvent[]> {
|
|
return this._onDidChangeFile.event;
|
|
}
|
|
|
|
copy?(): void | Thenable<void> {
|
|
throw FileSystemError.NoPermissions;
|
|
}
|
|
createDirectory(): void | Thenable<void> {
|
|
throw FileSystemError.NoPermissions;
|
|
}
|
|
delete(): void | Thenable<void> {
|
|
throw FileSystemError.NoPermissions;
|
|
}
|
|
|
|
async readDirectory(uri: Uri): Promise<[string, FileType][]> {
|
|
const { path, ref, repoPath } = fromGitLensFSUri(uri);
|
|
|
|
const tree = await this.getTree(path, ref, repoPath);
|
|
if (tree === undefined) {
|
|
debugger;
|
|
throw FileSystemError.FileNotFound(uri);
|
|
}
|
|
|
|
const items = [
|
|
...Iterables.map<GitTree, [string, FileType]>(tree, t => [
|
|
path != null && path.length !== 0 ? Strings.normalizePath(paths.relative(path, t.path)) : t.path,
|
|
typeToFileType(t.type)
|
|
])
|
|
];
|
|
return items;
|
|
}
|
|
|
|
async readFile(uri: Uri): Promise<Uint8Array> {
|
|
const { path, ref, repoPath } = fromGitLensFSUri(uri);
|
|
|
|
if (ref === GitService.deletedOrMissingSha) return emptyArray;
|
|
|
|
const buffer = await Container.git.getVersionedFileBuffer(repoPath, path, ref);
|
|
if (buffer === undefined) return emptyArray;
|
|
|
|
return buffer;
|
|
}
|
|
|
|
rename(): void | Thenable<void> {
|
|
throw FileSystemError.NoPermissions;
|
|
}
|
|
|
|
async stat(uri: Uri): Promise<FileStat> {
|
|
const { path, ref, repoPath } = fromGitLensFSUri(uri);
|
|
|
|
if (ref === GitService.deletedOrMissingSha) {
|
|
return {
|
|
type: FileType.File,
|
|
size: 0,
|
|
ctime: 0,
|
|
mtime: 0
|
|
};
|
|
}
|
|
|
|
let treeItem;
|
|
|
|
const searchTree = this._searchTreeMap.get(ref);
|
|
if (searchTree !== undefined) {
|
|
// Add the fake root folder to the path
|
|
treeItem = (await searchTree).get(`/~/${path}`);
|
|
}
|
|
else {
|
|
treeItem = await Container.git.getTreeFileForRevision(repoPath, path, ref);
|
|
}
|
|
|
|
if (treeItem === undefined) {
|
|
throw FileSystemError.FileNotFound(uri);
|
|
}
|
|
|
|
return {
|
|
type: typeToFileType(treeItem.type),
|
|
size: treeItem.size,
|
|
ctime: 0,
|
|
mtime: 0
|
|
};
|
|
}
|
|
|
|
watch(): Disposable {
|
|
return { dispose: () => {} };
|
|
}
|
|
|
|
writeFile(): void | Thenable<void> {
|
|
throw FileSystemError.NoPermissions;
|
|
}
|
|
|
|
private async createSearchTree(ref: string, repoPath: string) {
|
|
const searchTree = TernarySearchTree.forPaths() as TernarySearchTree<GitTree>;
|
|
const trees = await Container.git.getTreeForRevision(repoPath, ref);
|
|
|
|
// Add a fake root folder so that searches will work
|
|
searchTree.set(`~`, { commitSha: '', path: '~', size: 0, type: 'tree' });
|
|
for (const item of trees) {
|
|
searchTree.set(`~/${item.path}`, item);
|
|
}
|
|
|
|
return searchTree;
|
|
}
|
|
|
|
private async getOrCreateSearchTree(ref: string, repoPath: string) {
|
|
let searchTree = this._searchTreeMap.get(ref);
|
|
if (searchTree === undefined) {
|
|
searchTree = this.createSearchTree(ref, repoPath);
|
|
this._searchTreeMap.set(ref, searchTree);
|
|
}
|
|
|
|
return searchTree;
|
|
}
|
|
|
|
private async getTree(path: string, ref: string, repoPath: string) {
|
|
const searchTree = await this.getOrCreateSearchTree(ref, repoPath);
|
|
// Add the fake root folder to the path
|
|
return searchTree!.findSuperstr(`/~/${path}`, true);
|
|
}
|
|
}
|
|
|
|
function typeToFileType(type: 'blob' | 'tree' | undefined | null) {
|
|
switch (type) {
|
|
case 'blob':
|
|
return FileType.File;
|
|
case 'tree':
|
|
return FileType.Directory;
|
|
default:
|
|
return FileType.Unknown;
|
|
}
|
|
}
|