summaryrefslogtreecommitdiff
path: root/ci/github-script/get-pr-commit-details.js
blob: b268e7cf6202970971ac87c845f5d24f4f3c5426 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// @ts-check
const { promisify } = require('node:util')
const execFile = promisify(require('node:child_process').execFile)

/**
 * @typedef {{
 *  subject: string,
 *  sha: string,
 *  author: { name: string, email: string },
 *  committer: { name: string, email: string}
 *  changedPaths: string[],
 *  changedPathSegments: Set<string>,
 * }} Commit
 */

/**
 * @param {{
 *  args: string[]
 *  core: import('@actions/core'),
 *  quiet?: boolean,
 *  repoPath?: string,
 * }} RunGitProps
 */
async function runGit({ args, repoPath, core, quiet }) {
  if (repoPath) {
    args = ['-C', repoPath, ...args]
  }

  if (!quiet) {
    core.info(`About to run \`git ${args.map((s) => `'${s}'`).join(' ')}\``)
  }

  return await execFile('git', args)
}

/**
 * Gets the SHA, subject and changed files for each commit in the given PR.
 *
 * Don't use GitHub API at all: the "list commits on PR" endpoint has a limit
 * of 250 commits and doesn't return the changed files.
 *
 * @param {{
 *  core: import('@actions/core'),
 *  pr: Awaited<ReturnType<InstanceType<import('@actions/github/lib/utils').GitHub>["rest"]["pulls"]["get"]>>["data"]
 *  repoPath?: string,
 * }} GetCommitMessagesForPRProps
 *
 * @returns {Promise<Commit[]>}
 */
async function getCommitDetailsForPR({ core, pr, repoPath }) {
  await runGit({
    args: ['fetch', `--depth=1`, 'origin', pr.base.sha],
    repoPath,
    core,
  })
  await runGit({
    args: ['fetch', `--depth=${pr.commits + 1}`, 'origin', pr.head.sha],
    repoPath,
    core,
  })

  const shas = (
    await runGit({
      args: [
        'rev-list',
        `--max-count=${pr.commits}`,
        `${pr.base.sha}..${pr.head.sha}`,
      ],
      repoPath,
      core,
    })
  ).stdout
    .split('\n')
    .map((s) => s.trim())
    .filter(Boolean)

  return Promise.all(
    shas.map(async (sha) => {
      // Subject, author name, author email, committer name, committer email (all tab-seperated)
      // then a blank line, then filenames.
      const result = (
        await runGit({
          args: [
            'log',
            '--format=%s\t%aN\t%aE\t%cN\t%cE',
            '--name-only',
            '-1',
            sha,
          ],
          repoPath,
          core,
          quiet: true,
        })
      ).stdout.split('\n')

      const [subject, authorName, authorEmail, committerName, committerEmail] =
        result[0].split('\t')

      const changedPaths = result.slice(2, -1)

      const changedPathSegments = new Set(
        changedPaths.flatMap((path) => path.split('/')),
      )

      return {
        sha,
        subject,
        author: { name: authorName, email: authorEmail },
        committer: { name: committerName, email: committerEmail },
        changedPaths,
        changedPathSegments,
      }
    }),
  )
}

module.exports = { getCommitDetailsForPR }