Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Generate release notes
- Loading branch information
fcrisci
committed
Feb 25, 2013
0 parents
commit 32a369f
Showing
11 changed files
with
414 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
node_modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
node_modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
## Release Notes | ||
|
||
Generate release note pages from git commit history. | ||
|
||
### Installation | ||
|
||
It's preferable to install it globally through `npm` | ||
|
||
npm install -g release-notes | ||
|
||
### Usage | ||
|
||
The basic usage is | ||
|
||
cd <your_git_project> | ||
release-notes <since>..<until> <template> | ||
|
||
Where | ||
|
||
* `<since>..<until>` specifies the range of commits as in `git log`, see [gitrevisions(7)](http://www.kernel.org/pub/software/scm/git/docs/gitrevisions.html) | ||
* `<template>` is an [ejs](https://github.com/visionmedia/ejs) template file used to generate the release notes | ||
|
||
Two templates are included as reference, `markdown` and `html`. | ||
|
||
This are for instance the release notes generated from `joyent/node` running | ||
|
||
release-notes v0.9.8..v0.9.9 html > changelog.html | ||
|
||
<a href="https://github.com/ariatemplates/release-notes/raw/master/templates/node.png" target="_blank"><img src="https://github.com/ariatemplates/release-notes/raw/master/templates/node_thumb.png" alt="Node's release notes"></a> | ||
|
||
#### Custom template | ||
|
||
The second parameter of `release-notes` can be any path to a valid ejs template files. | ||
|
||
The only available template local variable is `commits` that is an array of commits, each containing | ||
|
||
* `sha1` commit hash (%H) | ||
* `authorName` author name (%an) | ||
* `authorEmail` author email (%ae) | ||
* `authorDate` author date (%aD) | ||
* `committerName` committer name (%cn) | ||
* `committerEmail` committer email (%ce) | ||
* `committerDate` committer date (%cD) | ||
* `title` subject (%s) | ||
* `messageLines` array of body lines (%b) | ||
|
||
|
||
### Options | ||
|
||
More advanced options are | ||
|
||
* `p` or `path` Git project path, defaults to the current working path | ||
* `b` or `branch` Git branch, defaults to `master` | ||
* `t` or `title` Regular expression to parse the commit title (see next chapter) | ||
* `m` or `meaning` Meaning of capturing block in title's regular expression | ||
* `f` or `file` JSON Configuration file, better option when you don't want to pass all parameters to the command line, for an example see [options.json]() | ||
|
||
#### Title Parsing | ||
|
||
Some projects might have special naming conventions for the commit title. | ||
|
||
The options `t` and `m` allow to specify this logic and extract additional information from the title. | ||
|
||
For instance, [Aria Templates]() has the following convention | ||
|
||
fix #123 Title of a bug fix commit | ||
feat #234 Title of a cool new feature | ||
|
||
In this case using | ||
|
||
release-notes -t "^([a-z]+) #(\d+) (.*)$" -m type -m issue -m title v1.3.6..HEAD html | ||
|
||
generates the additional fields on the commit object | ||
|
||
* `type` first capturing block | ||
* `issue` second capturing block | ||
* `title` third capturing block (redefines the title) | ||
|
||
|
||
Another project using similar conventions is [AngularJs](https://github.com/angular/angular.js), [commit message conventions](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#). | ||
|
||
release-notes -t "^(\w*)(?:\(([\w\$\.]*)\))?\: (.*)$" -m type -m scope -m title v1.1.2..v1.1.3 markdown |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
#!/usr/bin/env node | ||
var argv = require("optimist").usage("release-notes [<options>] <since>..<until> <template>") | ||
.options("f", { | ||
"alias" : "file" | ||
}) | ||
.options("p", { | ||
"alias" : "path", | ||
"default" : process.cwd() | ||
}) | ||
.options("t", { | ||
"alias" : "title", | ||
"default" : "(.*)" | ||
}) | ||
.options("m", { | ||
"alias" : "meaning", | ||
"default" : ['type'] | ||
}) | ||
.options("b", { | ||
"alias" : "branch", | ||
"default" : "master" | ||
}) | ||
.describe({ | ||
"f" : "Configuration file", | ||
"p" : "Git project path", | ||
"t" : "Commit title regular expression", | ||
"m" : "Meaning of capturing block in title's regular expression", | ||
"b" : "Git branch, defaults to master" | ||
}) | ||
.boolean("version") | ||
.check(function (argv) { | ||
if (argv._.length == 2) { | ||
return true; | ||
} | ||
throw "Invalid parameters, please specify an interval and the template"; | ||
}) | ||
.argv; | ||
|
||
var git = require("./lib/git"); | ||
var fs = require("fs"); | ||
var ejs = require("ejs"); | ||
var path = require("path"); | ||
|
||
var template = argv._[1]; | ||
if (!fs.existsSync(template)) { | ||
// Template name? | ||
if (template.match(/[a-z]+(\.ejs)?/)) { | ||
template = path.resolve(__dirname, "./templates/" + path.basename(template, ".ejs") + ".ejs"); | ||
} else { | ||
require("optimist").showHelp(); | ||
console.error("\nUnable to locate template file " + template); | ||
process.exit(1); | ||
} | ||
} | ||
fs.readFile(template, function (err, templateContent) { | ||
if (err) { | ||
require("optimist").showHelp(); | ||
console.error("\nUnable to locate template file " + argv._[1]); | ||
process.exit(5); | ||
} else { | ||
getOptions(function (options) { | ||
git.log({ | ||
branch : options.b, | ||
range : argv._[0], | ||
title : new RegExp(options.t), | ||
meaning : Array.isArray(options.m) ? options.m : [options.m], | ||
cwd : options.p | ||
}, function (commits) { | ||
var output = ejs.render(templateContent.toString(), { | ||
commits : commits | ||
}); | ||
process.stdout.write(output + "\n"); | ||
}); | ||
}); | ||
} | ||
}); | ||
|
||
function getOptions (callback) { | ||
if (argv.f) { | ||
fs.readFile(argv.f, function (err, data) { | ||
if (err) { | ||
console.error("Unable to read configuration file\n" + err.message); | ||
} else { | ||
var options; | ||
try { | ||
var stored = JSON.parse(data); | ||
options = { | ||
b : stored.b || stored.branch || argv.b, | ||
t : stored.t || stored.title || argv.t, | ||
m : stored.m || stored.meaning || argv.m, | ||
p : stored.p || stored.path || argv.p | ||
}; | ||
} catch (ex) { | ||
console.error("Invalid JSON in configuration file"); | ||
} | ||
if (options) { | ||
callback(options); | ||
} | ||
} | ||
}); | ||
} else { | ||
callback(argv); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
exports.log = function (options, callback) { | ||
var spawn = require("child_process").spawn; | ||
var gitArgs = ["log", "--no-color", "--no-merges", "--branches=" + options.branch, "--format=" + formatOptions, options.range]; | ||
var gitLog = spawn("git", gitArgs, { | ||
cwd : options.cwd, | ||
stdio : ["ignore", "pipe", process.stderr] | ||
}); | ||
|
||
var allCommits = ""; | ||
gitLog.stdout.on("data", function (data) { | ||
allCommits += data; | ||
}); | ||
|
||
gitLog.on("exit", function (code) { | ||
if (code === 0) { | ||
// Build the list of commits from git log | ||
var commits = processCommits(allCommits.replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, ''), options); | ||
callback(commits); | ||
} | ||
}); | ||
}; | ||
|
||
var newCommit = "___"; | ||
var formatOptions = [ | ||
newCommit, "sha1:%H", "authorName:%an", "authorEmail:%ae", "authorDate:%aD", | ||
"committerName:%cn", "committerEmail:%ce", "committerDate:%cD", | ||
"title:%s", "%w(80,1,1)%b" | ||
].join("%n"); | ||
|
||
function processCommits (commitMessages, options) { | ||
// This return an object with the same properties described above | ||
var stream = commitMessages.split("\n"); | ||
var commits = []; | ||
var workingCommit; | ||
stream.forEach(function (rawLine) { | ||
var line = parseLine(rawLine); | ||
if (line.type === "new") { | ||
workingCommit = { | ||
messageLines : [] | ||
}; | ||
commits.push(workingCommit); | ||
} else if (line.type === "message") { | ||
workingCommit.messageLines.push(line.message); | ||
} else if (line.type === "title") { | ||
var title = parseTitle(line.message, options); | ||
for (var prop in title) { | ||
workingCommit[prop] = title[prop]; | ||
} | ||
if (!workingCommit.title) { | ||
// The parser doesn't return a title | ||
workingCommit.title = line.message; | ||
} | ||
} else { | ||
workingCommit[line.type] = line.message; | ||
} | ||
}); | ||
return commits; | ||
} | ||
|
||
function parseLine (line) { | ||
if (line === newCommit) { | ||
return { | ||
type : "new" | ||
}; | ||
} | ||
|
||
var match = line.match(/^([a-zA-Z]+1?)\s?:\s?(.*)$/i); | ||
|
||
if (match) { | ||
return { | ||
type : match[1], | ||
message : match[2].trim() | ||
}; | ||
} else { | ||
return { | ||
type : "message", | ||
message : line.substring(1) // padding | ||
}; | ||
} | ||
} | ||
|
||
function parseTitle (title, options) { | ||
var expression = options.title; | ||
var names = options.meaning; | ||
|
||
var match = title.match(expression); | ||
if (!match) { | ||
return { | ||
title : title | ||
}; | ||
} else { | ||
var builtObject = {}; | ||
for (var i = 0; i < names.length; i += 1) { | ||
var name = names[i]; | ||
var index = i + 1; | ||
builtObject[name] = match[index]; | ||
} | ||
return builtObject; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"title" : "^([a-z]+) #(\\d+) (.*)$", | ||
"meaning" : ["type", "issue", "title"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"author": "ariatemplates <contact@ariatemplates.com> (http://github.com/ariatemplates)", | ||
"name": "release-notes", | ||
"description": "Generate beautiful release notes from a git log.", | ||
"keywords" : ["git", "log", "release notes", "compare", "version"], | ||
"version": "0.0.1", | ||
"dependencies": { | ||
"optimist" : "0.3", | ||
"ejs" : "0.8.x" | ||
}, | ||
"contributors" : { | ||
"name" : "Fabio Crisci", | ||
"email" : "fabio@ariatemplates.com", | ||
"url" : "https://github.com/piuccio" | ||
}, | ||
"bin" : "./index.js", | ||
"repository" : { | ||
"type" : "git", | ||
"url" : "https://github.com/ariatemplates/release-notes" | ||
}, | ||
"preferGlobal" : true | ||
} |
Oops, something went wrong.