| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
- #!/usr/bin/env node
- /**
- * Git COMMIT-MSG hook for validating commit message
- * See https://docs.google.com/document/d/1rk04jEuGfk9kYzfqCuOlPTSJw3hEDZJTBN5E5f1SALo/edit
- *
- * Installation:
- * >> use ghooks, config in package.json
- */
- const fs = require("fs");
- const util = require("util");
- const resolve = require("path").resolve;
- const findup = require("findup");
- function semverRegex() {
- return /(?<=^v?|\sv?)(?:(?:0|[1-9]\d{0,9}?)\.){2}(?:0|[1-9]\d{0,9})(?:-(?:--+)?(?:0|[1-9]\d*|\d*[a-z]+\d*)){0,100}(?=$| |\+|\.)(?:(?<=-\S+)(?:\.(?:--?|[\da-z-]*[a-z-]\d*|0|[1-9]\d*)){1,100}?)?(?!\.)(?:\+(?:[\da-z]\.?-?){1,100}?(?!\w))?(?!\+)/gi;
- }
- const config = getConfig();
- const MAX_LENGTH = config.maxSubjectLength || 100;
- // 忽略commit msg 中 以 ‘WIP、v’开头的字符串或语义化字符串
- const IGNORED = new RegExp(
- util.format("(^WIP)|(^v)|(^%s$)", semverRegex().source)
- );
- function log(...content) {
- return console.log("validate commit msg:", ...content);
- }
- log("校验 commit message");
- // 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
- const PATTERN = /^((fixup! |squash! )?(\w+)(?:\(([^)\s]+)\))?: (.+))(?:\n|$)/;
- const MERGE_COMMIT_PATTERN = /^Merge /;
- const error = function (title, ...argv) {
- // 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
- const type = config.warnOnFail ? "warn" : "error";
- console[type](`validate commit msg: commit message 不符合规范`);
- console[type](`validate commit msg: ${title}`, ...argv);
- };
- const validateMessage = function (raw) {
- console.log(raw);
- // config.types = config.types || resolve(process.cwd(), '.vsc-commitizen.json');
- const types = config.types ? config.types.map((item) => item.label) : null;
- const scopes = config.scopes ? config.scopes.map((item) => item.label) : null;
- const messageWithBody = (raw || "")
- .split("\n")
- .filter((str) => str.indexOf("#") !== 0)
- .join("\n");
- const message = messageWithBody.split("\n").shift();
- if (message === "") {
- log("由于 commit message 为空,所以中止提交。");
- return false;
- }
- let isValid = true;
- if (MERGE_COMMIT_PATTERN.test(message)) {
- log("检测到有 Merge commit。");
- return true;
- }
- if (IGNORED.test(message)) {
- log("忽略 Commit message。");
- return true;
- }
- const match = PATTERN.exec(message);
- if (!match) {
- error(
- `与规范不相符:
- <类型 *>[(<作用域>)]: [<emoji>] <主题 *>
- [空行]
- [详细描述]
- [空行]
- [底部信息]
-
- 规范地址:http://confluence.pri.ibanyu.com/pages/viewpage.action?pageId=1737437#Git&Gitlab工作流-4、Git提交信息格式规范
- 类型、作用域 查看:./.vsc-commitizen.json`
- );
- isValid = false;
- } else {
- const firstLine = match[1];
- const squashing = !!match[2];
- const type = match[3];
- const scope = match[4];
- const subject = match[5];
- const SUBJECT_PATTERN = new RegExp(config.subjectPattern || ".+");
- const SUBJECT_PATTERN_ERROR_MSG =
- config.subjectPatternErrorMsg || "主题不符合规范!";
- if (firstLine.length > MAX_LENGTH && !squashing) {
- error("主题超过 %d 字符限制!", MAX_LENGTH);
- isValid = false;
- }
- if (scopes !== null && scope !== void 0 && scopes.indexOf(scope) === -1) {
- error('"%s" 是无效的scope!请选择以下范围: %s', scope, scopes.join(", "));
- isValid = false;
- }
- if (types !== null && types.indexOf(type) === -1) {
- error('"%s" 是无效的type!请选择以下类型: %s', type, types.join(", "));
- isValid = false;
- }
- if (!SUBJECT_PATTERN.exec(subject)) {
- error(SUBJECT_PATTERN_ERROR_MSG);
- 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
- isValid = isValid || config.warnOnFail;
- if (isValid) {
- // exit early and skip messaging logics
- log("校验完成!👏 👏 👏");
- return true;
- }
- const argInHelp =
- config.helpMessage && config.helpMessage.indexOf("%s") !== -1;
- if (argInHelp) {
- log(config.helpMessage, messageWithBody);
- } else if (message) {
- log(message);
- }
- if (!argInHelp && config.helpMessage) {
- log(config.helpMessage);
- }
- return false;
- };
- // publish for testing
- exports.validateMessage = validateMessage;
- exports.getGitFolder = getGitFolder;
- exports.config = config;
- // hacky start if not run by mocha :-D istanbul ignore next
- if (process.argv.join("").indexOf("mocha") === -1) {
- const commitMsgFile = `${getGitFolder()}/COMMIT_EDITMSG`;
- // const incorrectLogFile = commitMsgFile.replace('COMMIT_EDITMSG', 'logs/incorrect-commit-msgs');
- const hasToString = function hasToString(x) {
- return x && typeof x.toString === "function";
- };
- fs.readFile(commitMsgFile, (err, buffer) => {
- const msg = getCommitMessage(buffer);
- if (!validateMessage(msg)) {
- console.log(`提交信息:\n${msg}\n`);
- process.exit(1);
- } else {
- process.exit(0);
- }
- function getCommitMessage(buf) {
- return hasToString(buf) && buf.toString();
- }
- });
- }
- function getConfig() {
- const pkgFile = findup.sync(process.cwd(), "package.json");
- const pkg = JSON.parse(fs.readFileSync(resolve(pkgFile, "package.json")));
- if (pkg && pkg.config) {
- return pkg.config["validate-commit-msg"];
- }
- const vsc = resolve(process.cwd(), "scripts/.vsc-commitizen.json");
- const config = fs.existsSync(vsc) ? require(vsc) : {};
- return config;
- }
- function getGitFolder() {
- let gitDirLocation = "./.git";
- if (!fs.existsSync(gitDirLocation)) {
- throw new Error(`Cannot find file ${gitDirLocation}`);
- }
- if (!fs.lstatSync(gitDirLocation).isDirectory()) {
- const unparsedText = `${fs.readFileSync(gitDirLocation)}`;
- gitDirLocation = unparsedText.substring("gitdir: ".length).trim();
- }
- if (!fs.existsSync(gitDirLocation)) {
- throw new Error(`Cannot find file ${gitDirLocation}`);
- }
- return gitDirLocation;
- }
|