├── .gitignore ├── README.md ├── git-http-server.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | git-http-server 2 | =============== 3 | 4 | serve a directory of git repositories over http 5 | 6 | this tool is basically a thin command line wrapper around 7 | https://github.com/substack/git-http-backend 8 | 9 | Example 10 | ------- 11 | 12 | Start the server with one repository 13 | 14 | $ cd repos/ 15 | $ git init --bare foo.git 16 | Initialized empty Git repository in /Users/dave/dev/node-git-http-server/repos/foo.git/ 17 | $ git-http-server 18 | listening on http://0.0.0.0:8174 in /Users/dave/dev/node-git-http-server/repos 19 | 20 | Now, clone the empty repository 21 | 22 | $ git clone http://127.0.0.1:8174/foo.git 23 | Cloning into 'foo'... 24 | warning: You appear to have cloned an empty repository. 25 | Checking connectivity... done. 26 | $ cd foo 27 | 28 | Add some files and push them back 29 | 30 | $ touch bar 31 | $ git add bar 32 | $ git commit -m 'initial commit' bar 33 | [master (root-commit) 9a37778] initial commit 34 | 1 file changed, 0 insertions(+), 0 deletions(-) 35 | create mode 100644 bar 36 | $ git push origin master 37 | Counting objects: 3, done. 38 | Writing objects: 100% (3/3), 204 bytes | 0 bytes/s, done. 39 | Total 3 (delta 0), reused 0 (delta 0) 40 | To http://127.0.0.1:8174/foo.git 41 | * [new branch] master -> master 42 | 43 | Meanwhile, the logs look like 44 | 45 | 127.0.0.1 - - [28/Mar/2015:22:45:51 -0400] "GET /foo.git/info/refs?service=git-upload-pack HTTP/1.1" 200 - "-" "git/1.9.5 (Apple Git-50.3)" 46 | 127.0.0.1 - - [28/Mar/2015:22:46:44 -0400] "GET /foo.git/info/refs?service=git-receive-pack HTTP/1.1" 200 - "-" "git/1.9.5 (Apple Git-50.3)" 47 | 127.0.0.1 - - [28/Mar/2015:22:46:44 -0400] "POST /foo.git/git-receive-pack HTTP/1.1" 200 - "-" "git/1.9.5 (Apple Git-50.3)" 48 | 49 | Install 50 | ------- 51 | 52 | [sudo] npm install -g git-http-server 53 | 54 | Usage 55 | ----- 56 | 57 | usage: git-http-server [-r] [-p port] [-H host] [dir] 58 | 59 | options 60 | 61 | -h, --help print this message and exit 62 | -H, --host [env GIT_HTTP_HOST] host on which to listen 63 | -p, --port [env GIT_HTTP_PORT] port on which to listen 64 | -r, --readonly [env GIT_HTTP_READONLY] operate in read-only mode 65 | -u, --updates check for available updates 66 | -v, --version print the version number and exit 67 | 68 | License 69 | ------- 70 | 71 | MIT License 72 | -------------------------------------------------------------------------------- /git-http-server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * fire up an HTTP server to serve git repositories 4 | * 5 | * Author: Dave Eddy 6 | * Date: March 28, 2015 7 | * License: MIT 8 | */ 9 | 10 | var http = require('http'); 11 | var spawn = require('child_process').spawn; 12 | var path = require('path'); 13 | var url = require('url'); 14 | 15 | var accesslog = require('access-log'); 16 | var backend = require('git-http-backend'); 17 | var getopt = require('posix-getopt'); 18 | 19 | var package = require('./package.json'); 20 | 21 | var usage = [ 22 | 'usage: git-http-server [-r] [-p port] [-H host] [dir]', 23 | '', 24 | 'options', 25 | '', 26 | ' -h, --help print this message and exit', 27 | ' -H, --host [env GIT_HTTP_HOST] host on which to listen', 28 | ' -p, --port [env GIT_HTTP_PORT] port on which to listen', 29 | ' -r, --readonly [env GIT_HTTP_READONLY] operate in read-only mode', 30 | ' -u, --updates check for available updates', 31 | ' -v, --version print the version number and exit', 32 | ].join('\n'); 33 | 34 | var options = [ 35 | 'h(help)', 36 | 'H:(host)', 37 | 'p:(port)', 38 | 'r(readonly)', 39 | 'u(updates)', 40 | 'v(version)' 41 | ].join(''); 42 | var parser = new getopt.BasicParser(options, process.argv); 43 | 44 | var opts = { 45 | host: process.env.GIT_HTTP_HOST || '0.0.0.0', 46 | port: process.env.GIT_HTTP_PORT || 8174, 47 | readonly: process.env.GIT_HTTP_READONLY, 48 | }; 49 | var option; 50 | while ((option = parser.getopt())) { 51 | switch (option.option) { 52 | case 'h': console.log(usage); process.exit(0); break; 53 | case 'H': opts.host = option.optarg; break; 54 | case 'p': opts.port = option.optarg; break; 55 | case 'r': opts.readonly = true; break; 56 | case 'u': // check for updates 57 | require('latest').checkupdate(package, function(ret, msg) { 58 | console.log(msg); 59 | process.exit(ret); 60 | }); 61 | return; 62 | case 'v': console.log(package.version); process.exit(0); break; 63 | default: console.error(usage); process.exit(1); break; 64 | } 65 | } 66 | var args = process.argv.slice(parser.optind()); 67 | var dir = args[0]; 68 | 69 | if (dir) 70 | process.chdir(dir); 71 | 72 | http.createServer(onrequest).listen(opts.port, opts.host, started); 73 | 74 | function started() { 75 | console.log('listening on http://%s:%d in %s', opts.host, opts.port, process.cwd()); 76 | } 77 | 78 | function onrequest(req, res) { 79 | accesslog(req, res); 80 | 81 | // ensure the user isn't trying to send up a bad request 82 | var u = url.parse(req.url); 83 | if (u.pathname !== path.normalize(u.pathname)) { 84 | res.statusCode = 400; 85 | res.end(); 86 | return; 87 | } 88 | 89 | var repo = u.pathname.split('/')[1]; 90 | 91 | req.pipe(backend(req.url, function(err, service) { 92 | if (err) { 93 | res.statusCode = 500; 94 | res.end(err + '\n'); 95 | return; 96 | } 97 | 98 | res.setHeader('content-type', service.type); 99 | 100 | if (opts.readonly && service.cmd !== 'git-upload-pack') { 101 | res.statusCode = 403; 102 | res.end('server running in read-only mode\n'); 103 | return; 104 | } 105 | 106 | var ps = spawn(service.cmd, service.args.concat(repo)); 107 | ps.stdout.pipe(service.createStream()).pipe(ps.stdin); 108 | })).pipe(res); 109 | } 110 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "git-http-server", 3 | "description": "serve a directory of git repositories over http", 4 | "version": "0.0.0", 5 | "author": "Dave Eddy (http://www.daveeddy.com)", 6 | "contributors": [], 7 | "preferGlobal": true, 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/bahamas10/node-git-http-server.git" 11 | }, 12 | "bin": { 13 | "git-http-server": "./git-http-server.js" 14 | }, 15 | "scripts": { 16 | "test": "for f in tests/*.js; do echo \"$f\"; node \"$f\" || exit 1; done; echo 'Passed!'" 17 | }, 18 | "dependencies": { 19 | "access-log": "^0.3.9", 20 | "git-http-backend": "^0.1.5", 21 | "latest": "^0.2.0", 22 | "posix-getopt": "^1.1.0" 23 | }, 24 | "devDependencies": {}, 25 | "optionalDependencies": {}, 26 | "engines": { 27 | "node": "*" 28 | }, 29 | "engine": "node >= 0.8.0", 30 | "keywords": [ 31 | "git", 32 | "http" 33 | ] 34 | } 35 | --------------------------------------------------------------------------------