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: v1.3.0
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: v1.4.0
Choose a head ref
  • 10 commits
  • 22 files changed
  • 2 contributors

Commits on Dec 21, 2017

  1. Unverified

    No user is associated with the committer email.
    Copy the full SHA
    4462118 View commit details
  2. Add package version test

    cookpete committed Dec 21, 2017
    Copy the full SHA
    b6edd20 View commit details

Commits on Jan 15, 2018

  1. Add --tag-prefix option

    Closes #22
    cookpete committed Jan 15, 2018
    Copy the full SHA
    f77627e View commit details
  2. Copy the full SHA
    8cbe121 View commit details
  3. Update niceDate tests to avoid timezone issues

    Fixes #24
    Closes #25
    The formatting is the main thing we want to test, not the exact date being correct (we can assume the JS engine will not fail us).
    cookpete committed Jan 15, 2018
    Copy the full SHA
    08e90f3 View commit details
  4. Copy the full SHA
    a5c3f75 View commit details
  5. Change use of "origin" to "remote"

    "origin" doesn't really make sense, as it is just the name of the default remote for most repos
    "remote" is more correct in the context of the code
    cookpete committed Jan 15, 2018
    Copy the full SHA
    7c9c175 View commit details
  6. Fix tag prefix logic

    cookpete committed Jan 15, 2018
    Copy the full SHA
    40e40fe View commit details

Commits on Jan 18, 2018

  1. Update mocha to the latest version 🚀 (#26)

    * chore(package): update mocha to version 5.0.0
    
    * chore(package): update lockfile
    
    https://npm.im/greenkeeper-lockfile
    greenkeeper[bot] authored and cookpete committed Jan 18, 2018
    Copy the full SHA
    7e03a76 View commit details
  2. 1.4.0

    cookpete committed Jan 18, 2018
    Copy the full SHA
    d516a91 View commit details
15 changes: 13 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -3,13 +3,24 @@ All notable changes to this project will be documented in this file.

Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).

#### [v1.4.0](https://github.com/CookPete/auto-changelog/compare/v1.3.0...v1.4.0)
> 18 January 2018
- Update mocha to the latest version 🚀 [`#26`](https://github.com/CookPete/auto-changelog/pull/26)
- Add support for generating logs without a git remote [`#23`](https://github.com/CookPete/auto-changelog/issues/23)
- Update niceDate tests to avoid timezone issues [`#24`](https://github.com/CookPete/auto-changelog/issues/24) [`#25`](https://github.com/CookPete/auto-changelog/pull/25)
- Add --tag-prefix option [`#22`](https://github.com/CookPete/auto-changelog/issues/22)
- Change use of "origin" to "remote" [`7c9c175`](https://github.com/CookPete/auto-changelog/commit/7c9c1757257be10e3beae072941d119d195becd7)
- Add --ignore-commit-pattern option [`8cbe121`](https://github.com/CookPete/auto-changelog/commit/8cbe121e857b8d99361e65fa462d035610f741c9)
- Fix tag prefix logic [`40e40fe`](https://github.com/CookPete/auto-changelog/commit/40e40fe303f00f28f6118c933c9a93913d46c7ad)

#### [v1.3.0](https://github.com/CookPete/auto-changelog/compare/v1.2.2...v1.3.0)
> 21 December 2017
- Update fs-extra to the latest version 🚀 [`#19`](https://github.com/CookPete/auto-changelog/pull/19)
- Add --starting-commit option [`#21`](https://github.com/CookPete/auto-changelog/issues/21)
- Add --issue-pattern option [`#18`](https://github.com/CookPete/auto-changelog/issues/18)
- Add --latest-version option [`#17`](https://github.com/CookPete/auto-changelog/issues/17)
- Override package options with command line options [`beef893`](https://github.com/CookPete/auto-changelog/commit/beef89343d95022b14a415b0188c0c2a36bd25af)
- Update tests [`819fe7d`](https://github.com/CookPete/auto-changelog/commit/819fe7d9c311ce8899206847bf901fa5e8a48055)
- Override package options with command line options [`20e3962`](https://github.com/CookPete/auto-changelog/commit/20e3962175c8168ec5494d1527b16e996e113777)
- Update tests [`2a74da4`](https://github.com/CookPete/auto-changelog/commit/2a74da474c3366f740cedb1c31a9c2539a1c9261)

#### [v1.2.2](https://github.com/CookPete/auto-changelog/compare/v1.2.1...v1.2.2)
> 5 December 2017
33 changes: 21 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -25,18 +25,20 @@ Usage: auto-changelog [options]

Options:

-o, --output [file] # output file, default: CHANGELOG.md
-t, --template [template] # specify template to use [compact, keepachangelog, json], default: compact
-r, --remote [remote] # specify git remote to use for links, default: origin
-p, --package # use version from package.json as latest release
-v, --latest-version [version] # use specified version as latest release
-u, --unreleased # include section for unreleased changes
-l, --commit-limit [count] # number of commits to display per release, default: 3
-i, --issue-url [url] # override url for issues, use {id} for issue id
--issue-pattern [regex] # override regex pattern for issues in commit messages
--starting-commit [hash] # starting commit to use for changelog generation
-V, --version # output the version number
-h, --help # output usage information
-o, --output [file] # output file, default: CHANGELOG.md
-t, --template [template] # specify template to use [compact, keepachangelog, json], default: compact
-r, --remote [remote] # specify git remote to use for links, default: origin
-p, --package # use version from package.json as latest release
-v, --latest-version [version] # use specified version as latest release
-u, --unreleased # include section for unreleased changes
-l, --commit-limit [count] # number of commits to display per release, default: 3
-i, --issue-url [url] # override url for issues, use {id} for issue id
--issue-pattern [regex] # override regex pattern for issues in commit messages
--ignore-commit-pattern [regex] # pattern to ignore when parsing commits
--starting-commit [hash] # starting commit to use for changelog generation
--tag-prefix [prefix] # prefix used in version tags, default: v
-V, --version # output the version number
-h, --help # output usage information


# Write log to CHANGELOG.md in current directory
@@ -65,6 +67,13 @@ By default, changelogs will link to the appropriate pages for commits, issues an
auto-changelog --issue-url https://www.redmine.org/issues/{id}
```

Use `--tag-prefix [prefix]` if you prefix your version tags with a certain string:

```bash
# When all versions are tagged like my-package/1.2.3
auto-changelog --tag-prefix my-package/
```

You can also set any option in `package.json` under the `auto-changelog` key, using camelCase options:

```js
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "auto-changelog",
"version": "1.3.0",
"version": "1.4.0",
"description": "Command line tool for generating a changelog from git tags and commit history",
"bin": {
"auto-changelog": "./lib/index.js"
@@ -58,7 +58,7 @@
"chai": "^4.1.2",
"codecov": "^3.0.0",
"cross-env": "^5.1.0",
"mocha": "^4.0.1",
"mocha": "^5.0.0",
"nyc": "^11.2.1",
"snazzy": "^7.0.0",
"standard": "^10.0.3"
20 changes: 17 additions & 3 deletions scripts/generate-test-data.js
Original file line number Diff line number Diff line change
@@ -9,20 +9,34 @@ const parseCommits = __get__('parseCommits')

const DATA_DIR = join(__dirname, '..', 'test', 'data')

const origin = {
const remote = {
hostname: 'github.com',
url: 'https://github.com/user/repo'
}

async function run () {
const gitLog = await readFile(join(DATA_DIR, 'git-log.txt'), 'utf-8')
const commits = parseCommits(gitLog, origin)
const releases = parseReleases(commits, origin, null, { unreleased: false, commitLimit: 3 })
const commits = parseCommits(gitLog, remote, { tagPrefix: '' })
const releases = parseReleases(commits, remote, null, { unreleased: false, commitLimit: 3 })
await writeFile(join(DATA_DIR, 'commits.js'), 'export default ' + JSON.stringify(commits, null, 2))
await writeFile(join(DATA_DIR, 'commits-no-remote.js'), 'export default ' + JSON.stringify(commitsWithoutLinks(commits), null, 2))
await writeFile(join(DATA_DIR, 'releases.js'), 'export default ' + JSON.stringify(releases, null, 2))
await writeFile(join(DATA_DIR, 'template-compact.md'), await compileTemplate('compact', { releases }))
await writeFile(join(DATA_DIR, 'template-keepachangelog.md'), await compileTemplate('keepachangelog', { releases }))
await writeFile(join(DATA_DIR, 'template-json.json'), await compileTemplate('json', { releases }))
}

function commitsWithoutLinks (commits) {
return commits.map(commit => {
const merge = commit.merge ? { ...commit.merge, href: null } : null
const fixes = commit.fixes ? commit.fixes.map(fix => ({ ...fix, href: null })) : null
return {
...commit,
href: null,
fixes,
merge
}
})
}

run().catch(e => console.error(e))
78 changes: 49 additions & 29 deletions src/commits.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import semver from 'semver'

import { cmd, isLink } from './utils'

const COMMIT_SEPARATOR = '__AUTO_CHANGELOG_COMMIT_SEPARATOR__'
const MESSAGE_SEPARATOR = '__AUTO_CHANGELOG_MESSAGE_SEPARATOR__'
const LOG_FORMAT = COMMIT_SEPARATOR + '%H%n%D%n%aI%n%an%n%ae%n%B' + MESSAGE_SEPARATOR
const MATCH_COMMIT = /(.*)\n(.*)\n(.*)\n(.*)\n(.*)\n([\S\s]+)/
const MATCH_STATS = /(\d+) files? changed(?:, (\d+) insertions?...)?(?:, (\d+) deletions?...)?/
const TAG_PREFIX = 'tag: '

// https://help.github.com/articles/closing-issues-via-commit-messages
const DEFAULT_FIX_PATTERN = /(?:close[sd]?|fixe?[sd]?|resolve[sd]?)\s(?:#(\d+)|(https?:\/\/.+?\/(?:issues|pull|pull-requests|merge_requests)\/(\d+)))/gi
@@ -17,16 +18,22 @@ const MERGE_PATTERNS = [
/Merge branch .+ into .+\n\n(.+)[\S\s]+See merge request !(\d+)/ // GitLab merge
]

export async function fetchCommits (origin, options) {
export async function fetchCommits (remote, options) {
const log = await cmd(`git log --shortstat --pretty=format:${LOG_FORMAT}`)
return parseCommits(log, origin, options)
return parseCommits(log, remote, options)
}

function parseCommits (string, origin, options = {}) {
function parseCommits (string, remote, options = {}) {
const commits = string
.split(COMMIT_SEPARATOR)
.slice(1)
.map(commit => parseCommit(commit, origin, options))
.map(commit => parseCommit(commit, remote, options))
.filter(commit => {
if (options.ignoreCommitPattern) {
return new RegExp(options.ignoreCommitPattern).test(commit.subject) === false
}
return true
})

if (options.startingCommit) {
const index = commits.findIndex(c => c.hash.indexOf(options.startingCommit) === 0)
@@ -39,7 +46,7 @@ function parseCommits (string, origin, options = {}) {
return commits
}

function parseCommit (commit, origin, options = {}) {
function parseCommit (commit, remote, options = {}) {
const [, hash, refs, date, author, email, tail] = commit.match(MATCH_COMMIT)
const [message, stats] = tail.split(MESSAGE_SEPARATOR)
return {
@@ -48,21 +55,25 @@ function parseCommit (commit, origin, options = {}) {
author,
email,
date,
tag: getTag(refs),
tag: getTag(refs, options),
subject: getSubject(message),
message: message.trim(),
fixes: getFixes(message, origin, options),
merge: getMerge(message, origin),
href: getCommitLink(hash, origin),
fixes: getFixes(message, remote, options),
merge: getMerge(message, remote),
href: getCommitLink(hash, remote),
...getStats(stats.trim())
}
}

function getTag (refs) {
function getTag (refs, options) {
if (!refs) return null
for (let ref of refs.split(', ')) {
if (ref.indexOf(TAG_PREFIX) === 0) {
return ref.replace(TAG_PREFIX, '')
const prefix = `tag: ${options.tagPrefix}`
if (ref.indexOf(prefix) === 0) {
const version = ref.replace(prefix, '')
if (semver.valid(version)) {
return version
}
}
}
return null
@@ -82,14 +93,14 @@ function getStats (stats) {
}
}

function getFixes (message, origin, options = {}) {
function getFixes (message, remote, options = {}) {
const pattern = getFixPattern(options)
let fixes = []
let match = pattern.exec(message)
if (!match) return null
while (match) {
const id = getFixID(match)
const href = getIssueLink(match, id, origin, options.issueUrl)
const href = getIssueLink(match, id, remote, options.issueUrl)
fixes.push({ id, href })
match = pattern.exec(message)
}
@@ -112,7 +123,7 @@ function getFixPattern (options) {
return DEFAULT_FIX_PATTERN
}

function getMerge (message, origin, mergeUrl) {
function getMerge (message, remote, mergeUrl) {
for (let pattern of MERGE_PATTERNS) {
const match = message.match(pattern)
if (match) {
@@ -121,36 +132,45 @@ function getMerge (message, origin, mergeUrl) {
return {
id,
message,
href: getMergeLink(id, origin, mergeUrl)
href: getMergeLink(id, remote, mergeUrl)
}
}
}
return null
}

function getCommitLink (hash, origin) {
if (origin.hostname === 'bitbucket.org') {
return `${origin.url}/commits/${hash}`
function getCommitLink (hash, remote) {
if (!remote) {
return null
}
return `${origin.url}/commit/${hash}`
if (remote.hostname === 'bitbucket.org') {
return `${remote.url}/commits/${hash}`
}
return `${remote.url}/commit/${hash}`
}

function getIssueLink (match, id, origin, issueUrl) {
function getIssueLink (match, id, remote, issueUrl) {
if (!remote) {
return null
}
if (isLink(match[2])) {
return match[2]
}
if (issueUrl) {
return issueUrl.replace('{id}', id)
}
return `${origin.url}/issues/${id}`
return `${remote.url}/issues/${id}`
}

function getMergeLink (id, origin) {
if (origin.hostname === 'bitbucket.org') {
return `${origin.url}/pull-requests/${id}`
function getMergeLink (id, remote) {
if (!remote) {
return null
}
if (remote.hostname === 'bitbucket.org') {
return `${remote.url}/pull-requests/${id}`
}
if (origin.hostname === 'gitlab.com') {
return `${origin.url}/merge_requests/${id}`
if (remote.hostname === 'gitlab.com') {
return `${remote.url}/merge_requests/${id}`
}
return `${origin.url}/pull/${id}`
return `${remote.url}/pull/${id}`
}
17 changes: 0 additions & 17 deletions src/origin.js

This file was deleted.

17 changes: 10 additions & 7 deletions src/releases.js
Original file line number Diff line number Diff line change
@@ -4,15 +4,15 @@ import { niceDate } from './utils'

const MERGE_COMMIT_PATTERN = /^Merge (remote-tracking )?branch '.+'/

export function parseReleases (commits, origin, latestVersion, options) {
export function parseReleases (commits, remote, latestVersion, options) {
let release = newRelease(latestVersion)
const releases = []
for (let commit of commits) {
if (commit.tag && semver.valid(commit.tag)) {
if (commit.tag) {
if (release.tag || options.unreleased) {
releases.push({
...release,
href: getCompareLink(commit.tag, release.tag || 'HEAD', origin),
href: getCompareLink(commit.tag, release.tag || 'HEAD', remote),
commits: release.commits.sort(sortCommits),
major: commit.tag && release.tag && semver.diff(commit.tag, release.tag) === 'major'
})
@@ -67,11 +67,14 @@ function filterCommit (commit, release, limit) {
return release.commits.length < limit
}

function getCompareLink (from, to, origin) {
if (origin.hostname === 'bitbucket.org') {
return `${origin.url}/compare/${to}%0D${from}`
function getCompareLink (from, to, remote) {
if (!remote) {
return null
}
return `${origin.url}/compare/${from}...${to}`
if (remote.hostname === 'bitbucket.org') {
return `${remote.url}/compare/${to}%0D${from}`
}
return `${remote.url}/compare/${from}...${to}`
}

function sortCommits (a, b) {
19 changes: 19 additions & 0 deletions src/remote.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import parseRepoURL from 'parse-github-url'

import { cmd } from './utils'

export async function fetchRemote (name) {
const remoteURL = await cmd(`git config --get remote.${name}.url`)
if (!remoteURL) {
console.warn(`Warning: Git remote ${name} was not found`)
console.warn(`Warning: Changelog will not contain links to commits, issues, or PRs`)
return null
}
const remote = parseRepoURL(remoteURL)
const protocol = remote.protocol === 'http:' ? 'http:' : 'https:'
const hostname = remote.hostname || remote.host
return {
hostname,
url: `${protocol}//${hostname}/${remote.repo}`
}
}
Loading