├── .gitignore ├── demo.gif ├── .jshintrc ├── cli.js ├── package.json ├── README.md └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tatsuya/git-pull-all/HEAD/demo.gif -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "esnext": true, 6 | "freeze": true, 7 | "immed": true, 8 | "indent": 2, 9 | "latedef": "nofunc", 10 | "newcap": true, 11 | "node": true, 12 | "noarg": true, 13 | "quotmark": "single", 14 | "strict": false, 15 | "undef": true, 16 | "unused": true 17 | } -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require('path'); 4 | var gitPullAll = require('.'); 5 | var cwd = process.cwd(); 6 | 7 | // Parse command line arguments 8 | var argv = process.argv.slice(2); 9 | var isRecursive = argv.includes('-r'); 10 | var paramOne = argv.shift(); 11 | var parentDir = cwd; 12 | 13 | if (paramOne && paramOne != '-r') { 14 | parentDir = path.join(cwd, paramOne); 15 | } 16 | gitPullAll(parentDir, isRecursive); 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "git-pull-all", 3 | "version": "0.1.4", 4 | "description": "Concurrent git pull executor for multiple git repositories.", 5 | "bin": { 6 | "git-pull-all": "cli.js" 7 | }, 8 | "scripts": { 9 | "lint": "jshint *.js" 10 | }, 11 | "dependencies": { 12 | "async": "~0.9.0" 13 | }, 14 | "devDependencies": { 15 | "jshint": "^2.6.0" 16 | }, 17 | "repostitory": { 18 | "type": "git", 19 | "url": "https://github.com/tatsuyaoiw/git-pull-all.git" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/tatsuyaoiw/git-pull-all/issues" 23 | }, 24 | "homepage": "https://github.com/tatsuyaoiw/git-pull-all", 25 | "keywords": [ 26 | "git", 27 | "pull" 28 | ], 29 | "author": "Tatsuya Oiwa ", 30 | "license": "MIT" 31 | } 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # git-pull-all 2 | 3 | Concurrent [git pull][git-pull] executor for multiple git repositories. 4 | 5 | ![](demo.gif) 6 | 7 | [git-pull-all] is a command line tool to execute [git pull][git-pull] on multiple git repositories in parallel. Because it is asynchronous, it works really well especially for **many** projects and must be a lot faster than any synchronous ways, such as: 8 | 9 | ```sh 10 | #!/bin/sh 11 | find . -type d -name .git -exec sh -c "cd \"{}\"/../ && pwd && git pull" \; 12 | ``` 13 | 14 | ## Installation 15 | 16 | Using [npm]: 17 | 18 | ``` 19 | $ npm install -g git-pull-all 20 | ``` 21 | 22 | ## Usage 23 | 24 | Assume you have these files and directories: 25 | 26 | ``` 27 | ~/Projects/ 28 | cool-examples/ 29 | .git/ 30 | funny-movies/ 31 | my-todos.txt 32 | super-express/ 33 | .git/ 34 | ``` 35 | 36 | When you run `git-pull-all` command on `~/Projects` directory, it should find child git repositories (in the above case *cool-examples* and *super-express*) then execute `git pull` on each of them. 37 | 38 | ``` 39 | $ cd ~/Projects 40 | $ git-pull-all 41 | funny-movies/ 42 | Not a git repository 43 | cool-examples/ 44 | Already up-to-date. 45 | super-express/ 46 | Already up-to-date. 47 | ``` 48 | 49 | You can also specify the path where the command is executed. 50 | 51 | ``` 52 | $ git-pull-all ~/Projects 53 | ``` 54 | 55 | 56 | You can also update repositories recursively. 57 | 58 | ``` 59 | $ git-pull-all ~/Projects -r 60 | ``` 61 | 62 | 63 | Assume you have these files and directories: 64 | 65 | 66 | ``` 67 | ~/Projects/ 68 | github/ 69 | cool-examples/ 70 | .git/ 71 | funny-movies/ 72 | my-todos.txt 73 | super-express/ 74 | .git/ 75 | gitlab/ 76 | confidential/ 77 | .git/ 78 | ``` 79 | 80 | 81 | When you run `git-pull-all -r` command on `~/Projects` directory, it should find all nested child git repositories (in the above case *cool-examples*, *super-express* and *confidential*) then execute `git pull` on each of them. 82 | 83 | 84 | ``` 85 | $ cd ~/Projects 86 | $ git-pull-all 87 | github/ 88 | Not a git repository 89 | gitlab/ 90 | Not a git repository 91 | funny-movies/ 92 | Not a git repository 93 | cool-examples/ 94 | Already up-to-date. 95 | confidential/ 96 | Already up-to-date. 97 | super-express/ 98 | Already up-to-date. 99 | ``` 100 | 101 | 102 | ## Licence 103 | 104 | MIT 105 | 106 | [git-pull]: http://git-scm.com/docs/git-pull 107 | [git-pull-all]: https://github.com/tatsuyaoiw/git-pull-all 108 | [npm]: https://www.npmjs.com/ 109 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var exec = require('child_process').exec; 2 | var fs = require('fs'); 3 | var join = require('path').join; 4 | var basename = require('path').basename; 5 | 6 | var async = require('async'); 7 | 8 | /** 9 | * Return true if the given file path is a directory. 10 | * 11 | * @param {String} file 12 | * @param {Function} callback 13 | */ 14 | function isDirectory(file, callback) { 15 | fs.stat(file, function(err, stats) { 16 | if (err) { 17 | var message = [ 18 | 'Something went wrong on "' + file + '"', 19 | 'Message: ' + err.message 20 | ].join('\n'); 21 | console.log(message); 22 | return callback(false); 23 | } 24 | callback(stats.isDirectory()); 25 | }); 26 | } 27 | 28 | /** 29 | * Check if the given directory is a git repo. 30 | * 31 | * @param {String} dir 32 | * @param {Function} callback 33 | */ 34 | function isGitProject(dir, callback) { 35 | fs.exists(join(dir, '.git'), function(ret) { 36 | if (!ret) { 37 | console.log('\033[36m' + basename(dir) + '/\033[39m'); 38 | console.log('Not a git repository'); 39 | } 40 | callback(ret); 41 | }); 42 | } 43 | 44 | /** 45 | * Check if the given directory is not a git repo. 46 | * 47 | * @param {String} dir 48 | * @param {Function} callback 49 | */ 50 | function isNotGitProject(dir, callback) { 51 | fs.exists(join(dir, '.git'), function(ret) { 52 | callback(!ret); 53 | }); 54 | } 55 | 56 | /** 57 | * Run the given command. 58 | * 59 | * @param {String} command 60 | * @param {Object} options 61 | */ 62 | function run(command, options, callback) { 63 | options = options || {}; 64 | exec(command, options, callback); 65 | } 66 | 67 | /** 68 | * Check if remote tracking repo is defined. 69 | * 70 | * @param {String} dir 71 | * @param {Function} callback 72 | */ 73 | function hasRemoteRepo(dir, callback) { 74 | var command = 'git remote show'; 75 | run(command, { cwd: dir }, function(err, stdout, stderr) { 76 | if (err || stderr) { 77 | var message = ''; 78 | message += 'Something went wrong on "' + dir + '" ...'; 79 | message += 'Command: ' + command; 80 | if (err) { 81 | message += 'Message: ' + err.message; 82 | } else if (stderr) { 83 | message += 'Message: ' + stderr;; 84 | } 85 | console.log(message); 86 | return callback(false); 87 | } 88 | if (!stdout) { 89 | console.log('\033[36m' + basename(dir) + '/\033[39m'); 90 | console.log('Remote tracking repository is not defined'); 91 | } 92 | callback(!!stdout); 93 | }); 94 | } 95 | 96 | /** 97 | * Run "git pull" on the given directory. 98 | * 99 | * @param {String} dir 100 | * @param {Function} callback 101 | */ 102 | function gitPull(dir, callback) { 103 | var command = 'git pull'; 104 | run(command, { cwd: dir }, function(err, stdout, stderr) { 105 | if (err) { 106 | var message = [ 107 | 'Something went wrong on "' + dir + '" ...', 108 | 'Command: ' + command, 109 | 'Message: ' + err.message 110 | ].join('\n'); 111 | return callback(new Error(message)); 112 | } 113 | console.log('\033[36m' + basename(dir) + '/\033[39m'); 114 | if (stdout) { 115 | process.stdout.write(stdout); 116 | } 117 | if (stderr) { 118 | process.stdout.write(stderr); 119 | } 120 | callback(); 121 | }); 122 | } 123 | 124 | function readFiles(dir, callback) { 125 | fs.readdir(dir, function(err, children) { 126 | if (err) { 127 | return callback(err); 128 | } 129 | var files = children.map(function(child) { 130 | return join(dir, child); 131 | }); 132 | return callback(null, files); 133 | }); 134 | } 135 | 136 | function pullFromDirectoryRecursively(dir){ 137 | pullFromDirectory(dir); 138 | readFiles(dir, function(err, files) { 139 | if (err) { 140 | return console.log(err.message); 141 | } 142 | 143 | // Returns files 144 | async.filter(files, isDirectory, function(dirs) { 145 | // Returns non-git projects 146 | async.filter(dirs, isNotGitProject, function(nonGitProjects) { 147 | // pull recursively for non-git projects 148 | async.each(nonGitProjects, pullFromDirectoryRecursively, function(err) { 149 | if (err) { 150 | console.log(err.message); 151 | return; 152 | } 153 | }) 154 | }); 155 | }); 156 | }); 157 | } 158 | 159 | function pullFromDirectory(parent) { 160 | readFiles(parent, function(err, files) { 161 | if (err) { 162 | return console.log(err.message); 163 | } 164 | 165 | // Returns files 166 | async.filter(files, isDirectory, function(dirs) { 167 | 168 | // Returns git projects 169 | async.filter(dirs, isGitProject, function(gitProjects) { 170 | 171 | // Ignore if project does not have remote tracking repo 172 | async.filter(gitProjects, hasRemoteRepo, function(trackingRepos) { 173 | 174 | async.each(trackingRepos, gitPull, function(err) { 175 | if (err) { 176 | console.log(err.message); 177 | return; 178 | } 179 | }); 180 | }); 181 | }); 182 | }); 183 | }); 184 | } 185 | 186 | /** 187 | * Main function. 188 | * 189 | * @param {String} parent 190 | */ 191 | module.exports = function(parent, isRecursive) { 192 | if (isRecursive) { 193 | pullFromDirectoryRecursively(parent); 194 | }else{ 195 | pullFromDirectory(parent); 196 | } 197 | console.log('Done!'); 198 | }; 199 | --------------------------------------------------------------------------------