forked from angular/angular.js
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(scripts): add commit-msg hook (validation)
- Loading branch information
Showing
2 changed files
with
177 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,104 @@ | ||
#!/usr/bin/env node | ||
|
||
/** | ||
* Git COMMIT-MSG hook for validating commit message | ||
* See https://docs.google.com/document/d/1rk04jEuGfk9kYzfqCuOlPTSJw3hEDZJTBN5E5f1SALo/edit | ||
* | ||
* Installation: | ||
* >> cd <angular-repo> | ||
* >> ln -s ../../validate-commit-msg.js .git/hooks/commit-msg | ||
*/ | ||
var fs = require('fs'); | ||
var util = require('util'); | ||
|
||
|
||
var MAX_LENGTH = 70; | ||
var PATTERN = /^(\w*)(\(([\w\$\.]*)\))?\: (.*)$/; | ||
var IGNORED = /^WIP\:/; | ||
var TYPES = { | ||
feat: true, | ||
fix: true, | ||
docs: true, | ||
style: true, | ||
refactor: true, | ||
test: 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)); | ||
}; | ||
|
||
|
||
var validateMessage = function(message) { | ||
var isValid = true; | ||
|
||
if (IGNORED.test(message)) { | ||
console.log('Commit message validation ignored.'); | ||
return true; | ||
} | ||
|
||
if (message.length > MAX_LENGTH) { | ||
error('is longer than %d characters !', MAX_LENGTH); | ||
isValid = false; | ||
} | ||
|
||
var match = PATTERN.exec(message); | ||
|
||
if (!match) { | ||
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); | ||
return false; | ||
} | ||
|
||
// Some more ideas, do want anything like this ? | ||
// - allow only specific scopes (eg. fix(docs) should not be allowed ? | ||
// - auto correct the type to lower case ? | ||
// - auto correct first letter of the subject to lower case ? | ||
// - auto add empty line after subject ? | ||
// - auto remove empty () ? | ||
// - auto correct typos in type ? | ||
// - store incorrect messages, so that we can learn | ||
|
||
return isValid; | ||
}; | ||
|
||
|
||
var firstLineFromBuffer = function(buffer) { | ||
return buffer.toString().split('\n').shift(); | ||
}; | ||
|
||
|
||
|
||
// publish for testing | ||
exports.validateMessage = validateMessage; | ||
|
||
// hacky start if not run by jasmine :-D | ||
if (process.argv.join('').indexOf('jasmine-node') === -1) { | ||
var commitMsgFile = process.argv[2]; | ||
var incorrectLogFile = commitMsgFile.replace('COMMIT_EDITMSG', 'logs/incorrect-commit-msgs'); | ||
|
||
fs.readFile(commitMsgFile, function(err, buffer) { | ||
var msg = firstLineFromBuffer(buffer); | ||
|
||
if (!validateMessage(msg)) { | ||
fs.appendFile(incorrectLogFile, msg + '\n', function() { | ||
process.exit(1); | ||
}); | ||
} else { | ||
process.exit(0); | ||
} | ||
}); | ||
} |
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,73 @@ | ||
describe('validate-commit-msg.js', function() { | ||
var m = require('./validate-commit-msg'); | ||
var errors = []; | ||
var logs = []; | ||
|
||
var VALID = true; | ||
var INVALID = false; | ||
|
||
beforeEach(function() { | ||
errors.length = 0; | ||
logs.length = 0; | ||
|
||
spyOn(console, 'error').andCallFake(function(msg) { | ||
errors.push(msg.replace(/\x1B\[\d+m/g, '')); // uncolor | ||
}); | ||
|
||
spyOn(console, 'log').andCallFake(function(msg) { | ||
logs.push(msg.replace(/\x1B\[\d+m/g, '')); // uncolor | ||
}); | ||
}); | ||
|
||
describe('validateMessage', function() { | ||
|
||
it('should be valid', function() { | ||
expect(m.validateMessage('fix($compile): something')).toBe(VALID); | ||
expect(m.validateMessage('feat($location): something')).toBe(VALID); | ||
expect(m.validateMessage('docs($filter): something')).toBe(VALID); | ||
expect(m.validateMessage('style($http): something')).toBe(VALID); | ||
expect(m.validateMessage('refactor($httpBackend): something')).toBe(VALID); | ||
expect(m.validateMessage('test($resource): something')).toBe(VALID); | ||
expect(m.validateMessage('chore($controller): something')).toBe(VALID); | ||
expect(errors).toEqual([]); | ||
}); | ||
|
||
|
||
it('should validate 70 characters length', function() { | ||
var msg = 'fix($compile): something super mega extra giga tera long, maybe even longer... ' + | ||
'way over 80 characters'; | ||
|
||
expect(m.validateMessage(msg)).toBe(INVALID); | ||
expect(errors).toEqual(['INVALID COMMIT MSG: is longer than 70 characters !']); | ||
}); | ||
|
||
|
||
it('should validate "<type>(<scope>): <subject>" format', function() { | ||
var msg = 'not correct format'; | ||
|
||
expect(m.validateMessage(msg)).toBe(INVALID); | ||
expect(errors).toEqual(['INVALID COMMIT MSG: does not match "<type>(<scope>): <subject>" !']); | ||
}); | ||
|
||
|
||
it('should validate type', function() { | ||
expect(m.validateMessage('weird($filter): something')).toBe(INVALID); | ||
expect(errors).toEqual(['INVALID COMMIT MSG: "weird" is not allowed type !']); | ||
}); | ||
|
||
|
||
it('should allow empty scope', function() { | ||
expect(m.validateMessage('fix: blablabla')).toBe(VALID); | ||
}); | ||
|
||
|
||
it('should allow dot in scope', function() { | ||
expect(m.validateMessage('chore(mocks.$httpBackend): something')).toBe(VALID); | ||
}); | ||
|
||
|
||
it('should ignore msg prefixed with "WIP: "', function() { | ||
expect(m.validateMessage('WIP: bullshit')).toBe(VALID); | ||
}); | ||
}); | ||
}); |