import { getRemoteFileEntry, getRequiredContentEntrySha, readRemoteFolder } from '../content' import { getRepoApiPath, requestGitJson } from '../http' import { buildLfsPointer, splitLfsFiles, uploadToLfs } from '../lfs' import type { GitProvider, GitRemoteConfig, LfsPushFile, PushFilesParams } from '../types' import type { PushFile } from '@/lib/types' import { isRecord } from '@/lib/guards' interface GiteaFileOperation { content?: string operation: 'create' | 'update' | 'delete' path: string sha?: string } function getGiteaCommitUrl(value: unknown, remote: GitRemoteConfig, branch: string) { if (isRecord(value) && isRecord(value.commit)) { if (typeof value.commit.html_url === 'string') return value.commit.html_url if (typeof value.commit.sha === 'string') return `${remote.webUrl}/commit/${value.commit.sha}` } return `${remote.webUrl}/commits/branch/${branch}` } function buildCommittedFiles(regularFiles: PushFile[], lfsFiles: LfsPushFile[]): PushFile[] { return [ ...regularFiles, ...lfsFiles.map((file) => ({ path: file.path, contentBase64: Buffer.from(buildLfsPointer(file.oid, file.size), 'utf-8').toString('base64'), })), ] } async function buildGiteaOperations( remote: GitRemoteConfig, branch: string, committedFiles: PushFile[], deletePaths: string[], ) { const newFilePaths = new Set(committedFiles.map((file) => file.path)) const operations: GiteaFileOperation[] = [] for (const file of committedFiles) { const existing = await getRemoteFileEntry(remote, file.path, branch) operations.push({ content: file.contentBase64, operation: existing ? 'update' : 'create', path: file.path, sha: existing ? getRequiredContentEntrySha(existing, file.path) : undefined, }) } for (const path of deletePaths) { if (newFilePaths.has(path)) continue const existing = await getRemoteFileEntry(remote, path, branch) if (!existing) continue operations.push({ operation: 'delete', path, sha: getRequiredContentEntrySha(existing, path), }) } return operations } export function createGiteaProvider(remote: GitRemoteConfig, branch: string): GitProvider { return { getRemoteFolder(folderPath) { return readRemoteFolder(remote, branch, folderPath) }, async pushFiles({ files, deletePaths, commitMessage }: PushFilesParams) { const { lfsFiles, regularFiles } = splitLfsFiles(files) await uploadToLfs( remote, lfsFiles.map((file) => ({ oid: file.oid, size: file.size, contentBase64: file.contentBase64 })), ) const committedFiles = buildCommittedFiles(regularFiles, lfsFiles) const operations = await buildGiteaOperations(remote, branch, committedFiles, deletePaths) if (operations.length === 0) { return { commitUrl: `${remote.webUrl}/commits/branch/${branch}` } } const data = await requestGitJson(remote, `${getRepoApiPath(remote)}/contents`, { method: 'POST', body: { branch, files: operations, message: commitMessage, }, }) return { commitUrl: getGiteaCommitUrl(data, remote, branch) } }, } }