diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..dbc21c9 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,13 @@ +{ + "extends": "standard", + "plugins": [ + "standard", + "promise" + ], + "rules": { + "semi": [ + "error", + "always" + ] + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3c3629e..00cbbdf 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,59 @@ -node_modules +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..0cebcf9 --- /dev/null +++ b/.npmignore @@ -0,0 +1,23 @@ +*.log +*.pid +*.seed +.editorconfig +.eslintrc* +.eslintignore +.gitignore +.grunt +.lock-wscript +.node_repl_history +.stylelintrc* +.travis.yml +.vscode +.nyc_output +appveyor.yml +coverage +gulpfile.js +lib-cov +logs +node_modules +npm-debug.log* +pids +test diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..9e9639e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +sudo: false +language: node_js +node_js: + - "stable" + - "6" + - "4" +before_install: + - npm i npm@latest -g diff --git a/README.md b/README.md index 0ea7117..6157daa 100644 --- a/README.md +++ b/README.md @@ -5,32 +5,73 @@ Create a readable stream from a spawned git process. ## Usage ```js -gitSpawnedStream(repoPath, spawnArguments, limitInBytes, gitBinary) +gitSpawnedStream(args, options) ``` -Arguments: +### Parameters: -- `repoPath` - the path to the repo, ex: /home/alex/node/.git (or the path to the git bare repo) -- `spawnArguments` - the arguments that will be passed to the `child_process.spawn` function -- `limitInBytes` - kill the process if it exceeds the imposed limit (sends more data than allowed) -- `gitBinary` - path to the git binary to use (use the one in `PATH` by default) +- `args` - `{string[]}` - the arguments that will be passed to the `child_process.spawn` function +- `options` - `{Object}` - optional options -Example: +### Options: + +#### `config` `{Object}` + +`-c` from `git`, key-value pairs + +#### `gitDir` `{string}` + +`--git-dir` from `git`. + +#### `workTree` `{string}` + +`--work-tree` from `git` + +#### `pager` `{boolean}` + +Default: `false` + +`--no-pager` from `git`. + +#### `gitBinary` `{string}` + +'git' path to the git binary to use + +#### `limit` `{number}` + +kill the process if it exceeds the imposed limit (sends more data than allowed) + +#### `input` `{string | Buffer | Stream.Readable}` + +The value which will be passed as stdin to the spawned process + +#### `cwd` `string` + +Current working directory of git process + +#### `env` `Object` + +Environment key-value pairs + +## Example: ```js var gitSpawnedStream = require('git-spawned-stream'); var path = require('path'); -var repoPath = process.env.REPO || path.join(__dirname, '.git'); -repoPath = path.resolve(repoPath); -var byteLimit = 5 * 1024 * 1024; // 5 Mb +var gitDir = process.env.REPO || path.join(__dirname, '.git'); +gitDir = path.resolve(gitDir); +var limit = 5 * 1024 * 1024; // 5 Mb // sort of a git log -n 2 -var stream = gitSpawnedStream(repoPath, [ +var stream = gitSpawnedStream([ 'rev-list', '--max-count=2', '--header', 'HEAD' -], byteLimit); +], { + gitDir: gitDir, + limit: limit +}); stream.on('data', function(data) { console.log('DATA', data.toString('utf8')); diff --git a/example.js b/example.js index 8d62428..aed1e1b 100644 --- a/example.js +++ b/example.js @@ -1,26 +1,28 @@ -"use strict"; +'use strict'; var gitSpawnedStream = require('./'); var path = require('path'); -var repoPath = process.env.REPO || path.join(__dirname, '.git'); -repoPath = path.resolve(repoPath); -var byteLimit = 5 * 1024 * 1024; // 5 Mb +var gitDir = process.env.REPO ? path.resolve(process.env.REPO) : path.join(__dirname, '.git'); +var limit = 5 * 1024 * 1024; // 5 Mb // sort of a git log -n 2 -var stream = gitSpawnedStream(repoPath, [ +var stream = gitSpawnedStream([ 'rev-list', '--max-count=2', '--header', 'HEAD' -], byteLimit); +], { + gitDir: gitDir, + limit: limit +}); -stream.on('data', function(data) { +stream.on('data', function (data) { console.log('DATA', data.toString('utf8')); -}).on('error', function(err) { +}).on('error', function (err) { console.error('An error occurred:'); console.error('-----------------\n'); console.error(err.message); process.exit(1); -}).on('end', function() { +}).on('end', function () { console.log("\n±±±±±±±±±±±±±±±±±\nThat's all folks!"); }); diff --git a/index.js b/index.js index 0ac36b7..2ca24ee 100644 --- a/index.js +++ b/index.js @@ -1,26 +1,74 @@ -"use strict"; +'use strict'; + +/** + * git-spawned-stream + * @module git-spawned-stream + */ var run = require('spawn-to-readstream'); var spawn = require('child_process').spawn; var debug = require('debug')('git-spawned-stream'); -module.exports = function(repoPath, args, limit, gitCommand = 'git') { - var _args = ['--git-dir=' + repoPath]; +/** + * Create a readable stream from a spawned git process. + * @param {string[]} args the arguments that will be passed to the `child_process.spawn` function + * @param {Object} [options] options + * @param {Object} [options.config] `-c` from `git` + * @param {string} [options.gitDir] `--git-dir` from `git` + * @param {string} [options.workTree] `--work-tree` from `git` + * @param {boolean} [options.pager=false] `--no-pager` from `git` + * @param {string} [options.gitBinary='git'] path to the git binary to use + * @param {number} [options.limit] kill the process if it exceeds the imposed limit (sends more data than allowed) + * @param {string|Buffer|Stream.Readable} [options.input] The value which will be passed as stdin to the spawned process + * @param {string} [options.cwd] Current working directory of git process + * @param {Object} [options.env] Environment key-value pairs + * @returns {Stream.Readable} readable stream from spawned git process. + */ +module.exports = function (args, options) { + options = Object.assign({ + gitBinary: 'git' + }, options); + + var _args = []; + + if (!options.pager) { + _args.push('--no-pager'); + } + + if (options.gitDir) { + _args.push('--git-dir=' + options.gitDir); + } - // The limit is a git bin path - if (typeof limit === 'string') { - debug('got string limit, using it as gitCommand and unsetting limit'); - gitCommand = limit; - limit = undefined; + if (options.workTree) { + _args.push('--work-tree=' + options.workTree); } - args.forEach(function(item) { + if (options.config) { + Object.keys(options.config).forEach(function (key) { + _args.push('-c', key + '=' + String(options.config[key])); + }); + } + + args.forEach(function (item) { _args.push(item); }); debug('args', _args); - debug('limit', limit); - debug('gitCommand', gitCommand); + debug('limit', options.limit); + debug('gitBinary', options.gitBinary); - return run(spawn(gitCommand, _args), limit); + var ps = spawn(options.gitBinary, _args, { + cwd: options.cwd, + env: options.env + }); + + if (options.input) { + if (options.input.pipe) { + options.input.pipe(ps.stdin); + } else { + ps.stdin.write(options.input); + ps.stdin.end(); + } + } + return run(ps, options.limit); }; diff --git a/package.json b/package.json index e1b23e9..4fb650c 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,34 @@ { "name": "git-spawned-stream", + "nyc": { + "cache": true, + "reporter": [ + "lcov", + "text" + ] + }, "version": "1.0.0", "description": "Create a readable stream from a spawned git process.", "main": "index.js", "dependencies": { - "spawn-to-readstream": "~0.1.3", - "debug": "~0.8.1" + "spawn-to-readstream": "^0.1.3", + "debug": "^2.6.8" }, "devDependencies": { - "mocha": "~1.19.0", - "proxyquire": "~1.0.0", - "should": "~3.3.2" + "eslint": "^4.0.0", + "eslint-config-standard": "^10.2.1", + "eslint-plugin-import": "^2.3.0", + "eslint-plugin-node": "^5.0.0", + "eslint-plugin-promise": "^3.5.0", + "eslint-plugin-standard": "^3.0.1", + "mocha": "^3.4.2", + "nyc": "^11.0.2", + "proxyquire": "^1.8.0", + "should": "^11.2.1" }, "scripts": { - "test": "mocha" + "pretest": "eslint *.js", + "test": "nyc mocha" }, "repository": { "type": "git", diff --git a/test.js b/test.js index e4e958e..0a2ba1c 100644 --- a/test.js +++ b/test.js @@ -1,31 +1,81 @@ -"use strict"; - +'use strict'; +/* eslint-env node, mocha */ var proxyquire = require('proxyquire'); -var should = require('should'); +var path = require('path'); +var fs = require('fs'); +var execSync = require('child_process').execSync; +require('should'); -describe('git-spawned-stream', function() { - it('should delegate with the correct params', function(done) { +describe('git-spawned-stream', function () { + it('should delegate with the correct params', function (done) { var spawnArgs = ['rev-list', '--max-count=2', 'HEAD']; var limit = 1024 * 1024; var gitSpawnedStream = proxyquire.load('./index', { - 'spawn-to-readstream': function(spawnStream, bytes) { - spawnStream.should.eql('spawnStream'); + 'spawn-to-readstream': function (spawnStream, bytes) { + spawnStream.value.should.eql('spawnStream'); bytes.should.eql(limit); done(); }, 'child_process': { - spawn: function(cmd, args) { - spawnArgs.unshift('--git-dir=/home/node.git'); + spawn: function (cmd, args) { + spawnArgs.unshift('--no-pager'); cmd.should.eql('git'); args.should.eql(spawnArgs); - return 'spawnStream'; + return { + value: 'spawnStream', + stdin: { + write: function (stdin) { + stdin.should.eql('spawnInput'); + }, + end: function () { + + } + } + }; } } }); - gitSpawnedStream('/home/node.git', spawnArgs, limit); + gitSpawnedStream(spawnArgs, { + limit: limit, + input: 'spawnInput' + }); + }); + + it('git rev-list', function (done) { + var git = proxyquire.load('./', {}); + var result = ''; + git(['log', '--max-count=1'], { + pager: true, + workTree: __dirname, + gitDir: path.join(__dirname, '.git') + }) + .on('data', function (data) { + result += data; + }).on('end', function () { + result.should.match(/^commit \w+$/im); + done(); + }).on('error', done); + }); + + it('git blame', function (done) { + var git = proxyquire.load('./', {}); + var result = ''; + git(['blame', '--contents', '-', '--', 'README.md'], { + gitDir: path.join(__dirname, '.git'), + config: { + 'core.quotepath': false + }, + input: fs.createReadStream(path.join(__dirname, 'README.md')) + }) + .on('data', function (data) { + result += data; + }).on('end', function () { + result.should.eql(execSync('git blame -- README.md').toString()); + done(); + }).on('error', done); }); });