From 1fc916c293821eafc0a4a835313c9edca19828a2 Mon Sep 17 00:00:00 2001 From: Pete Cook Date: Sun, 26 Jul 2020 20:42:49 +0100 Subject: [PATCH] Mild refactor - Move any tag-specific logic from releases.js to tags.js - Combine remote and latestVersion into options object to be passed around - Use const rather than function where possible - Hopefully fixes various bugs with single tags, including https://github.com/CookPete/auto-changelog/issues/174 --- src/commits.js | 36 ++++----- src/releases.js | 118 ++++++++++-------------------- src/remote.js | 6 +- src/run.js | 40 +++++----- src/tags.js | 87 ++++++++++++++++------ src/template.js | 15 ++-- src/utils.js | 26 +++---- test/commits.js | 42 ++++++----- test/data/commits-map.js | 8 +- test/releases.js | 140 +++++++---------------------------- test/tags.js | 153 +++++++++++++++++++++++++++------------ test/template.js | 16 ++-- 12 files changed, 336 insertions(+), 351 deletions(-) diff --git a/src/commits.js b/src/commits.js index 0ce106c5..b9aada39 100644 --- a/src/commits.js +++ b/src/commits.js @@ -18,26 +18,26 @@ const MERGE_PATTERNS = [ /^Merge branch .+ into .+\n\n(.+)[\S\s]+See merge request [^!]*!(\d+)/ // GitLab merge ] -async function fetchCommits (diff, remote, options = {}) { +const fetchCommits = async (diff, options = {}) => { const format = await getLogFormat() const log = await cmd(`git log ${diff} --shortstat --pretty=format:${format} ${options.appendGitLog}`) - return parseCommits(log, remote, options) + return parseCommits(log, options) } -async function getLogFormat () { +const getLogFormat = async () => { const gitVersion = await getGitVersion() const bodyFormat = gitVersion && semver.gte(gitVersion, '1.7.2') ? BODY_FORMAT : FALLBACK_BODY_FORMAT return `${COMMIT_SEPARATOR}%H%n%ai%n%an%n%ae%n${bodyFormat}${MESSAGE_SEPARATOR}` } -function parseCommits (string, remote, options = {}) { +const parseCommits = (string, options = {}) => { return string .split(COMMIT_SEPARATOR) .slice(1) - .map(commit => parseCommit(commit, remote, options)) + .map(commit => parseCommit(commit, options)) } -function parseCommit (commit, remote, options = {}) { +const parseCommit = (commit, options = {}) => { const [, hash, date, author, email, tail] = commit.match(MATCH_COMMIT) const [body, stats] = tail.split(MESSAGE_SEPARATOR) const message = encodeHTML(body) @@ -49,25 +49,25 @@ function parseCommit (commit, remote, options = {}) { date: new Date(date).toISOString(), subject: replaceText(getSubject(message), options), message: message.trim(), - fixes: getFixes(message, author, remote, options), - href: remote.getCommitLink(hash), + fixes: getFixes(message, author, options), + href: options.getCommitLink(hash), breaking: !!options.breakingPattern && new RegExp(options.breakingPattern).test(message), ...getStats(stats) } return { ...parsed, - merge: getMerge(parsed, message, remote, options) + merge: getMerge(parsed, message, options) } } -function getSubject (message) { +const getSubject = (message) => { if (!message) { return '_No commit message_' } return message.match(/[^\n]+/)[0] } -function getStats (stats) { +const getStats = (stats) => { if (!stats.trim()) return {} const [, files, insertions, deletions] = stats.match(MATCH_STATS) return { @@ -77,21 +77,21 @@ function getStats (stats) { } } -function getFixes (message, author, remote, options = {}) { +const getFixes = (message, author, options = {}) => { const pattern = getFixPattern(options) const fixes = [] let match = pattern.exec(message) if (!match) return null while (match) { const id = getFixID(match) - const href = isLink(match[2]) ? match[2] : remote.getIssueLink(id) + const href = isLink(match[2]) ? match[2] : options.getIssueLink(id) fixes.push({ id, href, author }) match = pattern.exec(message) } return fixes } -function getFixID (match) { +const getFixID = (match) => { // Get the last non-falsey value in the match array for (let i = match.length; i >= 0; i--) { if (match[i]) { @@ -100,21 +100,21 @@ function getFixID (match) { } } -function getFixPattern (options) { +const getFixPattern = (options) => { if (options.issuePattern) { return new RegExp(options.issuePattern, 'g') } return DEFAULT_FIX_PATTERN } -function getMergePatterns (options) { +const getMergePatterns = (options) => { if (options.mergePattern) { return MERGE_PATTERNS.concat(new RegExp(options.mergePattern, 'g')) } return MERGE_PATTERNS } -function getMerge (commit, message, remote, options = {}) { +const getMerge = (commit, message, options = {}) => { const patterns = getMergePatterns(options) for (const pattern of patterns) { const match = pattern.exec(message) @@ -124,7 +124,7 @@ function getMerge (commit, message, remote, options = {}) { return { id, message: replaceText(message, options), - href: remote.getMergeLink(id), + href: options.getMergeLink(id), author: commit.author, commit } diff --git a/src/releases.js b/src/releases.js index 05392710..f835a0bc 100644 --- a/src/releases.js +++ b/src/releases.js @@ -1,72 +1,35 @@ const semver = require('semver') const { fetchCommits } = require('./commits') -const { niceDate } = require('./utils') const MERGE_COMMIT_PATTERN = /^Merge (remote-tracking )?branch '.+'/ const COMMIT_MESSAGE_PATTERN = /\n+([\S\s]+)/ -async function createRelease (tag, previousTag, date, diff, remote, options, onParsed) { - const commits = await fetchCommits(diff, remote, options) - const merges = commits.filter(commit => commit.merge).map(commit => commit.merge) - const fixes = commits.filter(commit => commit.fixes).map(commit => ({ fixes: commit.fixes, commit })) - const emptyRelease = merges.length === 0 && fixes.length === 0 - const { message } = commits[0] || { message: null } - const breakingCount = commits.filter(c => c.breaking).length - const filteredCommits = commits - .filter(commit => filterCommit(commit, options, merges)) - .sort(commitSorter(options)) - .slice(0, getCommitLimit(options, emptyRelease, breakingCount)) - const release = { - tag, - title: tag || 'Unreleased', - date, - isoDate: date.slice(0, 10), - niceDate: niceDate(date), - commits: filteredCommits, - merges, - fixes, - summary: getSummary(message, options), - major: Boolean(!options.tagPattern && tag && previousTag && semver.diff(tag, previousTag) === 'major'), - href: getCompareLink(previousTag, tag, remote, options) - } - if (onParsed) { - onParsed(release) - } - return release -} +const parseReleases = async (tags, options, onParsed) => { + return Promise.all(tags.map(async tag => { + const commits = await fetchCommits(tag.diff, options) + const merges = commits.filter(commit => commit.merge).map(commit => commit.merge) + const fixes = commits.filter(commit => commit.fixes).map(commit => ({ fixes: commit.fixes, commit })) + const emptyRelease = merges.length === 0 && fixes.length === 0 + const { message } = commits[0] || { message: null } + const breakingCount = commits.filter(c => c.breaking).length + const filteredCommits = commits + .filter(filterCommits(options, merges)) + .sort(sortCommits(options)) + .slice(0, getCommitLimit(options, emptyRelease, breakingCount)) -function parseReleases (tags, remote, latestVersion, options, onParsed) { - const releases = tags.map(({ tag, date }, index, tags) => { - if (tags[index - 1] && tags[index - 1].tag === options.startingVersion) { - return null - } - const previousTag = tags[index + 1] ? tags[index + 1].tag : null - const diff = previousTag ? `${previousTag}..${tag}` : tag - return createRelease(tag, previousTag, date, diff, remote, options, onParsed) - }) - if (latestVersion || options.unreleased || options.unreleasedOnly) { - const tag = latestVersion || null - const previousTag = tags[0] ? tags[0].tag : null - const date = new Date().toISOString() - const diff = `${previousTag}..` - const unreleased = createRelease(tag, previousTag, date, diff, remote, options, onParsed) - if (options.unreleasedOnly) { - return Promise.all([unreleased]) - } - releases.unshift(unreleased) - } - return Promise.all(releases.filter(release => release)) -} + if (onParsed) onParsed(tag) -function getCommitLimit ({ commitLimit, backfillLimit }, emptyRelease, breakingCount) { - if (commitLimit === false) { - return undefined // Return all commits - } - const limit = emptyRelease ? backfillLimit : commitLimit - return Math.max(breakingCount, limit) + return { + ...tag, + summary: getSummary(message, options), + commits: filteredCommits, + merges, + fixes + } + })) } -function filterCommit (commit, { ignoreCommitPattern }, merges) { +const filterCommits = ({ ignoreCommitPattern }, merges) => commit => { if (commit.fixes || commit.merge) { // Filter out commits that already appear in fix or merge lists return false @@ -92,33 +55,30 @@ function filterCommit (commit, { ignoreCommitPattern }, merges) { return true } -function getSummary (message, { releaseSummary }) { - if (!message || !releaseSummary) { - return null - } - if (COMMIT_MESSAGE_PATTERN.test(message)) { - return message.match(COMMIT_MESSAGE_PATTERN)[1] - } - return null +const sortCommits = ({ sortCommits }) => (a, b) => { + if (!a.breaking && b.breaking) return 1 + if (a.breaking && !b.breaking) return -1 + if (sortCommits === 'date') return new Date(a.date) - new Date(b.date) + if (sortCommits === 'date-desc') return new Date(b.date) - new Date(a.date) + return (b.insertions + b.deletions) - (a.insertions + a.deletions) } -function commitSorter ({ sortCommits }) { - return (a, b) => { - if (!a.breaking && b.breaking) return 1 - if (a.breaking && !b.breaking) return -1 - if (sortCommits === 'date') return new Date(a.date) - new Date(b.date) - if (sortCommits === 'date-desc') return new Date(b.date) - new Date(a.date) - return (b.insertions + b.deletions) - (a.insertions + a.deletions) +const getCommitLimit = ({ commitLimit, backfillLimit }, emptyRelease, breakingCount) => { + if (commitLimit === false) { + return undefined // Return all commits } + const limit = emptyRelease ? backfillLimit : commitLimit + return Math.max(breakingCount, limit) } -function getCompareLink (previousTag, tag, remote, { tagPrefix = '' }) { - if (!previousTag) { +const getSummary = (message, { releaseSummary }) => { + if (!message || !releaseSummary) { return null } - const from = `${tagPrefix}${previousTag}` - const to = tag ? `${tagPrefix}${tag}` : 'HEAD' - return remote.getCompareLink(from, to) + if (COMMIT_MESSAGE_PATTERN.test(message)) { + return message.match(COMMIT_MESSAGE_PATTERN)[1] + } + return null } module.exports = { diff --git a/src/remote.js b/src/remote.js index 877b5c25..667fd57a 100644 --- a/src/remote.js +++ b/src/remote.js @@ -1,12 +1,12 @@ const parseRepoURL = require('parse-github-url') const { cmd } = require('./utils') -async function fetchRemote (options) { +const fetchRemote = async options => { const remoteURL = await cmd(`git config --get remote.${options.remote}.url`) return getRemote(remoteURL, options) } -function getRemote (remoteURL, options = {}) { +const getRemote = (remoteURL, options = {}) => { const overrides = getOverrides(options) if (!remoteURL) { // No point warning if everything is overridden @@ -81,7 +81,7 @@ function getRemote (remoteURL, options = {}) { } } -function getOverrides ({ commitUrl, issueUrl, mergeUrl, compareUrl }) { +const getOverrides = ({ commitUrl, issueUrl, mergeUrl, compareUrl }) => { const overrides = {} if (commitUrl) overrides.getCommitLink = id => commitUrl.replace('{id}', id) if (issueUrl) overrides.getIssueLink = id => issueUrl.replace('{id}', id) diff --git a/src/run.js b/src/run.js index 692ed6d9..9c799eb6 100644 --- a/src/run.js +++ b/src/run.js @@ -23,8 +23,8 @@ const PACKAGE_FILE = 'package.json' const PACKAGE_OPTIONS_KEY = 'auto-changelog' const PREPEND_TOKEN = '' -async function getOptions (argv) { - const options = new Command() +const getOptions = async argv => { + const commandOptions = new Command() .option('-o, --output ', `output file, default: ${DEFAULT_OPTIONS.output}`) .option('-c, --config ', `config file location, default: ${DEFAULT_OPTIONS.config}`) .option('-t, --template