From bff628aac28de74e95c97dc30cc1549908178d9c Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Wed, 15 Jun 2022 17:42:09 +1000 Subject: [PATCH] feat: --find-matching-prs for commits without PR-URL --- README.md | 3 +- auth.js | 12 +++++++ collect-commit-labels.js | 11 ++---- find-matching-prs.js | 72 ++++++++++++++++++++++++++++++++++++++++ package.json | 1 + process-commits.js | 5 +++ test.js | 8 +++++ 7 files changed, 102 insertions(+), 10 deletions(-) create mode 100644 auth.js create mode 100644 find-matching-prs.js diff --git a/README.md b/README.md index ae013bc..5b585f4 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ npm i changelog-maker -g ## Usage -**`changelog-maker [--plaintext|p] [--markdown|md] [--sha] [--group|-g] [--reverse] [--commit-url=] [--start-ref=] [--end-ref=] [github-user[, github-project]]`** +**`changelog-maker [--plaintext|p] [--markdown|md] [--sha] [--group|-g] [--reverse] [--find-matching-prs] [--commit-url=] [--start-ref=] [--end-ref=] [github-user[, github-project]]`** `github-user` and `github-project` should point to the GitHub repository that can be used to find the `PR-URL` data if just an issue number is provided and will also impact how the PR-URL issue numbers are displayed @@ -62,6 +62,7 @@ npm i changelog-maker -g * `--start-ref=`: use the given git `` as a starting point rather than the _last tag_. The `` can be anything commit-ish including a commit sha, tag, branch name. If you specify a `--start-ref` argument the commit log will not be pruned so that version commits and `working on ` commits are left in the list. * `--end-ref=`: use the given git `` as a end-point rather than the _now_. The `` can be anything commit-ish including a commit sha, tag, branch name. * `--filter-release`: exclude Node-style release commits from the list. e.g. "Working on v1.0.0" or "2015-10-21 Version 2.0.0" and also "npm version X" style commits containing _only_ an `x.y.z` semver designator. +* `--find-matching-prs`: use the GitHub API to find the pull requests that match commits that don't have the `PR-URL` metadata in their message text. Without metadata, it may be necessary to also pass the org/user and repo name on the commandline (as the `github-user` and `github-project` arguments as demonstrated above, it may also be necessary to use `--find-matching-prs=true` in this case). * `--quiet` or `-q`: do not print to `process.stdout` * `--all` or `-a`: process all commits since beginning, instead of last tag. * `--help` or `-h`: show usage and help. diff --git a/auth.js b/auth.js new file mode 100644 index 0000000..efe1371 --- /dev/null +++ b/auth.js @@ -0,0 +1,12 @@ +import { promisify } from 'util' +import ghauth from 'ghauth' + +const authOptions = { + configName: 'changelog-maker', + scopes: ['repo'], + noDeviceFlow: true +} + +export async function auth () { + return await promisify(ghauth)(authOptions) +} diff --git a/collect-commit-labels.js b/collect-commit-labels.js index 4c6da41..d0f3918 100644 --- a/collect-commit-labels.js +++ b/collect-commit-labels.js @@ -1,16 +1,9 @@ 'use strict' -import { promisify } from 'util' -import ghauth from 'ghauth' +import { auth } from './auth.js' import ghissues from 'ghissues' import async from 'async' -const authOptions = { - configName: 'changelog-maker', - scopes: ['repo'], - noDeviceFlow: true -} - export async function collectCommitLabels (list) { const sublist = list.filter((commit) => { return typeof commit.ghIssue === 'number' && commit.ghUser && commit.ghProject @@ -20,7 +13,7 @@ export async function collectCommitLabels (list) { return } - const authData = await promisify(ghauth)(authOptions) + const authData = await auth() const cache = {} diff --git a/find-matching-prs.js b/find-matching-prs.js new file mode 100644 index 0000000..66dbe7b --- /dev/null +++ b/find-matching-prs.js @@ -0,0 +1,72 @@ +'use strict' + +import { auth } from './auth.js' +import { graphql } from '@octokit/graphql' +import async from 'async' + +// Query to find the first 4 pull requests that include the commit that we're +// concerned about. We'll filter them and take the first one that was MERGED +// as our prUrl. +const query = ` + query ($owner: String!, $name: String!, $commit: GitObjectID!) { + repository(owner: $owner, name: $name) { + object(oid: $commit) { + ... on Commit { + associatedPullRequests(first: 4) { + ... on PullRequestConnection { + edges { + node { + ... on PullRequest { + number + url + title + state + } + } + } + } + } + } + } + } + } +` + +export async function findMatchingPrs (ghId, list) { + // only look up commits that don't have a prUrl from metadata + const sublist = list.filter((commit) => typeof commit.prUrl !== 'string') + if (!sublist.length) { + return + } + + const authData = await auth() + const headers = { authorization: `token ${authData.token}` } + const cache = {} + + const q = async.queue(async (commit, next) => { + if (commit.ghUser === 'iojs') { + commit.ghUser = 'nodejs' // forcibly rewrite as the GH API doesn't do it for us + } + + // cache on commit, so we don't run the same commit twice (is this possible?) + cache[commit.sha] = cache[commit.sha] || (async () => { + try { + const res = await graphql(query, { owner: ghId.user, name: ghId.repo, commit: commit.sha, headers }) + if (res.repository?.object?.associatedPullRequests?.edges?.length) { + const pr = res.repository.object.associatedPullRequests.edges.filter((e) => e.node?.state === 'MERGED')[0] + if (pr) { + commit.ghIssue = pr.node.number + commit.prUrl = pr.node.url + } + } + } catch (err) { + console.error(`Error querying GitHub to find pull request for commit: ${err}`) + } + })() + await cache[commit.sha] + next() + }, 15) + + q.push(sublist) + await q.drain() +} diff --git a/package.json b/package.json index 6df6bd2..52a5544 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "keywords": [], "preferGlobal": true, "dependencies": { + "@octokit/graphql": "^4.8.0", "async": "^3.2.2", "chalk": "^5.0.0", "commit-stream": "^1.1.0", diff --git a/process-commits.js b/process-commits.js index 0e405e1..450ff43 100644 --- a/process-commits.js +++ b/process-commits.js @@ -5,6 +5,7 @@ import { toGroups } from './groups.js' import { formatMarkdown } from './format.js' import { supportsColor } from 'chalk' import { collectCommitLabels } from './collect-commit-labels.js' +import { findMatchingPrs } from './find-matching-prs.js' function getFormat (argv) { if (argv.format && Object.values(formatType).includes(argv.format)) { @@ -33,6 +34,10 @@ export async function processCommits (argv, ghId, list) { const reverse = argv.reverse const commitUrl = argv['commit-url'] || 'https://github.com/{ghUser}/{ghRepo}/commit/{ref}' + if (argv['find-matching-prs']) { + await findMatchingPrs(ghId, list) + } + await collectCommitLabels(list) const format = getFormat(argv) diff --git a/test.js b/test.js index 2053aaf..4a80f38 100644 --- a/test.js +++ b/test.js @@ -124,3 +124,11 @@ test('test markdown punctuation chars in commit message and author name', (t) => `) t.end() }) + +test('test find-matching-prs', (t) => { + t.equal( + exec('--start-ref=a059bc7ca9 --end-ref=a059bc7ca9 --find-matching-prs=true nodejs changelog-maker'), + `* [a059bc7ca9] - chore(deps): remove package-lock.json (#118) (Rod Vagg) https://github.com/nodejs/changelog-maker/pull/118 +`) + t.end() +})