fix: batch Git LFS uploads
This commit is contained in:
@@ -10,27 +10,26 @@ import type { FileDiff } from '@/lib/types'
|
|||||||
import { checkFolderDiffs, stageUpload, uploadDrive, uploadGit } from '@/lib/upload-api'
|
import { checkFolderDiffs, stageUpload, uploadDrive, uploadGit } from '@/lib/upload-api'
|
||||||
import type { CheckResult } from '@/lib/upload-api'
|
import type { CheckResult } from '@/lib/upload-api'
|
||||||
|
|
||||||
const UPLOAD_LOG_PREFIX = '[upload-gltf]'
|
|
||||||
|
|
||||||
function formatElapsed(startedAt: number) {
|
function formatElapsed(startedAt: number) {
|
||||||
return `${((performance.now() - startedAt) / 1000).toFixed(1)}s`
|
return `${((performance.now() - startedAt) / 1000).toFixed(1)}s`
|
||||||
}
|
}
|
||||||
|
|
||||||
function startTimedLog(label: string, details?: Record<string, unknown>) {
|
function logUpload(level: 'INFO' | 'ERROR', step: string, action: string, startedAt: number, details?: Record<string, unknown>) {
|
||||||
|
const log = level === 'ERROR' ? console.error : console.info
|
||||||
|
log(`[${level}] ${step} -> ${action} | Timer: ${formatElapsed(startedAt)}`, details || '')
|
||||||
|
}
|
||||||
|
|
||||||
|
function startTimedLog(step: string, action: string, details?: Record<string, unknown>) {
|
||||||
const startedAt = performance.now()
|
const startedAt = performance.now()
|
||||||
console.info(`${UPLOAD_LOG_PREFIX} ${label} started`, details || '')
|
logUpload('INFO', step, `${action} started`, startedAt, details)
|
||||||
|
|
||||||
const interval = window.setInterval(() => {
|
const interval = window.setInterval(() => {
|
||||||
console.info(`${UPLOAD_LOG_PREFIX} ${label} still running`, { elapsed: formatElapsed(startedAt), ...details })
|
logUpload('INFO', step, `${action} running`, startedAt, details)
|
||||||
}, 10_000)
|
}, 10_000)
|
||||||
|
|
||||||
return (status: 'done' | 'failed' | 'cancelled' = 'done', extra?: Record<string, unknown>) => {
|
return (status: 'done' | 'failed' | 'cancelled' = 'done', extra?: Record<string, unknown>) => {
|
||||||
window.clearInterval(interval)
|
window.clearInterval(interval)
|
||||||
console.info(`${UPLOAD_LOG_PREFIX} ${label} ${status}`, {
|
logUpload(status === 'failed' ? 'ERROR' : 'INFO', step, `${action} ${status}`, startedAt, { ...details, ...extra })
|
||||||
elapsed: formatElapsed(startedAt),
|
|
||||||
...details,
|
|
||||||
...extra,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +82,7 @@ export function useUploadOrchestrator({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const folderName = entriesRef.current[index]?.folderName
|
const folderName = entriesRef.current[index]?.folderName
|
||||||
const endGitLog = startTimedLog('Git upload', { folderName, stagingId })
|
const endGitLog = startTimedLog('Git', 'Upload', { folderName, stagingId })
|
||||||
let gitResult: Awaited<ReturnType<typeof uploadGit>>
|
let gitResult: Awaited<ReturnType<typeof uploadGit>>
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -145,7 +144,7 @@ export function useUploadOrchestrator({
|
|||||||
driveError: undefined,
|
driveError: undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
const endDriveLog = startTimedLog('Drive upload', {
|
const endDriveLog = startTimedLog('Drive', 'Upload', {
|
||||||
folderName: folderEntry.folderName,
|
folderName: folderEntry.folderName,
|
||||||
stagingId,
|
stagingId,
|
||||||
action: driveAction,
|
action: driveAction,
|
||||||
@@ -206,7 +205,7 @@ export function useUploadOrchestrator({
|
|||||||
abortRef.current = controller
|
abortRef.current = controller
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const endStageLog = startTimedLog('Verification: staging upload', {
|
const endStageLog = startTimedLog('Verification', 'Staging', {
|
||||||
folderName: folder.folderName,
|
folderName: folder.folderName,
|
||||||
files: 1 + folder.textures.length,
|
files: 1 + folder.textures.length,
|
||||||
modelSize: folder.modelFile.size,
|
modelSize: folder.modelFile.size,
|
||||||
@@ -225,7 +224,7 @@ export function useUploadOrchestrator({
|
|||||||
|
|
||||||
stagingIdRef.current = staged.stagingId
|
stagingIdRef.current = staged.stagingId
|
||||||
|
|
||||||
const endCheckLog = startTimedLog('Verification: GitHub diff check', {
|
const endCheckLog = startTimedLog('Verification', 'GitHub diff', {
|
||||||
folderName: folder.folderName,
|
folderName: folder.folderName,
|
||||||
stagingId: staged.stagingId,
|
stagingId: staged.stagingId,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { Octokit } from '@octokit/rest'
|
|||||||
import { LFS_EXTENSIONS } from './constants'
|
import { LFS_EXTENSIONS } from './constants'
|
||||||
import type { PushFile, RemoteFile } from './types'
|
import type { PushFile, RemoteFile } from './types'
|
||||||
|
|
||||||
|
const LFS_BATCH_SIZE = 100
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Octokit helpers
|
// Octokit helpers
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -55,6 +57,24 @@ function parseLfsPointer(content: string): { oid: string; size: number } | null
|
|||||||
return { oid: oidMatch[1], size: parseInt(sizeMatch[1], 10) }
|
return { oid: oidMatch[1], size: parseInt(sizeMatch[1], 10) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatElapsed(startedAt: number) {
|
||||||
|
return `${((performance.now() - startedAt) / 1000).toFixed(1)}s`
|
||||||
|
}
|
||||||
|
|
||||||
|
function logInfo(step: string, action: string, startedAt: number, details?: Record<string, unknown>) {
|
||||||
|
console.info(`[INFO] ${step} -> ${action} | Timer: ${formatElapsed(startedAt)}`, details || '')
|
||||||
|
}
|
||||||
|
|
||||||
|
function chunkArray<T>(items: T[], size: number) {
|
||||||
|
const chunks: T[][] = []
|
||||||
|
|
||||||
|
for (let i = 0; i < items.length; i += size) {
|
||||||
|
chunks.push(items.slice(i, i + size))
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunks
|
||||||
|
}
|
||||||
|
|
||||||
interface LfsObject {
|
interface LfsObject {
|
||||||
oid: string
|
oid: string
|
||||||
size: number
|
size: number
|
||||||
@@ -76,6 +96,25 @@ async function uploadToLfs(
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (objects.length === 0) return
|
if (objects.length === 0) return
|
||||||
|
|
||||||
|
const batches = chunkArray(objects, LFS_BATCH_SIZE)
|
||||||
|
|
||||||
|
for (let i = 0; i < batches.length; i++) {
|
||||||
|
await uploadToLfsBatch(owner, repo, batches[i], i + 1, batches.length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadToLfsBatch(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
objects: LfsObject[],
|
||||||
|
batchNumber: number,
|
||||||
|
totalBatches: number,
|
||||||
|
): Promise<void> {
|
||||||
|
const startedAt = performance.now()
|
||||||
|
logInfo('Git LFS', `Batch ${batchNumber}/${totalBatches} started`, startedAt, {
|
||||||
|
objects: objects.length,
|
||||||
|
})
|
||||||
|
|
||||||
const token = process.env.GITHUB_TOKEN!
|
const token = process.env.GITHUB_TOKEN!
|
||||||
const lfsUrl = `https://github.com/${owner}/${repo}.git/info/lfs/objects/batch`
|
const lfsUrl = `https://github.com/${owner}/${repo}.git/info/lfs/objects/batch`
|
||||||
|
|
||||||
@@ -96,6 +135,10 @@ async function uploadToLfs(
|
|||||||
|
|
||||||
if (!batchRes.ok) {
|
if (!batchRes.ok) {
|
||||||
const text = await batchRes.text()
|
const text = await batchRes.text()
|
||||||
|
console.error(`[ERROR] Git LFS -> Batch ${batchNumber}/${totalBatches} failed | Timer: ${formatElapsed(startedAt)}`, {
|
||||||
|
objects: objects.length,
|
||||||
|
status: batchRes.status,
|
||||||
|
})
|
||||||
throw new Error(`LFS batch request failed (${batchRes.status}): ${text}`)
|
throw new Error(`LFS batch request failed (${batchRes.status}): ${text}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,6 +208,10 @@ async function uploadToLfs(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logInfo('Git LFS', `Batch ${batchNumber}/${totalBatches} done`, startedAt, {
|
||||||
|
objects: objects.length,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user