aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLars Hjemli2006-12-10 22:31:36 +0100
committerLars Hjemli2006-12-10 22:31:36 +0100
commit25105d7ecaba474d4b7c364ebb586aac3dfc5abb (patch)
tree8beb08db1399b8efb8c7fbcd936044ae7fc232e6
parent856c026e221d8ed82c5b75bc8da4bd65e89ea953 (diff)
downloadcgit-25105d7ecaba474d4b7c364ebb586aac3dfc5abb.tar.gz
cgit-25105d7ecaba474d4b7c364ebb586aac3dfc5abb.tar.bz2
cgit-25105d7ecaba474d4b7c364ebb586aac3dfc5abb.zip
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--.gitignore1
-rw-r--r--Makefile6
-rw-r--r--README54
-rw-r--r--cache.c86
-rw-r--r--cgit.c117
-rw-r--r--cgit.h47
-rw-r--r--config.c4
-rw-r--r--git.h60
-rw-r--r--html.c6
9 files changed, 353 insertions, 28 deletions
diff --git a/.gitignore b/.gitignore
index 4eaec97..c4c9ac3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
2cgit 2cgit
3*.o 3*.o
4*~
diff --git a/Makefile b/Makefile
index 4e72b07..243f590 100644
--- a/Makefile
+++ b/Makefile
@@ -3,7 +3,9 @@ INSTALL_BIN = /var/www/htdocs/cgit.cgi
3INSTALL_CSS = /var/www/htdocs/cgit.css 3INSTALL_CSS = /var/www/htdocs/cgit.css
4 4
5EXTLIBS = ../git/libgit.a ../git/xdiff/lib.a -lz -lcrypto 5EXTLIBS = ../git/libgit.a ../git/xdiff/lib.a -lz -lcrypto
6OBJECTS = cgit.o config.o html.o 6OBJECTS = cgit.o config.o html.o cache.o
7
8CFLAGS += -Wall
7 9
8all: cgit 10all: cgit
9 11
@@ -15,6 +17,6 @@ clean:
15 rm -f cgit *.o 17 rm -f cgit *.o
16 18
17cgit: $(OBJECTS) 19cgit: $(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
diff --git a/README b/README
new file mode 100644
index 0000000..5917c37
--- /dev/null
+++ b/README
@@ -0,0 +1,54 @@
1Cache algorithm
2===============
3
4Cgit normally returns cached pages when invoked. If there is no cache file, or
5the cache file has expired, it is regenerated. Finally, the cache file is
6printed on stdout.
7
8When it is decided that a cache file needs to be regenerated, an attempt is
9made to create a corresponding lockfile. If this fails, the process gives up
10and uses the expired cache file instead.
11
12When there is no cache file for a request, an attempt is made to create a
13corresponding lockfile. If this fails, the process calls sched_yield(2) before
14restarting the request handling.
15
16In pseudocode:
17
18 name = generate_cache_name(request);
19top:
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
37The 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
44TTL is specified in minutes, -1 meaning "infinite caching".
45
46
47Naming of cache files
48---------------------
49Repository listing: <cachedir>/index.html
50Repository summary: <cachedir>/<repo>/index.html
51Repository subpage: <cachedir>/<repo>/<page>/<querystring>.html
52
53The corresponding lock files have a ".lock" suffix.
54
diff --git a/cache.c b/cache.c
new file mode 100644
index 0000000..1be1ea4
--- /dev/null
+++ b/cache.c
@@ -0,0 +1,86 @@
1#include "cgit.h"
2
3const int NOLOCK = -1;
4
5int 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
32int 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
52int 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
75int cache_unlock(struct cacheitem *item)
76{
77 close(item->fd);
78 return (rename(fmt("%s.lock", item->name), item->name) == 0);
79}
80
81int 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}
diff --git a/cgit.c b/cgit.c
index 4c14f77..09c857c 100644
--- a/cgit.c
+++ b/cgit.c
@@ -10,29 +10,47 @@ static const char cgit_error[] =
10static const char cgit_lib_error[] = 10static const char cgit_lib_error[] =
11"<div class='error'>%s: %s</div>"; 11"<div class='error'>%s: %s</div>";
12 12
13int htmlfd = 0;
13 14
14char *cgit_root = "/var/git"; 15char *cgit_root = "/usr/src/git";
15char *cgit_root_title = "Git repository browser"; 16char *cgit_root_title = "Git repository browser";
16char *cgit_css = "/cgit.css"; 17char *cgit_css = "/cgit.css";
17char *cgit_logo = "/git-logo.png"; 18char *cgit_logo = "/git-logo.png";
18char *cgit_logo_link = "http://www.kernel.org/pub/software/scm/git/docs/"; 19char *cgit_logo_link = "http://www.kernel.org/pub/software/scm/git/docs/";
19char *cgit_virtual_root = NULL; 20char *cgit_virtual_root = NULL;
20 21
22char *cgit_cache_root = "/var/cache/cgit";
23
24int cgit_cache_root_ttl = 5;
25int cgit_cache_repo_ttl = 5;
26int cgit_cache_dynamic_ttl = 5;
27int cgit_cache_static_ttl = -1;
28int cgit_cache_max_create_time = 5;
29
21char *cgit_repo_name = NULL; 30char *cgit_repo_name = NULL;
22char *cgit_repo_desc = NULL; 31char *cgit_repo_desc = NULL;
23char *cgit_repo_owner = NULL; 32char *cgit_repo_owner = NULL;
24 33
34int cgit_query_has_symref = 0;
35int cgit_query_has_sha1 = 0;
36
37char *cgit_querystring = NULL;
25char *cgit_query_repo = NULL; 38char *cgit_query_repo = NULL;
26char *cgit_query_page = NULL; 39char *cgit_query_page = NULL;
27char *cgit_query_head = NULL; 40char *cgit_query_head = NULL;
41char *cgit_query_sha1 = NULL;
42
43struct cacheitem cacheitem;
28 44
29int cgit_parse_query(char *txt, configfn fn) 45int 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';
@@ -82,8 +100,13 @@ void cgit_querystring_cb(const char *name, const char *value)
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
89char *cgit_repourl(const char *reponame) 112char *cgit_repourl(const char *reponame)
@@ -136,9 +159,32 @@ static int cgit_print_branch_cb(const char *refname, const unsigned char *sha1,
136 return 0; 159 return 0;
137} 160}
138 161
162/* Sun, 06 Nov 1994 08:49:37 GMT */
163static 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
174static int ttl_seconds(int ttl)
175{
176 if (ttl<0)
177 return 60 * 60 * 24 * 365;
178 else
179 return ttl * 60;
180}
181
139static void cgit_print_docstart(char *title) 182static 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");
@@ -175,6 +221,7 @@ static void cgit_print_repolist()
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
@@ -197,7 +244,7 @@ static void cgit_print_repolist()
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
@@ -291,7 +338,7 @@ static void cgit_print_commit_shortlog(struct commit *commit)
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);
@@ -371,8 +418,8 @@ static void cgit_print_object(char *hex)
371 418
372static void cgit_print_repo_page() 419static 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);
@@ -381,7 +428,7 @@ static void cgit_print_repo_page()
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);
@@ -390,21 +437,61 @@ static void cgit_print_repo_page()
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
398int main(int argc, const char **argv) 445static 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
455static 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
474static 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
489int 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}
diff --git a/cgit.h b/cgit.h
index 19f7ba7..1e084d4 100644
--- a/cgit.h
+++ b/cgit.h
@@ -3,6 +3,46 @@
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
9typedef void (*configfn)(const char *name, const char *value);
10
11struct cacheitem {
12 char *name;
13 struct stat st;
14 int ttl;
15 int fd;
16};
17
18extern char *cgit_root;
19extern char *cgit_root_title;
20extern char *cgit_css;
21extern char *cgit_logo;
22extern char *cgit_logo_link;
23extern char *cgit_virtual_root;
24extern char *cgit_cache_root;
25
26extern int cgit_cache_root_ttl;
27extern int cgit_cache_repo_ttl;
28extern int cgit_cache_dynamic_ttl;
29extern int cgit_cache_static_ttl;
30extern int cgit_cache_max_create_time;
31
32extern char *cgit_repo_name;
33extern char *cgit_repo_desc;
34extern char *cgit_repo_owner;
35
36extern int cgit_query_has_symref;
37extern int cgit_query_has_sha1;
38
39extern char *cgit_querystring;
40extern char *cgit_query_repo;
41extern char *cgit_query_page;
42extern char *cgit_query_head;
43extern char *cgit_query_sha1;
44
45extern int htmlfd;
6 46
7extern char *fmt(const char *format,...); 47extern char *fmt(const char *format,...);
8 48
@@ -10,12 +50,15 @@ extern void html(const char *txt);
10extern void htmlf(const char *format,...); 50extern void htmlf(const char *format,...);
11extern void html_txt(char *txt); 51extern void html_txt(char *txt);
12extern void html_attr(char *txt); 52extern void html_attr(char *txt);
13
14extern void html_link_open(char *url, char *title, char *class); 53extern void html_link_open(char *url, char *title, char *class);
15extern void html_link_close(void); 54extern void html_link_close(void);
16 55
17typedef void (*configfn)(const char *name, const char *value);
18 56
19extern int cgit_read_config(const char *filename, configfn fn); 57extern int cgit_read_config(const char *filename, configfn fn);
20 58
59extern int cache_lookup(struct cacheitem *item);
60extern int cache_lock(struct cacheitem *item);
61extern int cache_unlock(struct cacheitem *item);
62extern int cache_expired(struct cacheitem *item);
63
21#endif /* CGIT_H */ 64#endif /* CGIT_H */
diff --git a/config.c b/config.c
index 858ab69..ee49b62 100644
--- a/config.c
+++ b/config.c
@@ -32,7 +32,7 @@ int read_config_line(FILE *f, char *line, const char **value, int bufsize)
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) {
@@ -64,7 +64,7 @@ int cgit_read_config(const char *filename, configfn fn)
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);
diff --git a/git.h b/git.h
index 443f216..dfa3542 100644
--- a/git.h
+++ b/git.h
@@ -33,6 +33,26 @@
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
53extern void die(const char *err, ...) NORETURN __attribute__((format (printf, 1, 2)));
54
55
36static inline char* xstrdup(const char *str) 56static inline char* xstrdup(const char *str)
37{ 57{
38 char *ret = strdup(str); 58 char *ret = strdup(str);
@@ -108,9 +128,13 @@ static inline ssize_t xwrite(int fd, const void *buf, size_t len)
108#define MINIMUM_ABBREV 4 128#define MINIMUM_ABBREV 4
109#define DEFAULT_ABBREV 7 129#define DEFAULT_ABBREV 7
110 130
131extern int sha1_object_info(const unsigned char *, char *, unsigned long *);
111 132
112extern void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size); 133extern void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size);
113 134
135extern int get_sha1(const char *str, unsigned char *sha1);
136extern int get_sha1_hex(const char *hex, unsigned char *sha1);
137extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */
114 138
115 139
116 140
@@ -183,6 +207,21 @@ struct commit {
183}; 207};
184 208
185 209
210struct commit *lookup_commit(const unsigned char *sha1);
211struct commit *lookup_commit_reference(const unsigned char *sha1);
212struct commit *lookup_commit_reference_gently(const unsigned char *sha1,
213 int quiet);
214
215int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size);
216int parse_commit(struct commit *item);
217
218struct commit_list * commit_list_insert(struct commit *item, struct commit_list **list_p);
219struct commit_list * insert_by_date(struct commit *item, struct commit_list **list);
220
221void free_commit_list(struct commit_list *list);
222
223void sort_by_date(struct commit_list **list);
224
186/* Commit formats */ 225/* Commit formats */
187enum cmit_fmt { 226enum cmit_fmt {
188 CMIT_FMT_RAW, 227 CMIT_FMT_RAW,
@@ -197,13 +236,9 @@ enum cmit_fmt {
197 CMIT_FMT_UNSPECIFIED, 236 CMIT_FMT_UNSPECIFIED,
198}; 237};
199 238
239extern 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
202struct commit *lookup_commit(const unsigned char *sha1);
203struct commit *lookup_commit_reference(const unsigned char *sha1);
204struct commit *lookup_commit_reference_gently(const unsigned char *sha1,
205 int quiet);
206
207typedef void (*topo_sort_set_fn_t)(struct commit*, void *data); 242typedef void (*topo_sort_set_fn_t)(struct commit*, void *data);
208typedef void* (*topo_sort_get_fn_t)(struct commit*); 243typedef void* (*topo_sort_get_fn_t)(struct commit*);
209 244
@@ -306,6 +341,16 @@ enum color_diff {
306 341
307 342
308 343
344/*
345 * from git:refs.g
346 */
347
348typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data);
349extern int head_ref(each_ref_fn, void *);
350extern int for_each_ref(each_ref_fn, void *);
351extern int for_each_tag_ref(each_ref_fn, void *);
352extern int for_each_branch_ref(each_ref_fn, void *);
353extern int for_each_remote_ref(each_ref_fn, void *);
309 354
310 355
311 356
@@ -391,6 +436,11 @@ struct rev_info {
391}; 436};
392 437
393 438
439extern void init_revisions(struct rev_info *revs, const char *prefix);
440extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def);
441extern int handle_revision_arg(const char *arg, struct rev_info *revs,int flags,int cant_be_filename);
442
443extern void prepare_revision_walk(struct rev_info *revs);
394extern struct commit *get_revision(struct rev_info *revs); 444extern struct commit *get_revision(struct rev_info *revs);
395 445
396 446
diff --git a/html.c b/html.c
index 5780dc1..bf1490f 100644
--- a/html.c
+++ b/html.c
@@ -20,16 +20,18 @@ char *fmt(const char *format, ...)
20 20
21void html(const char *txt) 21void html(const char *txt)
22{ 22{
23 fputs(txt, stdout); 23 write(htmlfd, txt, strlen(txt));
24} 24}
25 25
26void htmlf(const char *format, ...) 26void 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
35void html_txt(char *txt) 37void html_txt(char *txt)