├── .gitignore ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── bin └── git.js ├── commands ├── archive.js ├── checkout.js ├── clone.js ├── config.js ├── ls-remote.js ├── rev-list.js └── rev-parse.js ├── lib ├── config.js ├── es6.js ├── js-git-api.js ├── js-git-fs-db.js ├── js-git-node-request.js ├── proxy.js └── utils.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | // List of configurations. Add new configurations or edit existing ones. 4 | // ONLY "node" and "mono" are supported, change "type" to switch. 5 | "configurations": [ 6 | { 7 | // Name of configuration; appears in the launch configuration drop down menu. 8 | "name": "Launch git.js", 9 | // Type of configuration. Possible values: "node", "mono". 10 | "type": "node", 11 | // Workspace relative or absolute path to the program. 12 | "program": "bin/git.js", 13 | // Automatically stop program after launch. 14 | "stopOnEntry": false, 15 | // Command line arguments passed to the program. 16 | "args": ["clone", "https://github.com/whyleee/global-tunnel.git", "-b", "v1.1.0", "--progress", "D:\\test", "--depth", "1"], 17 | // Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace. 18 | "cwd": ".", 19 | // Workspace relative or absolute path to the runtime executable to be used. Default is the runtime executable on the PATH. 20 | "runtimeExecutable": null, 21 | // Optional arguments passed to the runtime executable. 22 | "runtimeArgs": [], 23 | // Environment variables passed to the program. 24 | "env": { }, 25 | // Use JavaScript source maps (if they exist). 26 | "sourceMaps": false, 27 | // If JavaScript source maps are enabled, the generated code is expected in this directory. 28 | "outDir": null 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Pavel Nezhencev 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | nogit 2 | ===== 3 | 4 | Local git replacement as a last resort. Created to get rid of local git dependency in [npm](https://github.com/npm/npm) and [bower](https://github.com/bower/bower), so contains only tiny subset of git features for those tools. 5 | 6 | ## Usage 7 | 8 | ```sh 9 | > git 10 | 11 | Usage: git [options] [command] 12 | 13 | Commands: 14 | 15 | archive [options] Create an archive of files from a named tree 16 | checkout Checkout a branch or paths to the working tree 17 | clone [options] [dir] Clone a repository into a new directory 18 | config [options] Get repository options 19 | ls-remote [options] List references in a remote repository 20 | rev-list [options] Lists commit objects in reverse chronological order 21 | rev-parse [options] Pick out and massage parameters 22 | 23 | Options: 24 | 25 | -h, --help output usage information 26 | -V, --version output the version number 27 | -c ignored 28 | 29 | ``` 30 | 31 | ## Proxy settings 32 | 33 | If `nogit` should use a proxy for remote connections, use **one** of the next solutions: 34 | 35 | **1)** Set `HTTP_PROXY` and/or `HTTPS_PROXY` environment variables to the proxy URL. For Node.js delivered via NuGet, edit `~/.bin/node.cmd` file: 36 | 37 | SET HTTP_PROXY=http://1:1@127.0.0.1:8888 38 | SET HTTPS_PROXY=http://1:1@127.0.0.1:8888 39 | 40 | where `http://1:1@127.0.0.1:8888` is the proxy at `127.0.0.1:8888` with username `1` and password `1` used for authentication. 41 | 42 | Use this solution to set single proxy settings for all environments used in your project. This is a recommended solution, it will also force `bower` and `npm` to use the proxy. 43 | 44 | **2)** Add next lines to your local `%USERPROFILE%\.gitconfig` file: 45 | 46 | [http] 47 | proxy = http://1:1@127.0.0.1:8888 48 | [https] 49 | proxy = http://1:1@127.0.0.1:8888 50 | 51 | where `http://1:1@127.0.0.1:8888` is the proxy at `127.0.0.1:8888` with username `1` and password `1` used for authentication. 52 | 53 | Use this solution to set proxy settings for single environment only. 54 | 55 | **3)** If your proxy only doesn't allow `git://` URLs, you can add next lines to your local `%USERPROFILE%\.gitconfig` file: 56 | 57 | [url "https://"] 58 | insteadOf = git:// 59 | 60 | Then `nogit` will use `https://` URLs everywhere to work with remotes. Note, that for proxy settings in solutions 1) and 2), `nogit` will also use `https://` URLs everywhere. 61 | -------------------------------------------------------------------------------- /bin/git.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../lib/es6'); // polyfills 4 | var program = require('commander'); 5 | var commands = { 6 | archive: require('../commands/archive'), 7 | checkout: require('../commands/checkout'), 8 | clone: require('../commands/clone'), 9 | config: require('../commands/config'), 10 | lsRemote: require('../commands/ls-remote'), 11 | revList: require('../commands/rev-list'), 12 | revParse: require('../commands/rev-parse') 13 | }; 14 | var config = require('../lib/config'); 15 | var proxy = require('../lib/proxy'); 16 | 17 | config.read(function(config) { 18 | program 19 | .version('0.1.0') 20 | .option('-c ', 'ignored'); 21 | 22 | program 23 | .command('archive ') 24 | .description('Create an archive of files from a named tree') 25 | .option('--format ', 'format of the resulting archive: tar or zip') 26 | .option('--prefix ', 'prepend / to each filename in the archive') 27 | .action(commands.archive); 28 | 29 | program 30 | .command('checkout ') 31 | .description('Checkout a branch or paths to the working tree') 32 | .action(commands.checkout); 33 | 34 | program 35 | .command('clone [dir]') 36 | .description('Clone a repository into a new directory') 37 | .option('-b, --branch ', 'checkout to specefic branch, tag or ref') 38 | .option('--depth ', 'do a shallow clone with num commits deep') 39 | .option('--mirror', 'not supported yet (will act as a "git fetch")') 40 | .option('--progress', 'show progress status') 41 | .option('--template ', 'not supported') 42 | .action(commands.clone); 43 | 44 | program 45 | .command('config') 46 | .description('Get repository options') 47 | .option('--get ', 'get the value for a given key') 48 | .action(commands.config); 49 | 50 | program 51 | .command('ls-remote ') 52 | .description('List references in a remote repository') 53 | .option('-t, --tags', 'limit to tags') 54 | .option('-h, --heads', 'limit to heads') 55 | .action(commands.lsRemote); 56 | 57 | program 58 | .command('rev-list ') 59 | .description('Lists commit objects in reverse chronological order') 60 | .option('-n, --max-count ', 'limit the number of commits to output', parseInt) 61 | .action(commands.revList); 62 | 63 | program 64 | .command('rev-parse') 65 | .description('Pick out and massage parameters') 66 | .option('--is-inside-git-dir', 'print "true" if the current working dir is below the git dir, "false" otherwise') 67 | .action(commands.revParse); 68 | 69 | proxy.enableIfRequired(); 70 | 71 | fixNumArgs(); 72 | program.parse(process.argv); 73 | 74 | if (process.argv.length < 3) { 75 | program.outputHelp(); 76 | process.exit(1); 77 | } 78 | 79 | function fixNumArgs() { 80 | for (var i = 0; i < process.argv.length; ++i) { 81 | if (/^-[a-zA-Z](\d+)$/g.test(process.argv[i])) { 82 | process.argv.splice(i + 1, 0, process.argv[i].slice(2)); 83 | process.argv[i] = process.argv[i].slice(0, 2); 84 | } 85 | } 86 | } 87 | }); -------------------------------------------------------------------------------- /commands/archive.js: -------------------------------------------------------------------------------- 1 | var jsgit = require('../lib/js-git-api'); 2 | 3 | module.exports = function archive(ref, options) { 4 | jsgit.archive(jsgit.repo(), ref, options, function(err) { 5 | if (err) throw err; 6 | }); 7 | }; -------------------------------------------------------------------------------- /commands/checkout.js: -------------------------------------------------------------------------------- 1 | var jsgit = require('../lib/js-git-api'); 2 | 3 | module.exports = function checkout(ref, options) { 4 | var repo = jsgit.repo(); 5 | jsgit.checkout(repo, ref, options, function(err) { 6 | if (err) throw err; 7 | }); 8 | }; -------------------------------------------------------------------------------- /commands/clone.js: -------------------------------------------------------------------------------- 1 | var jsgit = require('../lib/js-git-api'); 2 | 3 | module.exports = function clone(repoUrl, targetPath, options) { 4 | if (options.progress) { 5 | options.onProgress = console.log; 6 | } 7 | 8 | var remote = jsgit.remote(repoUrl); 9 | jsgit.clone(remote, targetPath, options, function(err) { 10 | if (err) throw err; 11 | }); 12 | }; -------------------------------------------------------------------------------- /commands/config.js: -------------------------------------------------------------------------------- 1 | var jsgit = require('../lib/js-git-api'); 2 | 3 | module.exports = function(options) { 4 | // only '--get' supported 5 | if (options.get) { 6 | console.log(jsgit.getRemoteUrl()); 7 | } 8 | }; -------------------------------------------------------------------------------- /commands/ls-remote.js: -------------------------------------------------------------------------------- 1 | var jsgit = require('../lib/js-git-api'); 2 | 3 | function lsRemote(repoUrl, options) { 4 | var getAll = !options.heads && !options.tags; 5 | var remote = jsgit.remote(repoUrl); 6 | jsgit.ls(remote, function(err, refs) { 7 | if (err) throw err; 8 | Object.keys(refs).forEach(function (ref) { 9 | var wanted = false; 10 | if (options.heads && ~ref.indexOf('refs/heads/')) { 11 | wanted = true; 12 | } 13 | if (options.tags && ~ref.indexOf('refs/tags/')) { 14 | wanted = true; 15 | } 16 | if (getAll || wanted) { 17 | console.log(refs[ref] + "\t" + ref); 18 | } 19 | }); 20 | }); 21 | } 22 | 23 | module.exports = lsRemote; -------------------------------------------------------------------------------- /commands/rev-list.js: -------------------------------------------------------------------------------- 1 | var jsgit = require('../lib/js-git-api'); 2 | 3 | module.exports = function(ref, options) { 4 | // only '-n1' are supported 5 | if (options.maxCount == 1) { 6 | var repo = jsgit.repo(); 7 | jsgit.readRef(repo, ref, function(err, hash) { 8 | if (err) throw err; 9 | console.log(hash); 10 | }); 11 | } 12 | }; -------------------------------------------------------------------------------- /commands/rev-parse.js: -------------------------------------------------------------------------------- 1 | var jsgit = require('../lib/js-git-api'); 2 | 3 | module.exports = function(options) { 4 | if (options.isInsideGitDir) { 5 | console.log(jsgit.isInsideGitDir()); 6 | } 7 | }; -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | var ini = require('ini'); 4 | 5 | var config = { 6 | nogit: {} // custom settings 7 | }; 8 | 9 | module.exports = { 10 | read: function(callback) { 11 | var userConfigPath = path.join(process.env.USERPROFILE, '.gitconfig'); 12 | readFrom(userConfigPath, function() { 13 | fs.exists('.git', function(usualRepo) { 14 | var localConfigPath = usualRepo ? '.git/config' : /*bare*/ 'config'; 15 | readFrom(localConfigPath, callback); 16 | }); 17 | }); 18 | }, 19 | get: function() { 20 | return config; 21 | } 22 | }; 23 | 24 | function readFrom(configPath, callback) { 25 | fs.readFile(configPath, 'utf8', function(err, data) { 26 | if (err) { 27 | // TODO: log it, but not to stdout/stderr - git doesn't do that 28 | // console.log('"' + configPath + '" not found'); 29 | } else { 30 | var parsedConfig = ini.parse(data); 31 | mergeConfigs(config, parsedConfig); 32 | } 33 | callback(config); 34 | }); 35 | } 36 | 37 | function mergeConfigs(to, from) { 38 | for (var prop in from) {to[prop] = from[prop];} 39 | } 40 | -------------------------------------------------------------------------------- /lib/es6.js: -------------------------------------------------------------------------------- 1 | // string.startsWith() 2 | if (!String.prototype.startsWith) { 3 | String.prototype.startsWith = function(searchString, position) { 4 | position = position || 0; 5 | return this.indexOf(searchString, position) === position; 6 | }; 7 | } 8 | 9 | // string.endsWith() 10 | if (!String.prototype.endsWith) { 11 | String.prototype.endsWith = function(searchString, position) { 12 | var subjectString = this.toString(); 13 | if (position === undefined || position > subjectString.length) { 14 | position = subjectString.length; 15 | } 16 | position -= searchString.length; 17 | var lastIndex = subjectString.indexOf(searchString, position); 18 | return lastIndex !== -1 && lastIndex === position; 19 | }; 20 | } -------------------------------------------------------------------------------- /lib/js-git-api.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var urlParse = require('url').parse; 4 | var format = require('util').format; 5 | var tcp = require('js-git/net/tcp-node'); 6 | var tcpTransport = require('js-git/net/transport-tcp')(tcp); 7 | var request = require('../lib/js-git-node-request'); 8 | var httpTransport = require('js-git/net/transport-http')(request); 9 | var fetchPackProtocol = require('js-git/net/git-fetch-pack'); 10 | var modes = require('js-git/lib/modes'); 11 | var archiver = require('archiver'); 12 | var del = require('del'); 13 | var mkdirp = require('mkdirp'); 14 | var ncp = require('ncp').ncp; 15 | var config = require('../lib/config').get(); 16 | var utils = require('../lib/utils'); 17 | 18 | module.exports = { 19 | remote: function(repoUrl) { 20 | if (!repoUrl.startsWith('git:') && !repoUrl.startsWith('https:')) { 21 | var gitDir = path.join(repoUrl, '.git'); 22 | if (fs.existsSync(gitDir)) { 23 | repoUrl = gitDir; 24 | } 25 | return { 26 | url: { 27 | href: repoUrl, 28 | pathname: repoUrl, 29 | protocol: 'file:' 30 | }, 31 | transport: 'fs' 32 | }; 33 | } 34 | 35 | repoUrl = utils.fixUrl(repoUrl); 36 | var parsedUrl = urlParse(repoUrl); 37 | var transport, transportName; 38 | 39 | if (parsedUrl.protocol == 'git:') { 40 | transport = tcpTransport( 41 | parsedUrl.pathname, 42 | parsedUrl.hostname, 43 | parsedUrl.port 44 | ); 45 | transportName = 'tcp'; 46 | } else { 47 | transport = httpTransport(repoUrl); 48 | transportName = 'http'; 49 | } 50 | 51 | var api = fetchPackProtocol(transport); 52 | 53 | // add helpers 54 | api.url = parsedUrl; 55 | api.transport = transportName; 56 | api.done = function() { 57 | this.put({ done: true }); 58 | this.put(); 59 | }; 60 | 61 | return api; 62 | }, 63 | repo: function(rootPath, mirror) { 64 | var repo = {}; 65 | var fsDb = require('../lib/js-git-fs-db'); 66 | require('js-git/mixins/fs-db')(repo, fsDb); 67 | require('js-git/mixins/read-combiner')(repo); 68 | require('js-git/mixins/pack-ops')(repo); 69 | require('js-git/mixins/walkers')(repo); 70 | require('js-git/mixins/formats')(repo); 71 | 72 | if (!rootPath) { 73 | rootPath = process.cwd(); 74 | var gitDir = path.join(rootPath, '.git'); 75 | if (fs.existsSync(gitDir)) { 76 | rootPath = gitDir; 77 | } 78 | } else if (!mirror) { 79 | rootPath = path.join(rootPath, '.git'); 80 | } 81 | 82 | repo.rootPath = rootPath; 83 | 84 | return repo; 85 | }, 86 | ls: function(remote, callback, keepalive) { 87 | remote.take(function(err, refs) { 88 | if (remote.transport == 'tcp' && !keepalive) { 89 | remote.done(); 90 | } 91 | callback(err, refs); 92 | }); 93 | }, 94 | clone: function(remote, targetPath, options, callback) { 95 | var self = this; 96 | targetPath = targetPath || path.basename(remote.url.pathname, '.git'); 97 | 98 | if (options.onProgress) { 99 | var progress; 100 | if (options.mirror) { 101 | progress = "Cloning '%s' into bare repository '%s'..."; 102 | } else { 103 | progress = "Cloning '%s' into '%s'..."; 104 | } 105 | options.onProgress(format(progress, remote.url.href, targetPath)); 106 | } 107 | 108 | var repo = this.repo(targetPath, options.mirror); 109 | 110 | if (remote.transport == 'fs') { 111 | return mkdirp(repo.rootPath, function(err) { 112 | if (err) return callback(err); 113 | ncp(remote.url.href, repo.rootPath, { limit: 16 }, function(err) { 114 | if (err) return callback(err); 115 | self.checkout(repo, /*head*/null, options, callback); 116 | }); 117 | }); 118 | } 119 | 120 | var branch = options.branch; 121 | if (branch && !branch.startsWith('refs/') && branch[0] != '/') { 122 | branch = '/' + branch; 123 | } 124 | 125 | var ref, hash, packedRefs = {}; 126 | this.ls(remote, select, /*keepalive*/true); 127 | 128 | function select(err, refs) { 129 | if (err) return callback(err); 130 | 131 | var selectedRefs = Object.keys(refs).filter(function(ref) { 132 | if (branch) { 133 | return ref.endsWith(branch); 134 | } else { 135 | return ref.startsWith('refs/heads/') || 136 | (ref.startsWith('refs/tags/') && !ref.endsWith('^{}')); 137 | } 138 | }); 139 | 140 | if (branch) { 141 | ref = selectedRefs[0]; 142 | } else { 143 | ref = 'refs/heads/master'; 144 | } 145 | 146 | hash = refs[ref]; 147 | 148 | selectedRefs.forEach(function(ref) { 149 | remote.put({ want: refs[ref] }); 150 | packedRefs[ref] = refs[ref]; 151 | }); 152 | 153 | if (options.depth) { 154 | remote.put({ deepen: parseInt(options.depth, 10) }); 155 | } 156 | 157 | remote.put(null); 158 | remote.done(); 159 | remote.take(unpack); 160 | } 161 | 162 | function unpack(err, channels) { 163 | if (err) return callback(err); 164 | 165 | var packOpts = { 166 | onError: callback, 167 | onProgress: function(progress) { 168 | if (options.onProgress) { 169 | options.onProgress(progress); 170 | } 171 | // Quick hack for "repo.unpack callback is never called using HTTP transport" 172 | // issue https://github.com/creationix/js-git/issues/120 173 | if (remote.transport == 'http') { 174 | var statsRegex = /\((\d+)\/(\d+)\)/; 175 | var stats = statsRegex.exec(progress); 176 | 177 | if (stats[1] == stats[2] - 1) { 178 | if (options.onProgress) { 179 | progress = 'Receiving objects: 100% (' + stats[2] + '/' + stats[2] + ')\r\n'; 180 | options.onProgress(progress); 181 | } 182 | return addRef(); 183 | } 184 | } 185 | } 186 | }; 187 | 188 | repo.unpack(channels.pack, packOpts, addRef); 189 | } 190 | 191 | function addRef(err) { 192 | if (err) return callback(err); 193 | repo.updateRef(ref, hash, createPackedRefs); 194 | } 195 | 196 | function createPackedRefs(err) { 197 | if (err) return callback(err); 198 | 199 | var filePath = path.join(repo.rootPath, 'packed-refs'); 200 | var fileContent = ''; 201 | 202 | for (var packedRef in packedRefs) { 203 | fileContent += packedRefs[packedRef] + ' ' + packedRef + '\n'; 204 | } 205 | 206 | fs.writeFile(filePath, fileContent, createConfig); 207 | } 208 | 209 | function createConfig(err) { 210 | if (err) return callback(err); 211 | 212 | var configPath = path.join(repo.rootPath, 'config'); 213 | var config = '[remote "origin"]\r\n\turl = ' + remote.url.href; 214 | 215 | fs.writeFile(configPath, config, checkout); 216 | } 217 | 218 | function checkout(err) { 219 | if (err) return callback(err); 220 | 221 | if (!options.mirror) { 222 | return self.checkout(repo, ref, options, callback); 223 | } else { 224 | return self.writeHead(repo, ref, callback); 225 | } 226 | } 227 | }, 228 | readHead: function(repo, callback) { 229 | var self = this; 230 | var headPath = path.join(repo.rootPath, 'HEAD'); 231 | fs.readFile(headPath, function(err, head) { 232 | if (err) return callback(err); 233 | 234 | var ref = head.toString().replace('ref:', '').trim(); 235 | return self.readRef(repo, ref, callback); 236 | }); 237 | }, 238 | writeHead: function(repo, ref, callback) { 239 | var headPath = path.join(repo.rootPath, 'HEAD'); 240 | if (!this.isHash(ref)) { 241 | ref = 'ref: ' + ref; 242 | } 243 | fs.writeFile(headPath, ref, callback); 244 | }, 245 | isHash: function(str) { 246 | return str.match(/^[0-9a-f]{40}$/i); 247 | }, 248 | readRef: function(repo, ref, callback) { 249 | if (this.isHash(ref)) { 250 | return callback(null, ref); 251 | } 252 | 253 | if (ref.startsWith('refs/')) { 254 | repo.readRef(ref, function(err, hash) { 255 | if (err) return callback(err); 256 | if (hash) return callback(null, hash); 257 | 258 | return callback(new Error('Unknown ref: ' + ref)); 259 | }); 260 | } else { 261 | this.getPackedRefs(repo, function(err, packedRefs) { 262 | if (err) return callback(err); 263 | if (Object.keys(packedRefs).length == 0) { 264 | return callback(new Error('Unknown ref: ' + ref)); 265 | } 266 | 267 | // try heads 268 | for (var packedRef in packedRefs) { 269 | if (~packedRef.indexOf(ref) || 270 | ~packedRefs[packedRef].indexOf(ref)) { 271 | return callback(null, packedRefs[packedRef]); 272 | } 273 | } 274 | 275 | // try all commits for all heads 276 | var headIndex = 0; 277 | var commitsWalked = 0; 278 | var visitedCommits = {}; 279 | var commitFound = false; 280 | var heads = Object.keys(packedRefs) 281 | .filter(function(packedRef) { 282 | return packedRef.startsWith('refs/heads'); 283 | }); 284 | 285 | walkHead(packedRefs[heads[headIndex++]], onHeadWalked); 286 | 287 | function walkHead(hash, callback) { 288 | repo.loadAs('commit', hash, function(err, commit) { 289 | commit.hash = hash; 290 | onCommit(err, commit); 291 | }); 292 | 293 | function onCommit(err, commit) { 294 | if (err) return callback(err); 295 | if (commitFound || visitedCommits[commit.hash]) { 296 | return; 297 | } 298 | 299 | if (~commit.hash.indexOf(ref)) { 300 | commitFound = true; 301 | return callback(null, commit.hash); 302 | } 303 | 304 | visitedCommits[commit.hash] = true; 305 | ++commitsWalked; 306 | 307 | if (commit.parents.length == 0) { 308 | return callback(); 309 | } 310 | 311 | commit.parents.forEach(function(parentHash) { 312 | repo.loadAs('commit', parentHash, function(err, commit) { 313 | commit.hash = parentHash; 314 | onCommit(err, commit); 315 | }); 316 | }); 317 | } 318 | } 319 | 320 | function onHeadWalked(err, foundHash) { 321 | if (err) return callback(err); 322 | if (foundHash) return callback(null, foundHash); 323 | 324 | if (headIndex < heads.length) { 325 | walkHead(packedRefs[heads[headIndex++]], onHeadWalked); 326 | } else { 327 | return callback(new Error('Unknown ref: ' + ref)); 328 | } 329 | } 330 | }); 331 | } 332 | }, 333 | getPackedRefs: function(repo, callback) { 334 | var packedRefsPath = path.join(repo.rootPath, 'packed-refs'); 335 | fs.readFile(packedRefsPath, 'utf8', function(err, packedRefs) { 336 | if (err) return callback(err); 337 | 338 | var map = {}; 339 | packedRefs.split('\n') 340 | .filter(function(line) { return line; }) 341 | .forEach(function(line) { 342 | var refParts = line.split(' '); 343 | map[refParts[1]] = refParts[0]; 344 | }); 345 | return callback(null, map); 346 | }); 347 | }, 348 | loadTree: function(repo, ref, callback) { 349 | var self = this; 350 | this.readRef(repo, ref, loadCommit); 351 | 352 | function loadCommit(err, hash) { 353 | if (err) return callback(err); 354 | repo.loadAs('commit', hash, function(err, commit) { 355 | if (err instanceof TypeError && 356 | err.message == 'Type mismatch') { 357 | return loadTag(null, hash); 358 | } 359 | readTree(err, commit); 360 | }); 361 | } 362 | 363 | function loadTag(err, hash) { 364 | if (err) return callback(err); 365 | repo.loadAs('tag', hash, function(err, tag) { 366 | if (err) return callback(err); 367 | loadCommit(err, tag.object); 368 | }); 369 | } 370 | 371 | function readTree(err, commit) { 372 | if (err) return callback(err); 373 | repo.treeWalk(commit.tree, callback); 374 | } 375 | }, 376 | checkout: function(repo, ref, options, callback) { 377 | var self = this; 378 | 379 | if (!ref) { 380 | this.readHead(repo, function(err, head) { 381 | if (err) return callback(err); 382 | self.loadTree(repo, head, writeTree); 383 | }); 384 | } else { 385 | this.loadTree(repo, ref, writeTree); 386 | } 387 | 388 | function writeTree(err, tree) { 389 | if (err) return callback(err); 390 | 391 | var repoPath = repo.rootPath; 392 | if (path.basename(repoPath) == '.git') { 393 | repoPath = path.dirname(repoPath); 394 | } 395 | 396 | del([ 397 | path.join(repoPath, '*'), 398 | '!' + path.join(repoPath, '.git') 399 | ], function(err, paths) { 400 | tree.read(onEntry); 401 | }); 402 | 403 | function onEntry(err, entry) { 404 | if (err) return callback(err); 405 | if (!entry) { 406 | if (ref) { 407 | return self.writeHead(repo, ref, callback); 408 | } 409 | return callback(); 410 | } 411 | 412 | if (entry.path == '/') { 413 | return nextEntry(); 414 | } 415 | 416 | var entryPath = path.join(repoPath, entry.path); 417 | 418 | if (options.onProgress) { 419 | var colorPath = '\x1B[34m' + 420 | entryPath.replace(/\//g, '\x1B[1;34m/\x1B[0;34m') + '\x1B[0m'; 421 | options.onProgress(entry.hash + ' ' + colorPath); 422 | } 423 | 424 | var entryType = modes.toType(entry.mode); 425 | 426 | if (entryType == 'tree') { 427 | return fs.mkdir(entryPath, nextEntry); 428 | } else if (entryType == 'blob') { 429 | return repo.loadAs('blob', entry.hash, function(err, blob) { 430 | if (err) return callback(err); 431 | return fs.writeFile(entryPath, blob, nextEntry); 432 | }); 433 | } 434 | // skip commits, because we don't support submodules 435 | if (entryType != 'commit') { 436 | return callback(new Error('Unknown entry mode: ' + entry.mode)); 437 | } 438 | 439 | return nextEntry(); 440 | } 441 | 442 | function nextEntry(err) { 443 | if (err) return callback(err); 444 | return tree.read(onEntry); 445 | } 446 | } 447 | }, 448 | archive: function(repo, ref, options, callback) { 449 | this.loadTree(repo, ref, function(err, tree) { 450 | if (err) return callback(err); 451 | 452 | var archive = archiver(options.format || 'tar'); 453 | archive.pipe(options.pipe || process.stdout); 454 | archive.on('error', callback); 455 | 456 | tree.read(onEntry); 457 | 458 | function onEntry(err, entry) { 459 | if (err) return callback(err); 460 | if (!entry) { 461 | archive.finalize(); 462 | return callback(); 463 | } 464 | 465 | if (entry.path == '/') { 466 | return nextEntry(); 467 | } 468 | 469 | var prefixedPath = path.join(options.prefix || '', entry.path); 470 | 471 | if (entry.mode == modes.blob) { 472 | return repo.loadAs('blob', entry.hash, function(err, blob) { 473 | if (err) return callback(err); 474 | archive.append(blob, { name: prefixedPath }); 475 | nextEntry(); 476 | }); 477 | } 478 | 479 | return nextEntry(); 480 | } 481 | 482 | function nextEntry(err) { 483 | if (err) return callback(err); 484 | return tree.read(onEntry); 485 | } 486 | }); 487 | }, 488 | isInsideGitDir: function() { 489 | return process.cwd().indexOf('.git') >= 0; 490 | }, 491 | getRemoteUrl: function() { 492 | return config['remote "origin"'].url; 493 | } 494 | }; -------------------------------------------------------------------------------- /lib/js-git-fs-db.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var dirname = require('path').dirname; 3 | var mkdirp = require('mkdirp'); 4 | 5 | module.exports = { 6 | readFile: function(path, callback) { 7 | fs.readFile(path, function(err, buffer) { 8 | if (err) { 9 | if (err.code == 'ENOENT') return callback(); 10 | return callback(err); 11 | } 12 | callback(null, buffer); 13 | }); 14 | }, 15 | writeFile: function(path, buffer, callback) { 16 | mkdirp(dirname(path), function(err) { 17 | if (err) return callback(err); 18 | fs.writeFile(path, buffer, callback); 19 | }); 20 | }, 21 | readDir: function(path, callback) { 22 | fs.readdir(path, function(err, results) { 23 | if (err) { 24 | if (err.code == 'ENOENT') return callback(); 25 | return callback(err); 26 | } 27 | return callback(null, results); 28 | }); 29 | }, 30 | readChunk: function(path, start, end, callback) { 31 | fs.open(path, 'r', function(err, fd) { 32 | if (err) { 33 | if (err.code == 'ENOENT') return callback(); 34 | return callback(err); 35 | } 36 | if (end < 0) { 37 | end = fs.statSync(path)['size'] + end; 38 | } 39 | var chunkSize = end - start; 40 | var buf = new Buffer(chunkSize); 41 | fs.read(fd, buf, 0, chunkSize, start, function(err, bytesRead, buffer) { 42 | fs.close(fd, function(closeErr) { 43 | return callback(err || closeErr, buffer); 44 | }); 45 | }); 46 | }); 47 | }, 48 | rename: function(oldPath, newPath, callback) { 49 | return fs.rename(oldPath, newPath, callback); 50 | } 51 | } -------------------------------------------------------------------------------- /lib/js-git-node-request.js: -------------------------------------------------------------------------------- 1 | var https = require('https'); 2 | var urlParse = require('url').parse; 3 | 4 | function request(method, url, headers, body, callback) { 5 | if (typeof body == 'function') { 6 | callback = body; 7 | body = undefined; 8 | } 9 | 10 | var parsedUrl = urlParse(url); 11 | 12 | // port is required for correct proxy tunneling 13 | if (!parsedUrl.port) { 14 | if (parsedUrl.protocol == 'http:') { 15 | parsedUrl.port = 80; 16 | } else if (parsedUrl.protocol == 'https:') { 17 | parsedUrl.port = 443; 18 | } 19 | } 20 | 21 | var options = { 22 | method: method, 23 | hostname: parsedUrl.hostname, 24 | port: parsedUrl.port, 25 | path: parsedUrl.path, 26 | headers: headers 27 | }; 28 | 29 | var req = https.request(options, onResponse); 30 | if (body) { 31 | req.write(body); 32 | } 33 | req.on('error', function(e) { 34 | callback(e); 35 | }); 36 | 37 | req.end(); 38 | 39 | function onResponse(res) { 40 | var buf = new Buffer(0); 41 | res.on('data', function(chunk) { 42 | buf = Buffer.concat([buf, chunk]); 43 | }); 44 | res.on('end', function() { 45 | callback(null, { 46 | statusCode: res.statusCode, 47 | headers: res.headers, 48 | body: buf 49 | }); 50 | }); 51 | } 52 | } 53 | 54 | module.exports = request; -------------------------------------------------------------------------------- /lib/proxy.js: -------------------------------------------------------------------------------- 1 | var urlParse = require('url').parse; 2 | var globalTunnel = require('global-tunnel'); 3 | var config = require('../lib/config').get(); 4 | var env = process.env; 5 | 6 | module.exports = { 7 | enableIfRequired: function() { 8 | var tunnel = 'neither'; 9 | var configHttpProxy = config.http ? config.http.proxy : undefined; 10 | var configHttpsProxy = config.https ? config.https.proxy : undefined; 11 | 12 | if (env.http_proxy || configHttpProxy) { 13 | tunnel = 'both'; 14 | if (!env.http_proxy) { 15 | env.http_proxy = config.http.proxy; 16 | } 17 | } 18 | if (env.https_proxy || configHttpsProxy) { 19 | if (tunnel == 'neither') { 20 | tunnel = 'https'; 21 | } 22 | if (!env.https_proxy) { 23 | env.https_proxy = configHttpsProxy; 24 | } 25 | } 26 | 27 | var proxy = env.http_proxy || env.https_proxy; 28 | 29 | if (proxy) { 30 | var parsed = urlParse(proxy); 31 | var conf = { 32 | connect: tunnel, 33 | protocol: parsed.protocol, 34 | host: parsed.hostname, 35 | port: parseInt(parsed.port,10), 36 | proxyAuth: parsed.auth 37 | }; 38 | 39 | globalTunnel.initialize(conf); 40 | config.nogit.proxy = proxy; 41 | } 42 | } 43 | }; -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | var config = require('./config').get(); 2 | 3 | module.exports = { 4 | fixUrl: function(url) { 5 | // remove last '/' (to avoid url's ending with '/.git') 6 | if (url.slice(-1) == '/') { 7 | url = url.slice(0, -1); 8 | } 9 | 10 | // '.git' ending is required for js-git 11 | if (url.slice(-4) != '.git') { 12 | url += '.git'; 13 | } 14 | 15 | // can't use proxy for 'git://' urls (tcp) - use 'https://' instead 16 | // also check if such replacement required in the config 17 | var httpsInsteadOfGit = config['url "https://"'] && 18 | config['url "https://"'].insteadOf == 'git://'; 19 | if ((httpsInsteadOfGit || config.nogit.proxy) && url.indexOf('git://') == 0) { 20 | url = url.replace('git://', 'https://'); 21 | } 22 | 23 | return url; 24 | } 25 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nogit", 3 | "version": "0.1.0", 4 | "description": "Local git replacement for npm and bower, powered by js-git and node.js", 5 | "bin": { 6 | "git": "./bin/git.js" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/whyleee/nogit" 11 | }, 12 | "keywords": [ 13 | "git", 14 | "cli", 15 | "js-git" 16 | ], 17 | "dependencies": { 18 | "archiver": "^0.14.4", 19 | "commander": "https://github.com/whyleee/commander.js/tarball/master", 20 | "del": "^1.2.1", 21 | "global-tunnel": "https://github.com/whyleee/global-tunnel/tarball/master", 22 | "ini": "^1.3.4", 23 | "js-git": "^0.7.7", 24 | "mkdirp": "^0.5.1", 25 | "ncp": "^2.0.0" 26 | }, 27 | "devDependencies": {}, 28 | "author": "Pavel Nezhencev , based on js-git by Tim Caswell ", 29 | "license": "MIT" 30 | } 31 | --------------------------------------------------------------------------------