mirror of
https://github.com/silvanocerza/github-gitless-sync.git
synced 2026-07-22 12:10:28 +00:00
Slightly restructure GithubClient
This commit is contained in:
parent
6bff97ace2
commit
a1b083fe84
1 changed files with 112 additions and 76 deletions
|
|
@ -1,6 +1,30 @@
|
|||
import { Vault, requestUrl, normalizePath } from "obsidian";
|
||||
import MetadataStore from "../metadata-store";
|
||||
|
||||
/**
|
||||
* Represents a single item in a tree response from the GitHub API.
|
||||
*/
|
||||
type TreeItem = {
|
||||
path: string;
|
||||
mode: string;
|
||||
type: string;
|
||||
sha: string;
|
||||
size: number;
|
||||
url: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a git blob response from the GitHub API.
|
||||
*/
|
||||
type BlobFile = {
|
||||
sha: string;
|
||||
node_id: string;
|
||||
size: number;
|
||||
url: string;
|
||||
content: string;
|
||||
encoding: string;
|
||||
};
|
||||
|
||||
export default class GithubClient {
|
||||
constructor(
|
||||
private vault: Vault,
|
||||
|
|
@ -16,6 +40,86 @@ export default class GithubClient {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the content of a directory in the repo.
|
||||
* If repoContentDir is an empty string all files in the repo will be returned.
|
||||
*
|
||||
* @param owner Owner of the repo
|
||||
* @param repo Name of the repo
|
||||
* @param repoContentDir Directory in the repo to download relative to the root of the repo
|
||||
* @param branch Branch to download from
|
||||
* @returns Array of files in the directory in the remote repo
|
||||
*/
|
||||
async getRepoContent(
|
||||
owner: string,
|
||||
repo: string,
|
||||
repoContentDir: string,
|
||||
branch: string,
|
||||
): Promise<TreeItem[]> {
|
||||
const res = await requestUrl({
|
||||
url: `https://api.github.com/repos/${owner}/${repo}/git/trees/${branch}?recursive=1`,
|
||||
headers: this.headers(),
|
||||
});
|
||||
const files = res.json["tree"].filter(
|
||||
(file: TreeItem) =>
|
||||
file.type === "blob" && file.path.startsWith(repoContentDir),
|
||||
);
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a blob from a blob url
|
||||
* @param url blob url
|
||||
*/
|
||||
async getBlob(url: string): Promise<BlobFile> {
|
||||
const res = await requestUrl({
|
||||
url: url,
|
||||
headers: this.headers(),
|
||||
});
|
||||
return res.json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a single file from GitHub. If the file already exists locally it will be overwritten.
|
||||
*
|
||||
* @param file file info from the GitHub API
|
||||
* @param repoContentDir directory in the repo to download relative to the root of the repo
|
||||
* @param localContentDir local directory to download to
|
||||
*/
|
||||
async downloadFile(
|
||||
file: TreeItem,
|
||||
repoContentDir: string,
|
||||
localContentDir: string,
|
||||
) {
|
||||
const url = file.url;
|
||||
const destinationFile = file.path.replace(repoContentDir, localContentDir);
|
||||
const fileMetadata = this.metadataStore.data[destinationFile];
|
||||
if (fileMetadata && fileMetadata.sha === file.sha) {
|
||||
// File already exists and has the same SHA, no need to download it again.
|
||||
return;
|
||||
}
|
||||
|
||||
const blob = await this.getBlob(url);
|
||||
const destinationFolder = normalizePath(
|
||||
destinationFile.split("/").slice(0, -1).join("/"),
|
||||
);
|
||||
if (!(await this.vault.adapter.exists(destinationFolder))) {
|
||||
await this.vault.adapter.mkdir(destinationFolder);
|
||||
}
|
||||
this.vault.adapter.writeBinary(
|
||||
normalizePath(destinationFile),
|
||||
Buffer.from(blob.content, "base64"),
|
||||
);
|
||||
this.metadataStore.data[destinationFile] = {
|
||||
localPath: destinationFile,
|
||||
remotePath: file.path,
|
||||
sha: file.sha,
|
||||
dirty: false,
|
||||
justDownloaded: true,
|
||||
};
|
||||
await this.metadataStore.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively downloads the repo content to the local vault.
|
||||
* The repository directory structure is kept as is.
|
||||
|
|
@ -33,86 +137,18 @@ export default class GithubClient {
|
|||
branch: string,
|
||||
localContentDir: string,
|
||||
) {
|
||||
const res = await requestUrl({
|
||||
url: `https://api.github.com/repos/${owner}/${repo}/contents/${repoContentDir}?ref=${branch}`,
|
||||
headers: this.headers(),
|
||||
});
|
||||
|
||||
const directories = res.json.filter((file: any) => file.type === "dir");
|
||||
// As specified in the Github docs also submodules are specified as "file".
|
||||
// For the time being we're going to let this slide cause I don't really
|
||||
// want to handle this right now. I'll figure out a way to do this later. Maybe.
|
||||
// More info in the official docs:
|
||||
// https://docs.github.com/en/rest/repos/contents?apiVersion=2022-11-28#get-repository-content
|
||||
const files = res.json.filter((file: any) => file.type === "file");
|
||||
const files = await this.getRepoContent(
|
||||
owner,
|
||||
repo,
|
||||
repoContentDir,
|
||||
branch,
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
directories.map((dir: any) =>
|
||||
this.downloadRepoContent(
|
||||
owner,
|
||||
repo,
|
||||
dir.path,
|
||||
branch,
|
||||
localContentDir ? `${localContentDir}/${dir.name}` : dir.name,
|
||||
),
|
||||
files.map(async (file: TreeItem) =>
|
||||
this.downloadFile(file, repoContentDir, localContentDir),
|
||||
),
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
files.map(async (file: any) => {
|
||||
const url = file.download_url;
|
||||
const destinationFile = file.path.replace(
|
||||
repoContentDir,
|
||||
localContentDir,
|
||||
);
|
||||
const fileMetadata = this.metadataStore.data[destinationFile];
|
||||
if (fileMetadata && fileMetadata.sha === file.sha) {
|
||||
// File already exists and has the same SHA, no need to download it again.
|
||||
return;
|
||||
}
|
||||
|
||||
await this.downloadRawFile(url, destinationFile);
|
||||
this.metadataStore.data[destinationFile] = {
|
||||
localPath: destinationFile,
|
||||
remotePath: file.path,
|
||||
sha: file.sha,
|
||||
dirty: false,
|
||||
justDownloaded: true,
|
||||
};
|
||||
await this.metadataStore.save();
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a single file from GitHub. This doesn't use the API but the raw
|
||||
* file content endpoint that we receive from the API.
|
||||
* This makes some things slightly easier to handle.
|
||||
*
|
||||
* @param url URL to raw file content
|
||||
* @param destinationFile Local path where to save the file, relative to the vault
|
||||
*/
|
||||
async downloadRawFile(url: string, destinationFile: string) {
|
||||
// We're not setting auth headers here as we're not calling the Github API
|
||||
// directly but downloading the raw file, cause there are some size limitation
|
||||
// with the official API.
|
||||
// Using the headers above is not feasible as it triggers CORS preflight
|
||||
// and GH doesn't allow that for raw content endpoints. Thus breaking everything.
|
||||
// So this doesn't work with a private repo, will figure it out later.
|
||||
const res = await requestUrl({
|
||||
url: url,
|
||||
});
|
||||
const destinationFolder = normalizePath(
|
||||
destinationFile.split("/").slice(0, -1).join("/"),
|
||||
);
|
||||
if (!(await this.vault.adapter.exists(destinationFolder))) {
|
||||
await this.vault.adapter.mkdir(destinationFolder);
|
||||
}
|
||||
|
||||
this.vault.adapter.writeBinary(
|
||||
normalizePath(destinationFile),
|
||||
res.arrayBuffer,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue