From 47038707ba2aad13490eb63dd62e60b2aa3ec681 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 18 Mar 2014 16:30:44 -0600 Subject: [PATCH] Setup --- .gitignore | 1 + .jshintrc | 20 ++++ lib/git.js | 241 +++++++++++++++++++++++++++++++++++++++++++++++++ lib/index.js | 6 ++ package.json | 41 +++++++++ readme.md | 5 + tasks.js | 56 ++++++++++++ test/.jshintrc | 26 ++++++ 8 files changed, 396 insertions(+) create mode 100644 .gitignore create mode 100644 .jshintrc create mode 100644 lib/git.js create mode 100644 lib/index.js create mode 100644 package.json create mode 100644 readme.md create mode 100644 tasks.js create mode 100644 test/.jshintrc diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2ccbe46 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/node_modules/ diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..3dbf635 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,20 @@ +{ + "curly": true, + "eqeqeq": true, + "indent": 2, + "latedef": true, + "newcap": true, + "nonew": true, + "quotmark": "single", + "undef": true, + "trailing": true, + "maxlen": 80, + "globals": { + "Buffer": false, + "exports": true, + "module": false, + "process": false, + "require": false, + "__dirname": false + } +} \ No newline at end of file diff --git a/lib/git.js b/lib/git.js new file mode 100644 index 0000000..ad96a1b --- /dev/null +++ b/lib/git.js @@ -0,0 +1,241 @@ +var cp = require('child_process'); +var path = require('path'); +var util = require('util'); + +var Q = require('q'); +var fs = require('q-io/fs'); + +var git = 'git'; + + + +/** + * @constructor + * @param {number} code Error code. + * @param {string} message Error message. + */ +function ProcessError(code, message) { + var callee = arguments.callee; + Error.apply(this, [message]); + Error.captureStackTrace(this, callee); + this.code = code; + this.message = message; + this.name = callee.name; +} +util.inherits(ProcessError, Error); + + +/** + * Util function for handling spawned processes as promises. + * @param {string} exe Executable. + * @param {Array.} args Arguments. + * @param {string} cwd Working directory. + * @return {Promise} A promise. + */ +function spawn(exe, args, cwd) { + var deferred = Q.defer(); + var child = cp.spawn(exe, args, {cwd: cwd || process.cwd()}); + var buffer = []; + child.stderr.on('data', function(chunk) { + buffer.push(chunk.toString()); + }); + child.stdout.on('data', function(chunk) { + deferred.notify(chunk); + }); + child.on('close', function(code) { + if (code) { + var msg = buffer.join('') || 'Process failed: ' + code; + deferred.reject(new ProcessError(code, msg)); + } else { + deferred.resolve(code); + } + }); + return deferred.promise; +} + + +/** + * Execute a git command. + * @param {Array.} args Arguments (e.g. ['remote', 'update']). + * @param {string} cwd Repository directory. + * @return {Promise} A promise. The promise will be resolved with the exit code + * or rejected with an error. To get stdout, use a progress listener (e.g. + * `promise.progress(function(chunk) {console.log(String(chunk);}))`). + */ +exports = module.exports = function(args, cwd) { + return spawn(git, args, cwd); +}; + + +/** + * 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). + */ +exports.exe = function(exe) { + git = exe; +}; + + +/** + * 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 Q.resolve(); + } else { + return fs.makeTree(path.dirname(path.resolve(dir))).then(function() { + var args = ['clone', repo, dir, '--branch', branch, '--single-branch']; + if (options.depth) { + args.push('--depth', options.depth); + } + return spawn(git, args).fail(function(err) { + // try again without banch options + return spawn(git, ['clone', repo, dir]); + }); + }); + } + }); +}; + + +/** + * 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); +}; + + +/** + * 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); +}; + + +/** + * 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); +}; + + +/** + * 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) { + var treeish = remote + '/' + branch; + return spawn(git, ['ls-remote', '--exit-code', '.', treeish], cwd) + .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); + }); + }, function(error) { + if (error instanceof ProcessError && error.code === 2) { + // branch doesn't exist, create an orphan + return spawn(git, ['checkout', '--orphan', branch], cwd); + } else { + // unhandled error + return Q.reject(error); + } + }); +}; + + +/** + * 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); +}; + + +/** + * 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); +}; + + +/** + * Commit. + * @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) + .then(function() { + // nothing to commit + return Q.resolve(); + }) + .fail(function() { + return spawn(git, ['commit', '-m', message], cwd); + }); +}; + + +/** + * 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); +}; + + +/** + * 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); +}; diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..c2bfaec --- /dev/null +++ b/lib/index.js @@ -0,0 +1,6 @@ + + +/** + * Generate promises for spawned git commands. + */ +exports.git = require('./git'); diff --git a/package.json b/package.json new file mode 100644 index 0000000..1a2a510 --- /dev/null +++ b/package.json @@ -0,0 +1,41 @@ +{ + "name": "gh-pages", + "version": "0.0.0", + "description": "Publish to a gh-pages branch on GitHub (or any other branch on any other remote)", + "keywords": [ + "git", + "gh-pages", + "github" + ], + "author": { + "name": "Tim Schaub", + "url": "http://tschaub.net/" + }, + "licenses": [ + { + "type": "MIT", + "url": "http://tschaub.mit-license.org/" + } + ], + "homepage": "https://github.com/tschaub/gh-pages", + "repository": { + "type": "git", + "url": "git://github.com/tschaub/gh-pages.git" + }, + "bugs": { + "url": "https://github.com/tschaub/gh-pages/issues" + }, + "main": "lib/index.js", + "scripts": { + "test": "node tasks.js lint test" + }, + "dependencies": { + "q": "~1.0.1", + "q-io": "~1.11.0" + }, + "devDependencies": { + "glob": "~3.2.9", + "mocha": "~1.18.2", + "jshint": "~2.4.4" + } +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..41cdc88 --- /dev/null +++ b/readme.md @@ -0,0 +1,5 @@ +# `gh-pages` + +Publish files to a `gh-pages` branch on GitHub (or any other branch anywhere else). + +This will evolve into a more useful package. For now, it just some extracted bits from the [`grunt-gh-pages`](https://www.npmjs.org/package/grunt-gh-pages) package. diff --git a/tasks.js b/tasks.js new file mode 100644 index 0000000..6afd7a3 --- /dev/null +++ b/tasks.js @@ -0,0 +1,56 @@ +var path = require('path'); + +var jshint = require('jshint/src/cli').run; +var glob = require('glob'); +var Mocha = require('mocha'); + + +/** + * Run the linter. + * @param {function(Error)} done Callback. + */ +exports.lint = function(done) { + var args = ['lib', 'test', 'tasks.js']; + var passed = jshint({args: args}); + process.nextTick(function() { + done(passed ? null : new Error('JSHint failed')); + }); +}; + + +/** + * Run the tests. + * @param {function(Error)} done Callback. + */ +exports.test = function(done) { + var mocha = new Mocha(); + mocha.reporter('spec'); + mocha.ui('bdd'); + mocha.files = glob.sync('test/**/*.spec.js').map(function(file) { + return path.resolve(file); + }); + mocha.run(function(failures) { + done(failures ? new Error('Mocha failures') : null); + }); +}; + + +var tasks = process.argv.slice(2); + +function run(current) { + var task = tasks[current]; + if (task) { + exports[task](function(err) { + if (err) { + process.stderr.write(err.message + '\n'); + process.exit(1); + } else { + ++current; + run(current); + } + }); + } else { + process.exit(0); + } +} +run(0); diff --git a/test/.jshintrc b/test/.jshintrc new file mode 100644 index 0000000..ff66433 --- /dev/null +++ b/test/.jshintrc @@ -0,0 +1,26 @@ +{ + "curly": true, + "eqeqeq": true, + "indent": 2, + "latedef": true, + "newcap": true, + "nonew": true, + "quotmark": "single", + "undef": true, + "trailing": true, + "maxlen": 80, + "globals": { + "Buffer": false, + "exports": true, + "before": false, + "beforeEach": false, + "after": false, + "afterEach": false, + "describe": false, + "it": false, + "module": false, + "process": false, + "require": false, + "__dirname": false + } +} \ No newline at end of file