Skip to content

gitlint: Setup commit linter. #367

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ cache:
- app/node_modules

script:
- npm run travis
- node ./tools/gitlint --ci-mode
- npm run travis

notifications:
webhooks:
urls:
Expand Down
3 changes: 2 additions & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ os: Previous Visual Studio 2015

cache:
- node_modules

install:
- ps: Install-Product node 6 x64
- git reset --hard HEAD
Expand All @@ -20,5 +20,6 @@ install:
build: off

test_script:
- node ./tools/gitlint --ci-mode
- npm run test
- npm run test-e2e
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
"dist": "electron-builder",
"mas": "electron-builder --mac mas",
"travis": "cd ./scripts && ./travis-build-test.sh",
"build-locales": "node tools/locale-helper"
"build-locales": "node tools/locale-helper",
"setup-gitlint-hooks": "node ./tools/gitlint/setup-gitlint-hook",
"lint-commits": "node ./tools/gitlint --all-commits"
},
"pre-commit": [
"test"
Expand Down Expand Up @@ -111,6 +113,7 @@
],
"devDependencies": {
"assert": "1.4.1",
"chalk": "^2.3.0",
"cp-file": "^5.0.0",
"devtron": "1.4.0",
"electron": "1.8.4",
Expand Down
27 changes: 27 additions & 0 deletions tools/gitlint/ci.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module.exports = () => {
if (process.argv.TRAVIS !== undefined) {
return travis();
}

return appveyor();
};

function travis() {
if (!process.env.TRAVIS_PULL_REQUEST) {
// Building against master last commit
return 'git log -1 HEAD';
}

const cmd = `git log ${process.env.TRAVIS_COMMIT_RANGE}/.../..`;
return cmd;
}

function appveyor() {
if (!process.env.APPVEYOR_PULL_REQUEST_NUMBER) {
return 'git log -1 HEAD';
}

const cmd =
`git log origin/master...${process.env.APPVEYOR_PULL_REQUEST_HEAD_COMMIT}`;
return cmd;
}
117 changes: 117 additions & 0 deletions tools/gitlint/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#!/usr/bin/env node

const {spawnSync} = require('child_process');
const chalk = require('chalk');

const commitMsgRegex = /[A-Z]+.*\.$/;
const isFullCommitRegex = /(\w|\W){1,}:\s{1}/;
const fullCommitRegex = /(\w|\W){1,}:\s{1}[A-Z]+.*\.$/;

function run(script) {
script = script.split(' ');
const cmd = script.splice(0, 1)[0];
const args = script;
const output = spawnSync(cmd, args, {
cwd: process.cwd(),
encoding: 'utf8',
windowsHide: true
}).stdout;

return output;
}

function garbageCollect(a) {
a.forEach((content, index) => {
if (content === '' || content === undefined) {
a.splice(index, 1);
}
});
return a;
}

function getAllCommits(output) {
output = output.split('\ncommits');
if (!output.length > 1) {
exports.error('There are no commits to lint.');
process.exit(1);
}

output = garbageCollect(output);
output.forEach((commit, index) => {
output[index] = 'commit' + commit;
});

// Exclude travis Merge ... into ... commit since it will cause
// linting to fail for change not related to user's.
if (process.env.TRAVIS_PULL_REQUEST) {
const travisCommit = output.pop();
warn('Removing travis commit');
warn(travisCommit);
}

return output;
}

function parseCommit(output) {
output = output.split('\n\n');

const reasons = [];
let commit = output[0].replace('commit ', '');
commit = commit.replace(/\n.*/g, '');
let commitHash = commit.split('');
commitHash = commitHash.slice(6, 13);
commitHash = commitHash.join('');

const fullCommit = output[1].split('\n');
const commitMsg = fullCommit[0];
let lintingStatus = commitMsgRegex.test(commitMsg);

if (!lintingStatus) {
const msg = `${commitHash} does not have capital letter at start or period at the end.`;
reasons.push(msg);
lintingStatus = true;
}

lintingStatus = (commitMsg.length <= 72);
if (!lintingStatus) {
reasons.push(`${commitHash} has commit msg title greater than 72 characters.`);
lintingStatus = true;
}

if (isFullCommitRegex.test(commitMsg)) {
lintingStatus = fullCommitRegex.test(commitMsg);
if (!lintingStatus) {
reasons.push(`${commitHash} does not follow style have capital letter at start or period at the end.`);
}
}

const result = {
failed: reasons.length > 0,
reason: reasons.join('\n')
};

return result;
}

function logSuccess() {
console.log(chalk`{green commit linter:} commit linter passed.`);
process.exit(0);
}

function error(...args) {
args.unshift(chalk.red('ERROR! '));
console.error.apply(this, args);
}

function warn(msg) {
console.error(chalk`{yellow ${msg}}`);
}

module.exports = {
run,
getAllCommits,
parseCommit,
logSuccess,
error,
warn
};
37 changes: 37 additions & 0 deletions tools/gitlint/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env node

const helpers = require('./helpers');
const getCICmd = require('./ci');

let checkAllCommits = false;
let ciMode = false;
if (process.argv[2]) {
checkAllCommits = process.argv[2].includes('-a');
ciMode = process.argv[2] === '--ci-mode';
}

let cmd;
if (ciMode) {
cmd = getCICmd();
} else {
cmd =
checkAllCommits ? 'git log upstream/master...HEAD' : 'git log -1 HEAD';
}

const commits = helpers.run(cmd);
const commitsArray = helpers.getAllCommits(commits);
let lintFailed = false;
commitsArray.forEach(commit => {
const res = helpers.parseCommit(commit);
if (res.failed) {
helpers.error(res.reason);
lintFailed = true;
} else {
helpers.logSuccess('Commit[s] follow the zulip-electron commit rules.');
}
});

if (lintFailed) {
helpers.warn('Run with --no-verify flag to skip the commit-linter\n\n');
process.exit(1);
}
73 changes: 73 additions & 0 deletions tools/gitlint/setup-gitlint-hook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// This script sets up the pre-push
// git hook which will be used to lint
// commits

const fs = require('fs');
const path = require('path');

const gitHooks = '../../.git/hooks';
const postCommitPath = path.resolve(__dirname, `${gitHooks}/post-commit`);
const prePushPath = path.resolve(__dirname, `${gitHooks}/pre-push`);

function scriptTemplate(cmds) {
cmds = cmds.join('');
const script = [
'#!/bin/sh',
'set -e',
'echo running gitlint...',
cmds,
'exit $?'
];

return script.join('\n');
}

const postCommitFile = scriptTemplate`node scripts/gitlint`;
const prePushFile = scriptTemplate`node scripts/gitlint --all-commits`;

function writeAndChmod(file, data) {
fs.writeFile(file, data, err => {
if (err) {
throw err;
}

fs.chmod(file, '777', err => {
if (err) {
const msg =
'chmod post-commit, pre-push hooks, at .git/hooks 0777 so they work!';
console.error(msg);
}
});
});
}

[postCommitPath, prePushPath].forEach((file, index) => {
fs.open(file, 'w+', err => {
if (err && err.code !== 'EEXIST') {
throw err;
}

const data = index === 0 ? postCommitFile : prePushFile;
writeAndChmod(file, data);
});
});

// Remove .sample files since
// sometimes the hooks do not work
const postCommitSampleFile = `${postCommitPath}.sample`;
const prePushSampleFile = `${prePushPath}.sample`;
function removeSampleFile(file) {
fs.unlink(file, err => {
if (err) {
throw err;
}
});
}

[postCommitSampleFile, prePushSampleFile].forEach(file => {
fs.exists(file, exists => {
if (exists) {
removeSampleFile(file);
}
});
});