Adds file create, rename, & delete support

This commit is contained in:
Eric Amodio
2020-06-29 03:29:04 -04:00
parent 28ea0f0aaa
commit 017129571a
8 changed files with 462 additions and 2881 deletions
+78 -17
View File
@@ -17,7 +17,30 @@ export interface GitHubApiContext {
timestamp: number;
}
function getRootUri(uri: Uri) {
interface CreateCommitOperation {
type: 'created';
path: string;
content: string
}
interface ChangeCommitOperation {
type: 'changed';
path: string;
content: string
}
interface DeleteCommitOperation {
type: 'deleted';
path: string;
content: undefined
}
export type CommitOperation = CreateCommitOperation | ChangeCommitOperation | DeleteCommitOperation;
type ArrayElement<T extends Array<unknown>> = T extends (infer U)[] ? U : never;
type GitCreateTreeParamsTree = ArrayElement<NonNullable<Parameters<Octokit['git']['createTree']>[0]>['tree']>;
function getGitHubRootUri(uri: Uri) {
const rootIndex = uri.path.indexOf('/', uri.path.indexOf('/', 1) + 1);
return uri.with({
path: uri.path.substring(0, rootIndex === -1 ? undefined : rootIndex),
@@ -86,7 +109,7 @@ export class GitHubApi implements Disposable {
return new this._octokit(options);
}
async commit(rootUri: Uri, message: string, files: { path: string; content: string }[]): Promise<string | undefined> {
async commit(rootUri: Uri, message: string, operations: CommitOperation[]): Promise<string | undefined> {
let { owner, repo, ref } = fromGitHubUri(rootUri);
try {
@@ -102,25 +125,63 @@ export class GitHubApi implements Disposable {
throw new Error('Cannot commit — invalid context');
}
const hasDeletes = operations.some(op => op.type === 'deleted');
const github = await this.octokit();
const treeResp = await github.git.getTree({
owner: owner,
repo: repo,
tree_sha: context.sha
tree_sha: context.sha,
recursive: hasDeletes ? 'true' : undefined,
});
const updatedTreeItems: {
path: string;
mode?: '100644' | '100755' | '040000' | '160000' | '120000',
type?: 'blob' | 'tree' | 'commit',
sha?: string | undefined;
content: string;
}[] = [];
// 0100000000000000 (040000): Directory
// 1000000110100100 (100644): Regular non-executable file
// 1000000110110100 (100664): Regular non-executable group-writeable file
// 1000000111101101 (100755): Regular executable file
// 1010000000000000 (120000): Symbolic link
// 1110000000000000 (160000): Gitlink
let updatedTree: GitCreateTreeParamsTree[];
for (const file of files) {
for (const { path, mode, type } of treeResp.data.tree) {
if (path === file.path) {
updatedTreeItems.push({ path: path, mode: mode as any, type: type as any, content: file.content });
if (hasDeletes) {
updatedTree = treeResp.data.tree as GitCreateTreeParamsTree[];
for (const operation of operations) {
switch (operation.type) {
case 'created':
updatedTree.push({ path: operation.path, mode: '100644', type: 'blob', content: operation.content });
break;
case 'changed':
const item = updatedTree.find(item => item.path === operation.path);
if (item !== undefined) {
updatedTree.push({ ...item, content: operation.content });
}
break;
case 'deleted':
const index = updatedTree.findIndex(item => item.path === operation.path);
if (index !== -1) {
updatedTree.splice(index, 1);
}
break;
}
}
} else {
updatedTree = [];
for (const operation of operations) {
switch (operation.type) {
case 'created':
updatedTree.push({ path: operation.path, mode: '100644', type: 'blob', content: operation.content });
break;
case 'changed':
const item = treeResp.data.tree.find(item => item.path === operation.path) as GitCreateTreeParamsTree;
if (item !== undefined) {
updatedTree.push({ ...item, content: operation.content });
}
break;
}
}
}
@@ -128,8 +189,8 @@ export class GitHubApi implements Disposable {
const updatedTreeResp = await github.git.createTree({
owner: owner,
repo: repo,
base_tree: treeResp.data.sha,
tree: updatedTreeItems
base_tree: hasDeletes ? undefined : treeResp.data.sha,
tree: updatedTree
});
const resp = await github.git.createCommit({
@@ -354,7 +415,7 @@ export class GitHubApi implements Disposable {
private readonly pendingContextRequests = new Map<string, Promise<GitHubApiContext>>();
async getContext(uri: Uri): Promise<GitHubApiContext> {
const rootUri = getRootUri(uri);
const rootUri = getGitHubRootUri(uri);
let pending = this.pendingContextRequests.get(rootUri.toString());
if (pending === undefined) {
+14 -9
View File
@@ -89,9 +89,8 @@ export class GitHubFS implements FileSystemProvider, FileSearchProvider, TextSea
}
async stat(uri: Uri): Promise<FileStat> {
const context = await this.github.getContext(uri);
if (uri.path === '' || uri.path.lastIndexOf('/') === 0) {
const context = await this.github.getContext(uri);
return { type: FileType.Directory, size: 0, ctime: 0, mtime: context?.timestamp };
}
@@ -107,9 +106,15 @@ export class GitHubFS implements FileSystemProvider, FileSearchProvider, TextSea
this.getCache(uri),
);
if (data === undefined) {
throw FileSystemError.FileNotFound();
}
const context = await this.github.getContext(uri);
return {
type: typenameToFileType(data?.__typename),
size: data?.byteSize ?? 0,
type: typenameToFileType(data.__typename),
size: data.byteSize ?? 0,
ctime: 0,
mtime: context?.timestamp,
};
@@ -136,7 +141,7 @@ export class GitHubFS implements FileSystemProvider, FileSearchProvider, TextSea
}
createDirectory(_uri: Uri): void | Thenable<void> {
throw FileSystemError.NoPermissions;
throw FileSystemError.NoPermissions();
}
async readFile(uri: Uri): Promise<Uint8Array> {
@@ -169,19 +174,19 @@ export class GitHubFS implements FileSystemProvider, FileSearchProvider, TextSea
}
async writeFile(_uri: Uri, _content: Uint8Array, _options: { create: boolean, overwrite: boolean }): Promise<void> {
throw FileSystemError.NoPermissions;
throw FileSystemError.NoPermissions();
}
delete(_uri: Uri, _options: { recursive: boolean }): void | Thenable<void> {
throw FileSystemError.NoPermissions;
throw FileSystemError.NoPermissions();
}
rename(_oldUri: Uri, _newUri: Uri, _options: { overwrite: boolean }): void | Thenable<void> {
throw FileSystemError.NoPermissions;
throw FileSystemError.NoPermissions();
}
copy(_source: Uri, _destination: Uri, _options: { overwrite: boolean }): void | Thenable<void> {
throw FileSystemError.NoPermissions;
throw FileSystemError.NoPermissions();
}
//#endregion