Skip to content

Commit

Permalink
✨ Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jessedobbelaere committed Sep 7, 2017
0 parents commit a70e4f5
Show file tree
Hide file tree
Showing 6 changed files with 678 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# JIRA Smart Commits

A Node.js git hook script to prefix commits automatically with the JIRA ticket, based on a branch name.

## Usage

### Installation
1. Install [Husky](https://www.npmjs.com/package/husky) in your project to configure Git hooks easily
```
npm install --save-dev husky
```
2. Install this package in your project:
```
npm install --save-dev jira-smart-commit
```
3. Configure scripts in `package.json`. The script expects his first argument to be the JIRA tag of the project.
4. Do your git commits like usual. If the branch was prefixed with a JIRA tag, your commit message will get prefixed with
the same tag. E.g.
```
Branch: TAG-411-husky-git-hooks
Commit message: "Add git hooks to project" → "TAG-411 Add git hooks to project"
```
73 changes: 73 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env node

const fs = require("fs");

/**
* If commit message title:
* - Doesn't start with Merge branch
* - Doesn't start with Merge pull request
* - Branch name starts with ${jiraTag}-XXX (e.g. TAG-123-branch-description)
* - Branch name is not a forbidden branch (master/develop)
* then prepend the JIRA issue tag to the commit message.
* E.g My awesome commit -> TAG-123 My awesome commit
*/

if (!process.argv[2]) {
console.error("Please run this script with the JIRA ticket prefix as CLI argument (e.g. node smart-commit-msg.js SPAN)");
process.exit(1);
}

/**
* @param {string} commitMessage
* @returns {boolean}
*/
const isInvalidMessage = (commitMessage) => {
const startsWithMergeBranch = (commitMessage) => commitMessage.indexOf("Merge branch") === 0;
const startsWithMergePR = (commitMessage) => commitMessage.indexOf("Merge pull request") === 0;
return !startsWithMergeBranch(commitMessage) && !startsWithMergePR(commitMessage);
};

/**
* @returns {string}
*/
const getBranchName = () => {
const branchName = fetchBranchNameFromGit();
if (["master", "develop"].includes(branchName)) {
console.error(`Hold your horses! You cannot commit directly to '${branchName}'. Please set up a PR to 'master' from GitHub and merge from there`);
process.exit(1);
}
return branchName;
};

/**
* @returns {String}
*/
const fetchBranchNameFromGit = () => {
return require("child_process").execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf-8" }).split("\n")[0]
};

/**
* @param {string} branchName
* @returns {boolean}
*/
const getIssueTagFromBranchName = (branchName) => {
const matched = branchName.match(tagMatcher);
return matched && matched[0];
};

const jiraTag = process.argv[2];
const tagMatcher = new RegExp(`^${jiraTag}-\\d+`, "i");
const commitMsgFile = process.env.GIT_PARAMS;
const commitMsg = fs.readFileSync(commitMsgFile, { encoding: "utf-8" });
const commitMsgTitle = commitMsg.split("\n")[0];
const branchName = getBranchName();
const issueTag = getIssueTagFromBranchName(branchName);

if (issueTag && isInvalidMessage(commitMsgTitle)) {
// Add the JIRA issue tag to commit message title
const messageLines = commitMsg.split("\n");
messageLines[0] = `${issueTag.toUpperCase()} ${commitMsgTitle}`;
fs.writeFileSync(commitMsgFile, messageLines.join("\n"), { encoding: "utf-8" });
} else {
fs.writeFileSync(commitMsgFile, commitMsg, { encoding: "utf-8" });
}
46 changes: 46 additions & 0 deletions index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const sinon = require('sinon');
const chai = require('chai');
const expect = chai.expect;
const proxyquire = require( 'proxyquire' ).noCallThru().noPreserveCache();

describe('index.js', () => {
it('should add a prefix to my unprefixed commit message', () => {
expect(executeScriptMock("SPAN", "SPAN-1-proof-of-concept", "Initial commit✨")).to.equal("SPAN-1 Initial commit✨");
expect(executeScriptMock("PROJECT", "PROJECT-3-githook-test", "Add support for githooks")).to.equal("PROJECT-3 Add support for githooks");
expect(executeScriptMock("TAG", "TAG-5032-add-readme", "Add readme to project")).to.equal("TAG-5032 Add readme to project");
});

it('should not add a prefix in merge pull requests', () => {
const commitMergeMessage = "Merge branch 'develop' of github.com:jessedobbelaere/jira-smart-commit into bugfixes";
expect(executeScriptMock("SPAN", "SPAN-1-proof-of-concept", commitMergeMessage)).to.equal(commitMergeMessage);
});

it('should not add a prefix if branch was not prefixed', () => {
expect(executeScriptMock("TAG", "conquer-the-world-PoC", "Initial commit")).to.equal("Initial commit");
});

/**
*
* @param {string} jiraProjectPrefix
* @param {string} gitBranchName The branch name
* @param {string} commitMessage
* @returns {string}
*/
const executeScriptMock = (jiraProjectPrefix, gitBranchName, commitMessage) => {
process.argv = ["node", "index.js", jiraProjectPrefix];
const readFileSync = sinon.stub().returns(commitMessage);
const writeFileSync = sinon.stub();

// Mock the script dependencies
proxyquire('./index.js', {
fs: {
readFileSync,
writeFileSync
},
child_process: { execSync: () => gitBranchName }
});

// Return the prefixed commit message
return writeFileSync.args[0][1];
};
});
Loading

0 comments on commit a70e4f5

Please sign in to comment.