Skip to content

Commit

Permalink
Merge pull request #24 from ibarsi/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
ibarsi authored Mar 25, 2017
2 parents 3415da5 + 041068d commit f35484b
Show file tree
Hide file tree
Showing 18 changed files with 5,511 additions and 56 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
[![npm](https://img.shields.io/npm/v/git-velocity.svg?style=flat-square)](https://www.npmjs.com/package/git-velocity)
[![Travis branch](https://img.shields.io/travis/ibarsi/git-velocity/master.svg?style=flat-square)](https://travis-ci.org/ibarsi/git-velocity)

![Animated gif showing example usage](./git-velocity.gif)

`git-velocity` is a CLI tool used to calculate commit velocity over time.
When invoked, the tool reads your current directory (looking for `package.json`) and attempts to "guess" reasonable defaults for the repository you're wanting to analyze, accepting user input for overrides.
Once configured, a dashboard is displayed (courtesy of [blessed-contrib](https://github.com/yaronn/blessed-contrib)) with useful metrics regarding your repository's commit velocity.
Expand All @@ -24,7 +26,7 @@ A layered line graph, comparing previous vs current commits. Length of time comp
## GETTING STARTED ##

After cloning and installing dependencies via `npm install`, you're basically all set and ready to go.
The solution was built using many `ES2015` features that were not implemented during the time, and as such transpilation (via Babel) is required.
The solution was built using many new language features that were not implemented during the time, and as such transpilation (via Babel) is required.

There are several `npm scripts` that are configured for development, listed below:

Expand Down
Binary file added git-velocity.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ console.log(
const data = await wrapSpinner(commits.getCommitsByRepo, 'Pulling commits...')(repository, owner,
commit => !velocity.isDateWithinThisTimeFrame(commit.date) && !velocity.isDateWithinLastTimeFrame(commit.date));

const dashboard = CommitsDashboard();
await dashboard.render(format, data);
const dashboard = CommitsDashboard(velocity);
await dashboard.render(data);
}
catch (error) {
console.error(chalk.red('=== ERROR ==='));
Expand Down
Empty file removed main/index.test.js
Empty file.
6 changes: 5 additions & 1 deletion main/modules/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import fs from 'fs';
import os from 'os';

import { isEmpty } from 'lodash';

import { isFile } from './helpers';

export function Auth(token) {
Expand All @@ -15,7 +17,9 @@ export function Auth(token) {
async getCreds() {
return JSON.parse(fs.readFileSync(`${ os.homedir() }/${ token }`, 'utf8'));
},
storeCreds(username, password) {
async storeCreds(username, password) {
if (isEmpty(username) || isEmpty(password)) { throw new Error('Both username and password are required to store credentials.'); }

return new Promise((resolve, reject) => {
fs.writeFile(
`${ os.homedir() }/${ token }`,
Expand Down
50 changes: 37 additions & 13 deletions main/modules/commits.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
COMMITS
================================================== */

import { uniq, requestPromise } from './helpers';
import 'isomorphic-fetch';

import { uniq } from './helpers';
import { Auth } from './auth';

// PUBLIC
Expand Down Expand Up @@ -53,19 +55,27 @@ function BitBucketCommits(auth) {
isAuthorized: auth.isCredsTokenInitialized,
authorize: auth.storeCreds,
async getCommitsByRepo(repository, owner, takeWhile) {
if (!repository || !owner) { return []; }

const { username, password } = await auth.getCreds();

const options = {
url: config.commits_url.replace('{owner}', owner).replace('{repo}', repository),
config: {
headers: {
headers: new Headers({
Accept: 'application/json',
'User-Agent': owner,
Authorization: 'Basic ' + new Buffer(`${ username }:${ password }`).toString('base64')
}
})
}
};

const commits = await _requestPagedResponse(options, response => !response.data.values.map(BitBucketCommit).some(takeWhile) ? response.data.next : undefined);
const commits = await _requestPagedResponse(options,
(response, data) => {
if (!takeWhile) { return data.next; }

return !data.values.map(BitBucketCommit).some(takeWhile) ? data.next : undefined;
});

return commits.reduce((acc, value) => acc.concat(value.values), []).map(BitBucketCommit);
}
Expand All @@ -90,7 +100,7 @@ function GitHubCommits(auth) {
};

const nextPageFunc = response => {
const link = response.headers.link;
const link = response.headers.get('link');

if (link && link.indexOf('rel="next"') >= 0) {
const next_url = link.substring(0, link.indexOf('rel="next"'));
Expand All @@ -104,23 +114,32 @@ function GitHubCommits(auth) {
isAuthorized: auth.isCredsTokenInitialized,
authorize: auth.storeCreds,
async getCommitsByRepo(repository, owner, takeWhile) {
if (!repository || !owner) { return []; }

const { username, password } = await auth.getCreds();

const options = {
url: config.commits_url.replace('{owner}', owner).replace('{repo}', repository),
config: {
headers: {
headers: new Headers({
Accept: 'application/json',
'User-Agent': owner,
Authorization: 'Basic ' + new Buffer(`${ username }:${ password }`).toString('base64')
}
})
}
};

const branches = await requestPromise(config.branches_url.replace('{owner}', owner).replace('{repo}', repository), options.config);
const branch_commit_results = await Promise.all(branches.data.map(branch => {
const branches = await fetch(config.branches_url.replace('{owner}', owner).replace('{repo}', repository), options.config)
.then(response => response.json());

const branch_commit_results = await Promise.all(branches.map(branch => {
return _requestPagedResponse(Object.assign({}, options, {
url: `${ options.url }?sha=${ branch.name }`
}), response => !response.data.map(GitHubCommit).some(takeWhile) ? nextPageFunc(response) : undefined);
}), (response, data) => {
if (!takeWhile) { return nextPageFunc(response); }

return !data.map(GitHubCommit).some(takeWhile) ? nextPageFunc(response) : undefined;
});
}));

const github_commits = branch_commit_results.reduce((acc, list) => acc.concat(list), []);
Expand All @@ -144,10 +163,15 @@ function GitHubCommit(value) {

async function _requestPagedResponse(options, nextPage, values = []) {
const { url, config } = options;
const response = await requestPromise(url, config);
const chunked_values = values.concat(response.data);

const next_page_url = nextPage(response);
const response = await fetch(url, config);

if (response.status !== 200) { throw new Error(`ERROR: ${ response.status } - ${ response.statusText }`); }

const data = await response.json();
const chunked_values = values.concat(data);

const next_page_url = nextPage(response, data);

if (next_page_url) {
return _requestPagedResponse({ url: next_page_url, config }, nextPage, chunked_values);
Expand Down
17 changes: 9 additions & 8 deletions main/modules/dashboard.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
/* ==================================================
DASHBOARD
================================================== */

import { screen } from 'blessed';
import { grid, markdown, log, line } from 'blessed-contrib';
import moment from 'moment';

import { Velocity } from './velocity';

// SETTINGS

const info_content =
Expand Down Expand Up @@ -37,34 +39,33 @@ const line_settings = {

// PUBLIC

export default function CommitsDashboard() {
export default function CommitsDashboard(velocity) {
let dashboard;
let layout;

return {
async render(format, commits) {
async render(commits) {
// INIT

if (!dashboard) {
dashboard = _initScreen();
layout = _initLayout(dashboard);
}

const velocity = Velocity(format);
const grouped_commits = await velocity.groupCommitsByFormat(commits);

// INFO

const info_content_formatted = info_content
.replace('{{format}}', format)
.replace('{{format}}', velocity.getFormat())
.replace('{{current_commits}}', grouped_commits.current.length)
.replace('{{previous_commits}}', grouped_commits.previous.length);

// LISTING

const commit_messages = [ ...grouped_commits.current, ...grouped_commits.previous ]
.map(commit => `(${ moment(commit.date).format('MMM Do') }) ${ commit.author }: ${ commit.message.replace('\n', ' ') }`)
.reverse();
.sort((a, b) => a.date > b.date)
.map(commit => `(${ moment(commit.date).format('MMM Do') }) ${ commit.author }: ${ commit.message.replace('\n', ' ') }`);

// VELOCITY

Expand Down
28 changes: 1 addition & 27 deletions main/modules/helpers.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
/* ==================================================
FILE HELPER
HELPER
================================================== */

import fs from 'fs';
import req from 'request';
import CLI from 'clui';

const request = req.defaults({
encoding: 'utf8',
json: true
});

// PUBLIC

export function isFile(path) {
Expand Down Expand Up @@ -49,26 +43,6 @@ export function wrapSpinner(promise, message = '') {
});
}

export function requestPromise(url, config) {
return new Promise((resolve, reject) => {
request.get(url, config)
.on('response', response => {
if (response.statusCode !== 200) { reject(new Error(response.statusMessage)); }

let chunk = '';

response.on('data', result => { chunk += result; });

response.on('end', () => {
resolve({
data: JSON.parse(chunk),
headers: response.headers
});
});
});
});
}

// SOURCE: https://gist.github.com/ibarsi/856a0c46e37fb4c951b033995aec55d5
export function partial(func, ...args) {
return func.bind(undefined, ...args);
Expand Down
3 changes: 3 additions & 0 deletions main/modules/velocity.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ export function Velocity(format) {
const time = _getFormatTimeValue(format);

return {
getFormat() {
return format;
},
isDateWithinThisTimeFrame(date) {
const now = moment();
const start_of_time = moment(now).startOf(time).hours(0);
Expand Down
9 changes: 9 additions & 0 deletions main/tests/data/branch_github_data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[
{
"name": "master",
"commit": {
"sha": "1",
"url": "https://api.github.com/repos/ibarsi/git-velocity/commits/1"
}
}
]
16 changes: 16 additions & 0 deletions main/tests/data/branches_github_data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[
{
"name": "master",
"commit": {
"sha": "1",
"url": "https://api.github.com/repos/ibarsi/git-velocity/commits/1"
}
},
{
"name": "develop",
"commit": {
"sha": "2",
"url": "https://api.github.com/repos/ibarsi/git-velocity/commits/2"
}
}
]
Loading

0 comments on commit f35484b

Please sign in to comment.