Skip to content

Commit

Permalink
Integration tests
Browse files Browse the repository at this point in the history
  • Loading branch information
tschaub committed May 8, 2017
1 parent f8d65aa commit f93eb95
Show file tree
Hide file tree
Showing 11 changed files with 400 additions and 162 deletions.
237 changes: 140 additions & 97 deletions lib/git.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ var fs = require('fs-extra');
var path = require('path');
var util = require('util');

var git = 'git';

/**
* @constructor
* @param {number} code Error code.
Expand Down Expand Up @@ -34,6 +32,9 @@ function spawn(exe, args, cwd) {
child.stderr.on('data', function(chunk) {
buffer.push(chunk.toString());
});
child.stdout.on('data', function(chunk) {
buffer.push(chunk.toString());
});
child.on('close', function(code) {
var output = buffer.join('');
if (code) {
Expand All @@ -47,183 +48,225 @@ function spawn(exe, args, cwd) {
}

/**
* Execute a git command.
* @param {Array.<string>} args Arguments (e.g. ['remote', 'update']).
* Create an object for executing git commands.
* @param {string} cwd Repository directory.
* @return {Promise} A promise. The promise will be resolved with stdout output
* or rejected with an error.
* @param {string} exe Git executable (full path if not already on path).
* @constructor
*/
exports = module.exports = function(args, cwd) {
return spawn(git, args, cwd);
};
function Git(cwd, cmd) {
this.cwd = cwd;
this.cmd = cmd || 'git';
this.output = '';
}

/**
* Set the Git executable to be used by exported methods (defaults to 'git').
* @param {string} exe Git executable (full path if not already on path).
* Execute an arbitrary git command.
* @param {string} var_args Arguments (e.g. 'remote', 'update').
* @return {Promise} A promise. The promise will be resolved with this instance
* or rejected with an error.
*/
exports.exe = function(exe) {
git = exe;
Git.prototype.exec = function() {
return spawn(this.cmd, [].slice.call(arguments), this.cwd).then(
function(output) {
this.output = output;
return this;
}.bind(this)
);
};

/**
* Initialize repository.
* @param {string} cwd Repository directory.
* @return {ChildProcess} Child process.
*/
exports.init = function init(cwd) {
return spawn(git, ['init'], cwd);
};

/**
* Clone a repo into the given dir if it doesn't already exist.
* @param {string} repo Repository URL.
* @param {string} dir Target directory.
* @param {string} branch Branch name.
* @param {options} options All options.
* @return {Promise} A promise.
*/
exports.clone = function clone(repo, dir, branch, options) {
return fs.exists(dir).then(function(exists) {
if (exists) {
return Promise.resolve();
} else {
return fs.mkdirp(path.dirname(path.resolve(dir))).then(function() {
var args = [
'clone',
repo,
dir,
'--branch',
branch,
'--single-branch',
'--origin',
options.remote,
'--depth',
options.depth
];
return spawn(git, args).catch(function(err) {
// try again without banch options
return spawn(git, ['clone', repo, dir, '--origin', options.remote]);
});
});
}
});
Git.prototype.init = function() {
return this.exec('init');
};

/**
* Clean up unversioned files.
* @param {string} cwd Repository directory.
* @return {Promise} A promise.
*/
var clean = (exports.clean = function clean(cwd) {
return spawn(git, ['clean', '-f', '-d'], cwd);
});
Git.prototype.clean = function() {
return this.exec('clean', '-f', '-d');
};

/**
* Hard reset to remote/branch
* @param {string} remote Remote alias.
* @param {string} branch Branch name.
* @param {string} cwd Repository directory.
* @return {Promise} A promise.
*/
var reset = (exports.reset = function reset(remote, branch, cwd) {
return spawn(git, ['reset', '--hard', remote + '/' + branch], cwd);
});
Git.prototype.reset = function(remote, branch) {
return this.exec('reset', '--hard', remote + '/' + branch);
};

/**
* Fetch from a remote.
* @param {string} remote Remote alias.
* @param {string} cwd Repository directory.
* @return {Promise} A promise.
*/
exports.fetch = function fetch(remote, cwd) {
return spawn(git, ['fetch', remote], cwd);
Git.prototype.fetch = function(remote) {
return this.exec('fetch', remote);
};

/**
* Checkout a branch (create an orphan if it doesn't exist on the remote).
* @param {string} remote Remote alias.
* @param {string} branch Branch name.
* @param {string} cwd Repository directory.
* @return {Promise} A promise.
*/
exports.checkout = function checkout(remote, branch, cwd) {
Git.prototype.checkout = function(remote, branch) {
var treeish = remote + '/' + branch;
return spawn(git, ['ls-remote', '--exit-code', '.', treeish], cwd).then(
return this.exec('ls-remote', '--exit-code', '.', treeish).then(
function() {
// branch exists on remote, hard reset
return spawn(git, ['checkout', branch], cwd)
.then(function() {
return clean(cwd);
})
.then(function() {
return reset(remote, branch, cwd);
});
},
return this.exec('checkout', branch)
.then(
function() {
return this.clean();
}.bind(this)
)
.then(
function() {
return this.reset(remote, branch);
}.bind(this)
);
}.bind(this),
function(error) {
if (error instanceof ProcessError && error.code === 2) {
// branch doesn't exist, create an orphan
return spawn(git, ['checkout', '--orphan', branch], cwd);
return this.exec('checkout', '--orphan', branch);
} else {
// unhandled error
throw error;
}
}
}.bind(this)
);
};

/**
* Remove all unversioned files.
* @param {string} files Files argument.
* @param {string} cwd Repository directory.
* @return {Promise} A promise.
*/
exports.rm = function rm(files, cwd) {
return spawn(git, ['rm', '--ignore-unmatch', '-r', '-f', files], cwd);
Git.prototype.rm = function(files) {
return this.exec('rm', '--ignore-unmatch', '-r', '-f', files);
};

/**
* Add files.
* @param {string} files Files argument.
* @param {string} cwd Repository directory.
* @return {Promise} A promise.
*/
exports.add = function add(files, cwd) {
return spawn(git, ['add', files], cwd);
Git.prototype.add = function(files) {
return this.exec('add', files);
};

/**
* Commit.
* Commit (if there are any changes).
* @param {string} message Commit message.
* @param {string} cwd Repository directory.
* @return {Promise} A promise.
*/
exports.commit = function commit(message, cwd) {
return spawn(
git,
['diff-index', '--quiet', 'HEAD', '.'],
cwd
).catch(function() {
return spawn(git, ['commit', '-m', message], cwd);
});
Git.prototype.commit = function(message) {
return this.exec('diff-index', '--quiet', 'HEAD').catch(
function() {
return this.exec('commit', '-m', message);
}.bind(this)
);
};

/**
* Add tag
* @param {string} name Name of tag.
* @param {string} cwd Repository directory.
* @return {Promise} A promise.
*/
exports.tag = function tag(name, cwd) {
return spawn(git, ['tag', name], cwd);
Git.prototype.tag = function(name) {
return this.exec('tag', name);
};

/**
* Push a branch.
* @param {string} remote Remote alias.
* @param {string} branch Branch name.
* @param {string} cwd Repository directory.
* @return {Promise} A promise.
*/
exports.push = function push(remote, branch, cwd) {
return spawn(git, ['push', '--tags', remote, branch], cwd);
Git.prototype.push = function(remote, branch) {
return this.exec('push', '--tags', remote, branch);
};

/**
* Get the URL for a remote.
* @param {string} remote Remote alias.
* @return {Promise<string>} A promise for the remote URL.
*/
Git.prototype.getRemoteUrl = function(remote) {
return this.exec('config', '--get', 'remote.' + remote + '.url')
.then(function(git) {
var repo = git.output && git.output.split(/[\n\r]/).shift();
if (repo) {
return repo;
} else {
throw new Error(
'Failed to get repo URL from options or current directory.'
);
}
})
.catch(function(err) {
throw new Error(
'Failed to get remote.' +
remote +
'.url (task must either be ' +
'run in a git repository with a configured ' +
remote +
' remote ' +
'or must be configured with the "repo" option).'
);
});
};

/**
* Clone a repo into the given dir if it doesn't already exist.
* @param {string} repo Repository URL.
* @param {string} dir Target directory.
* @param {string} branch Branch name.
* @param {options} options All options.
* @return {Promise<Git>} A promise.
*/
Git.clone = function clone(repo, dir, branch, options) {
return fs.exists(dir).then(function(exists) {
if (exists) {
return Promise.resolve(new Git(dir, options.git));
} else {
return fs.mkdirp(path.dirname(path.resolve(dir))).then(function() {
var args = [
'clone',
repo,
dir,
'--branch',
branch,
'--single-branch',
'--origin',
options.remote,
'--depth',
options.depth
];
return spawn(options.git, args)
.catch(function(err) {
// try again without banch or depth options
return spawn(options.git, [
'clone',
repo,
dir,
'--origin',
options.remote
]);
})
.then(function() {
return new Git(dir, options.git);
});
});
}
});
};

module.exports = Git;
Loading

0 comments on commit f93eb95

Please sign in to comment.