Skip to content

Commit

Permalink
Merge pull request #2 from Yogu/merge-request-feature
Browse files Browse the repository at this point in the history
Merge request feature
  • Loading branch information
Yogu committed Jun 9, 2015
2 parents 00ccde2 + dc55f87 commit 81b7f36
Show file tree
Hide file tree
Showing 75 changed files with 213 additions and 22 deletions.
8 changes: 5 additions & 3 deletions public/partials/site-overview.html
Expand Up @@ -18,15 +18,17 @@ <h1 class="site-title">
<div ng-switch-when="mysql">MySQL (db: <strong>{{site.dbConfig.database}}</strong>,
host: {{site.dbConfig.host}}, user: {{site.dbConfig.user}})</div>
</dd>
<dt ng-if="site.stagingOf">Staging:</dt>
<dd ng-if="site.stagingOf">This is the staging site of <a href="#/sites/{{site.stagingOf}}">{{site.stagingOf}}</a>.</dd>
<dt ng-if="site.stagingOf && !site.isMergeRequestSite">Staging:</dt>
<dd ng-if="site.stagingOf && !site.isMergeRequestSite">This is the staging site of <a href="#/sites/{{site.stagingOf}}">{{site.stagingOf}}</a>.</dd>
<dt ng-if="site.isMergeRequestSite">Merge Request:</dt>
<dd ng-if="site.isMergeRequestSite">This is the site for merge request of branch <i>{{site.sourceBranch}}</i> into site <a href="#/sites/{{site.stagingOf}}">{{site.stagingOf}}</a>.</dd>
<dt>Links:</dt>
<dd><a href="#/sites/{{site.name}}/tasks">tasks</a></dd>
<dd><a href="#/sites/{{site.name}}/backups">backups</a></dd>
<dd>
<button ng-if="site.canUpgrade" class="btn btn-success" ng-click="upgrade()">Upgrade {{site.behindBy}} commit(s)</button>
<button ng-if="site.stagingOf" class="btn" ng-click="resetStaging()">Reset to {{site.stagingOf}}</button>
<button ng-if="stagingOfSite && stagingOfSite.revision != site.revision" class="btn btn-success" ng-click="upgradeToStaging()">Upgrade {{site.stagingOf}}</button>
<button ng-if="!site.isMergeRequestSite && stagingOfSite && stagingOfSite.revision != site.revision" class="btn btn-success" ng-click="upgradeToStaging()">Upgrade {{site.stagingOf}}</button>
<button class="btn btn-danger" ng-click="reset()">Reset Data Base</button>
<button class="btn btn-danger" ng-click="delete()">Delete</button>
</dd>
Expand Down
27 changes: 26 additions & 1 deletion src/server.js
Expand Up @@ -11,7 +11,7 @@ require('q').longStackSupport = true;
expressValidator.validator.extend('isIdentifier', function (str) {
if (typeof str != 'string')
return false;
return str.match(/[a-zA-Z0-9_\-\.]+/) !== null;
return str.match(/[a-zA-Z0-9\-]+/) !== null;
});

exports.start = function(port, dir) {
Expand Down Expand Up @@ -56,6 +56,31 @@ exports.start = function(port, dir) {
res.json({taskID: task.id});
});

app.post('/api/gitlab-merge-request', function(req, res) {
if (req.body.object_kind != 'merge_request') {
return res.send('Only merge_request events supported', 400);
}

if (req.body.object_attributes.action != 'open') {
return res.send('Only considering "open" events', 200);
}

req.checkBody('object_attributes.source_branch').isIdentifier();
req.checkBody('object_attributes.target_branch').isIdentifier();
var errors = req.validationErrors();
if (errors) {
return res.send('Validation errors: ' + JSON.stringify(errors), 400);
}

var sourceBranch = req.body.object_attributes.source_branch;
var targetBranch = req.body.object_attributes.target_branch;

var task = controller.manager.createMergeRequestSiteTask(sourceBranch, targetBranch);
controller.manager.schedule(task);

res.json({taskID: task.id});
});

app.post('/api/reload', function(req, res) {
controller.reload();
res.send(202 /* accepted */);
Expand Down
4 changes: 2 additions & 2 deletions src/site.js
Expand Up @@ -43,8 +43,8 @@ Site.prototype.upgradeTask = function() {
return new UpgradeSiteTask(this);
};

Site.prototype.upgradeToRevisionTask = function(revision) {
return new UpgradeToRevisionTask(this, revision);
Site.prototype.upgradeToRevisionTask = function(revision, allowNonFastForward) {
return new UpgradeToRevisionTask(this, revision, allowNonFastForward);
};

Site.prototype.backupTask = function(message) {
Expand Down
5 changes: 5 additions & 0 deletions src/siteManager.js
Expand Up @@ -4,6 +4,7 @@ var FetchTask = require('./tasks/fetch.js');
var LoadSiteManagerTask = require('./tasks/loadSiteManager.js');
var AddSiteTask = require('./tasks/addSite.js');
var DeleteSiteTask = require('./tasks/deleteSite.js');
var CreateMergeRequestSiteTask = require('./tasks/createMergeRequestSite.js');
var Config = require('./config.js');

// Register common hooks
Expand Down Expand Up @@ -36,6 +37,10 @@ SiteManager.prototype.deleteSiteTask = function(site) {
return new DeleteSiteTask(this, site);
};

SiteManager.prototype.createMergeRequestSiteTask = function(sourceBranch, targetBranch) {
return new CreateMergeRequestSiteTask(this, sourceBranch, targetBranch)
};

SiteManager.prototype.getSite = function(siteName) {
for (var i = 0; i < this.sites.length; i++) {
if (this.sites[i].name == siteName)
Expand Down
2 changes: 2 additions & 0 deletions src/tasks/addSite.js
Expand Up @@ -105,6 +105,8 @@ AddSiteTask.prototype.perform = function*() {
yield site.loaded;
yield hooks.call('afterCreate', this, site);
yield hooks.call('afterCheckout', this, site);

return site;
};

module.exports = AddSiteTask;
41 changes: 41 additions & 0 deletions src/tasks/createMergeRequestSite.js
@@ -0,0 +1,41 @@
var Task = require('../task.js');
var fs = require('q-io/fs');
var Q = require('q');
var yaml = require('js-yaml');

function CreateMergeRequestSiteTask(siteManager, sourceBranch, targetBranch) {
Task.call(this);
this.siteManager = siteManager;
this.sourceBranch = sourceBranch;
this.targetBranch = targetBranch;
this.name = 'Create Site for Merge Request from ' + sourceBranch + ' into ' + targetBranch;
}

CreateMergeRequestSiteTask.prototype = Object.create(Task.prototype);

CreateMergeRequestSiteTask.prototype.perform = function*() {
var manager = this.siteManager;
var siteName = 'mr-' + this.sourceBranch;

if (!(this.targetBranch in manager.siteBranchMapping)) {
throw new Error('siteBranchMapping config does not contain the site for branch ' + this.targetBranch);
}
var targetSiteName = manager.siteBranchMapping[this.targetBranch];

var site = yield this.runNested(manager.addSiteTask(siteName, this.targetBranch));

this.doLog('Configuring site: isMergeRequest: true, sourceBranch: ' + this.sourceBranch +
', stagingOf: ' + targetSiteName);
var sites = yaml.safeLoad(yield fs.read(manager.path + '/sites.yaml'));
sites[site.name].isMergeRequestSite = true;
sites[site.name].sourceBranch = this.sourceBranch;
sites[site.name].stagingOf = targetSiteName;
yield fs.write(manager.path + '/sites.yaml', yaml.safeDump(sites));

yield this.runNested(manager.loadTask());
var upgradeTask = site.upgradeTask();
site.schedule(upgradeTask);
yield upgradeTask;
};

module.exports = CreateMergeRequestSiteTask;
13 changes: 11 additions & 2 deletions src/tasks/loadSite.js
Expand Up @@ -18,11 +18,20 @@ LoadSiteTask.prototype.perform = function*() {
yield this.exec("git fetch");

this.site.revision = (yield this.exec("git rev-parse HEAD")).stdout.trim();
this.site.branch = (yield this.exec("git rev-parse --abbrev-ref HEAD")).stdout.trim();
if (this.site.isMergeRequestSite) {
// this site should be upgraded when sourceBranch changes
this.site.branch = this.site.sourceBranch;
} else {
this.site.branch = (yield this.exec("git rev-parse --abbrev-ref HEAD")).stdout.trim();
}
if (this.site.branch == 'HEAD') {
this.site.branch = ''; // this happens when no branch is checked out
}
if (this.site.branch) {
if (this.site.isMergeRequestSite) {
this.site.upstreamRevision = (yield this.exec("git rev-parse origin/" + this.site.sourceBranch)).stdout.trim();
this.site.aheadBy = 0; // not really useful because we are always ahead by the merge commit
this.site.behindBy = yield this._getCommitCountBetween('HEAD', 'origin/' + this.site.sourceBranch);
} else if (this.site.branch) {
this.site.upstreamRevision = (yield this.exec("git rev-parse origin/" + this.site.branch)).stdout.trim();
this.site.aheadBy = yield this._getCommitCountBetween('origin/' + this.site.branch, this.site.branch);
this.site.behindBy = yield this._getCommitCountBetween(this.site.branch, 'origin/' + this.site.branch);
Expand Down
3 changes: 3 additions & 0 deletions src/tasks/loadSiteManager.js
Expand Up @@ -50,6 +50,7 @@ LoadSiteManagerTask.prototype.perform = function*() {
manager.ownURL = config.ownURL || 'http://localhost:8888/';
manager.mailConfig = config.mail || { transport: 'sendmail', sender: 'Site Manager <site-manager@example.com>' };
manager.config = config;
manager.siteBranchMapping = config.siteBranchMapping || {};

var sites = yaml.safeLoad(yield fs.read(manager.path + '/sites.yaml'));

Expand Down Expand Up @@ -79,6 +80,8 @@ LoadSiteManagerTask.prototype.perform = function*() {
site.watchers = globalWatchers.concat(siteConfig.watchers || []);
site.ownURL = manager.ownURL + '#/sites/' + site.name;
site.stagingOf = siteConfig.stagingOf;
site.isMergeRequestSite = siteConfig.isMergeRequestSite;
site.sourceBranch = siteConfig.sourceBranch;

if (this.loadSites || existingSites.length == 0 /* always load new sites */)
site.schedule(site.loadTask());
Expand Down
12 changes: 9 additions & 3 deletions src/tasks/upgradeSite.js
Expand Up @@ -33,10 +33,16 @@ UpgradeSiteTask.prototype.perform = function*() {
yield this.runNested(site.resetStagingTask( { backup: false }));
}

this.doLog('Pulling incoming commits...');
yield this.exec('git pull --ff-only');
if (this.site.isMergeRequestSite) {
this.doLog('Merging source branch ' + this.site.sourceBranch + '...');
yield this.exec('git merge --no-ff origin/' + this.site.sourceBranch);
this.doLog('Merge completed');
} else {
this.doLog('Pulling incoming commits...');
yield this.exec('git pull --ff-only');
this.doLog('Pull completed');
}

this.doLog('Pull completed');
yield this.runNestedQuietly(site.loadTask());

yield hooks.call('afterPull', this, site);
Expand Down
15 changes: 11 additions & 4 deletions src/tasks/upgradeToRevision.js
Expand Up @@ -5,11 +5,12 @@ var hooks = require('../hooks.js');
/**
* Upgrades a site to a specific revision
*/
function UpgradeToRevisionTask(site, revision) {
function UpgradeToRevisionTask(site, revision, allowNonFastForward) {
Task.call(this);
this.site = site;
this.name = 'Upgrade to ' + revision;
this.revision = revision;
this.allowNonFastForward = allowNonFastForward;
}

UpgradeToRevisionTask.prototype = Object.create(Task.prototype);
Expand All @@ -23,11 +24,12 @@ UpgradeToRevisionTask.prototype.perform = function*() {
var commitsAhead = yield countCommitsBetween(this, 'HEAD', this.revision);
var commitsBehind = yield countCommitsBetween(this, this.revision, 'HEAD');

if (commitsBehind > 0) {
if (commitsBehind > 0 && !this.allowNonFastForward) {
throw new Error('The upgrade is not fast-forward, this site is ' +
commitsBehind + ' commits ahead of ' + this.revision + '. To go back to ' +
'an older version, either restore a backup or do a git revert.');
}
var needToMerge = commitsBehind > 0;

if (commitsAhead == 0) {
this.doLog('Already up-to-date.');
Expand All @@ -40,8 +42,13 @@ UpgradeToRevisionTask.prototype.perform = function*() {

var oldRevision = site.revision;
try {
this.doLog('Checking ot new revision...');
yield this.exec('git checkout ' + this.revision);
if (needToMerge) {
this.doLog('Merging new revision...');
yield this.exec('git merge ' + this.revision);
} else {
this.doLog('Checking out new revision...');
yield this.exec('git checkout ' + this.revision);
}
yield this.runNestedQuietly(site.loadTask());

yield hooks.call('afterPull', this, site);
Expand Down
@@ -0,0 +1 @@
x��Mj�0@�u���F��R�M��I3����"r��� ���+m]������ I�-��L5��Tmv.=y�a��+2�;u�� ����j9J-I|2��6Zgt6��}�Z�o����r���&��j��B}=W������ NDŽV��o��R]�{oO���U��wVB�Ir
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,2 @@
x��Aj1 @Ѯ}
�E���B�6�BKM 3Ɠ�۷���i�z�}~]�Eʒq�:e$�I C�<[��&CC2w��ۀd^�P*��T��<S�9�Ԥ�0c�'���g��Ko�E;����W��o����@�yf!�=�[���o�_�N뽷�V�� �2���[pH:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,2 @@
x��Aj1 @Ѯ}
� ��e{ !t�!dY�23�x(�}�� ���ɶ,���1�*(�`JX�3nͨ��y�I�i���=��: W���B�h�RF-��l*�&�+U9����7^�[2k��}������˩���s�O��;����~�tV�S{W�dG�
@@ -0,0 +1,2 @@
x��Aj�0s�+�3�d�žr� ƣVvamE>��1�A�MU�ֶ�>h����Z�-�C�s���X�̘,-���AV�3��/����i��5�I"�QB��j�9Hqz�[�t՝��:����.?��xh�^
�H™O92�3O���������>��A߰���qt�_��H�
Binary file not shown.
1 change: 1 addition & 0 deletions test/resources/remote.git/refs/heads/new-feature
@@ -0,0 +1 @@
b8731878376e3c8c2e9bbfe5947519f8d852b5d7
5 changes: 5 additions & 0 deletions test/resources/site-collection/config.yaml
Expand Up @@ -15,3 +15,8 @@ notifyLastCommitterOnFailedUpgrade: false
# data base defaults (prototype for site db option)
db:
type: sqlite
path: ../db/test.sqlite # default, to prevent errors in the add site tests

# specify which site should be used as base for merge requests into specific branches
siteBranchMapping:
master: dev
2 changes: 1 addition & 1 deletion test/resources/site-collection/repo.git/FETCH_HEAD
@@ -1 +1 @@
7bf49e2636c5f54672e970b7fec548acd5b4bc13 branch 'master' of ../../remote
f2614a5777fad66b7fdc8f582da7f2e200b1438f branch 'second-feature' of ../../remote
Binary file not shown.
@@ -0,0 +1,2 @@
x���J1�a�y��$�d6(��^�^|�dv�v���P�^�����o[���F�K"zV"T�%d��3�Y�E�ɖD�+wibѐ'?1)�)��hKTa
s�J(���q�;��Y�*��������ܷ�E^��'��,,Zk��o����F�C��o��j�����r6?'�I�
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,2 @@
x��Aj1 @Ѯ}
�E���B�6�BKM 3Ɠ�۷���i�z�}~]�Eʒq�:e$�I C�<[��&CC2w��ۀd^�P*��T��<S�9�Ԥ�0c�'���g��Ko�E;����W��o����@�yf!�=�[���o�_�N뽷�V�� �2���[pH:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,2 @@
x��Aj1 @Ѯ}
� ��e{ !t�!dY�23�x(�}�� ���ɶ,���1�*(�`JX�3nͨ��y�I�i���=��: W���B�h�RF-��l*�&�+U9����7^�[2k��}������˩���s�O��;����~�tV�S{W�dG�
@@ -0,0 +1,2 @@
x��Aj�0s�+�3�d�žr� ƣVvamE>��1�A�MU�ֶ�>h����Z�-�C�s���X�̘,-���AV�3��/����i��5�I"�QB��j�9Hqz�[�t՝��:����.?��xh�^
�H™O92�3O���������>��A߰���qt�_��H�
@@ -0,0 +1 @@
7f2efb17ba04bd394f2315a870ad7e2b330da45c
@@ -0,0 +1 @@
f2614a5777fad66b7fdc8f582da7f2e200b1438f
@@ -0,0 +1 @@
1cccade31b5671504e1eea5147fd31be4f71641d
@@ -0,0 +1 @@
b8731878376e3c8c2e9bbfe5947519f8d852b5d7
@@ -0,0 +1 @@
f2614a5777fad66b7fdc8f582da7f2e200b1438f
9 changes: 9 additions & 0 deletions test/resources/site-collection/sites.yaml
Expand Up @@ -6,6 +6,8 @@ test:
path: ../db/test.sqlite # only for sqlite, relative to this file

dev:
db:
path: ../db/test.sqlite # only for sqlite, relative to this file

feature-x:
root: ../feature-x-site # optional, specifies path to repo relative to siteRoot
Expand All @@ -18,3 +20,10 @@ staging:
db:
path: ../db/staging.sqlite
stagingOf: production

mr-new-feature:
isMergeRequestSite: true
stagingOf: dev
sourceBranch: new-feature
db:
path: ../db/test.sqlite # only for sqlite, relative to this file
6 changes: 6 additions & 0 deletions test/resources/site-collection/sites/mr-new-feature/README
@@ -0,0 +1,6 @@
First commit
second line
third line
fourth line

NEW FEATURE!
@@ -0,0 +1,3 @@
INSERT INTO customers
(prename, surname)
VALUES ('John', 'Doe');
@@ -0,0 +1,3 @@
INSERT INTO customers
(prename, surname)
VALUES ('Mary', 'Smith');
@@ -0,0 +1,2 @@
7bf49e2636c5f54672e970b7fec548acd5b4bc13 branch 'master' of ../../repo
f19b21f71a8fd10c5edf5d130bae783f06a11c5e not-for-merge branch 'new-feature' of ../../repo
@@ -0,0 +1 @@
ref: refs/heads/master
@@ -0,0 +1 @@
0c3ba58efb54b70c53cc49a24a160bfcc5680c82
12 changes: 12 additions & 0 deletions test/resources/site-collection/sites/mr-new-feature/dot_git/config
@@ -0,0 +1,12 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[remote "origin"]
url = ../../repo.git/
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master

Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,2 @@
x��Aj1 @Ѯ}
� ��e{ !t�!dY�23�x(�}�� ���ɶ,���1�*(�`JX�3nͨ��y�I�i���=��: W���B�h�RF-��l*�&�+U9����7^�[2k��}������˩���s�O��;����~�tV�S{W�dG�
@@ -0,0 +1,2 @@
P pack-f74139904d152508e36f507c344da636b58192c4.pack

Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
f19b21f71a8fd10c5edf5d130bae783f06a11c5e
@@ -0,0 +1 @@
7bf49e2636c5f54672e970b7fec548acd5b4bc13
@@ -0,0 +1 @@
f19b21f71a8fd10c5edf5d130bae783f06a11c5e
2 changes: 1 addition & 1 deletion test/server-unit/_spec.js
@@ -1,6 +1,6 @@
require('../utils/resources.js');

jasmine.getEnv().defaultTimeoutInterval = 500;
jasmine.getEnv().defaultTimeoutInterval = 1000;

require('q').longStackSupport = true;

Expand Down
2 changes: 1 addition & 1 deletion test/server-unit/serverSpec.js
Expand Up @@ -12,5 +12,5 @@ describe("server", function() {
}
done();
}.bind(this));
});
}, 5000);
});

0 comments on commit 81b7f36

Please sign in to comment.