Git-aware CGI to provide dumb HTTP transport
The git-http-backend CGI can be configured into any Apache server using ScriptAlias, such as with the following configuration: LoadModule cgi_module /usr/libexec/apache2/mod_cgi.so LoadModule alias_module /usr/libexec/apache2/mod_alias.so ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/ Repositories are accessed via the translated PATH_INFO. The CGI is backwards compatible with the dumb client, allowing all older HTTP clients to continue to download repositories which are managed by the CGI. Signed-off-by: Shawn O. Pearce <spearce@spearce.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Shawn O. Pearce committed
Oct 30, 2009 at 17:47 UTC
2f4038ab337e55017d4ff21ddbae9427544ca02c
4 files changed
+396
.gitignore
+1
@@ -55,6 +55,7 @@ git-get-tar-commit-id
55
git-grep
56
git-hash-object
57
git-help
58
+git-http-backend
59
git-http-fetch
60
git-http-push
61
git-imap-send
Documentation/git-http-backend.txt
new
+105
@@ -0,0 +1,105 @@
1
+git-http-backend(1)
2
+===================
3
+
4
+NAME
5
+----
6
+git-http-backend - Server side implementation of Git over HTTP
7
+
8
+SYNOPSIS
9
+--------
10
+[verse]
11
+'git-http-backend'
12
+
13
+DESCRIPTION
14
+-----------
15
+A simple CGI program to serve the contents of a Git repository to Git
16
+clients accessing the repository over http:// and https:// protocols.
17
+
18
+By default, only the `upload-pack` service is enabled, which serves
19
+'git-fetch-pack' and 'git-ls-remote' clients, which are invoked from
20
+'git-fetch', 'git-pull', and 'git-clone'.
21
+
22
+This is ideally suited for read-only updates, i.e., pulling from
23
+git repositories.
24
+
25
+URL TRANSLATION
26
+---------------
27
+'git-http-backend' relies on the invoking web server to perform
28
+URL to path translation, and store the repository path into the
29
+PATH_TRANSLATED environment variable. Most web servers will do
30
+this translation automatically, resolving the suffix after the
31
+CGI name relative to the server's document root.
32
+
33
+EXAMPLES
34
+--------
35
+
36
+Apache 2.x::
37
+ To serve all Git repositories contained within the '/git/'
38
+ subdirectory of the DocumentRoot, ensure mod_cgi and
39
+ mod_alias are enabled, and create a ScriptAlias to the CGI:
40
++
41
+----------------------------------------------------------------
42
+ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/git/
43
+
44
+<Directory /usr/libexec/git-core>
45
+ Options None
46
+</Directory>
47
+<Files /usr/libexec/git-core/git-http-backend>
48
+ Options ExecCGI
49
+</Files>
50
+----------------------------------------------------------------
51
++
52
+To require authentication for reads, use a Directory
53
+directive around the repository, or one of its parent directories:
54
++
55
+----------------------------------------------------------------
56
+<Directory /var/www/git/private>
57
+ AuthType Basic
58
+ AuthName "Private Git Access"
59
+ Require group committers
60
+ ...
61
+</Directory>
62
+----------------------------------------------------------------
63
+
64
+Accelerated static Apache 2.x::
65
+ Similar to the above, but Apache can be used to return static
66
+ files that are stored on disk. On many systems this may
67
+ be more efficient as Apache can ask the kernel to copy the
68
+ file contents from the file system directly to the network:
69
++
70
+----------------------------------------------------------------
71
+DocumentRoot /var/www
72
+
73
+ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/git/
74
+Alias /git_static/ /var/www/git/
75
+
76
+RewriteEngine on
77
+RewriteRule ^/git/(.*/objects/[0-9a-f]{2}/[0-9a-f]{38})$ /git_static/$1 [PT]
78
+RewriteRule ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.pack)$ /git_static/$1 [PT]
79
+RewriteRule ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.idx)$ /git_static/$1 [PT]
80
+----------------------------------------------------------------
81
+
82
+
83
+ENVIRONMENT
84
+-----------
85
+'git-http-backend' relies upon the CGI environment variables set
86
+by the invoking web server, including:
87
+
88
+* PATH_TRANSLATED
89
+* REMOTE_USER
90
+* REMOTE_ADDR
91
+* CONTENT_TYPE
92
+* QUERY_STRING
93
+* REQUEST_METHOD
94
+
95
+Author
96
+------
97
+Written by Shawn O. Pearce <spearce@spearce.org>.
98
+
99
+Documentation
100
+--------------
101
+Documentation by Shawn O. Pearce <spearce@spearce.org>.
102
+
103
+GIT
104
+---
105
+Part of the linkgit:git[1] suite
Makefile
+1
@@ -365,6 +365,7 @@ PROGRAMS += git-show-index$X
365
PROGRAMS += git-unpack-file$X
366
PROGRAMS += git-upload-pack$X
367
PROGRAMS += git-var$X
368
+PROGRAMS += git-http-backend$X
369
370
# List built-in command $C whose implementation cmd_$C() is not in
371
# builtin-$C.o but is linked in as part of some other command.
http-backend.c
new
+289
@@ -0,0 +1,289 @@
1
+#include "cache.h"
2
+#include "refs.h"
3
+#include "pkt-line.h"
4
+#include "object.h"
5
+#include "tag.h"
6
+#include "exec_cmd.h"
7
+
8
+static const char content_type[] = "Content-Type";
9
+static const char content_length[] = "Content-Length";
10
+static const char last_modified[] = "Last-Modified";
11
+
12
+static void format_write(int fd, const char *fmt, ...)
13
+{
14
+ static char buffer[1024];
15
+
16
+ va_list args;
17
+ unsigned n;
18
+
19
+ va_start(args, fmt);
20
+ n = vsnprintf(buffer, sizeof(buffer), fmt, args);
21
+ va_end(args);
22
+ if (n >= sizeof(buffer))
23
+ die("protocol error: impossibly long line");
24
+
25
+ safe_write(fd, buffer, n);
26
+}
27
+
28
+static void http_status(unsigned code, const char *msg)
29
+{
30
+ format_write(1, "Status: %u %s\r\n", code, msg);
31
+}
32
+
33
+static void hdr_str(const char *name, const char *value)
34
+{
35
+ format_write(1, "%s: %s\r\n", name, value);
36
+}
37
+
38
+static void hdr_int(const char *name, size_t value)
39
+{
40
+ format_write(1, "%s: %" PRIuMAX "\r\n", name, value);
41
+}
42
+
43
+static void hdr_date(const char *name, unsigned long when)
44
+{
45
+ const char *value = show_date(when, 0, DATE_RFC2822);
46
+ hdr_str(name, value);
47
+}
48
+
49
+static void hdr_nocache(void)
50
+{
51
+ hdr_str("Expires", "Fri, 01 Jan 1980 00:00:00 GMT");
52
+ hdr_str("Pragma", "no-cache");
53
+ hdr_str("Cache-Control", "no-cache, max-age=0, must-revalidate");
54
+}
55
+
56
+static void hdr_cache_forever(void)
57
+{
58
+ unsigned long now = time(NULL);
59
+ hdr_date("Date", now);
60
+ hdr_date("Expires", now + 31536000);
61
+ hdr_str("Cache-Control", "public, max-age=31536000");
62
+}
63
+
64
+static void end_headers(void)
65
+{
66
+ safe_write(1, "\r\n", 2);
67
+}
68
+
69
+static NORETURN void not_found(const char *err, ...)
70
+{
71
+ va_list params;
72
+
73
+ http_status(404, "Not Found");
74
+ hdr_nocache();
75
+ end_headers();
76
+
77
+ va_start(params, err);
78
+ if (err && *err)
79
+ vfprintf(stderr, err, params);
80
+ va_end(params);
81
+ exit(0);
82
+}
83
+
84
+static void send_strbuf(const char *type, struct strbuf *buf)
85
+{
86
+ hdr_int(content_length, buf->len);
87
+ hdr_str(content_type, type);
88
+ end_headers();
89
+ safe_write(1, buf->buf, buf->len);
90
+}
91
+
92
+static void send_file(const char *the_type, const char *name)
93
+{
94
+ const char *p = git_path("%s", name);
95
+ size_t buf_alloc = 8192;
96
+ char *buf = xmalloc(buf_alloc);
97
+ int fd;
98
+ struct stat sb;
99
+ size_t size;
100
+
101
+ fd = open(p, O_RDONLY);
102
+ if (fd < 0)
103
+ not_found("Cannot open '%s': %s", p, strerror(errno));
104
+ if (fstat(fd, &sb) < 0)
105
+ die_errno("Cannot stat '%s'", p);
106
+
107
+ size = xsize_t(sb.st_size);
108
+
109
+ hdr_int(content_length, size);
110
+ hdr_str(content_type, the_type);
111
+ hdr_date(last_modified, sb.st_mtime);
112
+ end_headers();
113
+
114
+ while (size) {
115
+ ssize_t n = xread(fd, buf, buf_alloc);
116
+ if (n < 0)
117
+ die_errno("Cannot read '%s'", p);
118
+ if (!n)
119
+ break;
120
+ safe_write(1, buf, n);
121
+ }
122
+ close(fd);
123
+ free(buf);
124
+}
125
+
126
+static void get_text_file(char *name)
127
+{
128
+ hdr_nocache();
129
+ send_file("text/plain", name);
130
+}
131
+
132
+static void get_loose_object(char *name)
133
+{
134
+ hdr_cache_forever();
135
+ send_file("application/x-git-loose-object", name);
136
+}
137
+
138
+static void get_pack_file(char *name)
139
+{
140
+ hdr_cache_forever();
141
+ send_file("application/x-git-packed-objects", name);
142
+}
143
+
144
+static void get_idx_file(char *name)
145
+{
146
+ hdr_cache_forever();
147
+ send_file("application/x-git-packed-objects-toc", name);
148
+}
149
+
150
+static int show_text_ref(const char *name, const unsigned char *sha1,
151
+ int flag, void *cb_data)
152
+{
153
+ struct strbuf *buf = cb_data;
154
+ struct object *o = parse_object(sha1);
155
+ if (!o)
156
+ return 0;
157
+
158
+ strbuf_addf(buf, "%s\t%s\n", sha1_to_hex(sha1), name);
159
+ if (o->type == OBJ_TAG) {
160
+ o = deref_tag(o, name, 0);
161
+ if (!o)
162
+ return 0;
163
+ strbuf_addf(buf, "%s\t%s^{}\n", sha1_to_hex(o->sha1), name);
164
+ }
165
+ return 0;
166
+}
167
+
168
+static void get_info_refs(char *arg)
169
+{
170
+ struct strbuf buf = STRBUF_INIT;
171
+
172
+ for_each_ref(show_text_ref, &buf);
173
+ hdr_nocache();
174
+ send_strbuf("text/plain", &buf);
175
+ strbuf_release(&buf);
176
+}
177
+
178
+static void get_info_packs(char *arg)
179
+{
180
+ size_t objdirlen = strlen(get_object_directory());
181
+ struct strbuf buf = STRBUF_INIT;
182
+ struct packed_git *p;
183
+ size_t cnt = 0;
184
+
185
+ prepare_packed_git();
186
+ for (p = packed_git; p; p = p->next) {
187
+ if (p->pack_local)
188
+ cnt++;
189
+ }
190
+
191
+ strbuf_grow(&buf, cnt * 53 + 2);
192
+ for (p = packed_git; p; p = p->next) {
193
+ if (p->pack_local)
194
+ strbuf_addf(&buf, "P %s\n", p->pack_name + objdirlen + 6);
195
+ }
196
+ strbuf_addch(&buf, '\n');
197
+
198
+ hdr_nocache();
199
+ send_strbuf("text/plain; charset=utf-8", &buf);
200
+ strbuf_release(&buf);
201
+}
202
+
203
+static NORETURN void die_webcgi(const char *err, va_list params)
204
+{
205
+ char buffer[1000];
206
+
207
+ http_status(500, "Internal Server Error");
208
+ hdr_nocache();
209
+ end_headers();
210
+
211
+ vsnprintf(buffer, sizeof(buffer), err, params);
212
+ fprintf(stderr, "fatal: %s\n", buffer);
213
+ exit(0);
214
+}
215
+
216
+static struct service_cmd {
217
+ const char *method;
218
+ const char *pattern;
219
+ void (*imp)(char *);
220
+} services[] = {
221
+ {"GET", "/HEAD$", get_text_file},
222
+ {"GET", "/info/refs$", get_info_refs},
223
+ {"GET", "/objects/info/alternates$", get_text_file},
224
+ {"GET", "/objects/info/http-alternates$", get_text_file},
225
+ {"GET", "/objects/info/packs$", get_info_packs},
226
+ {"GET", "/objects/[0-9a-f]{2}/[0-9a-f]{38}$", get_loose_object},
227
+ {"GET", "/objects/pack/pack-[0-9a-f]{40}\\.pack$", get_pack_file},
228
+ {"GET", "/objects/pack/pack-[0-9a-f]{40}\\.idx$", get_idx_file}
229
+};
230
+
231
+int main(int argc, char **argv)
232
+{
233
+ char *method = getenv("REQUEST_METHOD");
234
+ char *dir = getenv("PATH_TRANSLATED");
235
+ struct service_cmd *cmd = NULL;
236
+ char *cmd_arg = NULL;
237
+ int i;
238
+
239
+ git_extract_argv0_path(argv[0]);
240
+ set_die_routine(die_webcgi);
241
+
242
+ if (!method)
243
+ die("No REQUEST_METHOD from server");
244
+ if (!strcmp(method, "HEAD"))
245
+ method = "GET";
246
+ if (!dir)
247
+ die("No PATH_TRANSLATED from server");
248
+
249
+ for (i = 0; i < ARRAY_SIZE(services); i++) {
250
+ struct service_cmd *c = &services[i];
251
+ regex_t re;
252
+ regmatch_t out[1];
253
+
254
+ if (regcomp(&re, c->pattern, REG_EXTENDED))
255
+ die("Bogus regex in service table: %s", c->pattern);
256
+ if (!regexec(&re, dir, 1, out, 0)) {
257
+ size_t n = out[0].rm_eo - out[0].rm_so;
258
+
259
+ if (strcmp(method, c->method)) {
260
+ const char *proto = getenv("SERVER_PROTOCOL");
261
+ if (proto && !strcmp(proto, "HTTP/1.1"))
262
+ http_status(405, "Method Not Allowed");
263
+ else
264
+ http_status(400, "Bad Request");
265
+ hdr_nocache();
266
+ end_headers();
267
+ return 0;
268
+ }
269
+
270
+ cmd = c;
271
+ cmd_arg = xmalloc(n);
272
+ strncpy(cmd_arg, dir + out[0].rm_so + 1, n);
273
+ cmd_arg[n] = '\0';
274
+ dir[out[0].rm_so] = 0;
275
+ break;
276
+ }
277
+ regfree(&re);
278
+ }
279
+
280
+ if (!cmd)
281
+ not_found("Request not supported: '%s'", dir);
282
+
283
+ setup_path();
284
+ if (!enter_repo(dir, 0))
285
+ not_found("Not a git repository: '%s'", dir);
286
+
287
+ cmd->imp(cmd_arg);
288
+ return 0;
289
+}