summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKimmo Brunfeldt <kimmobrunfeldt@gmail.com>2015-09-22 23:22:43 +0300
committerKimmo Brunfeldt <kimmobrunfeldt@gmail.com>2015-09-22 23:22:43 +0300
commitb57bcd3f4c79ed4bcdccb7b6f9b0c7636183b916 (patch)
tree726ba24a648d6eeb23f42433e5a05c5c208b70a2
parent471cdea43e44d79f3024ff849c85776238144b8f (diff)
downloadgit-hours-b57bcd3f4c79ed4bcdccb7b6f9b0c7636183b916.zip
git-hours-b57bcd3f4c79ed4bcdccb7b6f9b0c7636183b916.tar.gz
git-hours-b57bcd3f4c79ed4bcdccb7b6f9b0c7636183b916.tar.bz2
Normalize project structure. Fix linter errors. Conver since comparison to use moment
-rw-r--r--.eslintrc59
-rw-r--r--.jscsrc73
-rw-r--r--.travis.yml6
-rw-r--r--CONTRIBUTING.md55
-rw-r--r--Gruntfile.js40
-rw-r--r--LICENSE4
-rw-r--r--README.md31
-rw-r--r--package.json23
-rwxr-xr-xsrc/index.js (renamed from index.js)99
-rw-r--r--test/test-functional.js101
-rwxr-xr-xtools/ci.sh16
11 files changed, 309 insertions, 198 deletions
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..6c07347
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,59 @@
+{
+ "env": {
+ "browser": true,
+ "amd": true,
+ "node": true
+ },
+
+ "globals": {
+ "define": false,
+ "require": true,
+
+ "alert": true,
+ "console": true,
+ "module": true,
+
+ "describe": true,
+ "it": true,
+ "before": true,
+ "beforeEach": true,
+ "after": true,
+ "afterEach": true,
+ "expect": true,
+ "should": true,
+
+ "Promise": true, // allow possibly overwriting Promise
+ },
+
+ "rules": {
+ "dot-notation": 0, // allow obj['somePropertyName']
+ "new-cap": 0, // do not require 'new' keyword, allows i.e. "var promise = $.Deferred()"
+ "no-alert": 0, // disencourage alert() and confirm()
+ "no-console": 0,
+ "no-mixed-spaces-and-tabs": 1,
+ "semi-spacing": [2, {"before": false, "after": true}],
+ "no-spaced-func": 1,
+ "no-undef": 1,
+ "no-shadow": 1,
+ "no-trailing-spaces": 1,
+ "no-extra-parens": 1,
+ "no-underscore-dangle": 0, // allow _variableName
+ "no-new": 0,
+ "no-nested-ternary": 1,
+ "no-process-exit": 0,
+
+ // requires local variable names to be used, but allows unused arguments
+ "no-unused-vars": [2, { "vars": "all", "args": "none" }],
+ "no-use-before-define": 0,
+
+ // disable requirements for strict spacing rules in object properties or assignments
+ "key-spacing": 0,
+ "no-multi-spaces": 0,
+
+ "quotes": 0, // allow both single and double quotes
+ "space-after-keywords": 1,
+ "space-infix-ops": 1,
+ "space-return-throw-case": 1,
+ "strict": 0
+ }
+}
diff --git a/.jscsrc b/.jscsrc
new file mode 100644
index 0000000..b0de16a
--- /dev/null
+++ b/.jscsrc
@@ -0,0 +1,73 @@
+{
+ "disallowImplicitTypeConversion": ["string"],
+ "disallowMixedSpacesAndTabs": true,
+ "disallowMultipleLineBreaks": true,
+ "disallowMultipleVarDecl": true,
+ "disallowNewlineBeforeBlockStatements": true,
+ "disallowMultipleLineStrings": true,
+ "disallowMultipleVarDecl": null,
+ "disallowSpaceAfterPrefixUnaryOperators": true,
+ "disallowSpacesInAnonymousFunctionExpression": {
+ "beforeOpeningRoundBrace": true
+ },
+ "disallowSpacesInsideObjectBrackets": null,
+ "disallowSpacesInsideArrayBrackets": "all",
+ "disallowSpacesInsideParentheses": true,
+ "disallowTrailingWhitespace": true,
+
+ "maximumLineLength": {
+ "value": 100,
+ "allowComments": true,
+ "allowRegex": true
+ },
+
+ "fileExtensions": [".js", ".jsx"],
+
+ "requireCamelCaseOrUpperCaseIdentifiers": true,
+ "requireCommaBeforeLineBreak": true,
+ "requireCurlyBraces": [
+ "if",
+ "else",
+ "for",
+ "while",
+ "do",
+ "try",
+ "catch"
+ ],
+ "requireSpaceAfterBinaryOperators": true,
+ "requireSpaceAfterKeywords": [
+ "do",
+ "for",
+ "if",
+ "else",
+ "switch",
+ "case",
+ "try",
+ "catch",
+ "void",
+ "while",
+ "with",
+ "return",
+ "typeof"
+ ],
+ "requireSpaceBeforeBinaryOperators": [
+ "=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=",
+ "&=", "|=", "^=", "+=",
+
+ "+", "-", "*", "/", "%", "<<", ">>", ">>>", "&",
+ "|", "^", "&&", "||", "===", "==", ">=",
+ "<=", "<", ">", "!=", "!=="
+ ],
+ "requireSpacesInConditionalExpression": true,
+ "requireSpaceBeforeBlockStatements": true,
+ "requireSpacesInForStatement": true,
+ "requireLineFeedAtFileEnd": true,
+ "requireSpacesInFunctionExpression": {
+ "beforeOpeningCurlyBrace": true
+ },
+
+ "validateLineBreaks": "LF",
+ "validateIndentation": 4,
+
+ "excludeFiles": ["node_modules/**"]
+}
diff --git a/.travis.yml b/.travis.yml
index f6f876a..e80033d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,9 +1,11 @@
language: node_js
node_js:
-- '0.12'
+ - '0.12'
sudo: false
before_script:
-- npm install -g mocha grunt-cli nodegit
+ - npm install -g nodegit
+script:
+ - npm run ci
notifications:
email:
- kimmobrunfeldt+git-hours@gmail.com
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..bfd0d1f
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,55 @@
+# Contribution documentation
+
+Pull requests and contributions are warmly welcome.
+Please follow existing code style and commit message conventions. Also remember to keep documentation
+updated.
+
+**Pull requests:** You don't need to bump version numbers or modify anything related to releasing. That stuff is fully automated, just write the functionality.
+
+## Get started with development
+
+* [Install local environment](#install-environment).
+
+## General project stuff
+
+This package uses npm/node tools just in the developer environment. NPM scripts
+are used to run tasks.
+
+#### Versioning
+
+Versioning follows [Semantic Versioning 2.0.0](http://semver.org/).
+All versions are pushed as git tags.
+
+## Install environment
+
+Install tools needed for development:
+
+ npm install
+
+
+## Test
+
+Tests are written with [Mocha](http://mochajs.org/).
+
+Running tests:
+
+ npm test
+
+## Release
+
+**Before releasing, make sure there are no uncommitted files,
+and CI passes.**
+
+Creating a new release of the package is simple:
+
+1. Commit and push all changes
+2. Run local tests and linters with `npm run ci`
+3. Make sure Travis passes tests too.
+4. Run `releasor`, which will create new tag and publish code to GitHub and NPM.
+5. Edit GitHub release notes
+
+By default, patch release is done. You can specify the version bump as a parameter:
+
+ releasor --bump minor
+
+Valid version bump values: `major`, `minor`, `patch`.
diff --git a/Gruntfile.js b/Gruntfile.js
deleted file mode 100644
index ce4e96b..0000000
--- a/Gruntfile.js
+++ /dev/null
@@ -1,40 +0,0 @@
-module.exports = function(grunt) {
-
- grunt.initConfig({
- pkg: grunt.file.readJSON('package.json'),
- jshint: {
- files: ['*.js', 'test/**/*.js'],
- options: {
- globals: {
- jQuery: true,
- console: true,
- module: true,
- Promise: true
- }
- }
- },
- shell: {
- mocha: {
- options: {
- stdout: true
- },
- command: 'mocha'
- }
- },
- release: {
- options: {
- // Don't release to NPM since travis does this
- npm: false,
- npmtag: false,
- // default: 'release <%= version %>'
- commitMessage: 'Release <%= version %>'
- }
- }
- });
-
- grunt.loadNpmTasks('grunt-contrib-jshint');
- grunt.loadNpmTasks('grunt-shell');
- grunt.loadNpmTasks('grunt-release');
-
- grunt.registerTask('test', ['jshint', 'shell:mocha']);
-};
diff --git a/LICENSE b/LICENSE
index 2b22998..0845b87 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2014 Kimmo Brunfeldt
+Copyright (c) 2015 Kimmo Brunfeldt
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE. \ No newline at end of file
+SOFTWARE.
diff --git a/README.md b/README.md
index a09cefb..49c930a 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,8 @@ From a person working 8 hours per day, it would take more than 3 years to build
$ npm install -g git-hours
+**WARNING: Does not work with node 4.0! See [this issue](https://github.com/nodegit/nodegit/issues/723)**
+
**NOTE: Use node version >=0.12.x.** *You can try to get nodegit working
with older versions too but 0.12.x was the easiest. With older node versions,
you might need to `npm install -g nodegit` too.*
@@ -164,32 +166,3 @@ system by running:
$ exit
$ vagrant destroy -f
```
-
-# For contributors
-
-Documentation for git-hours developers.
-
-## Release
-
-* Commit all changes
-* Run `grunt release`, which will create new tag and publish code to GitHub
-* Edit GitHub release notes
-* Release to NPM
-
- $ git checkout x.x.x
- $ npm publish
-
-
-To see an example how to release minor/major, check https://github.com/geddski/grunt-release
-
-## Test
-
-Tests can be run with command
-
- $ grunt test
-
-or
-
- $ npm test
-
-You need to have *mocha* installed globally with `npm install -g mocha`.
diff --git a/package.json b/package.json
index 3b8cb59..98479c6 100644
--- a/package.json
+++ b/package.json
@@ -2,12 +2,9 @@
"name": "git-hours",
"version": "1.0.0",
"description": "Estimate time spent on a git repository",
- "main": "index.js",
- "scripts": {
- "test": "grunt test"
- },
+ "main": "./src/index.js",
"bin": {
- "git-hours": "./index.js"
+ "git-hours": "./src/index.js"
},
"repository": {
"type": "git",
@@ -31,13 +28,19 @@
"bluebird": "^2.1.3",
"commander": "^2.2.0",
"lodash": "^2.4.1",
+ "moment": "^2.10.6",
"nodegit": "^0.4.1"
},
"devDependencies": {
- "grunt": "^0.4.5",
- "grunt-contrib-jshint": "^0.10.0",
- "grunt-release": "^0.7.0",
- "grunt-shell": "^0.7.0",
- "mocha": "^2.2.5"
+ "eslint": "^1.5.1",
+ "jscs": "^2.1.1",
+ "mocha": "^2.3.3",
+ "releasor": "^0.1.3"
+ },
+ "scripts": {
+ "ci": "./tools/ci.sh",
+ "test": "mocha",
+ "eslint": "eslint --ext .js ./src ./test",
+ "jscs": "jscs ./src ./test"
}
}
diff --git a/index.js b/src/index.js
index 066d912..54e26a5 100755
--- a/index.js
+++ b/src/index.js
@@ -1,14 +1,14 @@
#!/usr/bin/env node
-var fs = require('fs');
-
var Promise = require('bluebird');
var git = require('nodegit');
var program = require('commander');
var _ = require('lodash');
-
+var moment = require('moment');
var exec = Promise.promisify(require('child_process').exec);
+var DATE_FORMAT = 'YYYY-MM-DD';
+
var config = {
// Maximum time diff between 2 subsequent commits in minutes which are
// counted to be in the same coding "session"
@@ -17,16 +17,16 @@ var config = {
// How many minutes should be added for the first commit of coding session
firstCommitAdditionInMinutes: 2 * 60,
- //Since data
+ // Include commits since time x
since: 'always'
};
function main() {
parseArgs();
config = mergeDefaultsWithArgs(config);
- parseSinceDate(config);
+ config.since = parseSinceDate(config.since);
- commits('.').then(function(commits) {
+ getCommits('.').then(function(commits) {
var commitsByEmail = _.groupBy(commits, function(commit) {
return commit.author.email || 'unknown';
});
@@ -63,30 +63,29 @@ function main() {
}
function parseArgs() {
- function list(val) {
- return val.split(',');
- }
-
function int(val) {
return parseInt(val, 10);
}
program
- .version(require('./package.json').version)
+ .version(require('../package.json').version)
.usage('[options]')
.option(
'-d, --max-commit-diff [max-commit-diff]',
- 'maximum difference in minutes between commits counted to one session. Default: ' + config.maxCommitDiffInMinutes,
+ 'maximum difference in minutes between commits counted to one' +
+ ' session. Default: ' + config.maxCommitDiffInMinutes,
int
)
.option(
'-a, --first-commit-add [first-commit-add]',
- 'how many minutes first commit of session should add to total. Default: ' + config.firstCommitAdditionInMinutes,
+ 'how many minutes first commit of session should add to total.' +
+ ' Default: ' + config.firstCommitAdditionInMinutes,
int
)
.option(
'-s, --since [since-certain-date]',
- 'Analyze data since certain date. [always|yesterday|tonight|lastweek|yyyy-mm-dd] Default: ' + config.since,
+ 'Analyze data since certain date.' +
+ ' [always|yesterday|today|lastweek|thisweek|yyyy-mm-dd] Default: ' + config.since,
String
);
@@ -97,11 +96,13 @@ function parseArgs() {
console.log('');
console.log(' $ git hours');
console.log('');
- console.log(' - Estimate hours in repository where developers commit more seldom: they might have 4h(240min) pause between commits');
+ console.log(' - Estimate hours in repository where developers commit' +
+ ' more seldom: they might have 4h(240min) pause between commits');
console.log('');
console.log(' $ git hours --max-commit-diff 240');
console.log('');
- console.log(' - Estimate hours in repository where developer works 5 hours before first commit in day');
+ console.log(' - Estimate hours in repository where developer works 5' +
+ ' hours before first commit in day');
console.log('');
console.log(' $ git hours --first-commit-add 300');
console.log('');
@@ -120,42 +121,30 @@ function parseArgs() {
program.parse(process.argv);
}
-function parseSinceDate(options){
- var justNow, thisPeriod, paramDate;
- switch(options.since){
- case 'tonight':
- justNow = new Date();
- thisPeriod = new Date(justNow.getFullYear(), justNow.getMonth(), justNow.getUTCDate());
- config.since = thisPeriod;
- break;
+function parseSinceDate(since) {
+ switch (since) {
+ case 'today':
+ return moment().startOf('day');
case 'yesterday':
- justNow = new Date();
- thisPeriod = new Date(justNow.getFullYear(), justNow.getMonth(), justNow.getUTCDate()-1);
- config.since = thisPeriod;
- break;
+ return moment().startOf('day').subtract(1, 'day');
+ case 'thisweek':
+ return moment().startOf('week');
case 'lastweek':
- justNow = new Date();
- thisPeriod = new Date(justNow.getFullYear(), justNow.getMonth(), justNow.getUTCDate()-7);
- config.since = thisPeriod;
- break;
+ return moment().startOf('week').subtract(1, 'week');
case 'always':
- break;
+ return 'always';
default:
- paramDate = new Date(String(config.since));
- if(paramDate === undefined){
- config.since = 'always';
- }else{
- config.since = paramDate;
- }
+ // XXX: Moment tries to parse anything, results might be weird
+ return moment(since, DATE_FORMAT);
}
}
-function mergeDefaultsWithArgs(config) {
+function mergeDefaultsWithArgs(conf) {
return {
range: program.range,
- maxCommitDiffInMinutes: program.maxCommitDiff || config.maxCommitDiffInMinutes,
- firstCommitAdditionInMinutes: program.firstCommitAdd || config.firstCommitAdditionInMinutes,
- since: program.since || config.since
+ maxCommitDiffInMinutes: program.maxCommitDiff || conf.maxCommitDiffInMinutes,
+ firstCommitAdditionInMinutes: program.firstCommitAdd || conf.firstCommitAdditionInMinutes,
+ since: program.since || conf.since
};
}
@@ -171,27 +160,27 @@ function estimateHours(dates) {
});
var allButLast = _.take(sortedDates, sortedDates.length - 1);
- var hours = _.reduce(allButLast, function(hours, date, index) {
+ var totalHours = _.reduce(allButLast, function(hours, date, index) {
var nextDate = sortedDates[index + 1];
var diffInMinutes = (nextDate - date) / 1000 / 60;
// Check if commits are counted to be in same coding session
if (diffInMinutes < config.maxCommitDiffInMinutes) {
- return hours + (diffInMinutes / 60);
+ return hours + diffInMinutes / 60;
}
// The date difference is too big to be inside single coding session
// The work of first commit of a session cannot be seen in git history,
// so we make a blunt estimate of it
- return hours + (config.firstCommitAdditionInMinutes / 60);
+ return hours + config.firstCommitAdditionInMinutes / 60;
}, 0);
- return Math.round(hours);
+ return Math.round(totalHours);
}
// Promisify nodegit's API of getting all commits in repository
-function commits(gitPath) {
+function getCommits(gitPath) {
return git.Repository.open(gitPath)
.then(function(repo) {
var branchNames = getBranchNames(gitPath);
@@ -237,18 +226,7 @@ function getBranchNames(gitPath) {
});
}
-function getBranches(repo, names) {
- var branches = [];
- for (var i = 0; i < names.length; ++i) {
- branches.push(getBranch(repo, names[i]));
- }
-
- return Promise.all(branches);
-}
-
function getBranchLatestCommit(repo, branchName) {
- var type = git.Reference.TYPE.SYMBOLIC;
-
return repo.getBranch(branchName).then(function(reference) {
return repo.getBranchCommit(reference.name());
});
@@ -275,7 +253,8 @@ function getBranchCommits(branchLatestCommit) {
author: author
};
- if(commitData.date > config.since || config.since === 'always'){
+ var sinceAlways = config.since === 'always' || !config.since;
+ if (sinceAlways || moment(commitData.date.toISOString()).isAfter(config.since)) {
commits.push(commitData);
}
});
diff --git a/test/test-functional.js b/test/test-functional.js
index d716e07..a852597 100644
--- a/test/test-functional.js
+++ b/test/test-functional.js
@@ -1,75 +1,66 @@
-var _ = require('lodash');
-var assert = require("assert");
+var assert = require('assert');
var exec = require('child_process').exec;
var totalHoursCount;
describe('git-hours', function() {
+ it('should output json', function(done) {
+ exec('node ./src/index.js', function(err, stdout, stderr) {
+ if (err !== null) {
+ throw new Error(stderr);
+ }
- describe('cli', function() {
-
- it('should output json', function(done) {
- exec('node index.js', function(err, stdout, stderr) {
- if (err !== null) {
- throw new Error(stderr);
- }
-
- console.log('stdout:', stdout);
- var work = JSON.parse(stdout);
- assert.notEqual(work.total.hours.length, 0);
- assert.notEqual(work.total.commits.length, 0);
- totalHoursCount = work.total.hours;
- done();
- });
+ console.log('stdout:', stdout);
+ var work = JSON.parse(stdout);
+ assert.notEqual(work.total.hours.length, 0);
+ assert.notEqual(work.total.commits.length, 0);
+ totalHoursCount = work.total.hours;
+ done();
});
});
- describe('Since option', function(){
- it('Should analyse since today', function(done) {
- exec('node index.js --since today', function(err, stdout, stderr) {
- assert.ifError(err);
- var work = JSON.parse(stdout);
- assert.strictEqual(typeof work.total.hours, 'number');
- done();
- });
+ it('Should analyse since today', function(done) {
+ exec('node ./src/index.js --since today', function(err, stdout, stderr) {
+ assert.ifError(err);
+ var work = JSON.parse(stdout);
+ assert.strictEqual(typeof work.total.hours, 'number');
+ done();
});
+ });
- it('Should analyse since yesterday', function(done) {
- exec('node index.js --since yesterday', function(err, stdout, stderr) {
- assert.ifError(err);
- var work = JSON.parse(stdout);
- assert.strictEqual(typeof work.total.hours, 'number');
- done();
- });
+ it('Should analyse since yesterday', function(done) {
+ exec('node ./src/index.js --since yesterday', function(err, stdout, stderr) {
+ assert.ifError(err);
+ var work = JSON.parse(stdout);
+ assert.strictEqual(typeof work.total.hours, 'number');
+ done();
});
+ });
- it('Should analyse since last week', function(done){
- exec('node index.js --since lastweek', function(err, stdout, stderr) {
- assert.ifError(err);
- var work = JSON.parse(stdout);
- assert.strictEqual(typeof work.total.hours, 'number');
- done();
- });
+ it('Should analyse since last week', function(done) {
+ exec('node ./src/index.js --since lastweek', function(err, stdout, stderr) {
+ assert.ifError(err);
+ var work = JSON.parse(stdout);
+ assert.strictEqual(typeof work.total.hours, 'number');
+ done();
});
+ });
- it('Should analyse since a specific date', function(done){
- exec('node index.js --since 2015-01-01', function(err, stdout, stderr) {
- assert.ifError(err);
- var work = JSON.parse(stdout);
- assert.notEqual(work.total.hours, 0);
- done();
- });
+ it('Should analyse since a specific date', function(done) {
+ exec('node ./src/index.js --since 2015-01-01', function(err, stdout, stderr) {
+ assert.ifError(err);
+ var work = JSON.parse(stdout);
+ assert.notEqual(work.total.hours, 0);
+ done();
});
+ });
- it('Should analyse as without param', function(done){
- exec('node index.js --since always', function(err, stdout, stderr) {
- assert.ifError(err);
- var work = JSON.parse(stdout);
- assert.equal(work.total.hours, totalHoursCount);
- done();
- });
+ it('Should analyse as without param', function(done) {
+ exec('node ./src/index.js --since always', function(err, stdout, stderr) {
+ assert.ifError(err);
+ var work = JSON.parse(stdout);
+ assert.equal(work.total.hours, totalHoursCount);
+ done();
});
});
-
-
});
diff --git a/tools/ci.sh b/tools/ci.sh
new file mode 100755
index 0000000..480fa1a
--- /dev/null
+++ b/tools/ci.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+# NOTE: Run this only from project root!
+
+# Run all commands and if one fails, return exit status of the last failed
+# command
+
+EXIT_STATUS=0
+
+echo -e "\n--> Linting code..\n"
+npm run jscs || EXIT_STATUS=$?
+npm run eslint || EXIT_STATUS=$?
+
+echo -e "\n--> Running tests..\n"
+npm test || EXIT_STATUS=$?
+
+exit $EXIT_STATUS