0

Commit files

by
Published today

Create or update one or more files on a branch in a single commit. Each file is created when it doesn't exist on the branch and updated otherwise. When the branch doesn't exist, set start_branch to create it from that branch.

Script gitlab Verified

The script

Submitted by hugo989 Typescript (fetch-only)
Verified 8 hours ago
1
//native
2

3
/**
4
 * Commit files
5
 * Create or update one or more files on a branch in a single commit. Each file is created when it doesn't exist on the branch and updated otherwise. When the branch doesn't exist, set start_branch to create it from that branch.
6
 */
7
export async function main(
8
  auth: RT.Gitlab,
9
  project: string,
10
  branch: string,
11
  commit_message: string,
12
  files: { file_path: string; content: string; encoding?: "text" | "base64" }[],
13
  start_branch: string | undefined,
14
  author_name: string | undefined,
15
  author_email: string | undefined
16
) {
17
  const api = `${(auth.baseUrl || "https://gitlab.com").replace(/\/+$/, "")}/api/v4/projects/${encodeURIComponent(project)}/repository`
18
  const paths = files.map((f) => f?.file_path)
19
  const repeated = paths.find((path, i) => paths.indexOf(path) !== i)
20
  if (!files.length || paths.some((path) => !path) || repeated) {
21
    throw new Error(
22
      repeated
23
        ? `${repeated} is listed twice in files`
24
        : "Every file needs a file_path"
25
    )
26
  }
27
  const headers = {
28
    Authorization: `Bearer ${auth.token}`,
29
    Accept: "application/json",
30
  }
31
  // A 404 is a missing branch only when GitLab says so: it also means a project the token can't see.
32
  const branchResponse = await fetch(
33
    `${api}/branches/${encodeURIComponent(branch)}`,
34
    { headers }
35
  )
36
  const branchText = await branchResponse.text()
37
  const branchExists = branchResponse.ok
38
  if (!branchExists && !branchText.includes("Branch Not Found")) {
39
    throw new Error(`${branchResponse.status} ${branchText}`)
40
  }
41
  if (!branchExists && !start_branch) {
42
    throw new Error(
43
      `Branch ${branch} does not exist: set start_branch to create it`
44
    )
45
  }
46
  // Each file is created or updated against the branch, or start_branch for a new branch.
47
  const ref = branchExists ? branch : start_branch!
48
  // Ten checks at a time, so a large commit doesn't burst the files API rate limit.
49
  const actions = []
50
  for (let i = 0; i < files.length; i += 10) {
51
    actions.push(
52
      ...(await Promise.all(
53
        files.slice(i, i + 10).map(async (f) => {
54
          const head = await fetch(
55
            `${api}/files/${encodeURIComponent(f.file_path)}?ref=${encodeURIComponent(ref)}`,
56
            { method: "HEAD", headers }
57
          )
58
          if (!head.ok && head.status !== 404) {
59
            throw new Error(`${head.status} checking ${f.file_path} on ${ref}`)
60
          }
61
          return {
62
            action: head.ok ? "update" : "create",
63
            file_path: f.file_path,
64
            content: f.content,
65
            encoding: f.encoding || "text",
66
          }
67
        })
68
      ))
69
    )
70
  }
71

72
  const response = await fetch(`${api}/commits`, {
73
    method: "POST",
74
    headers: { ...headers, "Content-Type": "application/json" },
75
    body: JSON.stringify({
76
      branch,
77
      start_branch: branchExists ? undefined : start_branch,
78
      commit_message,
79
      actions,
80
      author_name: author_name || undefined,
81
      author_email: author_email || undefined,
82
    }),
83
  })
84

85
  if (!response.ok) {
86
    throw new Error(`${response.status} ${await response.text()}`)
87
  }
88

89
  return await response.json()
90
}
91