author | Lars Hjemli <hjemli@gmail.com> | 2006-12-10 21:31:36 (UTC) |
---|---|---|
committer | Lars Hjemli <hjemli@gmail.com> | 2006-12-10 21:31:36 (UTC) |
commit | 25105d7ecaba474d4b7c364ebb586aac3dfc5abb (patch) (unidiff) | |
tree | 8beb08db1399b8efb8c7fbcd936044ae7fc232e6 | |
parent | 856c026e221d8ed82c5b75bc8da4bd65e89ea953 (diff) | |
download | cgit-25105d7ecaba474d4b7c364ebb586aac3dfc5abb.zip cgit-25105d7ecaba474d4b7c364ebb586aac3dfc5abb.tar.gz cgit-25105d7ecaba474d4b7c364ebb586aac3dfc5abb.tar.bz2 |
Add caching infrastructure
This enables internal caching of page output.
Page requests are split into four groups:
1) repo listing (front page)
2) repo summary
3) repo pages w/symbolic references in query string
4) repo pages w/constant sha1's in query string
Each group has a TTL specified in minutes. When a page is requested, a cached
filename is stat(2)'ed and st_mtime is compared to time(2). If TTL has expired
(or the file didn't exist), the cached file is regenerated.
When generating a cached file, locking is used to avoid parallell processing
of the request. If multiple processes tries to aquire the same lock, the ones
who fail to get the lock serves the (expired) cached file. If the cached file
don't exist, the process instead calls sched_yield(2) before restarting the
request processing.
Signed-off-by: Lars Hjemli <hjemli@gmail.com>
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile | 6 | ||||
-rw-r--r-- | README | 54 | ||||
-rw-r--r-- | cache.c | 86 | ||||
-rw-r--r-- | cgit.c | 117 | ||||
-rw-r--r-- | cgit.h | 47 | ||||
-rw-r--r-- | config.c | 4 | ||||
-rw-r--r-- | git.h | 60 | ||||
-rw-r--r-- | html.c | 6 |
9 files changed, 353 insertions, 28 deletions
@@ -1,3 +1,4 @@ | |||
1 | # Files I don't care to see in git-status/commit | 1 | # Files I don't care to see in git-status/commit |
2 | cgit | 2 | cgit |
3 | *.o | 3 | *.o |
4 | *~ | ||
@@ -1,20 +1,22 @@ | |||
1 | 1 | ||
2 | INSTALL_BIN = /var/www/htdocs/cgit.cgi | 2 | INSTALL_BIN = /var/www/htdocs/cgit.cgi |
3 | INSTALL_CSS = /var/www/htdocs/cgit.css | 3 | INSTALL_CSS = /var/www/htdocs/cgit.css |
4 | 4 | ||
5 | EXTLIBS = ../git/libgit.a ../git/xdiff/lib.a -lz -lcrypto | 5 | EXTLIBS = ../git/libgit.a ../git/xdiff/lib.a -lz -lcrypto |
6 | OBJECTS = cgit.o config.o html.o | 6 | OBJECTS = cgit.o config.o html.o cache.o |
7 | |||
8 | CFLAGS += -Wall | ||
7 | 9 | ||
8 | all: cgit | 10 | all: cgit |
9 | 11 | ||
10 | install: all | 12 | install: all |
11 | install cgit $(INSTALL_BIN) | 13 | install cgit $(INSTALL_BIN) |
12 | install cgit.css $(INSTALL_CSS) | 14 | install cgit.css $(INSTALL_CSS) |
13 | 15 | ||
14 | clean: | 16 | clean: |
15 | rm -f cgit *.o | 17 | rm -f cgit *.o |
16 | 18 | ||
17 | cgit: $(OBJECTS) | 19 | cgit: $(OBJECTS) |
18 | $(CC) -o cgit $(OBJECTS) $(EXTLIBS) | 20 | $(CC) $(CFLAGS) -o cgit $(OBJECTS) $(EXTLIBS) |
19 | 21 | ||
20 | $(OBJECTS): cgit.h git.h | 22 | $(OBJECTS): cgit.h git.h |
@@ -0,0 +1,54 @@ | |||
1 | Cache algorithm | ||
2 | =============== | ||
3 | |||
4 | Cgit normally returns cached pages when invoked. If there is no cache file, or | ||
5 | the cache file has expired, it is regenerated. Finally, the cache file is | ||
6 | printed on stdout. | ||
7 | |||
8 | When it is decided that a cache file needs to be regenerated, an attempt is | ||
9 | made to create a corresponding lockfile. If this fails, the process gives up | ||
10 | and uses the expired cache file instead. | ||
11 | |||
12 | When there is no cache file for a request, an attempt is made to create a | ||
13 | corresponding lockfile. If this fails, the process calls sched_yield(2) before | ||
14 | restarting the request handling. | ||
15 | |||
16 | In pseudocode: | ||
17 | |||
18 | name = generate_cache_name(request); | ||
19 | top: | ||
20 | if (!exists(name)) { | ||
21 | if (lock_cache(name)) { | ||
22 | generate_cache(request, name); | ||
23 | unlock_cache(name); | ||
24 | } else { | ||
25 | sched_yield(); | ||
26 | goto top; | ||
27 | } | ||
28 | } else if (expired(name)) { | ||
29 | if (lock_cache(name)) { | ||
30 | generate_cache(request, name); | ||
31 | unlock_cache(name); | ||
32 | } | ||
33 | } | ||
34 | print_file(name); | ||
35 | |||
36 | |||
37 | The following options can be set in /etc/cgitrc to control cache behaviour: | ||
38 | cache-root: root directory for cache files | ||
39 | cache-root-ttl: TTL for the repo listing page | ||
40 | cache-repo-ttl: TTL for any repos summary page | ||
41 | cache-dynamic-ttl: TTL for pages with symbolic references (not SHA1) | ||
42 | cache-static-ttl: TTL for pages with sha1 references | ||
43 | |||
44 | TTL is specified in minutes, -1 meaning "infinite caching". | ||
45 | |||
46 | |||
47 | Naming of cache files | ||
48 | --------------------- | ||
49 | Repository listing: <cachedir>/index.html | ||
50 | Repository summary: <cachedir>/<repo>/index.html | ||
51 | Repository subpage: <cachedir>/<repo>/<page>/<querystring>.html | ||
52 | |||
53 | The corresponding lock files have a ".lock" suffix. | ||
54 | |||
@@ -0,0 +1,86 @@ | |||
1 | #include "cgit.h" | ||
2 | |||
3 | const int NOLOCK = -1; | ||
4 | |||
5 | int cache_lookup(struct cacheitem *item) | ||
6 | { | ||
7 | if (!cgit_query_repo) { | ||
8 | item->name = xstrdup(fmt("%s/index.html", cgit_cache_root)); | ||
9 | item->ttl = cgit_cache_root_ttl; | ||
10 | } else if (!cgit_query_page) { | ||
11 | item->name = xstrdup(fmt("%s/%s/index.html", cgit_cache_root, | ||
12 | cgit_query_repo)); | ||
13 | item->ttl = cgit_cache_repo_ttl; | ||
14 | } else { | ||
15 | item->name = xstrdup(fmt("%s/%s/%s/%s.html", cgit_cache_root, | ||
16 | cgit_query_repo, cgit_query_page, | ||
17 | cgit_querystring)); | ||
18 | if (cgit_query_has_symref) | ||
19 | item->ttl = cgit_cache_dynamic_ttl; | ||
20 | else if (cgit_query_has_sha1) | ||
21 | item->ttl = cgit_cache_static_ttl; | ||
22 | else | ||
23 | item->ttl = cgit_cache_repo_ttl; | ||
24 | } | ||
25 | if (stat(item->name, &item->st)) { | ||
26 | item->st.st_mtime = 0; | ||
27 | return 0; | ||
28 | } | ||
29 | return 1; | ||
30 | } | ||
31 | |||
32 | int cache_create_dirs() | ||
33 | { | ||
34 | char *path; | ||
35 | |||
36 | if (!cgit_query_repo) | ||
37 | return 0; | ||
38 | |||
39 | path = fmt("%s/%s", cgit_cache_root, cgit_query_repo); | ||
40 | if (mkdir(path, S_IRWXU) && errno!=EEXIST) | ||
41 | return 0; | ||
42 | |||
43 | if (cgit_query_page) { | ||
44 | path = fmt("%s/%s/%s", cgit_cache_root, cgit_query_repo, | ||
45 | cgit_query_page); | ||
46 | if (mkdir(path, S_IRWXU) && errno!=EEXIST) | ||
47 | return 0; | ||
48 | } | ||
49 | return 1; | ||
50 | } | ||
51 | |||
52 | int cache_lock(struct cacheitem *item) | ||
53 | { | ||
54 | int ret; | ||
55 | char *lockfile = fmt("%s.lock", item->name); | ||
56 | |||
57 | top: | ||
58 | item->fd = open(lockfile, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR|S_IWUSR); | ||
59 | if (item->fd == NOLOCK && errno == ENOENT && cache_create_dirs()) | ||
60 | goto top; | ||
61 | if (item->fd == NOLOCK && errno == EEXIST) { | ||
62 | struct stat st; | ||
63 | time_t t; | ||
64 | if (stat(lockfile, &st)) | ||
65 | return ret; | ||
66 | t = time(NULL); | ||
67 | if (t-st.st_mtime > cgit_cache_max_create_time && | ||
68 | !unlink(lockfile)) | ||
69 | goto top; | ||
70 | return 0; | ||
71 | } | ||
72 | return (item->fd > 0); | ||
73 | } | ||
74 | |||
75 | int cache_unlock(struct cacheitem *item) | ||
76 | { | ||
77 | close(item->fd); | ||
78 | return (rename(fmt("%s.lock", item->name), item->name) == 0); | ||
79 | } | ||
80 | |||
81 | int cache_expired(struct cacheitem *item) | ||
82 | { | ||
83 | if (item->ttl < 0) | ||
84 | return 0; | ||
85 | return item->st.st_mtime + item->ttl * 60 < time(NULL); | ||
86 | } | ||
@@ -1,59 +1,77 @@ | |||
1 | #include "cgit.h" | 1 | #include "cgit.h" |
2 | 2 | ||
3 | static const char cgit_doctype[] = | 3 | static const char cgit_doctype[] = |
4 | "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" | 4 | "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" |
5 | " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"; | 5 | " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"; |
6 | 6 | ||
7 | static const char cgit_error[] = | 7 | static const char cgit_error[] = |
8 | "<div class='error'>%s</div>"; | 8 | "<div class='error'>%s</div>"; |
9 | 9 | ||
10 | static const char cgit_lib_error[] = | 10 | static const char cgit_lib_error[] = |
11 | "<div class='error'>%s: %s</div>"; | 11 | "<div class='error'>%s: %s</div>"; |
12 | 12 | ||
13 | int htmlfd = 0; | ||
13 | 14 | ||
14 | char *cgit_root = "/var/git"; | 15 | char *cgit_root = "/usr/src/git"; |
15 | char *cgit_root_title = "Git repository browser"; | 16 | char *cgit_root_title = "Git repository browser"; |
16 | char *cgit_css = "/cgit.css"; | 17 | char *cgit_css = "/cgit.css"; |
17 | char *cgit_logo = "/git-logo.png"; | 18 | char *cgit_logo = "/git-logo.png"; |
18 | char *cgit_logo_link = "http://www.kernel.org/pub/software/scm/git/docs/"; | 19 | char *cgit_logo_link = "http://www.kernel.org/pub/software/scm/git/docs/"; |
19 | char *cgit_virtual_root = NULL; | 20 | char *cgit_virtual_root = NULL; |
20 | 21 | ||
22 | char *cgit_cache_root = "/var/cache/cgit"; | ||
23 | |||
24 | int cgit_cache_root_ttl = 5; | ||
25 | int cgit_cache_repo_ttl = 5; | ||
26 | int cgit_cache_dynamic_ttl = 5; | ||
27 | int cgit_cache_static_ttl = -1; | ||
28 | int cgit_cache_max_create_time = 5; | ||
29 | |||
21 | char *cgit_repo_name = NULL; | 30 | char *cgit_repo_name = NULL; |
22 | char *cgit_repo_desc = NULL; | 31 | char *cgit_repo_desc = NULL; |
23 | char *cgit_repo_owner = NULL; | 32 | char *cgit_repo_owner = NULL; |
24 | 33 | ||
34 | int cgit_query_has_symref = 0; | ||
35 | int cgit_query_has_sha1 = 0; | ||
36 | |||
37 | char *cgit_querystring = NULL; | ||
25 | char *cgit_query_repo = NULL; | 38 | char *cgit_query_repo = NULL; |
26 | char *cgit_query_page = NULL; | 39 | char *cgit_query_page = NULL; |
27 | char *cgit_query_head = NULL; | 40 | char *cgit_query_head = NULL; |
41 | char *cgit_query_sha1 = NULL; | ||
42 | |||
43 | struct cacheitem cacheitem; | ||
28 | 44 | ||
29 | int cgit_parse_query(char *txt, configfn fn) | 45 | int cgit_parse_query(char *txt, configfn fn) |
30 | { | 46 | { |
31 | char *t = txt, *value = NULL, c; | 47 | char *t, *value = NULL, c; |
32 | 48 | ||
33 | if (!txt) | 49 | if (!txt) |
34 | return 0; | 50 | return 0; |
35 | 51 | ||
52 | t = txt = xstrdup(txt); | ||
53 | |||
36 | while((c=*t) != '\0') { | 54 | while((c=*t) != '\0') { |
37 | if (c=='=') { | 55 | if (c=='=') { |
38 | *t = '\0'; | 56 | *t = '\0'; |
39 | value = t+1; | 57 | value = t+1; |
40 | } else if (c=='&') { | 58 | } else if (c=='&') { |
41 | *t = '\0'; | 59 | *t = '\0'; |
42 | (*fn)(txt, value); | 60 | (*fn)(txt, value); |
43 | txt = t+1; | 61 | txt = t+1; |
44 | value = NULL; | 62 | value = NULL; |
45 | } | 63 | } |
46 | t++; | 64 | t++; |
47 | } | 65 | } |
48 | if (t!=txt) | 66 | if (t!=txt) |
49 | (*fn)(txt, value); | 67 | (*fn)(txt, value); |
50 | return 0; | 68 | return 0; |
51 | } | 69 | } |
52 | 70 | ||
53 | void cgit_global_config_cb(const char *name, const char *value) | 71 | void cgit_global_config_cb(const char *name, const char *value) |
54 | { | 72 | { |
55 | if (!strcmp(name, "root")) | 73 | if (!strcmp(name, "root")) |
56 | cgit_root = xstrdup(value); | 74 | cgit_root = xstrdup(value); |
57 | else if (!strcmp(name, "root-title")) | 75 | else if (!strcmp(name, "root-title")) |
58 | cgit_root_title = xstrdup(value); | 76 | cgit_root_title = xstrdup(value); |
59 | else if (!strcmp(name, "css")) | 77 | else if (!strcmp(name, "css")) |
@@ -61,50 +79,55 @@ void cgit_global_config_cb(const char *name, const char *value) | |||
61 | else if (!strcmp(name, "logo")) | 79 | else if (!strcmp(name, "logo")) |
62 | cgit_logo = xstrdup(value); | 80 | cgit_logo = xstrdup(value); |
63 | else if (!strcmp(name, "logo-link")) | 81 | else if (!strcmp(name, "logo-link")) |
64 | cgit_logo_link = xstrdup(value); | 82 | cgit_logo_link = xstrdup(value); |
65 | else if (!strcmp(name, "virtual-root")) | 83 | else if (!strcmp(name, "virtual-root")) |
66 | cgit_virtual_root = xstrdup(value); | 84 | cgit_virtual_root = xstrdup(value); |
67 | } | 85 | } |
68 | 86 | ||
69 | void cgit_repo_config_cb(const char *name, const char *value) | 87 | void cgit_repo_config_cb(const char *name, const char *value) |
70 | { | 88 | { |
71 | if (!strcmp(name, "name")) | 89 | if (!strcmp(name, "name")) |
72 | cgit_repo_name = xstrdup(value); | 90 | cgit_repo_name = xstrdup(value); |
73 | else if (!strcmp(name, "desc")) | 91 | else if (!strcmp(name, "desc")) |
74 | cgit_repo_desc = xstrdup(value); | 92 | cgit_repo_desc = xstrdup(value); |
75 | else if (!strcmp(name, "owner")) | 93 | else if (!strcmp(name, "owner")) |
76 | cgit_repo_owner = xstrdup(value); | 94 | cgit_repo_owner = xstrdup(value); |
77 | } | 95 | } |
78 | 96 | ||
79 | void cgit_querystring_cb(const char *name, const char *value) | 97 | void cgit_querystring_cb(const char *name, const char *value) |
80 | { | 98 | { |
81 | if (!strcmp(name,"r")) | 99 | if (!strcmp(name,"r")) |
82 | cgit_query_repo = xstrdup(value); | 100 | cgit_query_repo = xstrdup(value); |
83 | else if (!strcmp(name, "p")) | 101 | else if (!strcmp(name, "p")) |
84 | cgit_query_page = xstrdup(value); | 102 | cgit_query_page = xstrdup(value); |
85 | else if (!strcmp(name, "h")) | 103 | else if (!strcmp(name, "h")) { |
86 | cgit_query_head = xstrdup(value); | 104 | cgit_query_head = xstrdup(value); |
105 | cgit_query_has_symref = 1; | ||
106 | } else if (!strcmp(name, "id")) { | ||
107 | cgit_query_sha1 = xstrdup(value); | ||
108 | cgit_query_has_sha1 = 1; | ||
109 | } | ||
87 | } | 110 | } |
88 | 111 | ||
89 | char *cgit_repourl(const char *reponame) | 112 | char *cgit_repourl(const char *reponame) |
90 | { | 113 | { |
91 | if (cgit_virtual_root) { | 114 | if (cgit_virtual_root) { |
92 | return fmt("%s/%s/", cgit_virtual_root, reponame); | 115 | return fmt("%s/%s/", cgit_virtual_root, reponame); |
93 | } else { | 116 | } else { |
94 | return fmt("?r=%s", reponame); | 117 | return fmt("?r=%s", reponame); |
95 | } | 118 | } |
96 | } | 119 | } |
97 | 120 | ||
98 | char *cgit_pageurl(const char *reponame, const char *pagename, | 121 | char *cgit_pageurl(const char *reponame, const char *pagename, |
99 | const char *query) | 122 | const char *query) |
100 | { | 123 | { |
101 | if (cgit_virtual_root) { | 124 | if (cgit_virtual_root) { |
102 | return fmt("%s/%s/%s/?%s", cgit_virtual_root, reponame, | 125 | return fmt("%s/%s/%s/?%s", cgit_virtual_root, reponame, |
103 | pagename, query); | 126 | pagename, query); |
104 | } else { | 127 | } else { |
105 | return fmt("?r=%s&p=%s&%s", reponame, pagename, query); | 128 | return fmt("?r=%s&p=%s&%s", reponame, pagename, query); |
106 | } | 129 | } |
107 | } | 130 | } |
108 | 131 | ||
109 | static int cgit_print_branch_cb(const char *refname, const unsigned char *sha1, | 132 | static int cgit_print_branch_cb(const char *refname, const unsigned char *sha1, |
110 | int flags, void *cb_data) | 133 | int flags, void *cb_data) |
@@ -115,110 +138,134 @@ static int cgit_print_branch_cb(const char *refname, const unsigned char *sha1, | |||
115 | commit = lookup_commit(sha1); | 138 | commit = lookup_commit(sha1); |
116 | if (commit && !parse_commit(commit)){ | 139 | if (commit && !parse_commit(commit)){ |
117 | html("<tr><td>"); | 140 | html("<tr><td>"); |
118 | url = cgit_pageurl(cgit_query_repo, "log", | 141 | url = cgit_pageurl(cgit_query_repo, "log", |
119 | fmt("h=%s", refname)); | 142 | fmt("h=%s", refname)); |
120 | html_link_open(url, NULL, NULL); | 143 | html_link_open(url, NULL, NULL); |
121 | strncpy(buf, refname, sizeof(buf)); | 144 | strncpy(buf, refname, sizeof(buf)); |
122 | html_txt(buf); | 145 | html_txt(buf); |
123 | html_link_close(); | 146 | html_link_close(); |
124 | html("</td><td>"); | 147 | html("</td><td>"); |
125 | pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0, buf, | 148 | pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0, buf, |
126 | sizeof(buf), 0, NULL, NULL, 0); | 149 | sizeof(buf), 0, NULL, NULL, 0); |
127 | html_txt(buf); | 150 | html_txt(buf); |
128 | html("</td></tr>\n"); | 151 | html("</td></tr>\n"); |
129 | } else { | 152 | } else { |
130 | html("<tr><td>"); | 153 | html("<tr><td>"); |
131 | html_txt(buf); | 154 | html_txt(buf); |
132 | html("</td><td>"); | 155 | html("</td><td>"); |
133 | htmlf("*** bad ref %s", sha1_to_hex(sha1)); | 156 | htmlf("*** bad ref %s", sha1_to_hex(sha1)); |
134 | html("</td></tr>\n"); | 157 | html("</td></tr>\n"); |
135 | } | 158 | } |
136 | return 0; | 159 | return 0; |
137 | } | 160 | } |
138 | 161 | ||
162 | /* Sun, 06 Nov 1994 08:49:37 GMT */ | ||
163 | static char *http_date(time_t t) | ||
164 | { | ||
165 | static char day[][4] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; | ||
166 | static char month[][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", | ||
167 | "Jul", "Aug", "Sep", "Oct", "Now", "Dec"}; | ||
168 | struct tm *tm = gmtime(&t); | ||
169 | return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday], | ||
170 | tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year, | ||
171 | tm->tm_hour, tm->tm_min, tm->tm_sec); | ||
172 | } | ||
173 | |||
174 | static int ttl_seconds(int ttl) | ||
175 | { | ||
176 | if (ttl<0) | ||
177 | return 60 * 60 * 24 * 365; | ||
178 | else | ||
179 | return ttl * 60; | ||
180 | } | ||
181 | |||
139 | static void cgit_print_docstart(char *title) | 182 | static void cgit_print_docstart(char *title) |
140 | { | 183 | { |
141 | html("Content-Type: text/html; charset=utf-8\n"); | 184 | html("Content-Type: text/html; charset=utf-8\n"); |
185 | htmlf("Last-Modified: %s\n", http_date(cacheitem.st.st_mtime)); | ||
186 | htmlf("Expires: %s\n", http_date(cacheitem.st.st_mtime + | ||
187 | ttl_seconds(cacheitem.ttl))); | ||
142 | html("\n"); | 188 | html("\n"); |
143 | html(cgit_doctype); | 189 | html(cgit_doctype); |
144 | html("<html>\n"); | 190 | html("<html>\n"); |
145 | html("<head>\n"); | 191 | html("<head>\n"); |
146 | html("<title>"); | 192 | html("<title>"); |
147 | html_txt(title); | 193 | html_txt(title); |
148 | html("</title>\n"); | 194 | html("</title>\n"); |
149 | html("<link rel='stylesheet' type='text/css' href='"); | 195 | html("<link rel='stylesheet' type='text/css' href='"); |
150 | html_attr(cgit_css); | 196 | html_attr(cgit_css); |
151 | html("'/>\n"); | 197 | html("'/>\n"); |
152 | html("</head>\n"); | 198 | html("</head>\n"); |
153 | html("<body>\n"); | 199 | html("<body>\n"); |
154 | } | 200 | } |
155 | 201 | ||
156 | static void cgit_print_docend() | 202 | static void cgit_print_docend() |
157 | { | 203 | { |
158 | html("</body>\n</html>\n"); | 204 | html("</body>\n</html>\n"); |
159 | } | 205 | } |
160 | 206 | ||
161 | static void cgit_print_pageheader(char *title) | 207 | static void cgit_print_pageheader(char *title) |
162 | { | 208 | { |
163 | html("<div id='header'>"); | 209 | html("<div id='header'>"); |
164 | htmlf("<a href='%s'>", cgit_logo_link); | 210 | htmlf("<a href='%s'>", cgit_logo_link); |
165 | htmlf("<img id='logo' src='%s'/>\n", cgit_logo); | 211 | htmlf("<img id='logo' src='%s'/>\n", cgit_logo); |
166 | htmlf("</a>"); | 212 | htmlf("</a>"); |
167 | html_txt(title); | 213 | html_txt(title); |
168 | html("</div>"); | 214 | html("</div>"); |
169 | } | 215 | } |
170 | 216 | ||
171 | static void cgit_print_repolist() | 217 | static void cgit_print_repolist() |
172 | { | 218 | { |
173 | DIR *d; | 219 | DIR *d; |
174 | struct dirent *de; | 220 | struct dirent *de; |
175 | struct stat st; | 221 | struct stat st; |
176 | char *name; | 222 | char *name; |
177 | 223 | ||
224 | chdir(cgit_root); | ||
178 | cgit_print_docstart(cgit_root_title); | 225 | cgit_print_docstart(cgit_root_title); |
179 | cgit_print_pageheader(cgit_root_title); | 226 | cgit_print_pageheader(cgit_root_title); |
180 | 227 | ||
181 | if (!(d = opendir("."))) { | 228 | if (!(d = opendir("."))) { |
182 | htmlf(cgit_lib_error, "Unable to scan repository directory", | 229 | htmlf(cgit_lib_error, "Unable to scan repository directory", |
183 | strerror(errno)); | 230 | strerror(errno)); |
184 | cgit_print_docend(); | 231 | cgit_print_docend(); |
185 | return; | 232 | return; |
186 | } | 233 | } |
187 | 234 | ||
188 | html("<h2>Repositories</h2>\n"); | 235 | html("<h2>Repositories</h2>\n"); |
189 | html("<table class='list'>"); | 236 | html("<table class='list'>"); |
190 | html("<tr><th>Name</th><th>Description</th><th>Owner</th></tr>\n"); | 237 | html("<tr><th>Name</th><th>Description</th><th>Owner</th></tr>\n"); |
191 | while ((de = readdir(d)) != NULL) { | 238 | while ((de = readdir(d)) != NULL) { |
192 | if (de->d_name[0] == '.') | 239 | if (de->d_name[0] == '.') |
193 | continue; | 240 | continue; |
194 | if (stat(de->d_name, &st) < 0) | 241 | if (stat(de->d_name, &st) < 0) |
195 | continue; | 242 | continue; |
196 | if (!S_ISDIR(st.st_mode)) | 243 | if (!S_ISDIR(st.st_mode)) |
197 | continue; | 244 | continue; |
198 | 245 | ||
199 | cgit_repo_name = cgit_repo_desc = cgit_repo_owner = NULL; | 246 | cgit_repo_name = cgit_repo_desc = cgit_repo_owner = NULL; |
200 | name = fmt("%s/.git/info/cgit", de->d_name); | 247 | name = fmt("%s/info/cgit", de->d_name); |
201 | if (cgit_read_config(name, cgit_repo_config_cb)) | 248 | if (cgit_read_config(name, cgit_repo_config_cb)) |
202 | continue; | 249 | continue; |
203 | 250 | ||
204 | html("<tr><td>"); | 251 | html("<tr><td>"); |
205 | html_link_open(cgit_repourl(de->d_name), NULL, NULL); | 252 | html_link_open(cgit_repourl(de->d_name), NULL, NULL); |
206 | html_txt(cgit_repo_name); | 253 | html_txt(cgit_repo_name); |
207 | html_link_close(); | 254 | html_link_close(); |
208 | html("</td><td>"); | 255 | html("</td><td>"); |
209 | html_txt(cgit_repo_desc); | 256 | html_txt(cgit_repo_desc); |
210 | html("</td><td>"); | 257 | html("</td><td>"); |
211 | html_txt(cgit_repo_owner); | 258 | html_txt(cgit_repo_owner); |
212 | html("</td></tr>\n"); | 259 | html("</td></tr>\n"); |
213 | } | 260 | } |
214 | closedir(d); | 261 | closedir(d); |
215 | html("</table>"); | 262 | html("</table>"); |
216 | cgit_print_docend(); | 263 | cgit_print_docend(); |
217 | } | 264 | } |
218 | 265 | ||
219 | static void cgit_print_branches() | 266 | static void cgit_print_branches() |
220 | { | 267 | { |
221 | html("<table class='list'>"); | 268 | html("<table class='list'>"); |
222 | html("<tr><th>Branch name</th><th>Head commit</th></tr>\n"); | 269 | html("<tr><th>Branch name</th><th>Head commit</th></tr>\n"); |
223 | for_each_branch_ref(cgit_print_branch_cb, NULL); | 270 | for_each_branch_ref(cgit_print_branch_cb, NULL); |
224 | html("</table>"); | 271 | html("</table>"); |
@@ -270,49 +317,49 @@ static void cgit_print_commit_shortlog(struct commit *commit) | |||
270 | ; | 317 | ; |
271 | while(++p!=h && !isdigit(*p)) | 318 | while(++p!=h && !isdigit(*p)) |
272 | ; | 319 | ; |
273 | 320 | ||
274 | t = p; | 321 | t = p; |
275 | while(++p && isdigit(*p)) | 322 | while(++p && isdigit(*p)) |
276 | ; | 323 | ; |
277 | *p = '\0'; | 324 | *p = '\0'; |
278 | sec = atoi(t); | 325 | sec = atoi(t); |
279 | time = gmtime(&sec); | 326 | time = gmtime(&sec); |
280 | } | 327 | } |
281 | 328 | ||
282 | while((len = get_one_line(h)) > 0) | 329 | while((len = get_one_line(h)) > 0) |
283 | h += len+2; | 330 | h += len+2; |
284 | 331 | ||
285 | h++; | 332 | h++; |
286 | len = get_one_line(h); | 333 | len = get_one_line(h); |
287 | 334 | ||
288 | subject = h; | 335 | subject = h; |
289 | 336 | ||
290 | html("<tr><td>"); | 337 | html("<tr><td>"); |
291 | strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", time); | 338 | strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", time); |
292 | html_txt(buf); | 339 | html_txt(buf); |
293 | html("</td><td>"); | 340 | html("</td><td>"); |
294 | char *qry = fmt("h=%s", sha1_to_hex(commit->object.sha1)); | 341 | char *qry = fmt("id=%s", sha1_to_hex(commit->object.sha1)); |
295 | char *url = cgit_pageurl(cgit_query_repo, "view", qry); | 342 | char *url = cgit_pageurl(cgit_query_repo, "view", qry); |
296 | html_link_open(url, NULL, NULL); | 343 | html_link_open(url, NULL, NULL); |
297 | html_txt(subject); | 344 | html_txt(subject); |
298 | html_link_close(); | 345 | html_link_close(); |
299 | html("</td><td>"); | 346 | html("</td><td>"); |
300 | html_txt(author); | 347 | html_txt(author); |
301 | html("</td></tr>\n"); | 348 | html("</td></tr>\n"); |
302 | } | 349 | } |
303 | 350 | ||
304 | static void cgit_print_log(const char *tip, int ofs, int cnt) | 351 | static void cgit_print_log(const char *tip, int ofs, int cnt) |
305 | { | 352 | { |
306 | struct rev_info rev; | 353 | struct rev_info rev; |
307 | struct commit *commit; | 354 | struct commit *commit; |
308 | const char *argv[2] = {NULL, tip}; | 355 | const char *argv[2] = {NULL, tip}; |
309 | int n = 0; | 356 | int n = 0; |
310 | 357 | ||
311 | init_revisions(&rev, NULL); | 358 | init_revisions(&rev, NULL); |
312 | rev.abbrev = DEFAULT_ABBREV; | 359 | rev.abbrev = DEFAULT_ABBREV; |
313 | rev.commit_format = CMIT_FMT_DEFAULT; | 360 | rev.commit_format = CMIT_FMT_DEFAULT; |
314 | rev.verbose_header = 1; | 361 | rev.verbose_header = 1; |
315 | rev.show_root_diff = 0; | 362 | rev.show_root_diff = 0; |
316 | setup_revisions(2, argv, &rev, NULL); | 363 | setup_revisions(2, argv, &rev, NULL); |
317 | prepare_revision_walk(&rev); | 364 | prepare_revision_walk(&rev); |
318 | 365 | ||
@@ -350,61 +397,101 @@ static void cgit_print_object(char *hex) | |||
350 | return; | 397 | return; |
351 | } | 398 | } |
352 | 399 | ||
353 | if (sha1_object_info(sha1, type, NULL)){ | 400 | if (sha1_object_info(sha1, type, NULL)){ |
354 | htmlf(cgit_error, "Bad object name"); | 401 | htmlf(cgit_error, "Bad object name"); |
355 | return; | 402 | return; |
356 | } | 403 | } |
357 | 404 | ||
358 | buf = read_sha1_file(sha1, type, &size); | 405 | buf = read_sha1_file(sha1, type, &size); |
359 | if (!buf) { | 406 | if (!buf) { |
360 | htmlf(cgit_error, "Error reading object"); | 407 | htmlf(cgit_error, "Error reading object"); |
361 | return; | 408 | return; |
362 | } | 409 | } |
363 | 410 | ||
364 | buf[size] = '\0'; | 411 | buf[size] = '\0'; |
365 | html("<h2>Object view</h2>"); | 412 | html("<h2>Object view</h2>"); |
366 | htmlf("sha1=%s<br/>type=%s<br/>size=%i<br/>", hex, type, size); | 413 | htmlf("sha1=%s<br/>type=%s<br/>size=%i<br/>", hex, type, size); |
367 | html("<pre>"); | 414 | html("<pre>"); |
368 | html_txt(buf); | 415 | html_txt(buf); |
369 | html("</pre>"); | 416 | html("</pre>"); |
370 | } | 417 | } |
371 | 418 | ||
372 | static void cgit_print_repo_page() | 419 | static void cgit_print_repo_page() |
373 | { | 420 | { |
374 | if (chdir(cgit_query_repo) || | 421 | if (chdir(fmt("%s/%s", cgit_root, cgit_query_repo)) || |
375 | cgit_read_config(".git/info/cgit", cgit_repo_config_cb)) { | 422 | cgit_read_config("info/cgit", cgit_repo_config_cb)) { |
376 | char *title = fmt("%s - %s", cgit_root_title, "Bad request"); | 423 | char *title = fmt("%s - %s", cgit_root_title, "Bad request"); |
377 | cgit_print_docstart(title); | 424 | cgit_print_docstart(title); |
378 | cgit_print_pageheader(title); | 425 | cgit_print_pageheader(title); |
379 | htmlf(cgit_lib_error, "Unable to scan repository", | 426 | htmlf(cgit_lib_error, "Unable to scan repository", |
380 | strerror(errno)); | 427 | strerror(errno)); |
381 | cgit_print_docend(); | 428 | cgit_print_docend(); |
382 | return; | 429 | return; |
383 | } | 430 | } |
384 | 431 | setenv("GIT_DIR", fmt("%s/%s", cgit_root, cgit_query_repo), 1); | |
385 | char *title = fmt("%s - %s", cgit_repo_name, cgit_repo_desc); | 432 | char *title = fmt("%s - %s", cgit_repo_name, cgit_repo_desc); |
386 | cgit_print_docstart(title); | 433 | cgit_print_docstart(title); |
387 | cgit_print_pageheader(title); | 434 | cgit_print_pageheader(title); |
388 | if (!cgit_query_page) | 435 | if (!cgit_query_page) |
389 | cgit_print_repo_summary(); | 436 | cgit_print_repo_summary(); |
390 | else if (!strcmp(cgit_query_page, "log")) { | 437 | else if (!strcmp(cgit_query_page, "log")) { |
391 | cgit_print_log(cgit_query_head, 0, 100); | 438 | cgit_print_log(cgit_query_head, 0, 100); |
392 | } else if (!strcmp(cgit_query_page, "view")) { | 439 | } else if (!strcmp(cgit_query_page, "view")) { |
393 | cgit_print_object(cgit_query_head); | 440 | cgit_print_object(cgit_query_sha1); |
394 | } | 441 | } |
395 | cgit_print_docend(); | 442 | cgit_print_docend(); |
396 | } | 443 | } |
397 | 444 | ||
398 | int main(int argc, const char **argv) | 445 | static void cgit_fill_cache(struct cacheitem *item) |
399 | { | 446 | { |
400 | if (cgit_read_config("/etc/cgitrc", cgit_global_config_cb)) | 447 | htmlfd = item->fd; |
401 | die("Error reading config: %d %s", errno, strerror(errno)); | 448 | item->st.st_mtime = time(NULL); |
402 | |||
403 | chdir(cgit_root); | ||
404 | cgit_parse_query(getenv("QUERY_STRING"), cgit_querystring_cb); | ||
405 | if (cgit_query_repo) | 449 | if (cgit_query_repo) |
406 | cgit_print_repo_page(); | 450 | cgit_print_repo_page(); |
407 | else | 451 | else |
408 | cgit_print_repolist(); | 452 | cgit_print_repolist(); |
453 | } | ||
454 | |||
455 | static void cgit_refresh_cache(struct cacheitem *item) | ||
456 | { | ||
457 | top: | ||
458 | if (!cache_lookup(item)) { | ||
459 | if (cache_lock(item)) { | ||
460 | cgit_fill_cache(item); | ||
461 | cache_unlock(item); | ||
462 | } else { | ||
463 | sched_yield(); | ||
464 | goto top; | ||
465 | } | ||
466 | } else if (cache_expired(item)) { | ||
467 | if (cache_lock(item)) { | ||
468 | cgit_fill_cache(item); | ||
469 | cache_unlock(item); | ||
470 | } | ||
471 | } | ||
472 | } | ||
473 | |||
474 | static void cgit_print_cache(struct cacheitem *item) | ||
475 | { | ||
476 | static char buf[4096]; | ||
477 | ssize_t i; | ||
478 | |||
479 | int fd = open(item->name, O_RDONLY); | ||
480 | if (fd<0) | ||
481 | die("Unable to open cached file %s", item->name); | ||
482 | |||
483 | while((i=read(fd, buf, sizeof(buf))) > 0) | ||
484 | write(STDOUT_FILENO, buf, i); | ||
485 | |||
486 | close(fd); | ||
487 | } | ||
488 | |||
489 | int main(int argc, const char **argv) | ||
490 | { | ||
491 | cgit_read_config("/etc/cgitrc", cgit_global_config_cb); | ||
492 | cgit_querystring = xstrdup(getenv("QUERY_STRING")); | ||
493 | cgit_parse_query(cgit_querystring, cgit_querystring_cb); | ||
494 | cgit_refresh_cache(&cacheitem); | ||
495 | cgit_print_cache(&cacheitem); | ||
409 | return 0; | 496 | return 0; |
410 | } | 497 | } |
@@ -1,21 +1,64 @@ | |||
1 | #ifndef CGIT_H | 1 | #ifndef CGIT_H |
2 | #define CGIT_H | 2 | #define CGIT_H |
3 | 3 | ||
4 | #include "git.h" | 4 | #include "git.h" |
5 | #include <openssl/sha.h> | 5 | #include <openssl/sha.h> |
6 | #include <ctype.h> | ||
7 | #include <sched.h> | ||
8 | |||
9 | typedef void (*configfn)(const char *name, const char *value); | ||
10 | |||
11 | struct cacheitem { | ||
12 | char *name; | ||
13 | struct stat st; | ||
14 | int ttl; | ||
15 | int fd; | ||
16 | }; | ||
17 | |||
18 | extern char *cgit_root; | ||
19 | extern char *cgit_root_title; | ||
20 | extern char *cgit_css; | ||
21 | extern char *cgit_logo; | ||
22 | extern char *cgit_logo_link; | ||
23 | extern char *cgit_virtual_root; | ||
24 | extern char *cgit_cache_root; | ||
25 | |||
26 | extern int cgit_cache_root_ttl; | ||
27 | extern int cgit_cache_repo_ttl; | ||
28 | extern int cgit_cache_dynamic_ttl; | ||
29 | extern int cgit_cache_static_ttl; | ||
30 | extern int cgit_cache_max_create_time; | ||
31 | |||
32 | extern char *cgit_repo_name; | ||
33 | extern char *cgit_repo_desc; | ||
34 | extern char *cgit_repo_owner; | ||
35 | |||
36 | extern int cgit_query_has_symref; | ||
37 | extern int cgit_query_has_sha1; | ||
38 | |||
39 | extern char *cgit_querystring; | ||
40 | extern char *cgit_query_repo; | ||
41 | extern char *cgit_query_page; | ||
42 | extern char *cgit_query_head; | ||
43 | extern char *cgit_query_sha1; | ||
44 | |||
45 | extern int htmlfd; | ||
6 | 46 | ||
7 | extern char *fmt(const char *format,...); | 47 | extern char *fmt(const char *format,...); |
8 | 48 | ||
9 | extern void html(const char *txt); | 49 | extern void html(const char *txt); |
10 | extern void htmlf(const char *format,...); | 50 | extern void htmlf(const char *format,...); |
11 | extern void html_txt(char *txt); | 51 | extern void html_txt(char *txt); |
12 | extern void html_attr(char *txt); | 52 | extern void html_attr(char *txt); |
13 | |||
14 | extern void html_link_open(char *url, char *title, char *class); | 53 | extern void html_link_open(char *url, char *title, char *class); |
15 | extern void html_link_close(void); | 54 | extern void html_link_close(void); |
16 | 55 | ||
17 | typedef void (*configfn)(const char *name, const char *value); | ||
18 | 56 | ||
19 | extern int cgit_read_config(const char *filename, configfn fn); | 57 | extern int cgit_read_config(const char *filename, configfn fn); |
20 | 58 | ||
59 | extern int cache_lookup(struct cacheitem *item); | ||
60 | extern int cache_lock(struct cacheitem *item); | ||
61 | extern int cache_unlock(struct cacheitem *item); | ||
62 | extern int cache_expired(struct cacheitem *item); | ||
63 | |||
21 | #endif /* CGIT_H */ | 64 | #endif /* CGIT_H */ |
@@ -11,63 +11,63 @@ int next_char(FILE *f) | |||
11 | } | 11 | } |
12 | } | 12 | } |
13 | return c; | 13 | return c; |
14 | } | 14 | } |
15 | 15 | ||
16 | void skip_line(FILE *f) | 16 | void skip_line(FILE *f) |
17 | { | 17 | { |
18 | int c; | 18 | int c; |
19 | 19 | ||
20 | while((c=next_char(f)) && c!='\n' && c!=EOF) | 20 | while((c=next_char(f)) && c!='\n' && c!=EOF) |
21 | ; | 21 | ; |
22 | } | 22 | } |
23 | 23 | ||
24 | int read_config_line(FILE *f, char *line, const char **value, int bufsize) | 24 | int read_config_line(FILE *f, char *line, const char **value, int bufsize) |
25 | { | 25 | { |
26 | int i = 0, isname = 0; | 26 | int i = 0, isname = 0; |
27 | 27 | ||
28 | *value = NULL; | 28 | *value = NULL; |
29 | while(i<bufsize-1) { | 29 | while(i<bufsize-1) { |
30 | int c = next_char(f); | 30 | int c = next_char(f); |
31 | if (!isname && (c=='#' || c==';')) { | 31 | if (!isname && (c=='#' || c==';')) { |
32 | skip_line(f); | 32 | skip_line(f); |
33 | continue; | 33 | continue; |
34 | } | 34 | } |
35 | if (!isname && isblank(c)) | 35 | if (!isname && isspace(c)) |
36 | continue; | 36 | continue; |
37 | 37 | ||
38 | if (c=='=' && !*value) { | 38 | if (c=='=' && !*value) { |
39 | line[i] = 0; | 39 | line[i] = 0; |
40 | *value = &line[i+1]; | 40 | *value = &line[i+1]; |
41 | } else if (c=='\n' && !isname) { | 41 | } else if (c=='\n' && !isname) { |
42 | i = 0; | 42 | i = 0; |
43 | continue; | 43 | continue; |
44 | } else if (c=='\n' || c==EOF) { | 44 | } else if (c=='\n' || c==EOF) { |
45 | line[i] = 0; | 45 | line[i] = 0; |
46 | break; | 46 | break; |
47 | } else { | 47 | } else { |
48 | line[i]=c; | 48 | line[i]=c; |
49 | } | 49 | } |
50 | isname = 1; | 50 | isname = 1; |
51 | i++; | 51 | i++; |
52 | } | 52 | } |
53 | line[i+1] = 0; | 53 | line[i+1] = 0; |
54 | return i; | 54 | return i; |
55 | } | 55 | } |
56 | 56 | ||
57 | int cgit_read_config(const char *filename, configfn fn) | 57 | int cgit_read_config(const char *filename, configfn fn) |
58 | { | 58 | { |
59 | int ret = 0, len; | 59 | int ret = 0, len; |
60 | char line[256]; | 60 | char line[256]; |
61 | const char *value; | 61 | const char *value; |
62 | FILE *f = fopen(filename, "r"); | 62 | FILE *f = fopen(filename, "r"); |
63 | 63 | ||
64 | if (!f) | 64 | if (!f) |
65 | return -1; | 65 | return -1; |
66 | 66 | ||
67 | while(len = read_config_line(f, line, &value, sizeof(line))) | 67 | while((len = read_config_line(f, line, &value, sizeof(line))) > 0) |
68 | (*fn)(line, value); | 68 | (*fn)(line, value); |
69 | 69 | ||
70 | fclose(f); | 70 | fclose(f); |
71 | return ret; | 71 | return ret; |
72 | } | 72 | } |
73 | 73 | ||
@@ -12,48 +12,68 @@ | |||
12 | #define FLEX_ARRAY 0 | 12 | #define FLEX_ARRAY 0 |
13 | #else | 13 | #else |
14 | #define FLEX_ARRAY /* empty */ | 14 | #define FLEX_ARRAY /* empty */ |
15 | #endif | 15 | #endif |
16 | #endif | 16 | #endif |
17 | 17 | ||
18 | 18 | ||
19 | #include <unistd.h> | 19 | #include <unistd.h> |
20 | #include <stdio.h> | 20 | #include <stdio.h> |
21 | #include <sys/stat.h> | 21 | #include <sys/stat.h> |
22 | #include <fcntl.h> | 22 | #include <fcntl.h> |
23 | #include <stddef.h> | 23 | #include <stddef.h> |
24 | #include <stdlib.h> | 24 | #include <stdlib.h> |
25 | #include <stdarg.h> | 25 | #include <stdarg.h> |
26 | #include <string.h> | 26 | #include <string.h> |
27 | #include <errno.h> | 27 | #include <errno.h> |
28 | #include <limits.h> | 28 | #include <limits.h> |
29 | #include <sys/param.h> | 29 | #include <sys/param.h> |
30 | #include <netinet/in.h> | 30 | #include <netinet/in.h> |
31 | #include <sys/types.h> | 31 | #include <sys/types.h> |
32 | #include <dirent.h> | 32 | #include <dirent.h> |
33 | #include <time.h> | 33 | #include <time.h> |
34 | 34 | ||
35 | 35 | ||
36 | /* On most systems <limits.h> would have given us this, but | ||
37 | * not on some systems (e.g. GNU/Hurd). | ||
38 | */ | ||
39 | #ifndef PATH_MAX | ||
40 | #define PATH_MAX 4096 | ||
41 | #endif | ||
42 | |||
43 | #ifdef __GNUC__ | ||
44 | #define NORETURN __attribute__((__noreturn__)) | ||
45 | #else | ||
46 | #define NORETURN | ||
47 | #ifndef __attribute__ | ||
48 | #define __attribute__(x) | ||
49 | #endif | ||
50 | #endif | ||
51 | |||
52 | |||
53 | extern void die(const char *err, ...) NORETURN __attribute__((format (printf, 1, 2))); | ||
54 | |||
55 | |||
36 | static inline char* xstrdup(const char *str) | 56 | static inline char* xstrdup(const char *str) |
37 | { | 57 | { |
38 | char *ret = strdup(str); | 58 | char *ret = strdup(str); |
39 | if (!ret) | 59 | if (!ret) |
40 | die("Out of memory, strdup failed"); | 60 | die("Out of memory, strdup failed"); |
41 | return ret; | 61 | return ret; |
42 | } | 62 | } |
43 | 63 | ||
44 | static inline void *xmalloc(size_t size) | 64 | static inline void *xmalloc(size_t size) |
45 | { | 65 | { |
46 | void *ret = malloc(size); | 66 | void *ret = malloc(size); |
47 | if (!ret && !size) | 67 | if (!ret && !size) |
48 | ret = malloc(1); | 68 | ret = malloc(1); |
49 | if (!ret) | 69 | if (!ret) |
50 | die("Out of memory, malloc failed"); | 70 | die("Out of memory, malloc failed"); |
51 | #ifdef XMALLOC_POISON | 71 | #ifdef XMALLOC_POISON |
52 | memset(ret, 0xA5, size); | 72 | memset(ret, 0xA5, size); |
53 | #endif | 73 | #endif |
54 | return ret; | 74 | return ret; |
55 | } | 75 | } |
56 | 76 | ||
57 | static inline void *xrealloc(void *ptr, size_t size) | 77 | static inline void *xrealloc(void *ptr, size_t size) |
58 | { | 78 | { |
59 | void *ret = realloc(ptr, size); | 79 | void *ret = realloc(ptr, size); |
@@ -87,51 +107,55 @@ static inline ssize_t xread(int fd, void *buf, size_t len) | |||
87 | 107 | ||
88 | static inline ssize_t xwrite(int fd, const void *buf, size_t len) | 108 | static inline ssize_t xwrite(int fd, const void *buf, size_t len) |
89 | { | 109 | { |
90 | ssize_t nr; | 110 | ssize_t nr; |
91 | while (1) { | 111 | while (1) { |
92 | nr = write(fd, buf, len); | 112 | nr = write(fd, buf, len); |
93 | if ((nr < 0) && (errno == EAGAIN || errno == EINTR)) | 113 | if ((nr < 0) && (errno == EAGAIN || errno == EINTR)) |
94 | continue; | 114 | continue; |
95 | return nr; | 115 | return nr; |
96 | } | 116 | } |
97 | } | 117 | } |
98 | 118 | ||
99 | 119 | ||
100 | 120 | ||
101 | 121 | ||
102 | /* | 122 | /* |
103 | * from git:cache.h | 123 | * from git:cache.h |
104 | */ | 124 | */ |
105 | 125 | ||
106 | 126 | ||
107 | /* Convert to/from hex/sha1 representation */ | 127 | /* Convert to/from hex/sha1 representation */ |
108 | #define MINIMUM_ABBREV 4 | 128 | #define MINIMUM_ABBREV 4 |
109 | #define DEFAULT_ABBREV 7 | 129 | #define DEFAULT_ABBREV 7 |
110 | 130 | ||
131 | extern int sha1_object_info(const unsigned char *, char *, unsigned long *); | ||
111 | 132 | ||
112 | extern void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size); | 133 | extern void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size); |
113 | 134 | ||
135 | extern int get_sha1(const char *str, unsigned char *sha1); | ||
136 | extern int get_sha1_hex(const char *hex, unsigned char *sha1); | ||
137 | extern char *sha1_to_hex(const unsigned char *sha1);/* static buffer result! */ | ||
114 | 138 | ||
115 | 139 | ||
116 | 140 | ||
117 | /* | 141 | /* |
118 | * from git:object.h | 142 | * from git:object.h |
119 | */ | 143 | */ |
120 | 144 | ||
121 | struct object_list { | 145 | struct object_list { |
122 | struct object *item; | 146 | struct object *item; |
123 | struct object_list *next; | 147 | struct object_list *next; |
124 | }; | 148 | }; |
125 | 149 | ||
126 | struct object_refs { | 150 | struct object_refs { |
127 | unsigned count; | 151 | unsigned count; |
128 | struct object *base; | 152 | struct object *base; |
129 | struct object *ref[FLEX_ARRAY]; /* more */ | 153 | struct object *ref[FLEX_ARRAY]; /* more */ |
130 | }; | 154 | }; |
131 | 155 | ||
132 | struct object_array { | 156 | struct object_array { |
133 | unsigned int nr; | 157 | unsigned int nr; |
134 | unsigned int alloc; | 158 | unsigned int alloc; |
135 | struct object_array_entry { | 159 | struct object_array_entry { |
136 | struct object *item; | 160 | struct object *item; |
137 | const char *name; | 161 | const char *name; |
@@ -162,69 +186,80 @@ struct tree { | |||
162 | void *buffer; | 186 | void *buffer; |
163 | unsigned long size; | 187 | unsigned long size; |
164 | }; | 188 | }; |
165 | 189 | ||
166 | 190 | ||
167 | 191 | ||
168 | 192 | ||
169 | /* from git:commit.h */ | 193 | /* from git:commit.h */ |
170 | 194 | ||
171 | struct commit_list { | 195 | struct commit_list { |
172 | struct commit *item; | 196 | struct commit *item; |
173 | struct commit_list *next; | 197 | struct commit_list *next; |
174 | }; | 198 | }; |
175 | 199 | ||
176 | struct commit { | 200 | struct commit { |
177 | struct object object; | 201 | struct object object; |
178 | void *util; | 202 | void *util; |
179 | unsigned long date; | 203 | unsigned long date; |
180 | struct commit_list *parents; | 204 | struct commit_list *parents; |
181 | struct tree *tree; | 205 | struct tree *tree; |
182 | char *buffer; | 206 | char *buffer; |
183 | }; | 207 | }; |
184 | 208 | ||
185 | 209 | ||
210 | struct commit *lookup_commit(const unsigned char *sha1); | ||
211 | struct commit *lookup_commit_reference(const unsigned char *sha1); | ||
212 | struct commit *lookup_commit_reference_gently(const unsigned char *sha1, | ||
213 | int quiet); | ||
214 | |||
215 | int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size); | ||
216 | int parse_commit(struct commit *item); | ||
217 | |||
218 | struct commit_list * commit_list_insert(struct commit *item, struct commit_list **list_p); | ||
219 | struct commit_list * insert_by_date(struct commit *item, struct commit_list **list); | ||
220 | |||
221 | void free_commit_list(struct commit_list *list); | ||
222 | |||
223 | void sort_by_date(struct commit_list **list); | ||
224 | |||
186 | /* Commit formats */ | 225 | /* Commit formats */ |
187 | enum cmit_fmt { | 226 | enum cmit_fmt { |
188 | CMIT_FMT_RAW, | 227 | CMIT_FMT_RAW, |
189 | CMIT_FMT_MEDIUM, | 228 | CMIT_FMT_MEDIUM, |
190 | CMIT_FMT_DEFAULT = CMIT_FMT_MEDIUM, | 229 | CMIT_FMT_DEFAULT = CMIT_FMT_MEDIUM, |
191 | CMIT_FMT_SHORT, | 230 | CMIT_FMT_SHORT, |
192 | CMIT_FMT_FULL, | 231 | CMIT_FMT_FULL, |
193 | CMIT_FMT_FULLER, | 232 | CMIT_FMT_FULLER, |
194 | CMIT_FMT_ONELINE, | 233 | CMIT_FMT_ONELINE, |
195 | CMIT_FMT_EMAIL, | 234 | CMIT_FMT_EMAIL, |
196 | 235 | ||
197 | CMIT_FMT_UNSPECIFIED, | 236 | CMIT_FMT_UNSPECIFIED, |
198 | }; | 237 | }; |
199 | 238 | ||
239 | extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject, const char *after_subject, int relative_date); | ||
200 | 240 | ||
201 | 241 | ||
202 | struct commit *lookup_commit(const unsigned char *sha1); | ||
203 | struct commit *lookup_commit_reference(const unsigned char *sha1); | ||
204 | struct commit *lookup_commit_reference_gently(const unsigned char *sha1, | ||
205 | int quiet); | ||
206 | |||
207 | typedef void (*topo_sort_set_fn_t)(struct commit*, void *data); | 242 | typedef void (*topo_sort_set_fn_t)(struct commit*, void *data); |
208 | typedef void* (*topo_sort_get_fn_t)(struct commit*); | 243 | typedef void* (*topo_sort_get_fn_t)(struct commit*); |
209 | 244 | ||
210 | 245 | ||
211 | 246 | ||
212 | 247 | ||
213 | /* | 248 | /* |
214 | * from git:diff.h | 249 | * from git:diff.h |
215 | */ | 250 | */ |
216 | 251 | ||
217 | 252 | ||
218 | struct rev_info; | 253 | struct rev_info; |
219 | struct diff_options; | 254 | struct diff_options; |
220 | struct diff_queue_struct; | 255 | struct diff_queue_struct; |
221 | 256 | ||
222 | typedef void (*change_fn_t)(struct diff_options *options, | 257 | typedef void (*change_fn_t)(struct diff_options *options, |
223 | unsigned old_mode, unsigned new_mode, | 258 | unsigned old_mode, unsigned new_mode, |
224 | const unsigned char *old_sha1, | 259 | const unsigned char *old_sha1, |
225 | const unsigned char *new_sha1, | 260 | const unsigned char *new_sha1, |
226 | const char *base, const char *path); | 261 | const char *base, const char *path); |
227 | 262 | ||
228 | typedef void (*add_remove_fn_t)(struct diff_options *options, | 263 | typedef void (*add_remove_fn_t)(struct diff_options *options, |
229 | int addremove, unsigned mode, | 264 | int addremove, unsigned mode, |
230 | const unsigned char *sha1, | 265 | const unsigned char *sha1, |
@@ -285,48 +320,58 @@ struct diff_options { | |||
285 | 320 | ||
286 | int nr_paths; | 321 | int nr_paths; |
287 | const char **paths; | 322 | const char **paths; |
288 | int *pathlens; | 323 | int *pathlens; |
289 | change_fn_t change; | 324 | change_fn_t change; |
290 | add_remove_fn_t add_remove; | 325 | add_remove_fn_t add_remove; |
291 | diff_format_fn_t format_callback; | 326 | diff_format_fn_t format_callback; |
292 | void *format_callback_data; | 327 | void *format_callback_data; |
293 | }; | 328 | }; |
294 | 329 | ||
295 | enum color_diff { | 330 | enum color_diff { |
296 | DIFF_RESET = 0, | 331 | DIFF_RESET = 0, |
297 | DIFF_PLAIN = 1, | 332 | DIFF_PLAIN = 1, |
298 | DIFF_METAINFO = 2, | 333 | DIFF_METAINFO = 2, |
299 | DIFF_FRAGINFO = 3, | 334 | DIFF_FRAGINFO = 3, |
300 | DIFF_FILE_OLD = 4, | 335 | DIFF_FILE_OLD = 4, |
301 | DIFF_FILE_NEW = 5, | 336 | DIFF_FILE_NEW = 5, |
302 | DIFF_COMMIT = 6, | 337 | DIFF_COMMIT = 6, |
303 | DIFF_WHITESPACE = 7, | 338 | DIFF_WHITESPACE = 7, |
304 | }; | 339 | }; |
305 | 340 | ||
306 | 341 | ||
307 | 342 | ||
308 | 343 | ||
344 | /* | ||
345 | * from git:refs.g | ||
346 | */ | ||
347 | |||
348 | typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data); | ||
349 | extern int head_ref(each_ref_fn, void *); | ||
350 | extern int for_each_ref(each_ref_fn, void *); | ||
351 | extern int for_each_tag_ref(each_ref_fn, void *); | ||
352 | extern int for_each_branch_ref(each_ref_fn, void *); | ||
353 | extern int for_each_remote_ref(each_ref_fn, void *); | ||
309 | 354 | ||
310 | 355 | ||
311 | 356 | ||
312 | /* | 357 | /* |
313 | * from git:revision.h | 358 | * from git:revision.h |
314 | */ | 359 | */ |
315 | 360 | ||
316 | struct rev_info; | 361 | struct rev_info; |
317 | struct log_info; | 362 | struct log_info; |
318 | 363 | ||
319 | typedef void (prune_fn_t)(struct rev_info *revs, struct commit *commit); | 364 | typedef void (prune_fn_t)(struct rev_info *revs, struct commit *commit); |
320 | 365 | ||
321 | struct rev_info { | 366 | struct rev_info { |
322 | /* Starting list */ | 367 | /* Starting list */ |
323 | struct commit_list *commits; | 368 | struct commit_list *commits; |
324 | struct object_array pending; | 369 | struct object_array pending; |
325 | 370 | ||
326 | /* Basic information */ | 371 | /* Basic information */ |
327 | const char *prefix; | 372 | const char *prefix; |
328 | void *prune_data; | 373 | void *prune_data; |
329 | prune_fn_t *prune_fn; | 374 | prune_fn_t *prune_fn; |
330 | 375 | ||
331 | /* Traversal flags */ | 376 | /* Traversal flags */ |
332 | unsigned intdense:1, | 377 | unsigned intdense:1, |
@@ -370,30 +415,35 @@ struct rev_info { | |||
370 | int nr, total; | 415 | int nr, total; |
371 | const char*mime_boundary; | 416 | const char*mime_boundary; |
372 | const char*message_id; | 417 | const char*message_id; |
373 | const char*ref_message_id; | 418 | const char*ref_message_id; |
374 | const char*add_signoff; | 419 | const char*add_signoff; |
375 | const char*extra_headers; | 420 | const char*extra_headers; |
376 | 421 | ||
377 | /* Filter by commit log message */ | 422 | /* Filter by commit log message */ |
378 | struct grep_opt*grep_filter; | 423 | struct grep_opt*grep_filter; |
379 | 424 | ||
380 | /* special limits */ | 425 | /* special limits */ |
381 | int max_count; | 426 | int max_count; |
382 | unsigned long max_age; | 427 | unsigned long max_age; |
383 | unsigned long min_age; | 428 | unsigned long min_age; |
384 | 429 | ||
385 | /* diff info for patches and for paths limiting */ | 430 | /* diff info for patches and for paths limiting */ |
386 | struct diff_options diffopt; | 431 | struct diff_options diffopt; |
387 | struct diff_options pruning; | 432 | struct diff_options pruning; |
388 | 433 | ||
389 | topo_sort_set_fn_t topo_setter; | 434 | topo_sort_set_fn_t topo_setter; |
390 | topo_sort_get_fn_t topo_getter; | 435 | topo_sort_get_fn_t topo_getter; |
391 | }; | 436 | }; |
392 | 437 | ||
393 | 438 | ||
439 | extern void init_revisions(struct rev_info *revs, const char *prefix); | ||
440 | extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def); | ||
441 | extern int handle_revision_arg(const char *arg, struct rev_info *revs,int flags,int cant_be_filename); | ||
442 | |||
443 | extern void prepare_revision_walk(struct rev_info *revs); | ||
394 | extern struct commit *get_revision(struct rev_info *revs); | 444 | extern struct commit *get_revision(struct rev_info *revs); |
395 | 445 | ||
396 | 446 | ||
397 | 447 | ||
398 | 448 | ||
399 | #endif /* GIT_H */ | 449 | #endif /* GIT_H */ |
@@ -1,56 +1,58 @@ | |||
1 | #include "cgit.h" | 1 | #include "cgit.h" |
2 | 2 | ||
3 | char *fmt(const char *format, ...) | 3 | char *fmt(const char *format, ...) |
4 | { | 4 | { |
5 | static char buf[8][1024]; | 5 | static char buf[8][1024]; |
6 | static int bufidx; | 6 | static int bufidx; |
7 | int len; | 7 | int len; |
8 | va_list args; | 8 | va_list args; |
9 | 9 | ||
10 | bufidx++; | 10 | bufidx++; |
11 | bufidx &= 7; | 11 | bufidx &= 7; |
12 | 12 | ||
13 | va_start(args, format); | 13 | va_start(args, format); |
14 | len = vsnprintf(buf[bufidx], sizeof(buf[bufidx]), format, args); | 14 | len = vsnprintf(buf[bufidx], sizeof(buf[bufidx]), format, args); |
15 | va_end(args); | 15 | va_end(args); |
16 | if (len>sizeof(buf[bufidx])) | 16 | if (len>sizeof(buf[bufidx])) |
17 | die("[html.c] string truncated: %s", format); | 17 | die("[html.c] string truncated: %s", format); |
18 | return buf[bufidx]; | 18 | return buf[bufidx]; |
19 | } | 19 | } |
20 | 20 | ||
21 | void html(const char *txt) | 21 | void html(const char *txt) |
22 | { | 22 | { |
23 | fputs(txt, stdout); | 23 | write(htmlfd, txt, strlen(txt)); |
24 | } | 24 | } |
25 | 25 | ||
26 | void htmlf(const char *format, ...) | 26 | void htmlf(const char *format, ...) |
27 | { | 27 | { |
28 | static char buf[65536]; | ||
28 | va_list args; | 29 | va_list args; |
29 | 30 | ||
30 | va_start(args, format); | 31 | va_start(args, format); |
31 | vprintf(format, args); | 32 | vsnprintf(buf, sizeof(buf), format, args); |
32 | va_end(args); | 33 | va_end(args); |
34 | html(buf); | ||
33 | } | 35 | } |
34 | 36 | ||
35 | void html_txt(char *txt) | 37 | void html_txt(char *txt) |
36 | { | 38 | { |
37 | char *t = txt; | 39 | char *t = txt; |
38 | while(*t){ | 40 | while(*t){ |
39 | int c = *t; | 41 | int c = *t; |
40 | if (c=='<' || c=='>' || c=='&') { | 42 | if (c=='<' || c=='>' || c=='&') { |
41 | *t = '\0'; | 43 | *t = '\0'; |
42 | html(txt); | 44 | html(txt); |
43 | *t = c; | 45 | *t = c; |
44 | if (c=='>') | 46 | if (c=='>') |
45 | html(">"); | 47 | html(">"); |
46 | else if (c=='<') | 48 | else if (c=='<') |
47 | html("<"); | 49 | html("<"); |
48 | else if (c=='&') | 50 | else if (c=='&') |
49 | html("&"); | 51 | html("&"); |
50 | txt = t+1; | 52 | txt = t+1; |
51 | } | 53 | } |
52 | t++; | 54 | t++; |
53 | } | 55 | } |
54 | if (t!=txt) | 56 | if (t!=txt) |
55 | html(txt); | 57 | html(txt); |
56 | } | 58 | } |