├── TODO.md ├── .travis.yml ├── .gitignore ├── lib └── scan.js ├── LICENSE ├── test.js ├── package.json ├── README.md └── index.js /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO's 2 | 3 | - Split `lib/scan.js` out into its own npm module 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.12' 4 | - '0.10' 5 | - '0.8' 6 | - 'iojs' 7 | before_install: 8 | - npm install -g npm@~1.4.6 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Deployed apps should consider commenting this line out: 24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 25 | node_modules 26 | 27 | # Custom directory auto-created when running tests 28 | test-sandbox 29 | -------------------------------------------------------------------------------- /lib/scan.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var fs = require('fs') 4 | var path = require('path') 5 | var filter = require('patterns')([/^\./, /\/\./]) 6 | 7 | module.exports = function (dir) { 8 | dir = path.resolve(dir) 9 | 10 | if (filter.match(dir)) return [] 11 | 12 | var content = fs.readdirSync(dir) 13 | 14 | if (~content.indexOf('.git')) return [dir] 15 | 16 | return content.map(joinPathWith(dir)).filter(isDir).reduce(function (result, dir) { 17 | var stat = fs.lstatSync(dir) 18 | if (stat.isSymbolicLink()) return result 19 | return result.concat(module.exports(dir)) 20 | }, []) 21 | } 22 | 23 | var isDir = function (path) { 24 | try { 25 | return fs.statSync(path).isDirectory() 26 | } catch (e) { 27 | return false 28 | } 29 | } 30 | 31 | var joinPathWith = function (a) { 32 | return function (b) { 33 | return path.join(a, b) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Thomas Watson Steen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var path = require('path') 4 | var test = require('tape') 5 | var mkdirp = require('mkdirp') 6 | var afterAll = require('after-all-results') 7 | var scan = require('./lib/scan') 8 | 9 | test('scan - cwd', function (t) { 10 | t.deepEqual(scan(process.cwd()), [process.cwd()]) 11 | t.end() 12 | }) 13 | 14 | test('scan - ./', function (t) { 15 | t.deepEqual(scan('./'), [process.cwd()]) 16 | t.end() 17 | }) 18 | 19 | test('scan - .', function (t) { 20 | t.deepEqual(scan('.'), [process.cwd()]) 21 | t.end() 22 | }) 23 | 24 | test('scan - test/sandbox', function (t) { 25 | var next = afterAll(function (err) { 26 | t.error(err) 27 | t.deepEqual(scan('test-sandbox'), [ 28 | path.join(process.cwd(), 'test-sandbox', 'p1'), 29 | path.join(process.cwd(), 'test-sandbox', 'p2') 30 | ]) 31 | t.end() 32 | }) 33 | 34 | mkdirp(path.join(process.cwd(), 'test-sandbox', 'p1', '.git'), next()) 35 | mkdirp(path.join(process.cwd(), 'test-sandbox', 'p1', 'sub'), next()) 36 | mkdirp(path.join(process.cwd(), 'test-sandbox', 'p2', '.git'), next()) 37 | mkdirp(path.join(process.cwd(), 'test-sandbox', 'p3', 'sub'), next()) 38 | mkdirp(path.join(process.cwd(), 'test-sandbox', '.p4', '.git'), next()) 39 | }) 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code-status", 3 | "version": "3.0.0", 4 | "description": "Check if any of your git projects needs attention", 5 | "main": "index.js", 6 | "dependencies": { 7 | "after-all-results": "^2.0.0", 8 | "chalk": "^1.1.3", 9 | "cli-table": "^0.3.1", 10 | "git-state": "^3.0.1", 11 | "minimist": "^1.2.0", 12 | "patterns": "^1.0.2", 13 | "queuealot": "^1.0.2" 14 | }, 15 | "devDependencies": { 16 | "mkdirp": "^0.5.1", 17 | "standard": "^8.1.0", 18 | "tape": "^4.6.0" 19 | }, 20 | "scripts": { 21 | "test": "standard && tape test.js" 22 | }, 23 | "bin": { 24 | "code-status": "index.js" 25 | }, 26 | "author": "Thomas Watson Steen (https://twitter.com/wa7son)", 27 | "license": "MIT", 28 | "keywords": [ 29 | "git", 30 | "status", 31 | "check", 32 | "tool", 33 | "project", 34 | "projects", 35 | "node", 36 | "modules", 37 | "push", 38 | "outdated" 39 | ], 40 | "repository": { 41 | "type": "git", 42 | "url": "https://github.com/watson/code-status.git" 43 | }, 44 | "bugs": { 45 | "url": "https://github.com/watson/code-status/issues" 46 | }, 47 | "homepage": "https://github.com/watson/code-status", 48 | "preferGlobal": true, 49 | "coordinates": [ 50 | 40.7463936, 51 | -73.9937348 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # code-status 2 | 3 | Check if any of your git projects needs attention: 4 | 5 | Runs through all your code projects and checks the following: 6 | 7 | - Is the repo on the master branch? 8 | - Is the current branch ahead of the remote tracking branch? 9 | - Is the repo dirty (i.e. does it contain changes that have not yet been 10 | checked in) 11 | - Is there any untracked files in the repo? 12 | 13 | If the answer is yes to any of those questions, this module will find 14 | and list the projects. 15 | 16 | [![Build status](https://travis-ci.org/watson/code-status.svg?branch=master)](https://travis-ci.org/watson/code-status) 17 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://github.com/feross/standard) 18 | 19 | ## Installation 20 | 21 | Install code-status globally: 22 | 23 | ``` 24 | npm install -g code-status 25 | ``` 26 | 27 | ## Example usage 28 | 29 | ``` 30 | $ code-status ~/code 31 | DIR BRANCH AHEAD DIRTY UNTRACKED 32 | after-all-results master 0 1 0 33 | airserver master 0 3 10 34 | connect master 95 1 0 35 | hubot-heroku patch-1 0 1 0 36 | ``` 37 | 38 | ## Docs 39 | 40 | ``` 41 | code-status [paths] [options] 42 | ``` 43 | 44 | The `paths` defaults to the current directory if not specified. The 45 | code-status program will look through that directory and all 46 | sub-directories scanning for git projects. 47 | 48 | Options: 49 | 50 | - `--help` - show the help 51 | - `--version` - show version 52 | - `--simple` - make the output more simple for easy grepping 53 | 54 | ## License 55 | 56 | MIT 57 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | var path = require('path') 5 | var git = require('git-state') 6 | var Table = require('cli-table') 7 | var chalk = require('chalk') 8 | var queue = require('queuealot')(done) 9 | var pkg = require('./package') 10 | var scan = require('./lib/scan') 11 | 12 | var VALID_BRANCHES = ['master', 'gh-pages'] 13 | 14 | var argv = require('minimist')(process.argv.slice(2)) 15 | var dirs = argv._.length ? argv._ : [process.cwd()] 16 | 17 | if (argv.version || argv.v) { 18 | version() 19 | process.exit() 20 | } 21 | 22 | if (argv.help || argv.h) { 23 | help() 24 | process.exit() 25 | } 26 | 27 | dirs 28 | .map(function (dir) { 29 | return { cwd: dir, repos: scan(dir) } 30 | }) 31 | .forEach(function (dir) { 32 | dir.repos.forEach(function (repo) { 33 | queue(function (cb) { 34 | git.check(repo, function (err, result) { 35 | if (err) return cb(err) 36 | result.dir = path.relative(dir.cwd, repo) 37 | cb(null, result) 38 | }) 39 | }) 40 | }) 41 | }) 42 | 43 | function version () { 44 | console.log(pkg.version) 45 | process.exit() 46 | } 47 | 48 | function help () { 49 | console.log( 50 | pkg.name + ' ' + pkg.version + '\n' + 51 | pkg.description + '\n\n' + 52 | 'Usage:\n' + 53 | ' ' + pkg.name + ' [paths] [options]\n\n' + 54 | 'The paths defaults to the current direcotry if not specified.\n\n' + 55 | 'Options:\n' + 56 | ' --help, -h show this help\n' + 57 | ' --version, -v show version\n' + 58 | ' --compact, -c output compact table\n' + 59 | ' --simple make the output more simple for easy grepping' 60 | ) 61 | process.exit() 62 | } 63 | 64 | function done (err, results) { 65 | if (err) throw err 66 | 67 | results = results.filter(function (result) { 68 | return Boolean(!~VALID_BRANCHES.indexOf(result.branch) || 69 | result.ahead || Number.isNaN(result.ahead) || 70 | result.dirty || result.untracked || result.stashes) 71 | }) 72 | 73 | if (argv.simple) { 74 | results = results.map(function (result) { 75 | return Object.keys(result).map(function (key) { return result[key] }) 76 | }).join('\n') 77 | } else { 78 | var tableOpts = { 79 | head: [ 80 | chalk.cyan('Directory'), 81 | chalk.cyan('Branch'), 82 | chalk.cyan('Ahead'), 83 | chalk.cyan('Dirty'), 84 | chalk.cyan('Untracked'), 85 | chalk.cyan('Stashes') 86 | ] 87 | } 88 | 89 | if (argv.compact || argv.c) { 90 | tableOpts.chars = {'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': ''} 91 | } 92 | 93 | var table = new Table(tableOpts) 94 | 95 | results.map(function (result) { 96 | var method = result.dirty === 0 97 | ? result.ahead === 0 98 | ? result.untracked === 0 99 | ? chalk.grey 100 | : chalk.yellow 101 | : chalk.green 102 | : chalk.red 103 | table.push([ 104 | method(result.dir), 105 | method(result.branch), 106 | method(result.ahead), 107 | method(result.dirty), 108 | method(result.untracked), 109 | method(result.stashes) 110 | ]) 111 | }) 112 | results = table.toString() 113 | } 114 | 115 | console.log(results) 116 | } 117 | --------------------------------------------------------------------------------