refactor uploadBlobs
This commit is contained in:
parent
f85834bcdf
commit
0922f8d982
2 changed files with 50 additions and 91 deletions
|
@ -1,4 +1,4 @@
|
||||||
export { default as downloadBlobFromPointer } from "./download";
|
export { default as downloadBlobFromPointer } from "./download";
|
||||||
export { default as populateCache } from "./populateCache";
|
export { default as populateCache } from "./populateCache";
|
||||||
export { readPointer, formatPointerInfo } from "./pointers";
|
export { readPointer, formatPointerInfo } from "./pointers";
|
||||||
export { default as uploadBlob } from "./upload";
|
export { default as uploadBlobs } from "./upload";
|
||||||
|
|
139
src/upload.ts
139
src/upload.ts
|
@ -2,7 +2,7 @@ import { Buffer } from "buffer";
|
||||||
|
|
||||||
import { HTTPRequest } from "./types";
|
import { HTTPRequest } from "./types";
|
||||||
import { buildPointerInfo, PointerInfo } from "./pointers";
|
import { buildPointerInfo, PointerInfo } from "./pointers";
|
||||||
import { bodyToBuffer, getAuthHeader } from "./util";
|
import { getAuthHeader } from "./util";
|
||||||
|
|
||||||
interface LFSInfoResponse {
|
interface LFSInfoResponse {
|
||||||
objects: {
|
objects: {
|
||||||
|
@ -31,113 +31,72 @@ function isValidLFSInfoResponseData(
|
||||||
* which the caller can then combine with object path into a Pointer
|
* which the caller can then combine with object path into a Pointer
|
||||||
* and commit in place of the original Git blob.
|
* and commit in place of the original Git blob.
|
||||||
*/
|
*/
|
||||||
export default async function uploadBlob(
|
export default async function uploadBlobs(
|
||||||
{ http: { request }, headers = {}, url, auth }: HTTPRequest,
|
{ headers = {}, url, auth }: HTTPRequest,
|
||||||
content: Buffer
|
contents: Buffer[]
|
||||||
): Promise<PointerInfo> {
|
): Promise<PointerInfo[]> {
|
||||||
const info = await buildPointerInfo(content);
|
const infos = await Promise.all(contents.map((c) => buildPointerInfo(c)));
|
||||||
|
|
||||||
const authHeaders: Record<string, string> = auth ? getAuthHeader(auth) : {};
|
const authHeaders: Record<string, string> = auth ? getAuthHeader(auth) : {};
|
||||||
|
|
||||||
// Request LFS transfer
|
// Request LFS transfer
|
||||||
|
|
||||||
const lfsInfoRequestData = {
|
const lfsInfoRequestData = {
|
||||||
operation: "upload",
|
operation: "upload",
|
||||||
transfers: ["basic"],
|
transfers: ["basic"],
|
||||||
objects: [info],
|
objects: infos,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { body: lfsInfoBody } = await request({
|
const lfsInfoRes = await fetch(`${url}/info/lfs/objects/batch`, {
|
||||||
url: `${url}/info/lfs/objects/batch`,
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
// Github LFS doesn’t seem to accept this UA
|
|
||||||
// 'User-Agent': `git/isomorphic-git@${git.version()}`,
|
|
||||||
...headers,
|
...headers,
|
||||||
...authHeaders,
|
...authHeaders,
|
||||||
Accept: "application/vnd.git-lfs+json",
|
Accept: "application/vnd.git-lfs+json",
|
||||||
"Content-Type": "application/vnd.git-lfs+json",
|
|
||||||
},
|
},
|
||||||
body: [Buffer.from(JSON.stringify(lfsInfoRequestData))],
|
body: JSON.stringify(lfsInfoRequestData),
|
||||||
});
|
});
|
||||||
|
const lfsInfoResponseData = await lfsInfoRes.json();
|
||||||
const lfsInfoResponseRaw = (await bodyToBuffer(lfsInfoBody)).toString();
|
if (!isValidLFSInfoResponseData(lfsInfoResponseData)) {
|
||||||
let lfsInfoResponseData: any;
|
|
||||||
try {
|
|
||||||
lfsInfoResponseData = JSON.parse(lfsInfoResponseRaw);
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error(
|
|
||||||
`Unexpected structure received from LFS server: unable to parse JSON ${lfsInfoResponseRaw}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isValidLFSInfoResponseData(lfsInfoResponseData)) {
|
|
||||||
// Upload the actual blob
|
|
||||||
|
|
||||||
const actions = lfsInfoResponseData.objects[0].actions;
|
|
||||||
|
|
||||||
if (!actions) {
|
|
||||||
// Presume LFS already has the blob. Don’t fail loudly.
|
|
||||||
return info;
|
|
||||||
} else {
|
|
||||||
const uploadAction = actions.upload;
|
|
||||||
const lfsObjectUploadURL = uploadAction.href;
|
|
||||||
const lfsObjectUploadHeaders = uploadAction.header ?? {};
|
|
||||||
|
|
||||||
const dlHeaders = {
|
|
||||||
...headers,
|
|
||||||
...authHeaders,
|
|
||||||
...lfsObjectUploadHeaders,
|
|
||||||
};
|
|
||||||
|
|
||||||
const resp = await request({
|
|
||||||
url: lfsObjectUploadURL,
|
|
||||||
method: "PUT",
|
|
||||||
headers: dlHeaders,
|
|
||||||
body: [content],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (resp.statusCode === 200) {
|
|
||||||
const verifyAction = actions.verify;
|
|
||||||
|
|
||||||
// Upload verification was requested. Do that
|
|
||||||
|
|
||||||
if (verifyAction) {
|
|
||||||
const verificationResp = await request({
|
|
||||||
url: verifyAction.href,
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
// Isomorphic Git’s UA header is considered invalid
|
|
||||||
// and missing UA header causes an error in this case;
|
|
||||||
// cURL is considered valid, so…
|
|
||||||
"User-Agent": `curl/7.54`,
|
|
||||||
// TODO: Generalize UA header handling
|
|
||||||
// - Leave UA header twiddling to callers?
|
|
||||||
// - Figure out which LFS implementation wants which UA header?
|
|
||||||
...(verifyAction.header ?? {}),
|
|
||||||
},
|
|
||||||
body: [Buffer.from(JSON.stringify(info))],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (verificationResp.statusCode === 200) {
|
|
||||||
return info;
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
|
||||||
`Upload might have been unsuccessful, verification action yielded HTTP ${verificationResp.statusCode}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
|
||||||
`Upload might have been unsuccessful, upload action yielded HTTP ${resp.statusCode}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Unexpected JSON structure received for LFS upload request"
|
"Unexpected JSON structure received for LFS upload request"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
lfsInfoResponseData.objects.map(async (object, index) => {
|
||||||
|
// server already has file
|
||||||
|
if (!object.actions) return;
|
||||||
|
const { actions } = object;
|
||||||
|
|
||||||
|
const resp = await fetch(actions.upload.href, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
...headers,
|
||||||
|
...authHeaders,
|
||||||
|
...(actions.upload.header ?? {}),
|
||||||
|
Accept: "application/vnd.git-lfs+json",
|
||||||
|
},
|
||||||
|
body: contents[index],
|
||||||
|
});
|
||||||
|
if (!resp.ok)
|
||||||
|
throw new Error(
|
||||||
|
`Upload might have been unsuccessful, upload action yielded HTTP ${resp.status}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (actions.verify) {
|
||||||
|
const verificationResp = await fetch(actions.verify.href, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
...(actions.verify.header ?? {}),
|
||||||
|
Accept: "application/vnd.git-lfs+json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(infos[index]),
|
||||||
|
});
|
||||||
|
throw new Error(
|
||||||
|
`Upload might have been unsuccessful, verification action yielded HTTP ${verificationResp.status}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return infos;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue