From 1c718f772dcabaa658ed16b95564eacc4deb4725 Mon Sep 17 00:00:00 2001 From: oleg Date: Tue, 14 Jul 2015 00:20:30 +0300 Subject: [PATCH] add git support --- lib/scm/git.js | 136 ++++++++++++++++++++++++++++++++++++++++++++++++ test/helpers.js | 19 +++++++ test/repos/git | 1 + test/scm.js | 25 +++++++-- 4 files changed, 177 insertions(+), 4 deletions(-) create mode 100644 lib/scm/git.js create mode 160000 test/repos/git diff --git a/lib/scm/git.js b/lib/scm/git.js new file mode 100644 index 0000000..ca3e965 --- /dev/null +++ b/lib/scm/git.js @@ -0,0 +1,136 @@ +'use strict'; + +var ParentScm = require('./base').Scm, + inherits = require('util').inherits, + Steppy = require('twostep').Steppy, + _ = require('underscore'); + +function Scm(params) { + ParentScm.call(this, params); +} + +exports.Scm = Scm; + +inherits(Scm, ParentScm); + +Scm.prototype.defaultRev = 'master'; + +// use 2 invisible separators as fields separator +Scm.prototype._fieldsSeparator = String.fromCharCode(2063); +Scm.prototype._fieldsSeparator += Scm.prototype._fieldsSeparator; + +Scm.prototype._revTemplate = [ + '%H', '%cn', '%cd', '%s', '%d' +].join(Scm.prototype._fieldsSeparator); + +Scm.prototype._parseRev = function(str) { + var parts = str.split(this._fieldsSeparator); + + var rev = { + id: parts[0], + author: parts[1], + date: new Date(parts[2]).getTime(), + comment: parts[3] + }; + + var refsStr = parts[4]; + if (refsStr) { + var refs = refsStr + .replace(/^ *\(/, '') + .replace(/\) *$/, '') + .split(', '); + + var tags = _(refs).chain().filter(function(ref) { + return /^tag: /.test(ref) + }).map(function(ref) { + return ref.replace(/^tag: /, ''); + }).value(); + + if (tags.length) { + // sort tags alphabetically + rev.tags = _(tags).sortBy(); + } + } + + return rev; +}; + +Scm.prototype.clone = function(dst, rev, callback) { + var self = this; + Steppy( + function() { + self.run({ + cmd: 'git', + args: ['clone', '--recursive', self.repository, dst] + }, this.slot()); + self.cwd = dst; + }, + function() { + self.run({ + cmd: 'git', + args: ['reset', '--hard', rev] + }, this.slot()); + }, + callback + ); +}; + +Scm.prototype.pull = function(rev, callback) { + // TODO: pull changes without update + this.run({cmd: 'git', args: ['pull']}, callback); +}; + +Scm.prototype.getRev = function(rev, callback) { + var self = this; + Steppy( + function() { + self.run({cmd: 'git', args: [ + 'show', rev, + '--pretty=' + self._revTemplate + ]}, this.slot()); + }, + function(err, stdout) { + var row = stdout.split('\n')[0]; + + this.pass(self._parseRev(row)); + }, + callback + ); +}; + +Scm.prototype.getCurrent = function(callback) { + var self = this; + Steppy( + function() { + self.getRev('HEAD', this.slot()); + }, + callback + ); +}; + +Scm.prototype.getChanges = function(rev1, rev2, callback) { + var self = this; + Steppy( + function() { + self.run({cmd: 'git', args: [ + 'log', rev1 + '..' + rev2, + '--pretty=' + self._revTemplate + ]}, this.slot()); + }, + function(err, stdout) { + // always skip last line - it's empty + var rows = stdout.split('\n').slice(0, -1); + + var changes = _(rows).map(function(str) { + return self._parseRev(str); + }); + + this.pass(changes); + }, + callback + ); +}; + +Scm.prototype.update = function(rev, callback) { + this.run({cmd: 'git', args: ['checkout', rev]}, callback); +}; diff --git a/test/helpers.js b/test/helpers.js index 8eed480..1c0ed27 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -44,6 +44,25 @@ exports.mercurialRevs = [{ comment: 'add tags' }]; +exports.gitRevs = [{ + id: '4ec4643d2044871177d758b3b0e7962b5c4f40d1', + tags: ['zero'], + author: 'oleg', + date: new Date('Mon Jul 13 22:30:58 2015 +0300').getTime(), + comment: 'zero revision' +}, { + id: 'f76bae67efc6fd5a43392517646bb9d685f4266f', + author: 'oleg', + date: new Date('Mon Jul 13 22:31:58 2015 +0300').getTime(), + comment: 'first revision' +}, { + id: '39245d9b93bcd2a0c6708d483b83c98a7bff1d3e', + tags: ['release-0.1.0', 'second-revision'], + author: 'oleg', + date: new Date('Mon Jul 13 22:32:59 2015 +0300').getTime(), + comment: 'third revision' +}]; + exports.initDb = function(callback) { db.init('path/to/db/ignored/for/memdown', { db: require('memdown'), diff --git a/test/repos/git b/test/repos/git new file mode 160000 index 0000000..39245d9 --- /dev/null +++ b/test/repos/git @@ -0,0 +1 @@ +Subproject commit 39245d9b93bcd2a0c6708d483b83c98a7bff1d3e diff --git a/test/scm.js b/test/scm.js index 4607d64..f18dc03 100644 --- a/test/scm.js +++ b/test/scm.js @@ -6,14 +6,16 @@ var expect = require('expect.js'), fs = require('fs'), createScm = require('../lib/scm').createScm, SpawnCommand = require('../lib/command/spawn').Command, - mercurialRevs = require('./helpers').mercurialRevs; + mercurialRevs = require('./helpers').mercurialRevs, + gitRevs = require('./helpers').gitRevs; var getTestData = function(type) { if (type === 'mercurial') return mercurialRevs; + if (type === 'git') return gitRevs; }; -['mercurial'].forEach(function(type) { +['mercurial', 'git'].forEach(function(type) { describe(type, function() { var data = getTestData(type), repositoryName = 'test-repository', @@ -58,7 +60,10 @@ var getTestData = function(type) { // specified revision no later revision will be cloned // including those one with add tag (it's after rev 0 in our // repo) - expect(rev).eql(_(data[0]).omit('tags')); + var expectedRev = ( + type === 'mercurial' ? _(data[0]).omit('tags') : data[0] + ); + expect(rev).eql(expectedRev); done(); }); }); @@ -67,7 +72,10 @@ var getTestData = function(type) { scm.getRev(data[0].id, function(err, rev) { if (err) return done(err); // no tag here, see note above - expect(rev).eql(_(data[0]).omit('tags')); + var expectedRev = ( + type === 'mercurial' ? _(data[0]).omit('tags') : data[0] + ); + expect(rev).eql(expectedRev); done(); }); }); @@ -95,6 +103,15 @@ var getTestData = function(type) { }); }); + var itOrSkip = type === 'git' ? it.skip : it; + itOrSkip('expect current revision still equals to rev0', function(done) { + scm.getCurrent(function(err, rev) { + if (err) return done(err); + expect(rev).eql(data[0]); + done(); + }); + }); + it('update to default revision (should update to last) without error', function(done) { scm.update(scm.defaultRev, done);