Skip to content

Commit

Permalink
feat: replace with the changelog task from karma
Browse files Browse the repository at this point in the history
The main thing I care about is the output, here's an example:
https://github.com/karma-runner/karma/blob/master/CHANGELOG.md

Couple of differences in the output:

- bigger header for major releases
- if a scope has only one change, it's on a single line, otherwise nested lists
- no scoped changes
- links to closed issues
- anchor/link to each release

Codewise:

- there are tests and travis build
- the code itself is grunt agnostic

Changes:
- the template is not configurable - I think having configurable template (as this plugin has now) is better, however during refactoring I realized that it makes it very hard to achieve the formatting I want (eg. different header for minor/patch release). Also, the template currently used is pretty much imperative JS anyway, so I went back to the original imperative style. I think we can allow people to configure individual template fragments, rather than the whole template (eg. minorHeaderTpl, patchHeaerTpl, changeItemTpl, etc...)

- **by default the output is written to CHANGELOG.md** - I think this is the 99% use case and therefore reasonable default; also this version does output some logging, which would clobber the actual output (however, the code is agnostic of writing into file, this is only done in the wrapper grunt task; so we can easily change this back)

- **adding editor option** - you can set this to say `sublime -w` and during the release process, you can amend the auto generated changelog before commiting

- **removing `enforce` option** - the git commit hook should not be installed during generating the
  changelog, we can add another task for it
  • Loading branch information
vojtajina committed Jul 9, 2013
1 parent 7a73fb2 commit 25a01c7
Show file tree
Hide file tree
Showing 10 changed files with 439 additions and 201 deletions.
3 changes: 3 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.*
test
Gruntfile.js
15 changes: 12 additions & 3 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,26 @@ module.exports = function (grunt) {
tagName: 'v<%= version %>'
}
},
changelog: {

simplemocha: {
options: {
dest: 'CHANGELOG.md'
ui: 'bdd',
reporter: 'dot'
},
unit: {
src: [
'test/**/*.coffee'
]
}
}
});

grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-release');
grunt.loadNpmTasks('grunt-simple-mocha');

grunt.loadTasks('tasks');

grunt.registerTask('default', ['jshint']);
grunt.registerTask('default', ['jshint', 'test']);
grunt.registerTask('test', ['simplemocha']);
};
232 changes: 232 additions & 0 deletions lib/changelog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
// TODO(vojta): report errors, currently Q silence everything which really sucks
// TODO(vojta): use grunt logger
// TODO(vojta): nicer breaking changes (https://github.com/angular/angular.js/commit/07a58dd7669431d33b61f8c3213c31eff744d02a)
// TODO(vojta): ignore "Merge pull request..." messages, or ignore them in git log ? (--no-merges)

var child = require('child_process');
var util = require('util');
var q = require('qq');


var GIT_LOG_CMD = 'git log --grep="%s" -E --format=%s %s..HEAD';
var GIT_TAG_CMD = 'git describe --tags --abbrev=0';

var EMPTY_COMPONENT = '$$';
var MAX_SUBJECT_LENGTH = 80;

var PATTERN = /^(\w*)(\(([\w\$\.\-\*]*)\))?\: (.*)$/;

var warn = function() {
console.log('WARNING:', util.format.apply(null, arguments));
};

var log = function() {
console.log(util.format.apply(null, arguments));
};


var parseRawCommit = function(raw) {
if (!raw) {
return null;
}

var lines = raw.split('\n');
var msg = {}, match;

msg.hash = lines.shift();
msg.subject = lines.shift();
msg.closes = [];
msg.breaks = [];

lines.forEach(function(line) {
match = line.match(/(?:Closes|Fixes)\s#(\d+)/);
if (match) {
msg.closes.push(parseInt(match[1], 10));
}
});

match = raw.match(/BREAKING CHANGE:\s([\s\S]*)/);
if (match) {
msg.breaks.push(match[1]);
}


msg.body = lines.join('\n');
match = msg.subject.match(PATTERN);

if (!match || !match[1] || !match[4]) {
warn('Incorrect message: %s %s', msg.hash, msg.subject);
return null;
}

if (match[4].length > MAX_SUBJECT_LENGTH) {
warn('Too long subject: %s %s', msg.hash, msg.subject);
match[4] = match[4].substr(0, MAX_SUBJECT_LENGTH);
}

msg.type = match[1];
msg.component = match[3];
msg.subject = match[4];

return msg;
};


var currentDate = function() {
var now = new Date();
var pad = function(i) {
return ('0' + i).substr(-2);
};

return util.format('%d-%s-%s', now.getFullYear(), pad(now.getMonth() + 1), pad(now.getDate()));
};


var readGitLog = function(grep, from) {
log('Reading git log since', from);

var deffered = q.defer();

child.exec(util.format(GIT_LOG_CMD, grep, '%H%n%s%n%b%n==END==', from), function(code, stdout) {
var commits = [];

stdout.split('\n==END==\n').forEach(function(rawCommit) {
var commit = parseRawCommit(rawCommit);
if (commit) {
commits.push(commit);
}
});

log('Parsed %s commits', commits.length);

deffered.resolve(commits);
});

return deffered.promise;
};


var PATCH_HEADER_TPL = '<a name="%s"></a>\n### %s (%s)\n\n';
var MINOR_HEADER_TPL = '<a name="%s"></a>\n## %s (%s)\n\n';
var LINK_ISSUE = '[#%s](%s/issues/%s)';
var LINK_COMMIT = '[%s](%s/commit/%s)';

var Writer = function(stream, githubRepo) {

var linkToIssue = function(issue) {
return util.format(LINK_ISSUE, issue, githubRepo, issue);
};

var linkToCommit = function(hash) {
return util.format(LINK_COMMIT, hash.substr(0, 8), githubRepo, hash);
};

this.header = function(version) {
var header = version.split('.')[2] === '0' ? MINOR_HEADER_TPL : PATCH_HEADER_TPL;
stream.write(util.format(header, version, version, currentDate()));
};

this.section = function(title, section) {
var components = Object.getOwnPropertyNames(section).sort();

if (!components.length) {
return;
}

stream.write(util.format('\n#### %s\n\n', title));

components.forEach(function(name) {
var prefix = '*';
var nested = section[name].length > 1;

if (name !== EMPTY_COMPONENT) {
if (nested) {
stream.write(util.format('* **%s:**\n', name));
prefix = ' *';
} else {
prefix = util.format('* **%s:**', name);
}
}

section[name].forEach(function(commit) {
stream.write(util.format('%s %s (%s', prefix, commit.subject, linkToCommit(commit.hash)));
if (commit.closes.length) {
stream.write(', closes ' + commit.closes.map(linkToIssue).join(', '));
}
stream.write(')\n');
});
});

stream.write('\n');
};
};

var writeChangelog = function(writer, commits, version) {
var sections = {
fix: {},
feat: {},
breaks: {}
};

commits.forEach(function(commit) {
var section = sections[commit.type];
var component = commit.component || EMPTY_COMPONENT;

if (section) {
section[component] = section[component] || [];
section[component].push(commit);
}

commit.breaks.forEach(function(breakMsg) {
sections.breaks[EMPTY_COMPONENT] = sections.breaks[EMPTY_COMPONENT] || [];

sections.breaks[EMPTY_COMPONENT].push({
subject: breakMsg,
hash: commit.hash,
closes: []
});
});
});

writer.header(version);
writer.section('Bug Fixes', sections.fix);
writer.section('Features', sections.feat);
writer.section('Breaking Changes', sections.breaks);
};


var getPreviousTag = function() {
var deffered = q.defer();
child.exec(GIT_TAG_CMD, function(code, stdout) {
if (code) {
deffered.reject('Cannot get the previous tag.');
}
else {
deffered.resolve(stdout.replace('\n', ''));
}
});
return deffered.promise;
};


// PUBLIC API
exports.generate = function(githubRepo, version) {
var buffer = {
data: '',
write: function(str) {
this.data += str;
}
};
var writer = new Writer(buffer, githubRepo);

return getPreviousTag().then(function(tag) {
return readGitLog('^fix|^feat|BREAKING', tag).then(function(commits) {
writeChangelog(writer, commits, version);
return buffer.data;
});
});
};


// publish for testing
exports.parseRawCommit = parseRawCommit;
22 changes: 9 additions & 13 deletions validate-commit-msg.js → lib/validate-commit-msg.js
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,25 @@
*/
var fs = require('fs');
var util = require('util');
var path = require('path');


var MAX_LENGTH = 70;
var PATTERN = /^(?:fixup!\s*)?(\w*)(\(([\w\$\.\-\*/]*)\))?\: (.*)$/;
var IGNORED = /^WIP\:/;
var PATTERN = /^(\w*)(\(([\w\$\.\-\*]*)\))?\: (.*)$/;
var IGNORED = /^(WIP\:|Merge pull request)/;
var TYPES = {
feat: true,
fix: true,
docs: true,
style: true,
refactor: true,
test: true,
chore: true,
revert: true
chore: true
};


var error = function() {
// gitx does not display it
// http://gitx.lighthouseapp.com/projects/17830/tickets/294-feature-display-hook-error-message-when-hook-fails
// https://groups.google.com/group/gitx/browse_thread/thread/a03bcab60844b812
console.error('INVALID COMMIT MSG: ' + util.format.apply(null, arguments));
};
Expand All @@ -51,13 +50,11 @@ var validateMessage = function(message) {
var match = PATTERN.exec(message);

if (!match) {
error('does not match "<type>(<scope>): <subject>" ! was: ' + message);
error('does not match "<type>(<scope>): <subject>" !');
return false;
}

var type = match[1];
var scope = match[3];
var subject = match[4];

if (!TYPES.hasOwnProperty(type)) {
error('"%s" is not allowed type !', type);
Expand All @@ -82,14 +79,13 @@ var firstLineFromBuffer = function(buffer) {
};



// publish for testing
exports.validateMessage = validateMessage;

// hacky start if not run by jasmine :-D
if (process.argv.join('').indexOf('jasmine-node') === -1) {
// lame test if run by git (so that it does not trigger during testing)
if (process.env.GIT_DIR) {
var commitMsgFile = process.argv[2];
var incorrectLogFile = commitMsgFile.replace('COMMIT_EDITMSG', 'logs/incorrect-commit-msgs');
var incorrectLogFile = path.dirname(commitMsgFile) + '/logs/incorrect-commit-msgs';

fs.readFile(commitMsgFile, function(err, buffer) {
var msg = firstLineFromBuffer(buffer);
Expand All @@ -102,4 +98,4 @@ if (process.argv.join('').indexOf('jasmine-node') === -1) {
process.exit(0);
}
});
}
}
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@
"author": "Brian Ford",
"license": "BSD",
"dependencies": {
"shelljs": "~0.1.4"
"qq": "~0.3.5"
},
"devDependencies": {
"grunt": "~0.4.1",
"grunt-release": "~0.3.3"
"grunt-contrib-jshint": "~0.6",
"grunt-release": "~0.3.3",
"grunt-simple-mocha": "~0.4.0",
"chai": "~1.7.2",
"sinon": "~1.7.3"
}
}
Loading

0 comments on commit 25a01c7

Please sign in to comment.