Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: switch to ESM, dedupe more code w/ changelog-maker #43

Merged
merged 2 commits into from
Jan 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/test-and-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node: [12, 14, 16]
node: [14, 16]
# windows support not quite ready: os: [ubuntu-latest, windows-latest]
os: [ubuntu-latest]

Expand All @@ -28,7 +28,7 @@ jobs:
- run: git --version

- name: Install npm dependencies
run: npm ci
run: npm install

- name: Run tests
run: npm test
Expand Down
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

## Usage

**`$ branch-diff [--simple] [--group] [--patch-only] base-branch comparison-branch`**
**`$ branch-diff [--sha] [--plaintext] [--markdown] [--group] [--reverse] [--patch-only] base-branch comparison-branch`**

A commit is considered to be in the comparison-branch but not in the base-branch if:

Expand All @@ -29,12 +29,16 @@ But the comparison isn't quite as strict, generally leading to a shorter list of
* `--exclude-label`: Exclude any commits from the list that come from a GitHub pull request with the given label. Multiple `--exclude-label` options may be provided, they will also be split by `,`. e.g. `--exclude-label=semver-major,meta`.
* `--require-label`: Only include commits in the list that come from a GitHub pull request with the given label. Multiple `--require-label` options may be provided, they will also be split by `,`. e.g. `--require-label=test,doc`.
* `--patch-only`: An alias for `--exclude-label=semver-major,semver-minor`.
* `--format`: Dictates what formatting the output will have. Possible options are: `simple`, `plaintext`, and `sha`. The default is to print markdown-formatted output; `plaintext` also implies that commits will be grouped.
* `--format`: Dictates what formatting the output will have. Possible options are: `simple`, `markdown`, `plaintext`, and `sha`. The default is to print a `simple` output suitable for stdout.
- `simple`: Don't print full markdown output, good for console printing without the additional fluff.
- `sha`: Print only the 10-character truncated commit shasums. Good for piping though additional tooling, such as `xargs git cherry-pick` for applying commits.
* `--simple` or `-s`: An alias for `--format=simple`.
- `sha`: Print only the 10-character truncated commit hashes. Good for piping though additional tooling, such as `xargs git cherry-pick` for applying commits.
- `plaintext`: A very simple form, without commit details, implies `--group`.
- `markdown`: A Markdown formatted from, with links and proper escaping.
* `--sha`: Same as `--format=sha`.
* `--plaintext`: Same as `--format=plaintext`.
* `--markdown`: Same as `--format=markdown`.
* `--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`.
* `--reverse`: Reverse the results, this is especially useful when piping output to `xargs`
* `--reverse`: Reverse the results, this is especially useful when piping output to `xargs`.
* `--commit-url`:A URL template which will be used to generate commit URLs for a repository not hosted in GitHub. `{ref}` is the placeholder that will be replaced with the commit, i.e. `--commit-url=https://gitlab.com/myUser/myRepo/commit/{ref}`. `{ghUser}` and `{ghRepo}` are available if they can be derived from package.json (Gitlab and Bitbucket URLs should be understood in package.json).

## License
Expand Down
197 changes: 59 additions & 138 deletions branch-diff.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
#!/usr/bin/env node

'use strict'

const fs = require('fs')
const path = require('path')
const commitStream = require('commit-stream')
const split2 = require('split2')
const listStream = require('list-stream')
const pkgtoId = require('pkg-to-id')
const stripAnsi = require('strip-ansi')
const map = require('map-async')
const { commitToOutput } = require('changelog-maker/commit-to-output')
const collectCommitLabels = require('changelog-maker/collect-commit-labels')
const groupCommits = require('changelog-maker/group-commits')
const { isReleaseCommit, toGroups } = require('changelog-maker/groups')
const gitexec = require('gitexec')

import fs from 'fs'
import path from 'path'
import process from 'process'
import { pipeline as _pipeline } from 'stream'
import { promisify } from 'util'
import commitStream from 'commit-stream'
import split2 from 'split2'
import pkgtoId from 'pkg-to-id'
import minimist from 'minimist'
import { isReleaseCommit } from 'changelog-maker/groups'
import { processCommits } from 'changelog-maker/process-commits'
import gitexec from 'gitexec'

const pipeline = promisify(_pipeline)
const pkgFile = path.join(process.cwd(), 'package.json')
const pkgData = fs.existsSync(pkgFile) ? require(pkgFile) : {}
const pkgId = pkgtoId(pkgData)
Expand All @@ -26,23 +24,6 @@ const ghId = {
user: pkgId.user || 'nodejs',
repo: pkgId.name || 'node'
}
const defaultCommitUrl = 'https://github.com/{ghUser}/{ghRepo}/commit/{ref}'

const formatType = {
PLAINTEXT: 'plaintext',
MARKDOWN: 'markdown',
SIMPLE: 'simple',
SHA: 'sha'
}

const getFormat = (argv) => {
if (argv.format && Object.values(formatType).includes(argv.format)) {
return argv.format
} else if (argv.simple || argv.s) {
return formatType.SIMPLE
}
return formatType.MARKDOWN
}

function replace (s, m) {
Object.keys(m).forEach(function (k) {
Expand All @@ -51,37 +32,26 @@ function replace (s, m) {
return s
}

function branchDiff (branch1, branch2, options, callback) {
export async function branchDiff (branch1, branch2, options) {
if (!branch1 || !branch2) {
return callback(new Error('Must supply two branch names to compare'))
throw new Error('Must supply two branch names to compare')
}

const repoPath = options.repoPath || process.cwd()

findMergeBase(repoPath, branch1, branch2, (err, commit) => {
if (err) { return callback(err) }
map(
[branch1, branch2], (branch, callback) => {
collect(repoPath, branch, commit, branch === branch2 && options.endRef).pipe(listStream.obj(callback))
}
, (err, branchCommits) => err ? callback(err) : diffCollected(options, branchCommits, callback)
)
})
const commit = await findMergeBase(repoPath, branch1, branch2)
const branchCommits = await Promise.all([branch1, branch2].map(async (branch) => {
return collect(repoPath, branch, commit, branch === branch2 && options.endRef)
}))
return await diffCollected(options, branchCommits)
}

function findMergeBase (repoPath, branch1, branch2, callback) {
async function findMergeBase (repoPath, branch1, branch2) {
const gitcmd = `git merge-base ${branch1} ${branch2}`

gitexec.execCollect(repoPath, gitcmd, (err, data) => {
if (err) {
return callback(err)
}

callback(null, data.substr(0, 10))
})
const data = await promisify(gitexec.execCollect)(repoPath, gitcmd)
return data.substr(0, 10)
}

function diffCollected (options, branchCommits, callback) {
async function diffCollected (options, branchCommits) {
function isInList (commit) {
return branchCommits[0].some((c) => {
if (commit.sha === c.sha) { return true }
Expand All @@ -102,102 +72,55 @@ function diffCollected (options, branchCommits, callback) {

let list = branchCommits[1].filter((commit) => !isInList(commit))

collectCommitLabels(list, (err) => {
if (err) {
return callback(err)
}

if (options.excludeLabels.length > 0) {
list = list.filter((commit) => {
return !commit.labels || !commit.labels.some((label) => {
return options.excludeLabels.indexOf(label) >= 0
})
})
}

if (options.requireLabels.length > 0) {
list = list.filter((commit) => {
return commit.labels && commit.labels.some((label) => {
return options.requireLabels.indexOf(label) >= 0
})
if (options.excludeLabels.length > 0) {
list = list.filter((commit) => {
return !commit.labels || !commit.labels.some((label) => {
return options.excludeLabels.indexOf(label) >= 0
})
}

if (options.group) {
list = groupCommits(list)
}

callback(null, list)
})
}

function printCommits (list, format, reverse, commitUrl) {
if (format === formatType.SHA) {
list = list.map((commit) => `${commit.sha.substr(0, 10)}`)
} else if (format === formatType.SIMPLE) {
list = list.map((commit) => commitToOutput(commit, formatType.SIMPLE, ghId, commitUrl))
} else if (format === formatType.PLAINTEXT) {
// Plaintext format implies grouping.
list = groupCommits(list)

const formatted = []
let currentGroup
for (const commit of list) {
const commitGroup = toGroups(commit.summary)
if (currentGroup !== commitGroup) {
formatted.push(`${commitGroup}:`)
currentGroup = commitGroup
}
formatted.push(commitToOutput(commit, formatType.PLAINTEXT, ghId, commitUrl))
}
list = formatted
} else {
list = list.map((commit) => {
return commitToOutput(commit, formatType.MARKDOWN, ghId, commitUrl)
})
}

if (reverse) {
list = list.reverse()
}

let out = list.join('\n') + '\n'

if (!process.stdout.isTTY) {
out = stripAnsi(out)
if (options.requireLabels.length > 0) {
list = list.filter((commit) => {
return commit.labels && commit.labels.some((label) => {
return options.requireLabels.indexOf(label) >= 0
})
})
}

process.stdout.write(out)
return list
}

function collect (repoPath, branch, startCommit, endRef) {
async function collect (repoPath, branch, startCommit, endRef) {
const endrefcmd = endRef && replace(refcmd, { ref: endRef })
const untilcmd = endRef ? replace(commitdatecmd, { refcmd: endrefcmd }) : ''
const _gitcmd = replace(gitcmd, { branch, startCommit, untilcmd })

return gitexec.exec(repoPath, _gitcmd)
.pipe(split2())
.pipe(commitStream(ghId.user, ghId.repo))
const commitList = []
await pipeline(
gitexec.exec(repoPath, _gitcmd),
split2(),
commitStream(ghId.user, ghId.repo),
async function * (source) {
for await (const commit of source) {
commitList.push(commit)
}
})
return commitList
}

module.exports = branchDiff

function main () {
async function main () {
const minimistConfig = {
boolean: ['version', 'group', 'patch-only', 'simple', 'filter-release', 'reverse']
}
const argv = require('minimist')(process.argv.slice(2), minimistConfig)
const argv = minimist(process.argv.slice(2), minimistConfig)
const branch1 = argv._[0]
const branch2 = argv._[1]
const reverse = argv.reverse
const group = argv.group || argv.g
const endRef = argv['end-ref']
const commitUrl = argv['commit-url'] || defaultCommitUrl
let excludeLabels = []
let requireLabels = []

const format = getFormat(argv)

if (argv.version || argv.v) {
return console.log(`v ${require('./package.json').version}`)
}
Expand Down Expand Up @@ -227,17 +150,15 @@ function main () {
endRef
}

branchDiff(branch1, branch2, options, (err, list) => {
if (err) { throw err }

if (argv['filter-release']) {
list = list.filter((commit) => !isReleaseCommit(commit.summary))
}
let list = await branchDiff(branch1, branch2, options)
if (argv['filter-release']) {
list = list.filter((commit) => !isReleaseCommit(commit.summary))
}

printCommits(list, format, reverse, commitUrl)
})
await processCommits(argv, ghId, list)
}

if (require.main === module) {
main()
}
main().catch((err) => {
console.error(err)
process.exit(1)
})
Loading