Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: cookpete/auto-changelog
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v2.2.1
Choose a base ref
...
head repository: cookpete/auto-changelog
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v2.3.0
Choose a head ref
  • 15 commits
  • 10 files changed
  • 4 contributors

Commits on May 21, 2021

  1. Feat: add sortCommits by subject and subject-desc (#186)

    * Feat: add sortCommits by subject and subject-desc
    
    Thanks a lot for your works ! 
    Inside ours project we use a prefix to categorize our commits. 
    In order to group them, we would like to sort it by subject.
    
    * update doc
    
    Co-authored-by: Mickael Dumand <mickael@getluko.com>
    kimak and Mickael Dumand authored May 21, 2021
    Copy the full SHA
    7404570 View commit details
  2. feat: add support for minor/preminor versions for custom templates (#185

    )
    
    * feat: add support for minor/preminor versions for custom templates
    
    * Tidy up
    
    Co-authored-by: Pete Cook <pete@cookpete.com>
    sri-vr and cookpete authored May 21, 2021
    Copy the full SHA
    44e8085 View commit details
  3. Apply ignoreCommitPattern earlier in the parsing logic

    Fixes #195
    Sort of fixes #187 too
    cookpete committed May 21, 2021
    Copy the full SHA
    d27152d View commit details
  4. Fix getSubject edge case

    Closes #179
    Fixes #184
    cookpete committed May 21, 2021
    Copy the full SHA
    2bdc772 View commit details
  5. Fix v prefix logic

    Fixes #33
    cookpete committed May 21, 2021
    Copy the full SHA
    b854fc0 View commit details
  6. Fix unreleased version diff

    Hopefully fixes #174
    cookpete committed May 21, 2021
    Copy the full SHA
    de35cfa View commit details
  7. Copy the full SHA
    6592ef2 View commit details
  8. Add --starting-date

    Fixes #181
    cookpete committed May 21, 2021
    Copy the full SHA
    a7fba3c View commit details

Commits on May 23, 2021

  1. Add --hide-empty-releases

    Fixes #189
    cookpete committed May 23, 2021
    Copy the full SHA
    2f7c3ab View commit details
  2. Add --append-git-tag

    Fixes #190 if you use --append-git-tag "--merged" with Git 2.7+
    cookpete committed May 23, 2021
    Copy the full SHA
    20bb145 View commit details
  3. Copy the full SHA
    a80e311 View commit details
  4. Remove lodash.uniqby

    cookpete committed May 23, 2021
    Copy the full SHA
    26e7d9c View commit details
  5. Copy the full SHA
    1b29530 View commit details
  6. Remove --sort=-creatordate from git tag logic

    Tags are now sorted according to semver, so this was pointlessly increasing the minimum Git version
    Fixes #196
    cookpete committed May 23, 2021
    Copy the full SHA
    f6a8ab1 View commit details
  7. 2.3.0

    cookpete committed May 23, 2021
    Copy the full SHA
    5d914d4 View commit details
Showing with 301 additions and 206 deletions.
  1. +201 −180 CHANGELOG.md
  2. +4 −1 README.md
  3. +1 −2 package.json
  4. +9 −1 src/commits.js
  5. +13 −6 src/releases.js
  6. +4 −0 src/run.js
  7. +17 −9 src/tags.js
  8. +2 −1 src/template.js
  9. +11 −0 test/releases.js
  10. +39 −6 test/tags.js
381 changes: 201 additions & 180 deletions CHANGELOG.md

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -41,12 +41,15 @@ Options:
--tag-pattern [regex] # override regex pattern for version tags
--tag-prefix [prefix] # prefix used in version tags, default: v
--starting-version [tag] # specify earliest version to include in changelog
--sort-commits [property] # sort commits by property [relevance, date, date-desc], default: relevance
--starting-date [yyyy-mm-dd] # specify earliest date to include in changelog
--sort-commits [property] # sort commits by property [relevance, date, date-desc, subject, subject-desc], default: relevance
--release-summary # display tagged commit message body as release summary
--unreleased-only # only output unreleased changes
--hide-empty-releases # hide empty releases
--hide-credit # hide auto-changelog credit
--handlebars-setup [file] # handlebars setup file
--append-git-log [string] # string to append to git log command
--append-git-tag [string] # string to append to git tag command
--prepend # prepend changelog to output file
--stdout # output changelog to stdout
-V, --version # output the version number
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "auto-changelog",
"version": "2.2.1",
"version": "2.3.0",
"description": "Command line tool for generating a changelog from git tags and commit history",
"main": "./src/index.js",
"bin": {
@@ -45,7 +45,6 @@
"dependencies": {
"commander": "^5.0.0",
"handlebars": "^4.7.3",
"lodash.uniqby": "^4.7.0",
"node-fetch": "^2.6.0",
"parse-github-url": "^1.0.2",
"semver": "^6.3.0"
10 changes: 9 additions & 1 deletion src/commits.js
Original file line number Diff line number Diff line change
@@ -35,6 +35,7 @@ const parseCommits = (string, options = {}) => {
.split(COMMIT_SEPARATOR)
.slice(1)
.map(commit => parseCommit(commit, options))
.filter(commit => filterCommit(commit, options))
}

const parseCommit = (commit, options = {}) => {
@@ -61,7 +62,7 @@ const parseCommit = (commit, options = {}) => {
}

const getSubject = (message) => {
if (!message) {
if (!message.trim()) {
return '_No commit message_'
}
return message.match(/[^\n]+/)[0]
@@ -133,6 +134,13 @@ const getMerge = (commit, message, options = {}) => {
return null
}

const filterCommit = (commit, { ignoreCommitPattern }) => {
if (ignoreCommitPattern && new RegExp(ignoreCommitPattern).test(commit.subject)) {
return false
}
return true
}

module.exports = {
COMMIT_SEPARATOR,
MESSAGE_SEPARATOR,
19 changes: 13 additions & 6 deletions src/releases.js
Original file line number Diff line number Diff line change
@@ -5,15 +5,15 @@ const MERGE_COMMIT_PATTERN = /^Merge (remote-tracking )?branch '.+'/
const COMMIT_MESSAGE_PATTERN = /\n+([\S\s]+)/

const parseReleases = async (tags, options, onParsed) => {
return Promise.all(tags.map(async tag => {
const releases = await 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))
.filter(filterCommits(merges))
.sort(sortCommits(options))
.slice(0, getCommitLimit(options, emptyRelease, breakingCount))

@@ -27,19 +27,17 @@ const parseReleases = async (tags, options, onParsed) => {
fixes
}
}))
return releases.filter(filterReleases(options))
}

const filterCommits = ({ ignoreCommitPattern }, merges) => commit => {
const filterCommits = merges => commit => {
if (commit.fixes || commit.merge) {
// Filter out commits that already appear in fix or merge lists
return false
}
if (commit.breaking) {
return true
}
if (ignoreCommitPattern && new RegExp(ignoreCommitPattern).test(commit.subject)) {
return false
}
if (semver.valid(commit.subject)) {
// Filter out version commits
return false
@@ -60,6 +58,8 @@ const sortCommits = ({ sortCommits }) => (a, b) => {
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)
if (sortCommits === 'subject') return a.subject.localeCompare(b.subject)
if (sortCommits === 'subject-desc') return b.subject.localeCompare(a.subject)
return (b.insertions + b.deletions) - (a.insertions + a.deletions)
}

@@ -81,6 +81,13 @@ const getSummary = (message, { releaseSummary }) => {
return null
}

const filterReleases = options => ({ merges, fixes, commits }) => {
if (options.hideEmptyReleases && (merges.length + fixes.length + commits.length) === 0) {
return false
}
return true
}

module.exports = {
parseReleases
}
4 changes: 4 additions & 0 deletions src/run.js
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ const DEFAULT_OPTIONS = {
tagPrefix: '',
sortCommits: 'relevance',
appendGitLog: '',
appendGitTag: '',
config: '.auto-changelog'
}

@@ -44,12 +45,15 @@ const getOptions = async argv => {
.option('--tag-pattern <regex>', 'override regex pattern for version tags')
.option('--tag-prefix <prefix>', 'prefix used in version tags')
.option('--starting-version <tag>', 'specify earliest version to include in changelog')
.option('--starting-date <yyyy-mm-dd>', 'specify earliest date to include in changelog')
.option('--sort-commits <property>', `sort commits by property [relevance, date, date-desc], default: ${DEFAULT_OPTIONS.sortCommits}`)
.option('--release-summary', 'use tagged commit message body as release summary')
.option('--unreleased-only', 'only output unreleased changes')
.option('--hide-empty-releases', 'hide empty releases')
.option('--hide-credit', 'hide auto-changelog credit')
.option('--handlebars-setup <file>', 'handlebars setup file')
.option('--append-git-log <string>', 'string to append to git log command')
.option('--append-git-tag <string>', 'string to append to git tag command')
.option('--prepend', 'prepend changelog to output file')
.option('--stdout', 'output changelog to stdout')
.version(version)
26 changes: 17 additions & 9 deletions src/tags.js
Original file line number Diff line number Diff line change
@@ -6,33 +6,32 @@ const MATCH_V = /^v\d/

const fetchTags = async (options, remote) => {
const format = `%(refname:short)${DIVIDER}%(creatordate:short)`
const tags = (await cmd(`git tag -l --sort=-creatordate --format=${format}`))
const tags = (await cmd(`git tag -l --format=${format} ${options.appendGitTag}`))
.trim()
.split('\n')
.map(parseTag(options))
.filter(isValidTag(options))
.sort(sortTags(options))
.sort(sortTags)

const { latestVersion, unreleased, unreleasedOnly, getCompareLink } = options
if (latestVersion || unreleased || unreleasedOnly) {
const v = !MATCH_V.test(latestVersion) && tags.some(({ version }) => MATCH_V.test(version)) ? 'v' : ''
const previous = tags[0]
const v = !MATCH_V.test(latestVersion) && previous && MATCH_V.test(previous.version) ? 'v' : ''
const compareTo = latestVersion ? `${v}${latestVersion}` : 'HEAD'
tags.unshift({
tag: null,
title: latestVersion ? `${v}${latestVersion}` : 'Unreleased',
date: new Date().toISOString(),
diff: previous ? `${previous.tag}..` : '',
diff: previous ? `${previous.tag}..` : 'HEAD',
href: previous ? getCompareLink(previous.tag, compareTo) : null
})
}

return tags
.map(enrichTag(options))
.slice(0, getLimit(tags, options))
const enriched = tags.map(enrichTag(options))
return enriched.slice(0, getLimit(enriched, options))
}

const getLimit = (tags, { unreleasedOnly, startingVersion }) => {
const getLimit = (tags, { unreleasedOnly, startingVersion, startingDate }) => {
if (unreleasedOnly) {
return 1
}
@@ -42,6 +41,9 @@ const getLimit = (tags, { unreleasedOnly, startingVersion }) => {
return index + 1
}
}
if (startingDate) {
return tags.filter(t => t.isoDate >= startingDate).length
}
return tags.length
}

@@ -68,6 +70,12 @@ const enrichTag = ({ getCompareLink, tagPattern }) => (t, index, tags) => {
semver.valid(previous.version) &&
semver.diff(t.version, previous.version) === 'major'
),
minor: Boolean(
previous &&
semver.valid(t.version) &&
semver.valid(previous.version) &&
['minor', 'preminor'].includes(semver.diff(t.version, previous.version))
),
...t
}
}
@@ -79,7 +87,7 @@ const isValidTag = ({ tagPattern }) => ({ tag, version }) => {
return semver.valid(version)
}

const sortTags = ({ tagPrefix }) => ({ version: a }, { version: b }) => {
const sortTags = ({ version: a }, { version: b }) => {
if (semver.valid(a) && semver.valid(b)) {
return semver.rcompare(a, b)
}
3 changes: 2 additions & 1 deletion src/template.js
Original file line number Diff line number Diff line change
@@ -80,7 +80,8 @@ const cleanTemplate = template => {
const compileTemplate = async (releases, options) => {
const { template, handlebarsSetup } = options
if (handlebarsSetup) {
const setup = require(join(process.cwd(), handlebarsSetup))
const path = /^\//.test(handlebarsSetup) ? handlebarsSetup : join(process.cwd(), handlebarsSetup)
const setup = require(path)
if (typeof setup === 'function') {
setup(Handlebars)
}
11 changes: 11 additions & 0 deletions test/releases.js
Original file line number Diff line number Diff line change
@@ -117,4 +117,15 @@ describe('parseReleases', () => {
expect(releases[0].commits).to.have.lengthOf(1)
expect(releases[0].commits[0]).to.include({ subject: 'First commit' })
})

it('hides empty releases', async () => {
const map = {
'v1.0.0': []
}
mock('fetchCommits', diff => Promise.resolve(map[diff]))
const options = { hideEmptyReleases: true }
const tags = [{ tag: 'v1.0.0', date: '2000-01-01', diff: 'v1.0.0' }]
const releases = await parseReleases(tags, options)
expect(releases).to.have.lengthOf(0)
})
})
45 changes: 39 additions & 6 deletions test/tags.js
Original file line number Diff line number Diff line change
@@ -38,7 +38,8 @@ describe('fetchTags', () => {
niceDate: '1 January 2001',
diff: 'v0.3.0..v1.0.0',
href: 'https://github.com/user/repo/compare/v0.3.0...v1.0.0',
major: true
major: true,
minor: false
},
{
tag: 'v0.3.0',
@@ -49,7 +50,8 @@ describe('fetchTags', () => {
niceDate: '1 April 2000',
diff: 'v0.2.2..v0.3.0',
href: 'https://github.com/user/repo/compare/v0.2.2...v0.3.0',
major: false
major: false,
minor: true
},
{
tag: 'v0.2.2',
@@ -60,7 +62,8 @@ describe('fetchTags', () => {
niceDate: '3 March 2000',
diff: 'v0.2.1..v0.2.2',
href: 'https://github.com/user/repo/compare/v0.2.1...v0.2.2',
major: false
major: false,
minor: false
},
{
tag: 'v0.2.1',
@@ -71,7 +74,8 @@ describe('fetchTags', () => {
niceDate: '2 March 2000',
diff: 'v0.2.0..v0.2.1',
href: 'https://github.com/user/repo/compare/v0.2.0...v0.2.1',
major: false
major: false,
minor: false
},
{
tag: 'v0.2.0',
@@ -82,7 +86,8 @@ describe('fetchTags', () => {
niceDate: '1 March 2000',
diff: 'v0.1.0..v0.2.0',
href: 'https://github.com/user/repo/compare/v0.1.0...v0.2.0',
major: false
major: false,
minor: true
},
{
tag: 'v0.1.0',
@@ -93,14 +98,21 @@ describe('fetchTags', () => {
niceDate: '1 February 2000',
diff: 'v0.1.0',
href: null,
major: false
major: false,
minor: false
}])
})

it('supports --starting-version', async () => {
expect(await fetchTags({ ...options, startingVersion: 'v0.2.2' })).to.have.lengthOf(3)
})

it('supports --starting-date', async () => {
expect(await fetchTags({ ...options, startingDate: '2000-03-01' })).to.have.lengthOf(5)
expect(await fetchTags({ ...options, startingDate: '2000-03-02' })).to.have.lengthOf(4)
expect(await fetchTags({ ...options, startingDate: '2000-05-01' })).to.have.lengthOf(1)
})

it('supports partial semver tags', async () => {
mock('cmd', () => Promise.resolve([
'v0.1---2000-02-01',
@@ -121,6 +133,27 @@ describe('fetchTags', () => {
])
})

it('supports --latest-version without v prefix', async () => {
mock('cmd', () => Promise.resolve([
'0.1.0---2000-02-01',
'0.2.0---2000-03-01',
'0.2.1---2000-03-02',
'0.2.2---2000-03-03',
'0.3.0---2000-04-01',
'1.0.0---2001-01-01'
].join('\n')))
const tags = await fetchTags({ ...options, latestVersion: '2.0.0' })
expect(tags.map(t => t.title)).to.deep.equal([
'2.0.0',
'1.0.0',
'0.3.0',
'0.2.2',
'0.2.1',
'0.2.0',
'0.1.0'
])
})

it('ignores invalid semver tags', async () => {
mock('cmd', () => Promise.resolve([
'v0.1.0---2000-02-01',