Skip to content
This repository was archived by the owner on Feb 5, 2018. It is now read-only.
Closed
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
76 changes: 52 additions & 24 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,58 @@
'use strict';
var exec = require('child_process').exec;
var semverValid = require('semver').valid;
var regex = /tag:\s*(.+?)[,\)]/gi;
var cmd = 'git log --decorate --no-color';

module.exports = function(callback) {
exec(cmd, {
maxBuffer: Infinity
}, function(err, data) {
if (err) {
callback(err);
return;
}
var semver = require('semver');

var findIndexByHash = require('./lib/find-index-by-hash');
var getCommits = require('./lib/get-commits');
var getSemverTags = require('./lib/get-semver-tags');
var loadRepository = require('./lib/load-repository');

var tags = [];
/**
* Get semantic version git tags of repository at process.cwd()
*
* @param {MainCallback} callback function to execute after information retrieval
*/
function gitSemverTags(callback) {
// Initialize repository
loadRepository(function(error, repository) {
if (error) {
return callback(error);
}

data.split('\n').forEach(function(decorations) {
var match;
while (match = regex.exec(decorations)) {
var tag = match[1];
if (semverValid(tag)) {
tags.push(tag);
}
// Get a list of commits
getCommits(repository, function(error, commits) {
/* istanbul ignore if */
if (error) {
return callback(error);
}
});

callback(null, tags);
// Get a list of tags matching semver pattern
var tagNames = getSemverTags(repository)
.sort(function(aTag, bTag) {
// if tags reference same hash sort descending by semantic version
if (aTag.hash === bTag.hash) {
return semver.compare(bTag.name, aTag.name);
}

// sort tags descending by occurence in commits list
var aCommit = findIndexByHash(commits, aTag.hash);
var bCommit = findIndexByHash(commits, bTag.hash);
return bCommit - aCommit;
})
// map out the name
.map(function(tag) {
return tag.name;
});

return callback(null, tagNames);
});
});
};

module.exports = gitSemverTags;

/**
* Main callback executed after all information has been collected
*
* @typedef {function} MainCallback
* @param {(Error|null)} error - encountered error, if any
* @param {array} [tags] - semantic git tags of repository at process.cwd()
*/
23 changes: 23 additions & 0 deletions lib/find-index-by-hash.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Find index of item with item.hash === hash
*
* @param {array} commits haystack of commits to search in
* @param {string} hash hash to match commits against
* @private
*/
function findIndexByHash(commits, hash) {
// Get first commit matching hash
var match = commits.filter(function(commit) {
return commit.hash === hash;
})[0];

// No index to find if no match was found
if (!match) {
return -1;
}

// Return the index of match in haystack
return commits.indexOf(match);
}

module.exports = findIndexByHash;
28 changes: 28 additions & 0 deletions lib/get-commit-human.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
var human = require('git-parse-human');

/**
* Get human object for commit tagger, author or committer
*
* @param {GitCommit} commit
* @returns {GitHuman}
* @private
*/
function getCommitHuman(commit) {
// Use getter for raw information about author of commit, where author is
// the tagger, author or committer in said precedence
var getter = commit.tagger || commit.author || commit.committer;
var bound = getter.bind(commit);
// Parse raw author information into commit "human" object
return human(bound());
}

module.exports = getCommitHuman;

/**
* @typedef GitHuman
* @property {string} name
* @property {string} email
* @property {number} time unix epoch commit timestamp in milliseconds
* @property {number} tzoff time zone offset in milliseconds
* @see https://github.com/chrisdickinson/git-parse-human
*/
17 changes: 17 additions & 0 deletions lib/get-commit-timestamp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
var getCommitHuman = require('./get-commit-human');

/**
* Get timestamp for commit
*
* @param {GitCommit} commit
* @returns {number} commit unix epoch timestamp in milliseconds with time zone offset
* @private
*/
function getCommitTimeStamp(commit) {
// get a commiter object from commit object
var committer = getCommitHuman(commit);
// calculate the time zone offset in
return committer.time + committer.tzoff;
}

module.exports = getCommitTimeStamp;
50 changes: 50 additions & 0 deletions lib/get-commits.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
var walk = require('git-walk-refs');

/**
* Get commits for repository in reverse chronological order
*
* @param {!GitRepository} repository git repository
* @param {!CommitsCallback} callback function to execute after information retrieval
* @private
*/
function getCommits(repository, callback) {
var commits = []; // results array

// Get an array of refs
var head = repository.refs().map(function(ref) {
return ref.hash;
});

// Walk the git history for each ref
walk(repository.find, head)
.on('error', callback)
.on('end', function() {
// return list in inverse chronological order
return callback(null, commits.reverse());
})
.on('data', function(data) {
// add commit entry to results array
commits.push({
hash: data.hash,
message: data.message()
});
});
}

module.exports = getCommits;
/**
* @callback CommitsCallback
* @param {?Error} error error encountered if any
* @param {GitCommit[]} [commits] commits in inverse chronological order
* @private
*/

/**
* @typedef GitCommit
* @property {function?} author
* @property {function?} committer
* @property {function?} tagger
* @property {!string} hash
* @see https://github.com/chrisdickinson/git-walk-refs
* @private
*/
21 changes: 21 additions & 0 deletions lib/get-semver-tags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
var semver = require('semver');
var getTags = require('./get-tags');

/**
* Get git tags of repository matching semantic version pattern
*
* @param {GitRepository} repository
* @returns {GitTag[]}
* @see https://github.com/npm/node-semver#functions
* @private
*/
function getSemverTags(repository) {
// Get all tags
return getTags(repository)
// Filter for valid semver tags
.filter(function(tag) {
return semver.valid(tag.name);
});
}

module.exports = getSemverTags;
31 changes: 31 additions & 0 deletions lib/get-tags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Get tags for repository
*
* @param {!GitRepository} repository
* @returns {GitTag[]}
* @private
*/
function getTags(repository) {
// Get all references
return repository.refs(false)
// Filter for references matching git tag ref paths
.filter(function(ref){
return ref.name.indexOf('refs/tags/') === 0;
})
// Pick relevant information
.map(function(tag){
return {
name: tag.name.replace(/^refs\/tags\//, ''),
hash: tag.hash
};
});
}

module.exports = getTags;

/**
* @typedef GitTag
* @property name git tag name
* @property hash git hash referenced by this tag
* @private
*/
51 changes: 51 additions & 0 deletions lib/load-repository.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
var path = require('path');
var gitFsRepo = require('git-fs-repo');

/**
* Initialize a git repository
*
* @param {RepositoryCallback} callback
* @private
*/
function loadRepository(callback) {
// Use $cwd/.git as git database directory
var gitDirectory = path.join(process.cwd(), '.git');

// Initialize a js-backed representation of the local git repository
gitFsRepo(gitDirectory, function(error, gitRepository) {
/* istanbul ignore if */
if (error) {
return callback(error);
}

// get the current head object
var head = gitRepository.ref('HEAD');

// if there is no head object this repository most likely has no commits
if (!head) {
var headErrors = new Error('Git repository has no HEAD, are there any commits?');
return callback(headErrors);
}

callback(null, gitRepository);
});
}

module.exports = loadRepository;

/**
* @typedef {Object} GitRepository
* @property {function} ref
* @property {function} refs
* @see https://github.com/chrisdickinson/git-fs-repo
* @private
*/

/**
* Main callback executed after all information has been collected
*
* @callback RepositoryCallback
* @param {(Error|null)} error - encountered error, if any
* @param {GitRepositpry} [gitRepository]
* @private
*/
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"repository": "stevemao/git-semver-tags",
"license": "MIT",
"files": [
"lib",
"index.js",
"cli.js"
],
Expand All @@ -23,6 +24,10 @@
"git"
],
"dependencies": {
"async": "^2.0.0-rc.5",
"git-fs-repo": "0.0.5",
"git-parse-human": "0.0.1",
"git-walk-refs": "0.0.2",
"meow": "^3.3.0",
"semver": "^5.0.1"
},
Expand All @@ -37,7 +42,7 @@
},
"scripts": {
"coverage": "istanbul cover _mocha -- -R spec && rm -rf ./coverage",
"lint": "jshint *.js --exclude node_modules && jscs *.js",
"lint": "jshint **/*.js --exclude node_modules && jscs *.js",
"test": "npm run-script lint && mocha --timeout 10000"
},
"bin": {
Expand Down
Loading