| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186 |
1x
1x
1x
1x
1x
1x
1x
1x
1x
13x
14x
13x
13x
2x
2x
11x
11x
1x
1x
10x
2x
2x
8x
8x
2x
2x
6x
6x
6x
6x
6x
6x
6x
6x
2x
2x
6x
1x
1x
6x
1x
1x
6x
1x
1x
6x
1x
1x
8x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
3x
1x
8x
1x
1x
| #!/usr/bin/env node
/**
* 代码参考了 npm package: validate-commit-msg
*/
var fs = require('fs')
var path = require('path')
var util = require('util')
var findup = require('../libs/fs/findup')
// fixup! and squash! are part of Git, commits tagged with them are not intended to be merged, cf. https://git-scm.com/docs/git-commit
var PATTERN = /^((fixup! |squash! )?(\w+)(?:\(([^)\s]+)\))?: (.+))(?:\n|$)/
var MERGE_COMMIT_PATTERN = /^Merge .+/
var IGNORED_PATTERN = new RegExp(util.format('(^WIP)|(^%s$)', require('semver-regex')().source))
var isPackageFileExists = false
var config = {
command: __filename,
warnOnFail: false,
showHelp: false,
maxSubjectLength: 80,
subjectPattern: '.+',
types: ['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'chore', 'revert']
}
// istanbul ignore next
if (module.parent === null) {
// spawn('git', ['rev-parse', '--show-toplevel'], { stdio: ['ignore', 'pipe', 2] });
var dir = findup.git()
try {
var pkg = require(findup.pkg())
var pkgConfig = pkg.config && pkg.config.hooks && pkg.config.hooks['commit-msg']
if (typeof pkgConfig === 'object') for (var k in pkgConfig) config[k] = pkgConfig[k]
isPackageFileExists = true
} catch (e) {}
var commitMsgFile = process.argv[2] || path.join(dir, 'COMMIT_EDITMSG')
var commitErrFile = commitMsgFile.replace('COMMIT_EDITMSG', 'ERROR_COMMIT_EDITMSG')
var msg = fs.readFileSync(commitMsgFile).toString()
if (validate(msg)) {
process.exit(0)
} else {
outputHelp()
fs.appendFileSync(commitErrFile, msg + '\n')
process.exit(1)
}
}
function validate(raw) {
var messageWithBody = (raw || '').split(/\r?\n/).filter(function(str) {
return str.indexOf('#') !== 0
}).join('\n')
var message = messageWithBody.split('\n').shift().trim()
if (message === '') {
console.log('Aborting commit due to empty commit message.')
return false
}
var isValid = true
if (MERGE_COMMIT_PATTERN.test(message)) {
console.log('Merge commit detected.')
return true
}
if (IGNORED_PATTERN.test(message)) {
console.log('Commit message validation ignored.')
return true
}
var match = PATTERN.exec(message)
if (!match) {
error('does not match "<type>(<scope>): <subject>" !')
isValid = false
} else {
var firstLine = match[1]
var squashing = !!match[2]
var type = match[3]
// var scope = match[4]
var subject = match[5].trim()
var firstLetter = subject[0]
var lastLetter = subject[subject.length - 1]
var SUBJECT_PATTERN = new RegExp(config.subjectPattern)
if (firstLine.length > config.maxSubjectLength && !squashing) {
error('is longer than %d characters !', config.maxSubjectLength)
isValid = false
}
if (config.types !== '*' && config.types.indexOf(type) === -1) {
error('"%s" is not allowed type !', type)
isValid = false
}
if (!SUBJECT_PATTERN.exec(subject)) {
error('subject does not match subject pattern !')
isValid = false
}
if (firstLetter.toLowerCase() !== firstLetter) {
error('don\'t capitalize first letter !')
isValid = false
}
if (/[.,!;]/.test(lastLetter)) {
error('no punctuation mark at the end !')
isValid = false
}
}
// Some more ideas, do want anything like this ?
// - Validate the rest of the message (body, footer, BREAKING CHANGE annotations)
// - 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 || config.warnOnFail
}
function outputHelp() {
// istanbul ignore if
if (!config.showHelp) return
console.log('\x1b[90m\nCurrent commit-msg hook config:')
console.log(JSON.stringify(formatObject(config), null, 4).replace(/^|\n/g, '\n '))
// istanbul ignore if
if (isPackageFileExists) {
console.log('\nYou can overwrite the config use `config.hooks.commit-msg` in package.json')
}
console.log('\n============================================================================\n')
console.log(
'Git Commit Message Guides: \n'
+ ' https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit\n'
+ ' http://chris.beams.io/posts/git-commit/'
)
console.log('\nChangelog keywrods: Added, Changed, Breaks, Deprecated, Removed, Fixed, Security')
console.log('\nExample:\n')
console.log(
' fix($compile): couple of unit tests for IE9\n'
+ ' \n'
+ ' Older IEs serialize html uppercased, but IE9 does not...\n'
+ ' Would be better to expect case insensitive, unfortunately jasmine does\n'
+ ' not allow to user regexps for throw expectations.\n'
+ ' \n'
+ ' Closes #392, #400\n'
+ ' Breaks foo.bar api, foo.baz should be used instead'
)
console.log('\x1b[0m')
}
function formatObject(obj) {
obj = JSON.parse(JSON.stringify(obj))
for (var key in obj) {
// istanbul ignore if
if (Array.isArray(obj[key])) obj[key] = '[ ' + obj[key].join(', ') + ' ]'
// istanbul ignore next
else if (typeof obj[key] === 'object') obj[key] = formatObject(obj[key])
}
return obj
}
function error() {
// 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[config.warnOnFail ? 'warn' : 'error']('INVALID COMMIT MSG: ' + util.format.apply(null, arguments))
}
module.exports = validate
module.exports.help = outputHelp
|