(
token: string,
owner: string,
repo: string,
branch: string,
files: { path: string; content: string }[],
commitMessage: string,
coAuthors: { name: string; email: string }[]
)
| 338 | * This is the core function for Pair Extraordinaire badge. |
| 339 | */ |
| 340 | export async function createMultiFileCommitWithCoAuthors( |
| 341 | token: string, |
| 342 | owner: string, |
| 343 | repo: string, |
| 344 | branch: string, |
| 345 | files: { path: string; content: string }[], |
| 346 | commitMessage: string, |
| 347 | coAuthors: { name: string; email: string }[] |
| 348 | ) { |
| 349 | if (files.length === 0) throw new Error('No files to commit.'); |
| 350 | |
| 351 | const base = `/repos/${owner}/${repo}`; |
| 352 | const jsonHeaders = { 'Content-Type': 'application/json' }; |
| 353 | |
| 354 | // Build commit message with Co-authored-by trailers |
| 355 | let fullMessage = commitMessage; |
| 356 | if (coAuthors.length > 0) { |
| 357 | fullMessage += '\n\n'; |
| 358 | fullMessage += coAuthors |
| 359 | .map(ca => `Co-authored-by: ${ca.name} <${ca.email}>`) |
| 360 | .join('\n'); |
| 361 | } |
| 362 | |
| 363 | // Step 1: Get the current commit SHA that the branch points to |
| 364 | const refRes = await ghFetch(`${base}/git/ref/heads/${encodeURIComponent(branch)}`, token); |
| 365 | if (!refRes.ok) await handleGitError(refRes, 'Failed to get branch ref'); |
| 366 | const refData = await refRes.json(); |
| 367 | const latestCommitSHA: string = refData.object.sha; |
| 368 | |
| 369 | // Step 2: Get the tree SHA of that commit |
| 370 | const commitRes = await ghFetch(`${base}/git/commits/${latestCommitSHA}`, token); |
| 371 | if (!commitRes.ok) await handleGitError(commitRes, 'Failed to get commit'); |
| 372 | const commitData = await commitRes.json(); |
| 373 | const baseTreeSHA: string = commitData.tree.sha; |
| 374 | |
| 375 | // Step 3: Create blobs for EACH file |
| 376 | const treeEntries: { path: string; mode: string; type: string; sha: string }[] = []; |
| 377 | for (const file of files) { |
| 378 | const base64Content = utf8ToBase64(file.content); |
| 379 | const blobRes = await ghFetch(`${base}/git/blobs`, token, { |
| 380 | method: 'POST', |
| 381 | body: JSON.stringify({ content: base64Content, encoding: 'base64' }), |
| 382 | headers: jsonHeaders, |
| 383 | }); |
| 384 | if (!blobRes.ok) await handleGitError(blobRes, `Failed to create blob for ${file.path}`); |
| 385 | const blobData = await blobRes.json(); |
| 386 | treeEntries.push({ |
| 387 | path: file.path, |
| 388 | mode: '100644', |
| 389 | type: 'blob', |
| 390 | sha: blobData.sha, |
| 391 | }); |
| 392 | } |
| 393 | |
| 394 | // Step 4: Create a new tree with ALL file entries |
| 395 | const treeRes = await ghFetch(`${base}/git/trees`, token, { |
| 396 | method: 'POST', |
| 397 | body: JSON.stringify({ base_tree: baseTreeSHA, tree: treeEntries }), |
no test coverage detected