author | Lars Hjemli <hjemli@gmail.com> | 2008-12-06 16:38:19 (UTC) |
---|---|---|
committer | Lars Hjemli <hjemli@gmail.com> | 2008-12-06 16:38:19 (UTC) |
commit | f86a23ff537258d36bf8f1876fa7a4bede6673d8 (patch) (unidiff) | |
tree | 8328d415416058cdc5b0fd2c6564ddcab5766c7a | |
parent | 140012d7a8e51df5a9f9c556696778b86ade4fc9 (diff) | |
download | cgit-f86a23ff537258d36bf8f1876fa7a4bede6673d8.zip cgit-f86a23ff537258d36bf8f1876fa7a4bede6673d8.tar.gz cgit-f86a23ff537258d36bf8f1876fa7a4bede6673d8.tar.bz2 |
Add a 'stats' page to each repo
This new page, which is disabled by default, can be used to print some
statistics about the number of commits per period in the repository,
where period can be either weeks, months, quarters or years.
The function can be activated globally by setting 'enable-stats=1' in
cgitrc and disabled for individual repos by setting 'repo.enable-stats=0'.
Signed-off-by: Lars Hjemli <hjemli@gmail.com>
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | cgit.c | 6 | ||||
-rw-r--r-- | cgit.css | 77 | ||||
-rw-r--r-- | cgit.h | 3 | ||||
-rw-r--r-- | cgitrc.5.txt | 8 | ||||
-rw-r--r-- | cmd.c | 10 | ||||
-rw-r--r-- | shared.c | 1 | ||||
-rw-r--r-- | ui-shared.c | 3 | ||||
-rw-r--r-- | ui-stats.c | 380 | ||||
-rw-r--r-- | ui-stats.h | 8 |
10 files changed, 497 insertions, 0 deletions
@@ -1,129 +1,130 @@ | |||
1 | CGIT_VERSION = v0.8.1 | 1 | CGIT_VERSION = v0.8.1 |
2 | CGIT_SCRIPT_NAME = cgit.cgi | 2 | CGIT_SCRIPT_NAME = cgit.cgi |
3 | CGIT_SCRIPT_PATH = /var/www/htdocs/cgit | 3 | CGIT_SCRIPT_PATH = /var/www/htdocs/cgit |
4 | CGIT_CONFIG = /etc/cgitrc | 4 | CGIT_CONFIG = /etc/cgitrc |
5 | CACHE_ROOT = /var/cache/cgit | 5 | CACHE_ROOT = /var/cache/cgit |
6 | SHA1_HEADER = <openssl/sha.h> | 6 | SHA1_HEADER = <openssl/sha.h> |
7 | GIT_VER = 1.6.0.2 | 7 | GIT_VER = 1.6.0.2 |
8 | GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2 | 8 | GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2 |
9 | 9 | ||
10 | # | 10 | # |
11 | # Let the user override the above settings. | 11 | # Let the user override the above settings. |
12 | # | 12 | # |
13 | -include cgit.conf | 13 | -include cgit.conf |
14 | 14 | ||
15 | # | 15 | # |
16 | # Define a way to invoke make in subdirs quietly, shamelessly ripped | 16 | # Define a way to invoke make in subdirs quietly, shamelessly ripped |
17 | # from git.git | 17 | # from git.git |
18 | # | 18 | # |
19 | QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir | 19 | QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir |
20 | QUIET_SUBDIR1 = | 20 | QUIET_SUBDIR1 = |
21 | 21 | ||
22 | ifneq ($(findstring $(MAKEFLAGS),w),w) | 22 | ifneq ($(findstring $(MAKEFLAGS),w),w) |
23 | PRINT_DIR = --no-print-directory | 23 | PRINT_DIR = --no-print-directory |
24 | else # "make -w" | 24 | else # "make -w" |
25 | NO_SUBDIR = : | 25 | NO_SUBDIR = : |
26 | endif | 26 | endif |
27 | 27 | ||
28 | ifndef V | 28 | ifndef V |
29 | QUIET_CC = @echo ' ' CC $@; | 29 | QUIET_CC = @echo ' ' CC $@; |
30 | QUIET_MM = @echo ' ' MM $@; | 30 | QUIET_MM = @echo ' ' MM $@; |
31 | QUIET_SUBDIR0 = +@subdir= | 31 | QUIET_SUBDIR0 = +@subdir= |
32 | QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ | 32 | QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ |
33 | $(MAKE) $(PRINT_DIR) -C $$subdir | 33 | $(MAKE) $(PRINT_DIR) -C $$subdir |
34 | endif | 34 | endif |
35 | 35 | ||
36 | # | 36 | # |
37 | # Define a pattern rule for automatic dependency building | 37 | # Define a pattern rule for automatic dependency building |
38 | # | 38 | # |
39 | %.d: %.c | 39 | %.d: %.c |
40 | $(QUIET_MM)$(CC) $(CFLAGS) -MM $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@ | 40 | $(QUIET_MM)$(CC) $(CFLAGS) -MM $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@ |
41 | 41 | ||
42 | # | 42 | # |
43 | # Define a pattern rule for silent object building | 43 | # Define a pattern rule for silent object building |
44 | # | 44 | # |
45 | %.o: %.c | 45 | %.o: %.c |
46 | $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $< | 46 | $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $< |
47 | 47 | ||
48 | 48 | ||
49 | EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lcrypto | 49 | EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lcrypto |
50 | OBJECTS = | 50 | OBJECTS = |
51 | OBJECTS += cache.o | 51 | OBJECTS += cache.o |
52 | OBJECTS += cgit.o | 52 | OBJECTS += cgit.o |
53 | OBJECTS += cmd.o | 53 | OBJECTS += cmd.o |
54 | OBJECTS += configfile.o | 54 | OBJECTS += configfile.o |
55 | OBJECTS += html.o | 55 | OBJECTS += html.o |
56 | OBJECTS += parsing.o | 56 | OBJECTS += parsing.o |
57 | OBJECTS += scan-tree.o | 57 | OBJECTS += scan-tree.o |
58 | OBJECTS += shared.o | 58 | OBJECTS += shared.o |
59 | OBJECTS += ui-atom.o | 59 | OBJECTS += ui-atom.o |
60 | OBJECTS += ui-blob.o | 60 | OBJECTS += ui-blob.o |
61 | OBJECTS += ui-clone.o | 61 | OBJECTS += ui-clone.o |
62 | OBJECTS += ui-commit.o | 62 | OBJECTS += ui-commit.o |
63 | OBJECTS += ui-diff.o | 63 | OBJECTS += ui-diff.o |
64 | OBJECTS += ui-log.o | 64 | OBJECTS += ui-log.o |
65 | OBJECTS += ui-patch.o | 65 | OBJECTS += ui-patch.o |
66 | OBJECTS += ui-plain.o | 66 | OBJECTS += ui-plain.o |
67 | OBJECTS += ui-refs.o | 67 | OBJECTS += ui-refs.o |
68 | OBJECTS += ui-repolist.o | 68 | OBJECTS += ui-repolist.o |
69 | OBJECTS += ui-shared.o | 69 | OBJECTS += ui-shared.o |
70 | OBJECTS += ui-snapshot.o | 70 | OBJECTS += ui-snapshot.o |
71 | OBJECTS += ui-stats.o | ||
71 | OBJECTS += ui-summary.o | 72 | OBJECTS += ui-summary.o |
72 | OBJECTS += ui-tag.o | 73 | OBJECTS += ui-tag.o |
73 | OBJECTS += ui-tree.o | 74 | OBJECTS += ui-tree.o |
74 | 75 | ||
75 | ifdef NEEDS_LIBICONV | 76 | ifdef NEEDS_LIBICONV |
76 | EXTLIBS += -liconv | 77 | EXTLIBS += -liconv |
77 | endif | 78 | endif |
78 | 79 | ||
79 | 80 | ||
80 | .PHONY: all libgit test install uninstall clean force-version get-git | 81 | .PHONY: all libgit test install uninstall clean force-version get-git |
81 | 82 | ||
82 | all: cgit | 83 | all: cgit |
83 | 84 | ||
84 | VERSION: force-version | 85 | VERSION: force-version |
85 | @./gen-version.sh "$(CGIT_VERSION)" | 86 | @./gen-version.sh "$(CGIT_VERSION)" |
86 | -include VERSION | 87 | -include VERSION |
87 | 88 | ||
88 | 89 | ||
89 | CFLAGS += -g -Wall -Igit | 90 | CFLAGS += -g -Wall -Igit |
90 | CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)' | 91 | CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)' |
91 | CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"' | 92 | CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"' |
92 | CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"' | 93 | CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"' |
93 | CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"' | 94 | CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"' |
94 | CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"' | 95 | CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"' |
95 | 96 | ||
96 | ifdef NO_ICONV | 97 | ifdef NO_ICONV |
97 | CFLAGS += -DNO_ICONV | 98 | CFLAGS += -DNO_ICONV |
98 | endif | 99 | endif |
99 | 100 | ||
100 | cgit: $(OBJECTS) libgit | 101 | cgit: $(OBJECTS) libgit |
101 | $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS) | 102 | $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS) |
102 | 103 | ||
103 | cgit.o: VERSION | 104 | cgit.o: VERSION |
104 | 105 | ||
105 | -include $(OBJECTS:.o=.d) | 106 | -include $(OBJECTS:.o=.d) |
106 | 107 | ||
107 | libgit: | 108 | libgit: |
108 | $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) libgit.a | 109 | $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) libgit.a |
109 | $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) xdiff/lib.a | 110 | $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) xdiff/lib.a |
110 | 111 | ||
111 | test: all | 112 | test: all |
112 | $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all | 113 | $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all |
113 | 114 | ||
114 | install: all | 115 | install: all |
115 | mkdir -p $(DESTDIR)$(CGIT_SCRIPT_PATH) | 116 | mkdir -p $(DESTDIR)$(CGIT_SCRIPT_PATH) |
116 | install cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) | 117 | install cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) |
117 | install cgit.css $(DESTDIR)$(CGIT_SCRIPT_PATH)/cgit.css | 118 | install cgit.css $(DESTDIR)$(CGIT_SCRIPT_PATH)/cgit.css |
118 | install cgit.png $(DESTDIR)$(CGIT_SCRIPT_PATH)/cgit.png | 119 | install cgit.png $(DESTDIR)$(CGIT_SCRIPT_PATH)/cgit.png |
119 | 120 | ||
120 | uninstall: | 121 | uninstall: |
121 | rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) | 122 | rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) |
122 | rm -f $(CGIT_SCRIPT_PATH)/cgit.css | 123 | rm -f $(CGIT_SCRIPT_PATH)/cgit.css |
123 | rm -f $(CGIT_SCRIPT_PATH)/cgit.png | 124 | rm -f $(CGIT_SCRIPT_PATH)/cgit.png |
124 | 125 | ||
125 | clean: | 126 | clean: |
126 | rm -f cgit VERSION *.o *.d | 127 | rm -f cgit VERSION *.o *.d |
127 | 128 | ||
128 | get-git: | 129 | get-git: |
129 | curl $(GIT_URL) | tar -xj && rm -rf git && mv git-$(GIT_VER) git | 130 | curl $(GIT_URL) | tar -xj && rm -rf git && mv git-$(GIT_VER) git |
@@ -1,472 +1,478 @@ | |||
1 | /* cgit.c: cgi for the git scm | 1 | /* cgit.c: cgi for the git scm |
2 | * | 2 | * |
3 | * Copyright (C) 2006 Lars Hjemli | 3 | * Copyright (C) 2006 Lars Hjemli |
4 | * | 4 | * |
5 | * Licensed under GNU General Public License v2 | 5 | * Licensed under GNU General Public License v2 |
6 | * (see COPYING for full license text) | 6 | * (see COPYING for full license text) |
7 | */ | 7 | */ |
8 | 8 | ||
9 | #include "cgit.h" | 9 | #include "cgit.h" |
10 | #include "cache.h" | 10 | #include "cache.h" |
11 | #include "cmd.h" | 11 | #include "cmd.h" |
12 | #include "configfile.h" | 12 | #include "configfile.h" |
13 | #include "html.h" | 13 | #include "html.h" |
14 | #include "ui-shared.h" | 14 | #include "ui-shared.h" |
15 | #include "scan-tree.h" | 15 | #include "scan-tree.h" |
16 | 16 | ||
17 | const char *cgit_version = CGIT_VERSION; | 17 | const char *cgit_version = CGIT_VERSION; |
18 | 18 | ||
19 | void config_cb(const char *name, const char *value) | 19 | void config_cb(const char *name, const char *value) |
20 | { | 20 | { |
21 | if (!strcmp(name, "root-title")) | 21 | if (!strcmp(name, "root-title")) |
22 | ctx.cfg.root_title = xstrdup(value); | 22 | ctx.cfg.root_title = xstrdup(value); |
23 | else if (!strcmp(name, "root-desc")) | 23 | else if (!strcmp(name, "root-desc")) |
24 | ctx.cfg.root_desc = xstrdup(value); | 24 | ctx.cfg.root_desc = xstrdup(value); |
25 | else if (!strcmp(name, "root-readme")) | 25 | else if (!strcmp(name, "root-readme")) |
26 | ctx.cfg.root_readme = xstrdup(value); | 26 | ctx.cfg.root_readme = xstrdup(value); |
27 | else if (!strcmp(name, "css")) | 27 | else if (!strcmp(name, "css")) |
28 | ctx.cfg.css = xstrdup(value); | 28 | ctx.cfg.css = xstrdup(value); |
29 | else if (!strcmp(name, "favicon")) | 29 | else if (!strcmp(name, "favicon")) |
30 | ctx.cfg.favicon = xstrdup(value); | 30 | ctx.cfg.favicon = xstrdup(value); |
31 | else if (!strcmp(name, "footer")) | 31 | else if (!strcmp(name, "footer")) |
32 | ctx.cfg.footer = xstrdup(value); | 32 | ctx.cfg.footer = xstrdup(value); |
33 | else if (!strcmp(name, "logo")) | 33 | else if (!strcmp(name, "logo")) |
34 | ctx.cfg.logo = xstrdup(value); | 34 | ctx.cfg.logo = xstrdup(value); |
35 | else if (!strcmp(name, "index-header")) | 35 | else if (!strcmp(name, "index-header")) |
36 | ctx.cfg.index_header = xstrdup(value); | 36 | ctx.cfg.index_header = xstrdup(value); |
37 | else if (!strcmp(name, "index-info")) | 37 | else if (!strcmp(name, "index-info")) |
38 | ctx.cfg.index_info = xstrdup(value); | 38 | ctx.cfg.index_info = xstrdup(value); |
39 | else if (!strcmp(name, "logo-link")) | 39 | else if (!strcmp(name, "logo-link")) |
40 | ctx.cfg.logo_link = xstrdup(value); | 40 | ctx.cfg.logo_link = xstrdup(value); |
41 | else if (!strcmp(name, "module-link")) | 41 | else if (!strcmp(name, "module-link")) |
42 | ctx.cfg.module_link = xstrdup(value); | 42 | ctx.cfg.module_link = xstrdup(value); |
43 | else if (!strcmp(name, "virtual-root")) { | 43 | else if (!strcmp(name, "virtual-root")) { |
44 | ctx.cfg.virtual_root = trim_end(value, '/'); | 44 | ctx.cfg.virtual_root = trim_end(value, '/'); |
45 | if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) | 45 | if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) |
46 | ctx.cfg.virtual_root = ""; | 46 | ctx.cfg.virtual_root = ""; |
47 | } else if (!strcmp(name, "nocache")) | 47 | } else if (!strcmp(name, "nocache")) |
48 | ctx.cfg.nocache = atoi(value); | 48 | ctx.cfg.nocache = atoi(value); |
49 | else if (!strcmp(name, "snapshots")) | 49 | else if (!strcmp(name, "snapshots")) |
50 | ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); | 50 | ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); |
51 | else if (!strcmp(name, "enable-index-links")) | 51 | else if (!strcmp(name, "enable-index-links")) |
52 | ctx.cfg.enable_index_links = atoi(value); | 52 | ctx.cfg.enable_index_links = atoi(value); |
53 | else if (!strcmp(name, "enable-log-filecount")) | 53 | else if (!strcmp(name, "enable-log-filecount")) |
54 | ctx.cfg.enable_log_filecount = atoi(value); | 54 | ctx.cfg.enable_log_filecount = atoi(value); |
55 | else if (!strcmp(name, "enable-log-linecount")) | 55 | else if (!strcmp(name, "enable-log-linecount")) |
56 | ctx.cfg.enable_log_linecount = atoi(value); | 56 | ctx.cfg.enable_log_linecount = atoi(value); |
57 | else if (!strcmp(name, "enable-stats")) | ||
58 | ctx.cfg.enable_stats = atoi(value); | ||
57 | else if (!strcmp(name, "cache-size")) | 59 | else if (!strcmp(name, "cache-size")) |
58 | ctx.cfg.cache_size = atoi(value); | 60 | ctx.cfg.cache_size = atoi(value); |
59 | else if (!strcmp(name, "cache-root")) | 61 | else if (!strcmp(name, "cache-root")) |
60 | ctx.cfg.cache_root = xstrdup(value); | 62 | ctx.cfg.cache_root = xstrdup(value); |
61 | else if (!strcmp(name, "cache-root-ttl")) | 63 | else if (!strcmp(name, "cache-root-ttl")) |
62 | ctx.cfg.cache_root_ttl = atoi(value); | 64 | ctx.cfg.cache_root_ttl = atoi(value); |
63 | else if (!strcmp(name, "cache-repo-ttl")) | 65 | else if (!strcmp(name, "cache-repo-ttl")) |
64 | ctx.cfg.cache_repo_ttl = atoi(value); | 66 | ctx.cfg.cache_repo_ttl = atoi(value); |
65 | else if (!strcmp(name, "cache-static-ttl")) | 67 | else if (!strcmp(name, "cache-static-ttl")) |
66 | ctx.cfg.cache_static_ttl = atoi(value); | 68 | ctx.cfg.cache_static_ttl = atoi(value); |
67 | else if (!strcmp(name, "cache-dynamic-ttl")) | 69 | else if (!strcmp(name, "cache-dynamic-ttl")) |
68 | ctx.cfg.cache_dynamic_ttl = atoi(value); | 70 | ctx.cfg.cache_dynamic_ttl = atoi(value); |
69 | else if (!strcmp(name, "max-message-length")) | 71 | else if (!strcmp(name, "max-message-length")) |
70 | ctx.cfg.max_msg_len = atoi(value); | 72 | ctx.cfg.max_msg_len = atoi(value); |
71 | else if (!strcmp(name, "max-repodesc-length")) | 73 | else if (!strcmp(name, "max-repodesc-length")) |
72 | ctx.cfg.max_repodesc_len = atoi(value); | 74 | ctx.cfg.max_repodesc_len = atoi(value); |
73 | else if (!strcmp(name, "max-repo-count")) | 75 | else if (!strcmp(name, "max-repo-count")) |
74 | ctx.cfg.max_repo_count = atoi(value); | 76 | ctx.cfg.max_repo_count = atoi(value); |
75 | else if (!strcmp(name, "max-commit-count")) | 77 | else if (!strcmp(name, "max-commit-count")) |
76 | ctx.cfg.max_commit_count = atoi(value); | 78 | ctx.cfg.max_commit_count = atoi(value); |
77 | else if (!strcmp(name, "summary-log")) | 79 | else if (!strcmp(name, "summary-log")) |
78 | ctx.cfg.summary_log = atoi(value); | 80 | ctx.cfg.summary_log = atoi(value); |
79 | else if (!strcmp(name, "summary-branches")) | 81 | else if (!strcmp(name, "summary-branches")) |
80 | ctx.cfg.summary_branches = atoi(value); | 82 | ctx.cfg.summary_branches = atoi(value); |
81 | else if (!strcmp(name, "summary-tags")) | 83 | else if (!strcmp(name, "summary-tags")) |
82 | ctx.cfg.summary_tags = atoi(value); | 84 | ctx.cfg.summary_tags = atoi(value); |
83 | else if (!strcmp(name, "agefile")) | 85 | else if (!strcmp(name, "agefile")) |
84 | ctx.cfg.agefile = xstrdup(value); | 86 | ctx.cfg.agefile = xstrdup(value); |
85 | else if (!strcmp(name, "renamelimit")) | 87 | else if (!strcmp(name, "renamelimit")) |
86 | ctx.cfg.renamelimit = atoi(value); | 88 | ctx.cfg.renamelimit = atoi(value); |
87 | else if (!strcmp(name, "robots")) | 89 | else if (!strcmp(name, "robots")) |
88 | ctx.cfg.robots = xstrdup(value); | 90 | ctx.cfg.robots = xstrdup(value); |
89 | else if (!strcmp(name, "clone-prefix")) | 91 | else if (!strcmp(name, "clone-prefix")) |
90 | ctx.cfg.clone_prefix = xstrdup(value); | 92 | ctx.cfg.clone_prefix = xstrdup(value); |
91 | else if (!strcmp(name, "local-time")) | 93 | else if (!strcmp(name, "local-time")) |
92 | ctx.cfg.local_time = atoi(value); | 94 | ctx.cfg.local_time = atoi(value); |
93 | else if (!strcmp(name, "repo.group")) | 95 | else if (!strcmp(name, "repo.group")) |
94 | ctx.cfg.repo_group = xstrdup(value); | 96 | ctx.cfg.repo_group = xstrdup(value); |
95 | else if (!strcmp(name, "repo.url")) | 97 | else if (!strcmp(name, "repo.url")) |
96 | ctx.repo = cgit_add_repo(value); | 98 | ctx.repo = cgit_add_repo(value); |
97 | else if (!strcmp(name, "repo.name")) | 99 | else if (!strcmp(name, "repo.name")) |
98 | ctx.repo->name = xstrdup(value); | 100 | ctx.repo->name = xstrdup(value); |
99 | else if (ctx.repo && !strcmp(name, "repo.path")) | 101 | else if (ctx.repo && !strcmp(name, "repo.path")) |
100 | ctx.repo->path = trim_end(value, '/'); | 102 | ctx.repo->path = trim_end(value, '/'); |
101 | else if (ctx.repo && !strcmp(name, "repo.clone-url")) | 103 | else if (ctx.repo && !strcmp(name, "repo.clone-url")) |
102 | ctx.repo->clone_url = xstrdup(value); | 104 | ctx.repo->clone_url = xstrdup(value); |
103 | else if (ctx.repo && !strcmp(name, "repo.desc")) | 105 | else if (ctx.repo && !strcmp(name, "repo.desc")) |
104 | ctx.repo->desc = xstrdup(value); | 106 | ctx.repo->desc = xstrdup(value); |
105 | else if (ctx.repo && !strcmp(name, "repo.owner")) | 107 | else if (ctx.repo && !strcmp(name, "repo.owner")) |
106 | ctx.repo->owner = xstrdup(value); | 108 | ctx.repo->owner = xstrdup(value); |
107 | else if (ctx.repo && !strcmp(name, "repo.defbranch")) | 109 | else if (ctx.repo && !strcmp(name, "repo.defbranch")) |
108 | ctx.repo->defbranch = xstrdup(value); | 110 | ctx.repo->defbranch = xstrdup(value); |
109 | else if (ctx.repo && !strcmp(name, "repo.snapshots")) | 111 | else if (ctx.repo && !strcmp(name, "repo.snapshots")) |
110 | ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */ | 112 | ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */ |
111 | else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount")) | 113 | else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount")) |
112 | ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); | 114 | ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); |
113 | else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount")) | 115 | else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount")) |
114 | ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); | 116 | ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); |
117 | else if (ctx.repo && !strcmp(name, "repo.enable-stats")) | ||
118 | ctx.repo->enable_stats = ctx.cfg.enable_stats && atoi(value); | ||
115 | else if (ctx.repo && !strcmp(name, "repo.module-link")) | 119 | else if (ctx.repo && !strcmp(name, "repo.module-link")) |
116 | ctx.repo->module_link= xstrdup(value); | 120 | ctx.repo->module_link= xstrdup(value); |
117 | else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) { | 121 | else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) { |
118 | if (*value == '/') | 122 | if (*value == '/') |
119 | ctx.repo->readme = xstrdup(value); | 123 | ctx.repo->readme = xstrdup(value); |
120 | else | 124 | else |
121 | ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value)); | 125 | ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value)); |
122 | } else if (!strcmp(name, "include")) | 126 | } else if (!strcmp(name, "include")) |
123 | parse_configfile(value, config_cb); | 127 | parse_configfile(value, config_cb); |
124 | } | 128 | } |
125 | 129 | ||
126 | static void querystring_cb(const char *name, const char *value) | 130 | static void querystring_cb(const char *name, const char *value) |
127 | { | 131 | { |
128 | if (!strcmp(name,"r")) { | 132 | if (!strcmp(name,"r")) { |
129 | ctx.qry.repo = xstrdup(value); | 133 | ctx.qry.repo = xstrdup(value); |
130 | ctx.repo = cgit_get_repoinfo(value); | 134 | ctx.repo = cgit_get_repoinfo(value); |
131 | } else if (!strcmp(name, "p")) { | 135 | } else if (!strcmp(name, "p")) { |
132 | ctx.qry.page = xstrdup(value); | 136 | ctx.qry.page = xstrdup(value); |
133 | } else if (!strcmp(name, "url")) { | 137 | } else if (!strcmp(name, "url")) { |
134 | ctx.qry.url = xstrdup(value); | 138 | ctx.qry.url = xstrdup(value); |
135 | cgit_parse_url(value); | 139 | cgit_parse_url(value); |
136 | } else if (!strcmp(name, "qt")) { | 140 | } else if (!strcmp(name, "qt")) { |
137 | ctx.qry.grep = xstrdup(value); | 141 | ctx.qry.grep = xstrdup(value); |
138 | } else if (!strcmp(name, "q")) { | 142 | } else if (!strcmp(name, "q")) { |
139 | ctx.qry.search = xstrdup(value); | 143 | ctx.qry.search = xstrdup(value); |
140 | } else if (!strcmp(name, "h")) { | 144 | } else if (!strcmp(name, "h")) { |
141 | ctx.qry.head = xstrdup(value); | 145 | ctx.qry.head = xstrdup(value); |
142 | ctx.qry.has_symref = 1; | 146 | ctx.qry.has_symref = 1; |
143 | } else if (!strcmp(name, "id")) { | 147 | } else if (!strcmp(name, "id")) { |
144 | ctx.qry.sha1 = xstrdup(value); | 148 | ctx.qry.sha1 = xstrdup(value); |
145 | ctx.qry.has_sha1 = 1; | 149 | ctx.qry.has_sha1 = 1; |
146 | } else if (!strcmp(name, "id2")) { | 150 | } else if (!strcmp(name, "id2")) { |
147 | ctx.qry.sha2 = xstrdup(value); | 151 | ctx.qry.sha2 = xstrdup(value); |
148 | ctx.qry.has_sha1 = 1; | 152 | ctx.qry.has_sha1 = 1; |
149 | } else if (!strcmp(name, "ofs")) { | 153 | } else if (!strcmp(name, "ofs")) { |
150 | ctx.qry.ofs = atoi(value); | 154 | ctx.qry.ofs = atoi(value); |
151 | } else if (!strcmp(name, "path")) { | 155 | } else if (!strcmp(name, "path")) { |
152 | ctx.qry.path = trim_end(value, '/'); | 156 | ctx.qry.path = trim_end(value, '/'); |
153 | } else if (!strcmp(name, "name")) { | 157 | } else if (!strcmp(name, "name")) { |
154 | ctx.qry.name = xstrdup(value); | 158 | ctx.qry.name = xstrdup(value); |
155 | } else if (!strcmp(name, "mimetype")) { | 159 | } else if (!strcmp(name, "mimetype")) { |
156 | ctx.qry.mimetype = xstrdup(value); | 160 | ctx.qry.mimetype = xstrdup(value); |
161 | } else if (!strcmp(name, "period")) { | ||
162 | ctx.qry.period = xstrdup(value); | ||
157 | } | 163 | } |
158 | } | 164 | } |
159 | 165 | ||
160 | static void prepare_context(struct cgit_context *ctx) | 166 | static void prepare_context(struct cgit_context *ctx) |
161 | { | 167 | { |
162 | memset(ctx, 0, sizeof(ctx)); | 168 | memset(ctx, 0, sizeof(ctx)); |
163 | ctx->cfg.agefile = "info/web/last-modified"; | 169 | ctx->cfg.agefile = "info/web/last-modified"; |
164 | ctx->cfg.nocache = 0; | 170 | ctx->cfg.nocache = 0; |
165 | ctx->cfg.cache_size = 0; | 171 | ctx->cfg.cache_size = 0; |
166 | ctx->cfg.cache_dynamic_ttl = 5; | 172 | ctx->cfg.cache_dynamic_ttl = 5; |
167 | ctx->cfg.cache_max_create_time = 5; | 173 | ctx->cfg.cache_max_create_time = 5; |
168 | ctx->cfg.cache_repo_ttl = 5; | 174 | ctx->cfg.cache_repo_ttl = 5; |
169 | ctx->cfg.cache_root = CGIT_CACHE_ROOT; | 175 | ctx->cfg.cache_root = CGIT_CACHE_ROOT; |
170 | ctx->cfg.cache_root_ttl = 5; | 176 | ctx->cfg.cache_root_ttl = 5; |
171 | ctx->cfg.cache_static_ttl = -1; | 177 | ctx->cfg.cache_static_ttl = -1; |
172 | ctx->cfg.css = "/cgit.css"; | 178 | ctx->cfg.css = "/cgit.css"; |
173 | ctx->cfg.logo = "/git-logo.png"; | 179 | ctx->cfg.logo = "/git-logo.png"; |
174 | ctx->cfg.local_time = 0; | 180 | ctx->cfg.local_time = 0; |
175 | ctx->cfg.max_repo_count = 50; | 181 | ctx->cfg.max_repo_count = 50; |
176 | ctx->cfg.max_commit_count = 50; | 182 | ctx->cfg.max_commit_count = 50; |
177 | ctx->cfg.max_lock_attempts = 5; | 183 | ctx->cfg.max_lock_attempts = 5; |
178 | ctx->cfg.max_msg_len = 80; | 184 | ctx->cfg.max_msg_len = 80; |
179 | ctx->cfg.max_repodesc_len = 80; | 185 | ctx->cfg.max_repodesc_len = 80; |
180 | ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; | 186 | ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; |
181 | ctx->cfg.renamelimit = -1; | 187 | ctx->cfg.renamelimit = -1; |
182 | ctx->cfg.robots = "index, nofollow"; | 188 | ctx->cfg.robots = "index, nofollow"; |
183 | ctx->cfg.root_title = "Git repository browser"; | 189 | ctx->cfg.root_title = "Git repository browser"; |
184 | ctx->cfg.root_desc = "a fast webinterface for the git dscm"; | 190 | ctx->cfg.root_desc = "a fast webinterface for the git dscm"; |
185 | ctx->cfg.script_name = CGIT_SCRIPT_NAME; | 191 | ctx->cfg.script_name = CGIT_SCRIPT_NAME; |
186 | ctx->cfg.summary_branches = 10; | 192 | ctx->cfg.summary_branches = 10; |
187 | ctx->cfg.summary_log = 10; | 193 | ctx->cfg.summary_log = 10; |
188 | ctx->cfg.summary_tags = 10; | 194 | ctx->cfg.summary_tags = 10; |
189 | ctx->page.mimetype = "text/html"; | 195 | ctx->page.mimetype = "text/html"; |
190 | ctx->page.charset = PAGE_ENCODING; | 196 | ctx->page.charset = PAGE_ENCODING; |
191 | ctx->page.filename = NULL; | 197 | ctx->page.filename = NULL; |
192 | ctx->page.size = 0; | 198 | ctx->page.size = 0; |
193 | ctx->page.modified = time(NULL); | 199 | ctx->page.modified = time(NULL); |
194 | ctx->page.expires = ctx->page.modified; | 200 | ctx->page.expires = ctx->page.modified; |
195 | } | 201 | } |
196 | 202 | ||
197 | struct refmatch { | 203 | struct refmatch { |
198 | char *req_ref; | 204 | char *req_ref; |
199 | char *first_ref; | 205 | char *first_ref; |
200 | int match; | 206 | int match; |
201 | }; | 207 | }; |
202 | 208 | ||
203 | int find_current_ref(const char *refname, const unsigned char *sha1, | 209 | int find_current_ref(const char *refname, const unsigned char *sha1, |
204 | int flags, void *cb_data) | 210 | int flags, void *cb_data) |
205 | { | 211 | { |
206 | struct refmatch *info; | 212 | struct refmatch *info; |
207 | 213 | ||
208 | info = (struct refmatch *)cb_data; | 214 | info = (struct refmatch *)cb_data; |
209 | if (!strcmp(refname, info->req_ref)) | 215 | if (!strcmp(refname, info->req_ref)) |
210 | info->match = 1; | 216 | info->match = 1; |
211 | if (!info->first_ref) | 217 | if (!info->first_ref) |
212 | info->first_ref = xstrdup(refname); | 218 | info->first_ref = xstrdup(refname); |
213 | return info->match; | 219 | return info->match; |
214 | } | 220 | } |
215 | 221 | ||
216 | char *find_default_branch(struct cgit_repo *repo) | 222 | char *find_default_branch(struct cgit_repo *repo) |
217 | { | 223 | { |
218 | struct refmatch info; | 224 | struct refmatch info; |
219 | char *ref; | 225 | char *ref; |
220 | 226 | ||
221 | info.req_ref = repo->defbranch; | 227 | info.req_ref = repo->defbranch; |
222 | info.first_ref = NULL; | 228 | info.first_ref = NULL; |
223 | info.match = 0; | 229 | info.match = 0; |
224 | for_each_branch_ref(find_current_ref, &info); | 230 | for_each_branch_ref(find_current_ref, &info); |
225 | if (info.match) | 231 | if (info.match) |
226 | ref = info.req_ref; | 232 | ref = info.req_ref; |
227 | else | 233 | else |
228 | ref = info.first_ref; | 234 | ref = info.first_ref; |
229 | if (ref) | 235 | if (ref) |
230 | ref = xstrdup(ref); | 236 | ref = xstrdup(ref); |
231 | return ref; | 237 | return ref; |
232 | } | 238 | } |
233 | 239 | ||
234 | static int prepare_repo_cmd(struct cgit_context *ctx) | 240 | static int prepare_repo_cmd(struct cgit_context *ctx) |
235 | { | 241 | { |
236 | char *tmp; | 242 | char *tmp; |
237 | unsigned char sha1[20]; | 243 | unsigned char sha1[20]; |
238 | int nongit = 0; | 244 | int nongit = 0; |
239 | 245 | ||
240 | setenv("GIT_DIR", ctx->repo->path, 1); | 246 | setenv("GIT_DIR", ctx->repo->path, 1); |
241 | setup_git_directory_gently(&nongit); | 247 | setup_git_directory_gently(&nongit); |
242 | if (nongit) { | 248 | if (nongit) { |
243 | ctx->page.title = fmt("%s - %s", ctx->cfg.root_title, | 249 | ctx->page.title = fmt("%s - %s", ctx->cfg.root_title, |
244 | "config error"); | 250 | "config error"); |
245 | tmp = fmt("Not a git repository: '%s'", ctx->repo->path); | 251 | tmp = fmt("Not a git repository: '%s'", ctx->repo->path); |
246 | ctx->repo = NULL; | 252 | ctx->repo = NULL; |
247 | cgit_print_http_headers(ctx); | 253 | cgit_print_http_headers(ctx); |
248 | cgit_print_docstart(ctx); | 254 | cgit_print_docstart(ctx); |
249 | cgit_print_pageheader(ctx); | 255 | cgit_print_pageheader(ctx); |
250 | cgit_print_error(tmp); | 256 | cgit_print_error(tmp); |
251 | cgit_print_docend(); | 257 | cgit_print_docend(); |
252 | return 1; | 258 | return 1; |
253 | } | 259 | } |
254 | ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); | 260 | ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); |
255 | 261 | ||
256 | if (!ctx->qry.head) { | 262 | if (!ctx->qry.head) { |
257 | ctx->qry.nohead = 1; | 263 | ctx->qry.nohead = 1; |
258 | ctx->qry.head = find_default_branch(ctx->repo); | 264 | ctx->qry.head = find_default_branch(ctx->repo); |
259 | ctx->repo->defbranch = ctx->qry.head; | 265 | ctx->repo->defbranch = ctx->qry.head; |
260 | } | 266 | } |
261 | 267 | ||
262 | if (!ctx->qry.head) { | 268 | if (!ctx->qry.head) { |
263 | cgit_print_http_headers(ctx); | 269 | cgit_print_http_headers(ctx); |
264 | cgit_print_docstart(ctx); | 270 | cgit_print_docstart(ctx); |
265 | cgit_print_pageheader(ctx); | 271 | cgit_print_pageheader(ctx); |
266 | cgit_print_error("Repository seems to be empty"); | 272 | cgit_print_error("Repository seems to be empty"); |
267 | cgit_print_docend(); | 273 | cgit_print_docend(); |
268 | return 1; | 274 | return 1; |
269 | } | 275 | } |
270 | 276 | ||
271 | if (get_sha1(ctx->qry.head, sha1)) { | 277 | if (get_sha1(ctx->qry.head, sha1)) { |
272 | tmp = xstrdup(ctx->qry.head); | 278 | tmp = xstrdup(ctx->qry.head); |
273 | ctx->qry.head = ctx->repo->defbranch; | 279 | ctx->qry.head = ctx->repo->defbranch; |
274 | cgit_print_http_headers(ctx); | 280 | cgit_print_http_headers(ctx); |
275 | cgit_print_docstart(ctx); | 281 | cgit_print_docstart(ctx); |
276 | cgit_print_pageheader(ctx); | 282 | cgit_print_pageheader(ctx); |
277 | cgit_print_error(fmt("Invalid branch: %s", tmp)); | 283 | cgit_print_error(fmt("Invalid branch: %s", tmp)); |
278 | cgit_print_docend(); | 284 | cgit_print_docend(); |
279 | return 1; | 285 | return 1; |
280 | } | 286 | } |
281 | return 0; | 287 | return 0; |
282 | } | 288 | } |
283 | 289 | ||
284 | static void process_request(void *cbdata) | 290 | static void process_request(void *cbdata) |
285 | { | 291 | { |
286 | struct cgit_context *ctx = cbdata; | 292 | struct cgit_context *ctx = cbdata; |
287 | struct cgit_cmd *cmd; | 293 | struct cgit_cmd *cmd; |
288 | 294 | ||
289 | cmd = cgit_get_cmd(ctx); | 295 | cmd = cgit_get_cmd(ctx); |
290 | if (!cmd) { | 296 | if (!cmd) { |
291 | ctx->page.title = "cgit error"; | 297 | ctx->page.title = "cgit error"; |
292 | ctx->repo = NULL; | 298 | ctx->repo = NULL; |
293 | cgit_print_http_headers(ctx); | 299 | cgit_print_http_headers(ctx); |
294 | cgit_print_docstart(ctx); | 300 | cgit_print_docstart(ctx); |
295 | cgit_print_pageheader(ctx); | 301 | cgit_print_pageheader(ctx); |
296 | cgit_print_error("Invalid request"); | 302 | cgit_print_error("Invalid request"); |
297 | cgit_print_docend(); | 303 | cgit_print_docend(); |
298 | return; | 304 | return; |
299 | } | 305 | } |
300 | 306 | ||
301 | if (cmd->want_repo && !ctx->repo) { | 307 | if (cmd->want_repo && !ctx->repo) { |
302 | cgit_print_http_headers(ctx); | 308 | cgit_print_http_headers(ctx); |
303 | cgit_print_docstart(ctx); | 309 | cgit_print_docstart(ctx); |
304 | cgit_print_pageheader(ctx); | 310 | cgit_print_pageheader(ctx); |
305 | cgit_print_error(fmt("No repository selected")); | 311 | cgit_print_error(fmt("No repository selected")); |
306 | cgit_print_docend(); | 312 | cgit_print_docend(); |
307 | return; | 313 | return; |
308 | } | 314 | } |
309 | 315 | ||
310 | if (ctx->repo && prepare_repo_cmd(ctx)) | 316 | if (ctx->repo && prepare_repo_cmd(ctx)) |
311 | return; | 317 | return; |
312 | 318 | ||
313 | if (cmd->want_layout) { | 319 | if (cmd->want_layout) { |
314 | cgit_print_http_headers(ctx); | 320 | cgit_print_http_headers(ctx); |
315 | cgit_print_docstart(ctx); | 321 | cgit_print_docstart(ctx); |
316 | cgit_print_pageheader(ctx); | 322 | cgit_print_pageheader(ctx); |
317 | } | 323 | } |
318 | 324 | ||
319 | cmd->fn(ctx); | 325 | cmd->fn(ctx); |
320 | 326 | ||
321 | if (cmd->want_layout) | 327 | if (cmd->want_layout) |
322 | cgit_print_docend(); | 328 | cgit_print_docend(); |
323 | } | 329 | } |
324 | 330 | ||
325 | int cmp_repos(const void *a, const void *b) | 331 | int cmp_repos(const void *a, const void *b) |
326 | { | 332 | { |
327 | const struct cgit_repo *ra = a, *rb = b; | 333 | const struct cgit_repo *ra = a, *rb = b; |
328 | return strcmp(ra->url, rb->url); | 334 | return strcmp(ra->url, rb->url); |
329 | } | 335 | } |
330 | 336 | ||
331 | void print_repo(struct cgit_repo *repo) | 337 | void print_repo(struct cgit_repo *repo) |
332 | { | 338 | { |
333 | printf("repo.url=%s\n", repo->url); | 339 | printf("repo.url=%s\n", repo->url); |
334 | printf("repo.name=%s\n", repo->name); | 340 | printf("repo.name=%s\n", repo->name); |
335 | printf("repo.path=%s\n", repo->path); | 341 | printf("repo.path=%s\n", repo->path); |
336 | if (repo->owner) | 342 | if (repo->owner) |
337 | printf("repo.owner=%s\n", repo->owner); | 343 | printf("repo.owner=%s\n", repo->owner); |
338 | if (repo->desc) | 344 | if (repo->desc) |
339 | printf("repo.desc=%s\n", repo->desc); | 345 | printf("repo.desc=%s\n", repo->desc); |
340 | if (repo->readme) | 346 | if (repo->readme) |
341 | printf("repo.readme=%s\n", repo->readme); | 347 | printf("repo.readme=%s\n", repo->readme); |
342 | printf("\n"); | 348 | printf("\n"); |
343 | } | 349 | } |
344 | 350 | ||
345 | void print_repolist(struct cgit_repolist *list) | 351 | void print_repolist(struct cgit_repolist *list) |
346 | { | 352 | { |
347 | int i; | 353 | int i; |
348 | 354 | ||
349 | for(i = 0; i < list->count; i++) | 355 | for(i = 0; i < list->count; i++) |
350 | print_repo(&list->repos[i]); | 356 | print_repo(&list->repos[i]); |
351 | } | 357 | } |
352 | 358 | ||
353 | 359 | ||
354 | static void cgit_parse_args(int argc, const char **argv) | 360 | static void cgit_parse_args(int argc, const char **argv) |
355 | { | 361 | { |
356 | int i; | 362 | int i; |
357 | int scan = 0; | 363 | int scan = 0; |
358 | 364 | ||
359 | for (i = 1; i < argc; i++) { | 365 | for (i = 1; i < argc; i++) { |
360 | if (!strncmp(argv[i], "--cache=", 8)) { | 366 | if (!strncmp(argv[i], "--cache=", 8)) { |
361 | ctx.cfg.cache_root = xstrdup(argv[i]+8); | 367 | ctx.cfg.cache_root = xstrdup(argv[i]+8); |
362 | } | 368 | } |
363 | if (!strcmp(argv[i], "--nocache")) { | 369 | if (!strcmp(argv[i], "--nocache")) { |
364 | ctx.cfg.nocache = 1; | 370 | ctx.cfg.nocache = 1; |
365 | } | 371 | } |
366 | if (!strncmp(argv[i], "--query=", 8)) { | 372 | if (!strncmp(argv[i], "--query=", 8)) { |
367 | ctx.qry.raw = xstrdup(argv[i]+8); | 373 | ctx.qry.raw = xstrdup(argv[i]+8); |
368 | } | 374 | } |
369 | if (!strncmp(argv[i], "--repo=", 7)) { | 375 | if (!strncmp(argv[i], "--repo=", 7)) { |
370 | ctx.qry.repo = xstrdup(argv[i]+7); | 376 | ctx.qry.repo = xstrdup(argv[i]+7); |
371 | } | 377 | } |
372 | if (!strncmp(argv[i], "--page=", 7)) { | 378 | if (!strncmp(argv[i], "--page=", 7)) { |
373 | ctx.qry.page = xstrdup(argv[i]+7); | 379 | ctx.qry.page = xstrdup(argv[i]+7); |
374 | } | 380 | } |
375 | if (!strncmp(argv[i], "--head=", 7)) { | 381 | if (!strncmp(argv[i], "--head=", 7)) { |
376 | ctx.qry.head = xstrdup(argv[i]+7); | 382 | ctx.qry.head = xstrdup(argv[i]+7); |
377 | ctx.qry.has_symref = 1; | 383 | ctx.qry.has_symref = 1; |
378 | } | 384 | } |
379 | if (!strncmp(argv[i], "--sha1=", 7)) { | 385 | if (!strncmp(argv[i], "--sha1=", 7)) { |
380 | ctx.qry.sha1 = xstrdup(argv[i]+7); | 386 | ctx.qry.sha1 = xstrdup(argv[i]+7); |
381 | ctx.qry.has_sha1 = 1; | 387 | ctx.qry.has_sha1 = 1; |
382 | } | 388 | } |
383 | if (!strncmp(argv[i], "--ofs=", 6)) { | 389 | if (!strncmp(argv[i], "--ofs=", 6)) { |
384 | ctx.qry.ofs = atoi(argv[i]+6); | 390 | ctx.qry.ofs = atoi(argv[i]+6); |
385 | } | 391 | } |
386 | if (!strncmp(argv[i], "--scan-tree=", 12)) { | 392 | if (!strncmp(argv[i], "--scan-tree=", 12)) { |
387 | scan++; | 393 | scan++; |
388 | scan_tree(argv[i] + 12); | 394 | scan_tree(argv[i] + 12); |
389 | } | 395 | } |
390 | } | 396 | } |
391 | if (scan) { | 397 | if (scan) { |
392 | qsort(cgit_repolist.repos, cgit_repolist.count, | 398 | qsort(cgit_repolist.repos, cgit_repolist.count, |
393 | sizeof(struct cgit_repo), cmp_repos); | 399 | sizeof(struct cgit_repo), cmp_repos); |
394 | print_repolist(&cgit_repolist); | 400 | print_repolist(&cgit_repolist); |
395 | exit(0); | 401 | exit(0); |
396 | } | 402 | } |
397 | } | 403 | } |
398 | 404 | ||
399 | static int calc_ttl() | 405 | static int calc_ttl() |
400 | { | 406 | { |
401 | if (!ctx.repo) | 407 | if (!ctx.repo) |
402 | return ctx.cfg.cache_root_ttl; | 408 | return ctx.cfg.cache_root_ttl; |
403 | 409 | ||
404 | if (!ctx.qry.page) | 410 | if (!ctx.qry.page) |
405 | return ctx.cfg.cache_repo_ttl; | 411 | return ctx.cfg.cache_repo_ttl; |
406 | 412 | ||
407 | if (ctx.qry.has_symref) | 413 | if (ctx.qry.has_symref) |
408 | return ctx.cfg.cache_dynamic_ttl; | 414 | return ctx.cfg.cache_dynamic_ttl; |
409 | 415 | ||
410 | if (ctx.qry.has_sha1) | 416 | if (ctx.qry.has_sha1) |
411 | return ctx.cfg.cache_static_ttl; | 417 | return ctx.cfg.cache_static_ttl; |
412 | 418 | ||
413 | return ctx.cfg.cache_repo_ttl; | 419 | return ctx.cfg.cache_repo_ttl; |
414 | } | 420 | } |
415 | 421 | ||
416 | int main(int argc, const char **argv) | 422 | int main(int argc, const char **argv) |
417 | { | 423 | { |
418 | const char *cgit_config_env = getenv("CGIT_CONFIG"); | 424 | const char *cgit_config_env = getenv("CGIT_CONFIG"); |
419 | const char *path; | 425 | const char *path; |
420 | char *qry; | 426 | char *qry; |
421 | int err, ttl; | 427 | int err, ttl; |
422 | 428 | ||
423 | prepare_context(&ctx); | 429 | prepare_context(&ctx); |
424 | cgit_repolist.length = 0; | 430 | cgit_repolist.length = 0; |
425 | cgit_repolist.count = 0; | 431 | cgit_repolist.count = 0; |
426 | cgit_repolist.repos = NULL; | 432 | cgit_repolist.repos = NULL; |
427 | 433 | ||
428 | if (getenv("SCRIPT_NAME")) | 434 | if (getenv("SCRIPT_NAME")) |
429 | ctx.cfg.script_name = xstrdup(getenv("SCRIPT_NAME")); | 435 | ctx.cfg.script_name = xstrdup(getenv("SCRIPT_NAME")); |
430 | if (getenv("QUERY_STRING")) | 436 | if (getenv("QUERY_STRING")) |
431 | ctx.qry.raw = xstrdup(getenv("QUERY_STRING")); | 437 | ctx.qry.raw = xstrdup(getenv("QUERY_STRING")); |
432 | cgit_parse_args(argc, argv); | 438 | cgit_parse_args(argc, argv); |
433 | parse_configfile(cgit_config_env ? cgit_config_env : CGIT_CONFIG, | 439 | parse_configfile(cgit_config_env ? cgit_config_env : CGIT_CONFIG, |
434 | config_cb); | 440 | config_cb); |
435 | ctx.repo = NULL; | 441 | ctx.repo = NULL; |
436 | http_parse_querystring(ctx.qry.raw, querystring_cb); | 442 | http_parse_querystring(ctx.qry.raw, querystring_cb); |
437 | 443 | ||
438 | /* If virtual-root isn't specified in cgitrc and no url | 444 | /* If virtual-root isn't specified in cgitrc and no url |
439 | * parameter is specified on the querystring, lets pretend | 445 | * parameter is specified on the querystring, lets pretend |
440 | * that virtualroot equals SCRIPT_NAME and use PATH_INFO as | 446 | * that virtualroot equals SCRIPT_NAME and use PATH_INFO as |
441 | * url. This allows cgit to work with virtual urls without | 447 | * url. This allows cgit to work with virtual urls without |
442 | * the need for rewriterules in the webserver (as long as | 448 | * the need for rewriterules in the webserver (as long as |
443 | * PATH_INFO is included in the cache lookup key). | 449 | * PATH_INFO is included in the cache lookup key). |
444 | */ | 450 | */ |
445 | if (!ctx.cfg.virtual_root && !ctx.qry.url) { | 451 | if (!ctx.cfg.virtual_root && !ctx.qry.url) { |
446 | ctx.cfg.virtual_root = ctx.cfg.script_name; | 452 | ctx.cfg.virtual_root = ctx.cfg.script_name; |
447 | path = getenv("PATH_INFO"); | 453 | path = getenv("PATH_INFO"); |
448 | if (path) { | 454 | if (path) { |
449 | if (path[0] == '/') | 455 | if (path[0] == '/') |
450 | path++; | 456 | path++; |
451 | ctx.qry.url = xstrdup(path); | 457 | ctx.qry.url = xstrdup(path); |
452 | if (ctx.qry.raw) { | 458 | if (ctx.qry.raw) { |
453 | qry = ctx.qry.raw; | 459 | qry = ctx.qry.raw; |
454 | ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); | 460 | ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); |
455 | free(qry); | 461 | free(qry); |
456 | } else | 462 | } else |
457 | ctx.qry.raw = ctx.qry.url; | 463 | ctx.qry.raw = ctx.qry.url; |
458 | cgit_parse_url(ctx.qry.url); | 464 | cgit_parse_url(ctx.qry.url); |
459 | } | 465 | } |
460 | } | 466 | } |
461 | 467 | ||
462 | ttl = calc_ttl(); | 468 | ttl = calc_ttl(); |
463 | ctx.page.expires += ttl*60; | 469 | ctx.page.expires += ttl*60; |
464 | if (ctx.cfg.nocache) | 470 | if (ctx.cfg.nocache) |
465 | ctx.cfg.cache_size = 0; | 471 | ctx.cfg.cache_size = 0; |
466 | err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, | 472 | err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, |
467 | ctx.qry.raw, ttl, process_request, &ctx); | 473 | ctx.qry.raw, ttl, process_request, &ctx); |
468 | if (err) | 474 | if (err) |
469 | cgit_print_error(fmt("Error processing page: %s (%d)", | 475 | cgit_print_error(fmt("Error processing page: %s (%d)", |
470 | strerror(err), err)); | 476 | strerror(err), err)); |
471 | return err; | 477 | return err; |
472 | } | 478 | } |
@@ -75,384 +75,461 @@ table.tabs td { | |||
75 | padding: 0px 1em; | 75 | padding: 0px 1em; |
76 | vertical-align: bottom; | 76 | vertical-align: bottom; |
77 | } | 77 | } |
78 | 78 | ||
79 | table.tabs td a { | 79 | table.tabs td a { |
80 | padding: 2px 0.75em; | 80 | padding: 2px 0.75em; |
81 | color: #777; | 81 | color: #777; |
82 | font-size: 110%; | 82 | font-size: 110%; |
83 | } | 83 | } |
84 | 84 | ||
85 | table.tabs td a.active { | 85 | table.tabs td a.active { |
86 | color: #000; | 86 | color: #000; |
87 | background-color: #ccc; | 87 | background-color: #ccc; |
88 | } | 88 | } |
89 | 89 | ||
90 | table.tabs td.form { | 90 | table.tabs td.form { |
91 | text-align: right; | 91 | text-align: right; |
92 | } | 92 | } |
93 | 93 | ||
94 | table.tabs td.form form { | 94 | table.tabs td.form form { |
95 | padding-bottom: 2px; | 95 | padding-bottom: 2px; |
96 | font-size: 90%; | 96 | font-size: 90%; |
97 | white-space: nowrap; | 97 | white-space: nowrap; |
98 | } | 98 | } |
99 | 99 | ||
100 | table.tabs td.form input, | 100 | table.tabs td.form input, |
101 | table.tabs td.form select { | 101 | table.tabs td.form select { |
102 | font-size: 90%; | 102 | font-size: 90%; |
103 | } | 103 | } |
104 | 104 | ||
105 | div.content { | 105 | div.content { |
106 | margin: 0px; | 106 | margin: 0px; |
107 | padding: 2em; | 107 | padding: 2em; |
108 | border-top: solid 3px #ccc; | 108 | border-top: solid 3px #ccc; |
109 | border-bottom: solid 3px #ccc; | 109 | border-bottom: solid 3px #ccc; |
110 | } | 110 | } |
111 | 111 | ||
112 | 112 | ||
113 | table.list { | 113 | table.list { |
114 | width: 100%; | 114 | width: 100%; |
115 | border: none; | 115 | border: none; |
116 | border-collapse: collapse; | 116 | border-collapse: collapse; |
117 | } | 117 | } |
118 | 118 | ||
119 | table.list tr { | 119 | table.list tr { |
120 | background: white; | 120 | background: white; |
121 | } | 121 | } |
122 | 122 | ||
123 | table.list tr:hover { | 123 | table.list tr:hover { |
124 | background: #eee; | 124 | background: #eee; |
125 | } | 125 | } |
126 | 126 | ||
127 | table.list tr.nohover:hover { | 127 | table.list tr.nohover:hover { |
128 | background: white; | 128 | background: white; |
129 | } | 129 | } |
130 | 130 | ||
131 | table.list th { | 131 | table.list th { |
132 | font-weight: bold; | 132 | font-weight: bold; |
133 | /* color: #888; | 133 | /* color: #888; |
134 | border-top: dashed 1px #888; | 134 | border-top: dashed 1px #888; |
135 | border-bottom: dashed 1px #888; | 135 | border-bottom: dashed 1px #888; |
136 | */ | 136 | */ |
137 | padding: 0.1em 0.5em 0.05em 0.5em; | 137 | padding: 0.1em 0.5em 0.05em 0.5em; |
138 | vertical-align: baseline; | 138 | vertical-align: baseline; |
139 | } | 139 | } |
140 | 140 | ||
141 | table.list td { | 141 | table.list td { |
142 | border: none; | 142 | border: none; |
143 | padding: 0.1em 0.5em 0.1em 0.5em; | 143 | padding: 0.1em 0.5em 0.1em 0.5em; |
144 | } | 144 | } |
145 | 145 | ||
146 | table.list td a { | 146 | table.list td a { |
147 | color: black; | 147 | color: black; |
148 | } | 148 | } |
149 | 149 | ||
150 | table.list td a:hover { | 150 | table.list td a:hover { |
151 | color: #00f; | 151 | color: #00f; |
152 | } | 152 | } |
153 | 153 | ||
154 | img { | 154 | img { |
155 | border: none; | 155 | border: none; |
156 | } | 156 | } |
157 | 157 | ||
158 | input#switch-btn { | 158 | input#switch-btn { |
159 | margin: 2px 0px 0px 0px; | 159 | margin: 2px 0px 0px 0px; |
160 | } | 160 | } |
161 | 161 | ||
162 | td#sidebar input.txt { | 162 | td#sidebar input.txt { |
163 | width: 100%; | 163 | width: 100%; |
164 | margin: 2px 0px 0px 0px; | 164 | margin: 2px 0px 0px 0px; |
165 | } | 165 | } |
166 | 166 | ||
167 | table#grid { | 167 | table#grid { |
168 | margin: 0px; | 168 | margin: 0px; |
169 | } | 169 | } |
170 | 170 | ||
171 | td#content { | 171 | td#content { |
172 | vertical-align: top; | 172 | vertical-align: top; |
173 | padding: 1em 2em 1em 1em; | 173 | padding: 1em 2em 1em 1em; |
174 | border: none; | 174 | border: none; |
175 | } | 175 | } |
176 | 176 | ||
177 | div#summary { | 177 | div#summary { |
178 | vertical-align: top; | 178 | vertical-align: top; |
179 | margin-bottom: 1em; | 179 | margin-bottom: 1em; |
180 | } | 180 | } |
181 | 181 | ||
182 | table#downloads { | 182 | table#downloads { |
183 | float: right; | 183 | float: right; |
184 | border-collapse: collapse; | 184 | border-collapse: collapse; |
185 | border: solid 1px #777; | 185 | border: solid 1px #777; |
186 | margin-left: 0.5em; | 186 | margin-left: 0.5em; |
187 | margin-bottom: 0.5em; | 187 | margin-bottom: 0.5em; |
188 | } | 188 | } |
189 | 189 | ||
190 | table#downloads th { | 190 | table#downloads th { |
191 | background-color: #ccc; | 191 | background-color: #ccc; |
192 | } | 192 | } |
193 | 193 | ||
194 | div#blob { | 194 | div#blob { |
195 | border: solid 1px black; | 195 | border: solid 1px black; |
196 | } | 196 | } |
197 | 197 | ||
198 | div.error { | 198 | div.error { |
199 | color: red; | 199 | color: red; |
200 | font-weight: bold; | 200 | font-weight: bold; |
201 | margin: 1em 2em; | 201 | margin: 1em 2em; |
202 | } | 202 | } |
203 | 203 | ||
204 | a.ls-blob, a.ls-dir, a.ls-mod { | 204 | a.ls-blob, a.ls-dir, a.ls-mod { |
205 | font-family: monospace; | 205 | font-family: monospace; |
206 | } | 206 | } |
207 | 207 | ||
208 | td.ls-size { | 208 | td.ls-size { |
209 | text-align: right; | 209 | text-align: right; |
210 | font-family: monospace; | 210 | font-family: monospace; |
211 | width: 10em; | 211 | width: 10em; |
212 | } | 212 | } |
213 | 213 | ||
214 | td.ls-mode { | 214 | td.ls-mode { |
215 | font-family: monospace; | 215 | font-family: monospace; |
216 | width: 10em; | 216 | width: 10em; |
217 | } | 217 | } |
218 | 218 | ||
219 | table.blob { | 219 | table.blob { |
220 | margin-top: 0.5em; | 220 | margin-top: 0.5em; |
221 | border-top: solid 1px black; | 221 | border-top: solid 1px black; |
222 | } | 222 | } |
223 | 223 | ||
224 | table.blob td.no { | 224 | table.blob td.no { |
225 | border-right: solid 1px black; | 225 | border-right: solid 1px black; |
226 | color: black; | 226 | color: black; |
227 | background-color: #eee; | 227 | background-color: #eee; |
228 | text-align: right; | 228 | text-align: right; |
229 | } | 229 | } |
230 | 230 | ||
231 | table.blob td.no a { | 231 | table.blob td.no a { |
232 | color: black; | 232 | color: black; |
233 | } | 233 | } |
234 | 234 | ||
235 | table.blob td.no a:hover { | 235 | table.blob td.no a:hover { |
236 | color: black; | 236 | color: black; |
237 | text-decoration: none; | 237 | text-decoration: none; |
238 | } | 238 | } |
239 | 239 | ||
240 | table.blob td.txt { | 240 | table.blob td.txt { |
241 | white-space: pre; | 241 | white-space: pre; |
242 | font-family: monospace; | 242 | font-family: monospace; |
243 | padding-left: 0.5em; | 243 | padding-left: 0.5em; |
244 | } | 244 | } |
245 | 245 | ||
246 | table.nowrap td { | 246 | table.nowrap td { |
247 | white-space: nowrap; | 247 | white-space: nowrap; |
248 | } | 248 | } |
249 | 249 | ||
250 | table.commit-info { | 250 | table.commit-info { |
251 | border-collapse: collapse; | 251 | border-collapse: collapse; |
252 | margin-top: 1.5em; | 252 | margin-top: 1.5em; |
253 | } | 253 | } |
254 | 254 | ||
255 | table.commit-info th { | 255 | table.commit-info th { |
256 | text-align: left; | 256 | text-align: left; |
257 | font-weight: normal; | 257 | font-weight: normal; |
258 | padding: 0.1em 1em 0.1em 0.1em; | 258 | padding: 0.1em 1em 0.1em 0.1em; |
259 | vertical-align: top; | 259 | vertical-align: top; |
260 | } | 260 | } |
261 | 261 | ||
262 | table.commit-info td { | 262 | table.commit-info td { |
263 | font-weight: normal; | 263 | font-weight: normal; |
264 | padding: 0.1em 1em 0.1em 0.1em; | 264 | padding: 0.1em 1em 0.1em 0.1em; |
265 | } | 265 | } |
266 | 266 | ||
267 | div.commit-subject { | 267 | div.commit-subject { |
268 | font-weight: bold; | 268 | font-weight: bold; |
269 | font-size: 125%; | 269 | font-size: 125%; |
270 | margin: 1.5em 0em 0.5em 0em; | 270 | margin: 1.5em 0em 0.5em 0em; |
271 | padding: 0em; | 271 | padding: 0em; |
272 | } | 272 | } |
273 | 273 | ||
274 | div.commit-msg { | 274 | div.commit-msg { |
275 | white-space: pre; | 275 | white-space: pre; |
276 | font-family: monospace; | 276 | font-family: monospace; |
277 | } | 277 | } |
278 | 278 | ||
279 | div.diffstat-header { | 279 | div.diffstat-header { |
280 | font-weight: bold; | 280 | font-weight: bold; |
281 | padding-top: 1.5em; | 281 | padding-top: 1.5em; |
282 | } | 282 | } |
283 | 283 | ||
284 | table.diffstat { | 284 | table.diffstat { |
285 | border-collapse: collapse; | 285 | border-collapse: collapse; |
286 | border: solid 1px #aaa; | 286 | border: solid 1px #aaa; |
287 | background-color: #eee; | 287 | background-color: #eee; |
288 | } | 288 | } |
289 | 289 | ||
290 | table.diffstat th { | 290 | table.diffstat th { |
291 | font-weight: normal; | 291 | font-weight: normal; |
292 | text-align: left; | 292 | text-align: left; |
293 | text-decoration: underline; | 293 | text-decoration: underline; |
294 | padding: 0.1em 1em 0.1em 0.1em; | 294 | padding: 0.1em 1em 0.1em 0.1em; |
295 | font-size: 100%; | 295 | font-size: 100%; |
296 | } | 296 | } |
297 | 297 | ||
298 | table.diffstat td { | 298 | table.diffstat td { |
299 | padding: 0.2em 0.2em 0.1em 0.1em; | 299 | padding: 0.2em 0.2em 0.1em 0.1em; |
300 | font-size: 100%; | 300 | font-size: 100%; |
301 | border: none; | 301 | border: none; |
302 | } | 302 | } |
303 | 303 | ||
304 | table.diffstat td.mode { | 304 | table.diffstat td.mode { |
305 | white-space: nowrap; | 305 | white-space: nowrap; |
306 | } | 306 | } |
307 | 307 | ||
308 | table.diffstat td span.modechange { | 308 | table.diffstat td span.modechange { |
309 | padding-left: 1em; | 309 | padding-left: 1em; |
310 | color: red; | 310 | color: red; |
311 | } | 311 | } |
312 | 312 | ||
313 | table.diffstat td.add a { | 313 | table.diffstat td.add a { |
314 | color: green; | 314 | color: green; |
315 | } | 315 | } |
316 | 316 | ||
317 | table.diffstat td.del a { | 317 | table.diffstat td.del a { |
318 | color: red; | 318 | color: red; |
319 | } | 319 | } |
320 | 320 | ||
321 | table.diffstat td.upd a { | 321 | table.diffstat td.upd a { |
322 | color: blue; | 322 | color: blue; |
323 | } | 323 | } |
324 | 324 | ||
325 | table.diffstat td.graph { | 325 | table.diffstat td.graph { |
326 | width: 500px; | 326 | width: 500px; |
327 | vertical-align: middle; | 327 | vertical-align: middle; |
328 | } | 328 | } |
329 | 329 | ||
330 | table.diffstat td.graph table { | 330 | table.diffstat td.graph table { |
331 | border: none; | 331 | border: none; |
332 | } | 332 | } |
333 | 333 | ||
334 | table.diffstat td.graph td { | 334 | table.diffstat td.graph td { |
335 | padding: 0px; | 335 | padding: 0px; |
336 | border: 0px; | 336 | border: 0px; |
337 | height: 7pt; | 337 | height: 7pt; |
338 | } | 338 | } |
339 | 339 | ||
340 | table.diffstat td.graph td.add { | 340 | table.diffstat td.graph td.add { |
341 | background-color: #5c5; | 341 | background-color: #5c5; |
342 | } | 342 | } |
343 | 343 | ||
344 | table.diffstat td.graph td.rem { | 344 | table.diffstat td.graph td.rem { |
345 | background-color: #c55; | 345 | background-color: #c55; |
346 | } | 346 | } |
347 | 347 | ||
348 | div.diffstat-summary { | 348 | div.diffstat-summary { |
349 | color: #888; | 349 | color: #888; |
350 | padding-top: 0.5em; | 350 | padding-top: 0.5em; |
351 | } | 351 | } |
352 | 352 | ||
353 | table.diff { | 353 | table.diff { |
354 | width: 100%; | 354 | width: 100%; |
355 | } | 355 | } |
356 | 356 | ||
357 | table.diff td { | 357 | table.diff td { |
358 | font-family: monospace; | 358 | font-family: monospace; |
359 | white-space: pre; | 359 | white-space: pre; |
360 | } | 360 | } |
361 | 361 | ||
362 | table.diff td div.head { | 362 | table.diff td div.head { |
363 | font-weight: bold; | 363 | font-weight: bold; |
364 | margin-top: 1em; | 364 | margin-top: 1em; |
365 | color: black; | 365 | color: black; |
366 | } | 366 | } |
367 | 367 | ||
368 | table.diff td div.hunk { | 368 | table.diff td div.hunk { |
369 | color: #009; | 369 | color: #009; |
370 | } | 370 | } |
371 | 371 | ||
372 | table.diff td div.add { | 372 | table.diff td div.add { |
373 | color: green; | 373 | color: green; |
374 | } | 374 | } |
375 | 375 | ||
376 | table.diff td div.del { | 376 | table.diff td div.del { |
377 | color: red; | 377 | color: red; |
378 | } | 378 | } |
379 | 379 | ||
380 | .sha1 { | 380 | .sha1 { |
381 | font-family: monospace; | 381 | font-family: monospace; |
382 | font-size: 90%; | 382 | font-size: 90%; |
383 | } | 383 | } |
384 | 384 | ||
385 | .left { | 385 | .left { |
386 | text-align: left; | 386 | text-align: left; |
387 | } | 387 | } |
388 | 388 | ||
389 | .right { | 389 | .right { |
390 | text-align: right; | 390 | text-align: right; |
391 | } | 391 | } |
392 | 392 | ||
393 | table.list td.repogroup { | 393 | table.list td.repogroup { |
394 | font-style: italic; | 394 | font-style: italic; |
395 | color: #888; | 395 | color: #888; |
396 | } | 396 | } |
397 | 397 | ||
398 | a.button { | 398 | a.button { |
399 | font-size: 80%; | 399 | font-size: 80%; |
400 | padding: 0em 0.5em; | 400 | padding: 0em 0.5em; |
401 | } | 401 | } |
402 | 402 | ||
403 | a.primary { | 403 | a.primary { |
404 | font-size: 100%; | 404 | font-size: 100%; |
405 | } | 405 | } |
406 | 406 | ||
407 | a.secondary { | 407 | a.secondary { |
408 | font-size: 90%; | 408 | font-size: 90%; |
409 | } | 409 | } |
410 | 410 | ||
411 | td.toplevel-repo { | 411 | td.toplevel-repo { |
412 | 412 | ||
413 | } | 413 | } |
414 | 414 | ||
415 | table.list td.sublevel-repo { | 415 | table.list td.sublevel-repo { |
416 | padding-left: 1.5em; | 416 | padding-left: 1.5em; |
417 | } | 417 | } |
418 | 418 | ||
419 | div.pager { | 419 | div.pager { |
420 | text-align: center; | 420 | text-align: center; |
421 | margin: 1em 0em 0em 0em; | 421 | margin: 1em 0em 0em 0em; |
422 | } | 422 | } |
423 | 423 | ||
424 | div.pager a { | 424 | div.pager a { |
425 | color: #777; | 425 | color: #777; |
426 | margin: 0em 0.5em; | 426 | margin: 0em 0.5em; |
427 | } | 427 | } |
428 | 428 | ||
429 | span.age-mins { | 429 | span.age-mins { |
430 | font-weight: bold; | 430 | font-weight: bold; |
431 | color: #080; | 431 | color: #080; |
432 | } | 432 | } |
433 | 433 | ||
434 | span.age-hours { | 434 | span.age-hours { |
435 | color: #080; | 435 | color: #080; |
436 | } | 436 | } |
437 | 437 | ||
438 | span.age-days { | 438 | span.age-days { |
439 | color: #040; | 439 | color: #040; |
440 | } | 440 | } |
441 | 441 | ||
442 | span.age-weeks { | 442 | span.age-weeks { |
443 | color: #444; | 443 | color: #444; |
444 | } | 444 | } |
445 | 445 | ||
446 | span.age-months { | 446 | span.age-months { |
447 | color: #888; | 447 | color: #888; |
448 | } | 448 | } |
449 | 449 | ||
450 | span.age-years { | 450 | span.age-years { |
451 | color: #bbb; | 451 | color: #bbb; |
452 | } | 452 | } |
453 | div.footer { | 453 | div.footer { |
454 | margin-top: 0.5em; | 454 | margin-top: 0.5em; |
455 | text-align: center; | 455 | text-align: center; |
456 | font-size: 80%; | 456 | font-size: 80%; |
457 | color: #ccc; | 457 | color: #ccc; |
458 | } | 458 | } |
459 | table.stats { | ||
460 | border: solid 1px black; | ||
461 | border-collapse: collapse; | ||
462 | } | ||
463 | |||
464 | table.stats th { | ||
465 | text-align: left; | ||
466 | padding: 1px 0.5em; | ||
467 | background-color: #eee; | ||
468 | border: solid 1px black; | ||
469 | } | ||
470 | |||
471 | table.stats td { | ||
472 | text-align: right; | ||
473 | padding: 1px 0.5em; | ||
474 | border: solid 1px black; | ||
475 | } | ||
476 | |||
477 | table.stats td.total { | ||
478 | font-weight: bold; | ||
479 | text-align: left; | ||
480 | } | ||
481 | |||
482 | table.stats td.sum { | ||
483 | color: #c00; | ||
484 | font-weight: bold; | ||
485 | /*background-color: #eee; */ | ||
486 | } | ||
487 | |||
488 | table.stats td.left { | ||
489 | text-align: left; | ||
490 | } | ||
491 | |||
492 | table.vgraph { | ||
493 | border-collapse: separate; | ||
494 | border: solid 1px black; | ||
495 | height: 200px; | ||
496 | } | ||
497 | |||
498 | table.vgraph th { | ||
499 | background-color: #eee; | ||
500 | font-weight: bold; | ||
501 | border: solid 1px white; | ||
502 | padding: 1px 0.5em; | ||
503 | } | ||
504 | |||
505 | table.vgraph td { | ||
506 | vertical-align: bottom; | ||
507 | padding: 0px 10px; | ||
508 | } | ||
509 | |||
510 | table.vgraph div.bar { | ||
511 | background-color: #eee; | ||
512 | } | ||
513 | |||
514 | table.hgraph { | ||
515 | border: solid 1px black; | ||
516 | width: 800px; | ||
517 | } | ||
518 | |||
519 | table.hgraph th { | ||
520 | background-color: #eee; | ||
521 | font-weight: bold; | ||
522 | border: solid 1px black; | ||
523 | padding: 1px 0.5em; | ||
524 | } | ||
525 | |||
526 | table.hgraph td { | ||
527 | vertical-align: center; | ||
528 | padding: 2px 2px; | ||
529 | } | ||
530 | |||
531 | table.hgraph div.bar { | ||
532 | background-color: #eee; | ||
533 | height: 1em; | ||
534 | } | ||
535 | |||
@@ -1,243 +1,246 @@ | |||
1 | #ifndef CGIT_H | 1 | #ifndef CGIT_H |
2 | #define CGIT_H | 2 | #define CGIT_H |
3 | 3 | ||
4 | 4 | ||
5 | #include <git-compat-util.h> | 5 | #include <git-compat-util.h> |
6 | #include <cache.h> | 6 | #include <cache.h> |
7 | #include <grep.h> | 7 | #include <grep.h> |
8 | #include <object.h> | 8 | #include <object.h> |
9 | #include <tree.h> | 9 | #include <tree.h> |
10 | #include <commit.h> | 10 | #include <commit.h> |
11 | #include <tag.h> | 11 | #include <tag.h> |
12 | #include <diff.h> | 12 | #include <diff.h> |
13 | #include <diffcore.h> | 13 | #include <diffcore.h> |
14 | #include <refs.h> | 14 | #include <refs.h> |
15 | #include <revision.h> | 15 | #include <revision.h> |
16 | #include <log-tree.h> | 16 | #include <log-tree.h> |
17 | #include <archive.h> | 17 | #include <archive.h> |
18 | #include <xdiff/xdiff.h> | 18 | #include <xdiff/xdiff.h> |
19 | #include <utf8.h> | 19 | #include <utf8.h> |
20 | 20 | ||
21 | 21 | ||
22 | /* | 22 | /* |
23 | * Dateformats used on misc. pages | 23 | * Dateformats used on misc. pages |
24 | */ | 24 | */ |
25 | #define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)" | 25 | #define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)" |
26 | #define FMT_SHORTDATE "%Y-%m-%d" | 26 | #define FMT_SHORTDATE "%Y-%m-%d" |
27 | #define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ" | 27 | #define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ" |
28 | 28 | ||
29 | 29 | ||
30 | /* | 30 | /* |
31 | * Limits used for relative dates | 31 | * Limits used for relative dates |
32 | */ | 32 | */ |
33 | #define TM_MIN 60 | 33 | #define TM_MIN 60 |
34 | #define TM_HOUR (TM_MIN * 60) | 34 | #define TM_HOUR (TM_MIN * 60) |
35 | #define TM_DAY (TM_HOUR * 24) | 35 | #define TM_DAY (TM_HOUR * 24) |
36 | #define TM_WEEK (TM_DAY * 7) | 36 | #define TM_WEEK (TM_DAY * 7) |
37 | #define TM_YEAR (TM_DAY * 365) | 37 | #define TM_YEAR (TM_DAY * 365) |
38 | #define TM_MONTH (TM_YEAR / 12.0) | 38 | #define TM_MONTH (TM_YEAR / 12.0) |
39 | 39 | ||
40 | 40 | ||
41 | /* | 41 | /* |
42 | * Default encoding | 42 | * Default encoding |
43 | */ | 43 | */ |
44 | #define PAGE_ENCODING "UTF-8" | 44 | #define PAGE_ENCODING "UTF-8" |
45 | 45 | ||
46 | typedef void (*configfn)(const char *name, const char *value); | 46 | typedef void (*configfn)(const char *name, const char *value); |
47 | typedef void (*filepair_fn)(struct diff_filepair *pair); | 47 | typedef void (*filepair_fn)(struct diff_filepair *pair); |
48 | typedef void (*linediff_fn)(char *line, int len); | 48 | typedef void (*linediff_fn)(char *line, int len); |
49 | 49 | ||
50 | struct cgit_repo { | 50 | struct cgit_repo { |
51 | char *url; | 51 | char *url; |
52 | char *name; | 52 | char *name; |
53 | char *path; | 53 | char *path; |
54 | char *desc; | 54 | char *desc; |
55 | char *owner; | 55 | char *owner; |
56 | char *defbranch; | 56 | char *defbranch; |
57 | char *group; | 57 | char *group; |
58 | char *module_link; | 58 | char *module_link; |
59 | char *readme; | 59 | char *readme; |
60 | char *clone_url; | 60 | char *clone_url; |
61 | int snapshots; | 61 | int snapshots; |
62 | int enable_log_filecount; | 62 | int enable_log_filecount; |
63 | int enable_log_linecount; | 63 | int enable_log_linecount; |
64 | int enable_stats; | ||
64 | }; | 65 | }; |
65 | 66 | ||
66 | struct cgit_repolist { | 67 | struct cgit_repolist { |
67 | int length; | 68 | int length; |
68 | int count; | 69 | int count; |
69 | struct cgit_repo *repos; | 70 | struct cgit_repo *repos; |
70 | }; | 71 | }; |
71 | 72 | ||
72 | struct commitinfo { | 73 | struct commitinfo { |
73 | struct commit *commit; | 74 | struct commit *commit; |
74 | char *author; | 75 | char *author; |
75 | char *author_email; | 76 | char *author_email; |
76 | unsigned long author_date; | 77 | unsigned long author_date; |
77 | char *committer; | 78 | char *committer; |
78 | char *committer_email; | 79 | char *committer_email; |
79 | unsigned long committer_date; | 80 | unsigned long committer_date; |
80 | char *subject; | 81 | char *subject; |
81 | char *msg; | 82 | char *msg; |
82 | char *msg_encoding; | 83 | char *msg_encoding; |
83 | }; | 84 | }; |
84 | 85 | ||
85 | struct taginfo { | 86 | struct taginfo { |
86 | char *tagger; | 87 | char *tagger; |
87 | char *tagger_email; | 88 | char *tagger_email; |
88 | unsigned long tagger_date; | 89 | unsigned long tagger_date; |
89 | char *msg; | 90 | char *msg; |
90 | }; | 91 | }; |
91 | 92 | ||
92 | struct refinfo { | 93 | struct refinfo { |
93 | const char *refname; | 94 | const char *refname; |
94 | struct object *object; | 95 | struct object *object; |
95 | union { | 96 | union { |
96 | struct taginfo *tag; | 97 | struct taginfo *tag; |
97 | struct commitinfo *commit; | 98 | struct commitinfo *commit; |
98 | }; | 99 | }; |
99 | }; | 100 | }; |
100 | 101 | ||
101 | struct reflist { | 102 | struct reflist { |
102 | struct refinfo **refs; | 103 | struct refinfo **refs; |
103 | int alloc; | 104 | int alloc; |
104 | int count; | 105 | int count; |
105 | }; | 106 | }; |
106 | 107 | ||
107 | struct cgit_query { | 108 | struct cgit_query { |
108 | int has_symref; | 109 | int has_symref; |
109 | int has_sha1; | 110 | int has_sha1; |
110 | char *raw; | 111 | char *raw; |
111 | char *repo; | 112 | char *repo; |
112 | char *page; | 113 | char *page; |
113 | char *search; | 114 | char *search; |
114 | char *grep; | 115 | char *grep; |
115 | char *head; | 116 | char *head; |
116 | char *sha1; | 117 | char *sha1; |
117 | char *sha2; | 118 | char *sha2; |
118 | char *path; | 119 | char *path; |
119 | char *name; | 120 | char *name; |
120 | char *mimetype; | 121 | char *mimetype; |
121 | char *url; | 122 | char *url; |
123 | char *period; | ||
122 | int ofs; | 124 | int ofs; |
123 | int nohead; | 125 | int nohead; |
124 | }; | 126 | }; |
125 | 127 | ||
126 | struct cgit_config { | 128 | struct cgit_config { |
127 | char *agefile; | 129 | char *agefile; |
128 | char *cache_root; | 130 | char *cache_root; |
129 | char *clone_prefix; | 131 | char *clone_prefix; |
130 | char *css; | 132 | char *css; |
131 | char *favicon; | 133 | char *favicon; |
132 | char *footer; | 134 | char *footer; |
133 | char *index_header; | 135 | char *index_header; |
134 | char *index_info; | 136 | char *index_info; |
135 | char *logo; | 137 | char *logo; |
136 | char *logo_link; | 138 | char *logo_link; |
137 | char *module_link; | 139 | char *module_link; |
138 | char *repo_group; | 140 | char *repo_group; |
139 | char *robots; | 141 | char *robots; |
140 | char *root_title; | 142 | char *root_title; |
141 | char *root_desc; | 143 | char *root_desc; |
142 | char *root_readme; | 144 | char *root_readme; |
143 | char *script_name; | 145 | char *script_name; |
144 | char *virtual_root; | 146 | char *virtual_root; |
145 | int cache_size; | 147 | int cache_size; |
146 | int cache_dynamic_ttl; | 148 | int cache_dynamic_ttl; |
147 | int cache_max_create_time; | 149 | int cache_max_create_time; |
148 | int cache_repo_ttl; | 150 | int cache_repo_ttl; |
149 | int cache_root_ttl; | 151 | int cache_root_ttl; |
150 | int cache_static_ttl; | 152 | int cache_static_ttl; |
151 | int enable_index_links; | 153 | int enable_index_links; |
152 | int enable_log_filecount; | 154 | int enable_log_filecount; |
153 | int enable_log_linecount; | 155 | int enable_log_linecount; |
156 | int enable_stats; | ||
154 | int local_time; | 157 | int local_time; |
155 | int max_repo_count; | 158 | int max_repo_count; |
156 | int max_commit_count; | 159 | int max_commit_count; |
157 | int max_lock_attempts; | 160 | int max_lock_attempts; |
158 | int max_msg_len; | 161 | int max_msg_len; |
159 | int max_repodesc_len; | 162 | int max_repodesc_len; |
160 | int nocache; | 163 | int nocache; |
161 | int renamelimit; | 164 | int renamelimit; |
162 | int snapshots; | 165 | int snapshots; |
163 | int summary_branches; | 166 | int summary_branches; |
164 | int summary_log; | 167 | int summary_log; |
165 | int summary_tags; | 168 | int summary_tags; |
166 | }; | 169 | }; |
167 | 170 | ||
168 | struct cgit_page { | 171 | struct cgit_page { |
169 | time_t modified; | 172 | time_t modified; |
170 | time_t expires; | 173 | time_t expires; |
171 | size_t size; | 174 | size_t size; |
172 | char *mimetype; | 175 | char *mimetype; |
173 | char *charset; | 176 | char *charset; |
174 | char *filename; | 177 | char *filename; |
175 | char *title; | 178 | char *title; |
176 | }; | 179 | }; |
177 | 180 | ||
178 | struct cgit_context { | 181 | struct cgit_context { |
179 | struct cgit_query qry; | 182 | struct cgit_query qry; |
180 | struct cgit_config cfg; | 183 | struct cgit_config cfg; |
181 | struct cgit_repo *repo; | 184 | struct cgit_repo *repo; |
182 | struct cgit_page page; | 185 | struct cgit_page page; |
183 | }; | 186 | }; |
184 | 187 | ||
185 | struct cgit_snapshot_format { | 188 | struct cgit_snapshot_format { |
186 | const char *suffix; | 189 | const char *suffix; |
187 | const char *mimetype; | 190 | const char *mimetype; |
188 | write_archive_fn_t write_func; | 191 | write_archive_fn_t write_func; |
189 | int bit; | 192 | int bit; |
190 | }; | 193 | }; |
191 | 194 | ||
192 | extern const char *cgit_version; | 195 | extern const char *cgit_version; |
193 | 196 | ||
194 | extern struct cgit_repolist cgit_repolist; | 197 | extern struct cgit_repolist cgit_repolist; |
195 | extern struct cgit_context ctx; | 198 | extern struct cgit_context ctx; |
196 | extern const struct cgit_snapshot_format cgit_snapshot_formats[]; | 199 | extern const struct cgit_snapshot_format cgit_snapshot_formats[]; |
197 | 200 | ||
198 | extern struct cgit_repo *cgit_add_repo(const char *url); | 201 | extern struct cgit_repo *cgit_add_repo(const char *url); |
199 | extern struct cgit_repo *cgit_get_repoinfo(const char *url); | 202 | extern struct cgit_repo *cgit_get_repoinfo(const char *url); |
200 | extern void cgit_repo_config_cb(const char *name, const char *value); | 203 | extern void cgit_repo_config_cb(const char *name, const char *value); |
201 | 204 | ||
202 | extern int chk_zero(int result, char *msg); | 205 | extern int chk_zero(int result, char *msg); |
203 | extern int chk_positive(int result, char *msg); | 206 | extern int chk_positive(int result, char *msg); |
204 | extern int chk_non_negative(int result, char *msg); | 207 | extern int chk_non_negative(int result, char *msg); |
205 | 208 | ||
206 | extern char *trim_end(const char *str, char c); | 209 | extern char *trim_end(const char *str, char c); |
207 | extern char *strlpart(char *txt, int maxlen); | 210 | extern char *strlpart(char *txt, int maxlen); |
208 | extern char *strrpart(char *txt, int maxlen); | 211 | extern char *strrpart(char *txt, int maxlen); |
209 | 212 | ||
210 | extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); | 213 | extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); |
211 | extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, | 214 | extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, |
212 | int flags, void *cb_data); | 215 | int flags, void *cb_data); |
213 | 216 | ||
214 | extern void *cgit_free_commitinfo(struct commitinfo *info); | 217 | extern void *cgit_free_commitinfo(struct commitinfo *info); |
215 | 218 | ||
216 | extern int cgit_diff_files(const unsigned char *old_sha1, | 219 | extern int cgit_diff_files(const unsigned char *old_sha1, |
217 | const unsigned char *new_sha1, | 220 | const unsigned char *new_sha1, |
218 | linediff_fn fn); | 221 | linediff_fn fn); |
219 | 222 | ||
220 | extern void cgit_diff_tree(const unsigned char *old_sha1, | 223 | extern void cgit_diff_tree(const unsigned char *old_sha1, |
221 | const unsigned char *new_sha1, | 224 | const unsigned char *new_sha1, |
222 | filepair_fn fn, const char *prefix); | 225 | filepair_fn fn, const char *prefix); |
223 | 226 | ||
224 | extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); | 227 | extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); |
225 | 228 | ||
226 | extern char *fmt(const char *format,...); | 229 | extern char *fmt(const char *format,...); |
227 | 230 | ||
228 | extern struct commitinfo *cgit_parse_commit(struct commit *commit); | 231 | extern struct commitinfo *cgit_parse_commit(struct commit *commit); |
229 | extern struct taginfo *cgit_parse_tag(struct tag *tag); | 232 | extern struct taginfo *cgit_parse_tag(struct tag *tag); |
230 | extern void cgit_parse_url(const char *url); | 233 | extern void cgit_parse_url(const char *url); |
231 | 234 | ||
232 | extern const char *cgit_repobasename(const char *reponame); | 235 | extern const char *cgit_repobasename(const char *reponame); |
233 | 236 | ||
234 | extern int cgit_parse_snapshots_mask(const char *str); | 237 | extern int cgit_parse_snapshots_mask(const char *str); |
235 | 238 | ||
236 | /* libgit.a either links against or compiles its own implementation of | 239 | /* libgit.a either links against or compiles its own implementation of |
237 | * strcasestr(), and we'd like to reuse it. Simply re-declaring it | 240 | * strcasestr(), and we'd like to reuse it. Simply re-declaring it |
238 | * seems to do the trick. | 241 | * seems to do the trick. |
239 | */ | 242 | */ |
240 | extern char *strcasestr(const char *haystack, const char *needle); | 243 | extern char *strcasestr(const char *haystack, const char *needle); |
241 | 244 | ||
242 | 245 | ||
243 | #endif /* CGIT_H */ | 246 | #endif /* CGIT_H */ |
diff --git a/cgitrc.5.txt b/cgitrc.5.txt index 7887b02..60d3ea4 100644 --- a/cgitrc.5.txt +++ b/cgitrc.5.txt | |||
@@ -1,367 +1,375 @@ | |||
1 | CGITRC | 1 | CGITRC |
2 | ====== | 2 | ====== |
3 | 3 | ||
4 | 4 | ||
5 | NAME | 5 | NAME |
6 | ---- | 6 | ---- |
7 | cgitrc - runtime configuration for cgit | 7 | cgitrc - runtime configuration for cgit |
8 | 8 | ||
9 | 9 | ||
10 | DESCRIPTION | 10 | DESCRIPTION |
11 | ----------- | 11 | ----------- |
12 | Cgitrc contains all runtime settings for cgit, including the list of git | 12 | Cgitrc contains all runtime settings for cgit, including the list of git |
13 | repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank | 13 | repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank |
14 | lines, and lines starting with '#', are ignored. | 14 | lines, and lines starting with '#', are ignored. |
15 | 15 | ||
16 | 16 | ||
17 | GLOBAL SETTINGS | 17 | GLOBAL SETTINGS |
18 | --------------- | 18 | --------------- |
19 | agefile | 19 | agefile |
20 | Specifies a path, relative to each repository path, which can be used | 20 | Specifies a path, relative to each repository path, which can be used |
21 | to specify the date and time of the youngest commit in the repository. | 21 | to specify the date and time of the youngest commit in the repository. |
22 | The first line in the file is used as input to the "parse_date" | 22 | The first line in the file is used as input to the "parse_date" |
23 | function in libgit. Recommended timestamp-format is "yyyy-mm-dd | 23 | function in libgit. Recommended timestamp-format is "yyyy-mm-dd |
24 | hh:mm:ss". Default value: "info/web/last-modified". | 24 | hh:mm:ss". Default value: "info/web/last-modified". |
25 | 25 | ||
26 | cache-root | 26 | cache-root |
27 | Path used to store the cgit cache entries. Default value: | 27 | Path used to store the cgit cache entries. Default value: |
28 | "/var/cache/cgit". | 28 | "/var/cache/cgit". |
29 | 29 | ||
30 | cache-dynamic-ttl | 30 | cache-dynamic-ttl |
31 | Number which specifies the time-to-live, in minutes, for the cached | 31 | Number which specifies the time-to-live, in minutes, for the cached |
32 | version of repository pages accessed without a fixed SHA1. Default | 32 | version of repository pages accessed without a fixed SHA1. Default |
33 | value: "5". | 33 | value: "5". |
34 | 34 | ||
35 | cache-repo-ttl | 35 | cache-repo-ttl |
36 | Number which specifies the time-to-live, in minutes, for the cached | 36 | Number which specifies the time-to-live, in minutes, for the cached |
37 | version of the repository summary page. Default value: "5". | 37 | version of the repository summary page. Default value: "5". |
38 | 38 | ||
39 | cache-root-ttl | 39 | cache-root-ttl |
40 | Number which specifies the time-to-live, in minutes, for the cached | 40 | Number which specifies the time-to-live, in minutes, for the cached |
41 | version of the repository index page. Default value: "5". | 41 | version of the repository index page. Default value: "5". |
42 | 42 | ||
43 | cache-size | 43 | cache-size |
44 | The maximum number of entries in the cgit cache. Default value: "0" | 44 | The maximum number of entries in the cgit cache. Default value: "0" |
45 | (i.e. caching is disabled). | 45 | (i.e. caching is disabled). |
46 | 46 | ||
47 | cache-static-ttl | 47 | cache-static-ttl |
48 | Number which specifies the time-to-live, in minutes, for the cached | 48 | Number which specifies the time-to-live, in minutes, for the cached |
49 | version of repository pages accessed with a fixed SHA1. Default value: | 49 | version of repository pages accessed with a fixed SHA1. Default value: |
50 | "5". | 50 | "5". |
51 | 51 | ||
52 | clone-prefix | 52 | clone-prefix |
53 | Space-separated list of common prefixes which, when combined with a | 53 | Space-separated list of common prefixes which, when combined with a |
54 | repository url, generates valid clone urls for the repository. This | 54 | repository url, generates valid clone urls for the repository. This |
55 | setting is only used if `repo.clone-url` is unspecified. Default value: | 55 | setting is only used if `repo.clone-url` is unspecified. Default value: |
56 | none. | 56 | none. |
57 | 57 | ||
58 | css | 58 | css |
59 | Url which specifies the css document to include in all cgit pages. | 59 | Url which specifies the css document to include in all cgit pages. |
60 | Default value: "/cgit.css". | 60 | Default value: "/cgit.css". |
61 | 61 | ||
62 | enable-index-links | 62 | enable-index-links |
63 | Flag which, when set to "1", will make cgit generate extra links for | 63 | Flag which, when set to "1", will make cgit generate extra links for |
64 | each repo in the repository index (specifically, to the "summary", | 64 | each repo in the repository index (specifically, to the "summary", |
65 | "commit" and "tree" pages). Default value: "0". | 65 | "commit" and "tree" pages). Default value: "0". |
66 | 66 | ||
67 | enable-log-filecount | 67 | enable-log-filecount |
68 | Flag which, when set to "1", will make cgit print the number of | 68 | Flag which, when set to "1", will make cgit print the number of |
69 | modified files for each commit on the repository log page. Default | 69 | modified files for each commit on the repository log page. Default |
70 | value: "0". | 70 | value: "0". |
71 | 71 | ||
72 | enable-log-linecount | 72 | enable-log-linecount |
73 | Flag which, when set to "1", will make cgit print the number of added | 73 | Flag which, when set to "1", will make cgit print the number of added |
74 | and removed lines for each commit on the repository log page. Default | 74 | and removed lines for each commit on the repository log page. Default |
75 | value: "0". | 75 | value: "0". |
76 | 76 | ||
77 | enable-stats | ||
78 | Globally enable/disable statistics for each repository. Default | ||
79 | value: "0". | ||
80 | |||
77 | favicon | 81 | favicon |
78 | Url used as link to a shortcut icon for cgit. If specified, it is | 82 | Url used as link to a shortcut icon for cgit. If specified, it is |
79 | suggested to use the value "/favicon.ico" since certain browsers will | 83 | suggested to use the value "/favicon.ico" since certain browsers will |
80 | ignore other values. Default value: none. | 84 | ignore other values. Default value: none. |
81 | 85 | ||
82 | footer | 86 | footer |
83 | The content of the file specified with this option will be included | 87 | The content of the file specified with this option will be included |
84 | verbatim at the bottom of all pages (i.e. it replaces the standard | 88 | verbatim at the bottom of all pages (i.e. it replaces the standard |
85 | "generated by..." message. Default value: none. | 89 | "generated by..." message. Default value: none. |
86 | 90 | ||
87 | include | 91 | include |
88 | Name of a configfile to include before the rest of the current config- | 92 | Name of a configfile to include before the rest of the current config- |
89 | file is parsed. Default value: none. | 93 | file is parsed. Default value: none. |
90 | 94 | ||
91 | index-header | 95 | index-header |
92 | The content of the file specified with this option will be included | 96 | The content of the file specified with this option will be included |
93 | verbatim above the repository index. This setting is deprecated, and | 97 | verbatim above the repository index. This setting is deprecated, and |
94 | will not be supported by cgit-1.0 (use root-readme instead). Default | 98 | will not be supported by cgit-1.0 (use root-readme instead). Default |
95 | value: none. | 99 | value: none. |
96 | 100 | ||
97 | index-info | 101 | index-info |
98 | The content of the file specified with this option will be included | 102 | The content of the file specified with this option will be included |
99 | verbatim below the heading on the repository index page. This setting | 103 | verbatim below the heading on the repository index page. This setting |
100 | is deprecated, and will not be supported by cgit-1.0 (use root-desc | 104 | is deprecated, and will not be supported by cgit-1.0 (use root-desc |
101 | instead). Default value: none. | 105 | instead). Default value: none. |
102 | 106 | ||
103 | local-time | 107 | local-time |
104 | Flag which, if set to "1", makes cgit print commit and tag times in the | 108 | Flag which, if set to "1", makes cgit print commit and tag times in the |
105 | servers timezone. Default value: "0". | 109 | servers timezone. Default value: "0". |
106 | 110 | ||
107 | logo | 111 | logo |
108 | Url which specifies the source of an image which will be used as a logo | 112 | Url which specifies the source of an image which will be used as a logo |
109 | on all cgit pages. | 113 | on all cgit pages. |
110 | 114 | ||
111 | logo-link | 115 | logo-link |
112 | Url loaded when clicking on the cgit logo image. If unspecified the | 116 | Url loaded when clicking on the cgit logo image. If unspecified the |
113 | calculated url of the repository index page will be used. Default | 117 | calculated url of the repository index page will be used. Default |
114 | value: none. | 118 | value: none. |
115 | 119 | ||
116 | max-commit-count | 120 | max-commit-count |
117 | Specifies the number of entries to list per page in "log" view. Default | 121 | Specifies the number of entries to list per page in "log" view. Default |
118 | value: "50". | 122 | value: "50". |
119 | 123 | ||
120 | max-message-length | 124 | max-message-length |
121 | Specifies the maximum number of commit message characters to display in | 125 | Specifies the maximum number of commit message characters to display in |
122 | "log" view. Default value: "80". | 126 | "log" view. Default value: "80". |
123 | 127 | ||
124 | max-repo-count | 128 | max-repo-count |
125 | Specifies the number of entries to list per page on therepository | 129 | Specifies the number of entries to list per page on therepository |
126 | index page. Default value: "50". | 130 | index page. Default value: "50". |
127 | 131 | ||
128 | max-repodesc-length | 132 | max-repodesc-length |
129 | Specifies the maximum number of repo description characters to display | 133 | Specifies the maximum number of repo description characters to display |
130 | on the repository index page. Default value: "80". | 134 | on the repository index page. Default value: "80". |
131 | 135 | ||
132 | module-link | 136 | module-link |
133 | Text which will be used as the formatstring for a hyperlink when a | 137 | Text which will be used as the formatstring for a hyperlink when a |
134 | submodule is printed in a directory listing. The arguments for the | 138 | submodule is printed in a directory listing. The arguments for the |
135 | formatstring are the path and SHA1 of the submodule commit. Default | 139 | formatstring are the path and SHA1 of the submodule commit. Default |
136 | value: "./?repo=%s&page=commit&id=%s" | 140 | value: "./?repo=%s&page=commit&id=%s" |
137 | 141 | ||
138 | nocache | 142 | nocache |
139 | If set to the value "1" caching will be disabled. This settings is | 143 | If set to the value "1" caching will be disabled. This settings is |
140 | deprecated, and will not be honored starting with cgit-1.0. Default | 144 | deprecated, and will not be honored starting with cgit-1.0. Default |
141 | value: "0". | 145 | value: "0". |
142 | 146 | ||
143 | renamelimit | 147 | renamelimit |
144 | Maximum number of files to consider when detecting renames. The value | 148 | Maximum number of files to consider when detecting renames. The value |
145 | "-1" uses the compiletime value in git (for further info, look at | 149 | "-1" uses the compiletime value in git (for further info, look at |
146 | `man git-diff`). Default value: "-1". | 150 | `man git-diff`). Default value: "-1". |
147 | 151 | ||
148 | repo.group | 152 | repo.group |
149 | A value for the current repository group, which all repositories | 153 | A value for the current repository group, which all repositories |
150 | specified after this setting will inherit. Default value: none. | 154 | specified after this setting will inherit. Default value: none. |
151 | 155 | ||
152 | robots | 156 | robots |
153 | Text used as content for the "robots" meta-tag. Default value: | 157 | Text used as content for the "robots" meta-tag. Default value: |
154 | "index, nofollow". | 158 | "index, nofollow". |
155 | 159 | ||
156 | root-desc | 160 | root-desc |
157 | Text printed below the heading on the repository index page. Default | 161 | Text printed below the heading on the repository index page. Default |
158 | value: "a fast webinterface for the git dscm". | 162 | value: "a fast webinterface for the git dscm". |
159 | 163 | ||
160 | root-readme: | 164 | root-readme: |
161 | The content of the file specified with this option will be included | 165 | The content of the file specified with this option will be included |
162 | verbatim below the "about" link on the repository index page. Default | 166 | verbatim below the "about" link on the repository index page. Default |
163 | value: none. | 167 | value: none. |
164 | 168 | ||
165 | root-title | 169 | root-title |
166 | Text printed as heading on the repository index page. Default value: | 170 | Text printed as heading on the repository index page. Default value: |
167 | "Git Repository Browser". | 171 | "Git Repository Browser". |
168 | 172 | ||
169 | snapshots | 173 | snapshots |
170 | Text which specifies the default (and allowed) set of snapshot formats | 174 | Text which specifies the default (and allowed) set of snapshot formats |
171 | supported by cgit. The value is a space-separated list of zero or more | 175 | supported by cgit. The value is a space-separated list of zero or more |
172 | of the following values: | 176 | of the following values: |
173 | "tar" uncompressed tar-file | 177 | "tar" uncompressed tar-file |
174 | "tar.gz"gzip-compressed tar-file | 178 | "tar.gz"gzip-compressed tar-file |
175 | "tar.bz2"bzip-compressed tar-file | 179 | "tar.bz2"bzip-compressed tar-file |
176 | "zip" zip-file | 180 | "zip" zip-file |
177 | Default value: none. | 181 | Default value: none. |
178 | 182 | ||
179 | summary-branches | 183 | summary-branches |
180 | Specifies the number of branches to display in the repository "summary" | 184 | Specifies the number of branches to display in the repository "summary" |
181 | view. Default value: "10". | 185 | view. Default value: "10". |
182 | 186 | ||
183 | summary-log | 187 | summary-log |
184 | Specifies the number of log entries to display in the repository | 188 | Specifies the number of log entries to display in the repository |
185 | "summary" view. Default value: "10". | 189 | "summary" view. Default value: "10". |
186 | 190 | ||
187 | summary-tags | 191 | summary-tags |
188 | Specifies the number of tags to display in the repository "summary" | 192 | Specifies the number of tags to display in the repository "summary" |
189 | view. Default value: "10". | 193 | view. Default value: "10". |
190 | 194 | ||
191 | virtual-root | 195 | virtual-root |
192 | Url which, if specified, will be used as root for all cgit links. It | 196 | Url which, if specified, will be used as root for all cgit links. It |
193 | will also cause cgit to generate 'virtual urls', i.e. urls like | 197 | will also cause cgit to generate 'virtual urls', i.e. urls like |
194 | '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default | 198 | '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default |
195 | value: none. | 199 | value: none. |
196 | NOTE: cgit has recently learned how to use PATH_INFO to achieve the | 200 | NOTE: cgit has recently learned how to use PATH_INFO to achieve the |
197 | same kind of virtual urls, so this option will probably be deprecated. | 201 | same kind of virtual urls, so this option will probably be deprecated. |
198 | 202 | ||
199 | REPOSITORY SETTINGS | 203 | REPOSITORY SETTINGS |
200 | ------------------- | 204 | ------------------- |
201 | repo.clone-url | 205 | repo.clone-url |
202 | A list of space-separated urls which can be used to clone this repo. | 206 | A list of space-separated urls which can be used to clone this repo. |
203 | Default value: none. | 207 | Default value: none. |
204 | 208 | ||
205 | repo.defbranch | 209 | repo.defbranch |
206 | The name of the default branch for this repository. If no such branch | 210 | The name of the default branch for this repository. If no such branch |
207 | exists in the repository, the first branch name (when sorted) is used | 211 | exists in the repository, the first branch name (when sorted) is used |
208 | as default instead. Default value: "master". | 212 | as default instead. Default value: "master". |
209 | 213 | ||
210 | repo.desc | 214 | repo.desc |
211 | The value to show as repository description. Default value: none. | 215 | The value to show as repository description. Default value: none. |
212 | 216 | ||
213 | repo.enable-log-filecount | 217 | repo.enable-log-filecount |
214 | A flag which can be used to disable the global setting | 218 | A flag which can be used to disable the global setting |
215 | `enable-log-filecount'. Default value: none. | 219 | `enable-log-filecount'. Default value: none. |
216 | 220 | ||
217 | repo.enable-log-linecount | 221 | repo.enable-log-linecount |
218 | A flag which can be used to disable the global setting | 222 | A flag which can be used to disable the global setting |
219 | `enable-log-linecount'. Default value: none. | 223 | `enable-log-linecount'. Default value: none. |
220 | 224 | ||
225 | repo.enable-stats | ||
226 | A flag which can be used to disable the global setting | ||
227 | `enable-stats'. Default value: none. | ||
228 | |||
221 | repo.name | 229 | repo.name |
222 | The value to show as repository name. Default value: <repo.url>. | 230 | The value to show as repository name. Default value: <repo.url>. |
223 | 231 | ||
224 | repo.owner | 232 | repo.owner |
225 | A value used to identify the owner of the repository. Default value: | 233 | A value used to identify the owner of the repository. Default value: |
226 | none. | 234 | none. |
227 | 235 | ||
228 | repo.path | 236 | repo.path |
229 | An absolute path to the repository directory. For non-bare repositories | 237 | An absolute path to the repository directory. For non-bare repositories |
230 | this is the .git-directory. Default value: none. | 238 | this is the .git-directory. Default value: none. |
231 | 239 | ||
232 | repo.readme | 240 | repo.readme |
233 | A path (relative to <repo.path>) which specifies a file to include | 241 | A path (relative to <repo.path>) which specifies a file to include |
234 | verbatim as the "About" page for this repo. Default value: none. | 242 | verbatim as the "About" page for this repo. Default value: none. |
235 | 243 | ||
236 | repo.snapshots | 244 | repo.snapshots |
237 | A mask of allowed snapshot-formats for this repo, restricted by the | 245 | A mask of allowed snapshot-formats for this repo, restricted by the |
238 | "snapshots" global setting. Default value: <snapshots>. | 246 | "snapshots" global setting. Default value: <snapshots>. |
239 | 247 | ||
240 | repo.url | 248 | repo.url |
241 | The relative url used to access the repository. This must be the first | 249 | The relative url used to access the repository. This must be the first |
242 | setting specified for each repo. Default value: none. | 250 | setting specified for each repo. Default value: none. |
243 | 251 | ||
244 | 252 | ||
245 | EXAMPLE CGITRC FILE | 253 | EXAMPLE CGITRC FILE |
246 | ------------------- | 254 | ------------------- |
247 | 255 | ||
248 | # Enable caching of up to 1000 output entriess | 256 | # Enable caching of up to 1000 output entriess |
249 | cache-size=1000 | 257 | cache-size=1000 |
250 | 258 | ||
251 | 259 | ||
252 | # Specify some default clone prefixes | 260 | # Specify some default clone prefixes |
253 | clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git | 261 | clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git |
254 | 262 | ||
255 | # Specify the css url | 263 | # Specify the css url |
256 | css=/css/cgit.css | 264 | css=/css/cgit.css |
257 | 265 | ||
258 | 266 | ||
259 | # Show extra links for each repository on the index page | 267 | # Show extra links for each repository on the index page |
260 | enable-index-links=1 | 268 | enable-index-links=1 |
261 | 269 | ||
262 | 270 | ||
263 | # Show number of affected files per commit on the log pages | 271 | # Show number of affected files per commit on the log pages |
264 | enable-log-filecount=1 | 272 | enable-log-filecount=1 |
265 | 273 | ||
266 | 274 | ||
267 | # Show number of added/removed lines per commit on the log pages | 275 | # Show number of added/removed lines per commit on the log pages |
268 | enable-log-linecount=1 | 276 | enable-log-linecount=1 |
269 | 277 | ||
270 | 278 | ||
271 | # Add a cgit favicon | 279 | # Add a cgit favicon |
272 | favicon=/favicon.ico | 280 | favicon=/favicon.ico |
273 | 281 | ||
274 | 282 | ||
275 | # Use a custom logo | 283 | # Use a custom logo |
276 | logo=/img/mylogo.png | 284 | logo=/img/mylogo.png |
277 | 285 | ||
278 | 286 | ||
279 | # Set the title and heading of the repository index page | 287 | # Set the title and heading of the repository index page |
280 | root-title=foobar.com git repositories | 288 | root-title=foobar.com git repositories |
281 | 289 | ||
282 | 290 | ||
283 | # Set a subheading for the repository index page | 291 | # Set a subheading for the repository index page |
284 | root-desc=tracking the foobar development | 292 | root-desc=tracking the foobar development |
285 | 293 | ||
286 | 294 | ||
287 | # Include some more info about foobar.com on the index page | 295 | # Include some more info about foobar.com on the index page |
288 | root-readme=/var/www/htdocs/about.html | 296 | root-readme=/var/www/htdocs/about.html |
289 | 297 | ||
290 | 298 | ||
291 | # Allow download of tar.gz, tar.bz and zip-files | 299 | # Allow download of tar.gz, tar.bz and zip-files |
292 | snapshots=tar.gz tar.bz zip | 300 | snapshots=tar.gz tar.bz zip |
293 | 301 | ||
294 | 302 | ||
295 | ## | 303 | ## |
296 | ## List of repositories. | 304 | ## List of repositories. |
297 | ## PS: Any repositories listed when repo.group is unset will not be | 305 | ## PS: Any repositories listed when repo.group is unset will not be |
298 | ## displayed under a group heading | 306 | ## displayed under a group heading |
299 | ## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos') | 307 | ## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos') |
300 | ## and included like this: | 308 | ## and included like this: |
301 | ## include=/etc/cgitrepos | 309 | ## include=/etc/cgitrepos |
302 | ## | 310 | ## |
303 | 311 | ||
304 | 312 | ||
305 | repo.url=foo | 313 | repo.url=foo |
306 | repo.path=/pub/git/foo.git | 314 | repo.path=/pub/git/foo.git |
307 | repo.desc=the master foo repository | 315 | repo.desc=the master foo repository |
308 | repo.owner=fooman@foobar.com | 316 | repo.owner=fooman@foobar.com |
309 | repo.readme=info/web/about.html | 317 | repo.readme=info/web/about.html |
310 | 318 | ||
311 | 319 | ||
312 | repo.url=bar | 320 | repo.url=bar |
313 | repo.path=/pub/git/bar.git | 321 | repo.path=/pub/git/bar.git |
314 | repo.desc=the bars for your foo | 322 | repo.desc=the bars for your foo |
315 | repo.owner=barman@foobar.com | 323 | repo.owner=barman@foobar.com |
316 | repo.readme=info/web/about.html | 324 | repo.readme=info/web/about.html |
317 | 325 | ||
318 | 326 | ||
319 | # The next repositories will be displayed under the 'extras' heading | 327 | # The next repositories will be displayed under the 'extras' heading |
320 | repo.group=extras | 328 | repo.group=extras |
321 | 329 | ||
322 | 330 | ||
323 | repo.url=baz | 331 | repo.url=baz |
324 | repo.path=/pub/git/baz.git | 332 | repo.path=/pub/git/baz.git |
325 | repo.desc=a set of extensions for bar users | 333 | repo.desc=a set of extensions for bar users |
326 | 334 | ||
327 | repo.url=wiz | 335 | repo.url=wiz |
328 | repo.path=/pub/git/wiz.git | 336 | repo.path=/pub/git/wiz.git |
329 | repo.desc=the wizard of foo | 337 | repo.desc=the wizard of foo |
330 | 338 | ||
331 | 339 | ||
332 | # Add some mirrored repositories | 340 | # Add some mirrored repositories |
333 | repo.group=mirrors | 341 | repo.group=mirrors |
334 | 342 | ||
335 | 343 | ||
336 | repo.url=git | 344 | repo.url=git |
337 | repo.path=/pub/git/git.git | 345 | repo.path=/pub/git/git.git |
338 | repo.desc=the dscm | 346 | repo.desc=the dscm |
339 | 347 | ||
340 | 348 | ||
341 | repo.url=linux | 349 | repo.url=linux |
342 | repo.path=/pub/git/linux.git | 350 | repo.path=/pub/git/linux.git |
343 | repo.desc=the kernel | 351 | repo.desc=the kernel |
344 | 352 | ||
345 | # Disable adhoc downloads of this repo | 353 | # Disable adhoc downloads of this repo |
346 | repo.snapshots=0 | 354 | repo.snapshots=0 |
347 | 355 | ||
348 | # Disable line-counts for this repo | 356 | # Disable line-counts for this repo |
349 | repo.enable-log-linecount=0 | 357 | repo.enable-log-linecount=0 |
350 | 358 | ||
351 | 359 | ||
352 | BUGS | 360 | BUGS |
353 | ---- | 361 | ---- |
354 | Comments currently cannot appear on the same line as a setting; the comment | 362 | Comments currently cannot appear on the same line as a setting; the comment |
355 | will be included as part of the value. E.g. this line: | 363 | will be included as part of the value. E.g. this line: |
356 | 364 | ||
357 | robots=index # allow indexing | 365 | robots=index # allow indexing |
358 | 366 | ||
359 | will generate the following html element: | 367 | will generate the following html element: |
360 | 368 | ||
361 | <meta name='robots' content='index # allow indexing'/> | 369 | <meta name='robots' content='index # allow indexing'/> |
362 | 370 | ||
363 | 371 | ||
364 | 372 | ||
365 | AUTHOR | 373 | AUTHOR |
366 | ------ | 374 | ------ |
367 | Lars Hjemli <hjemli@gmail.com> | 375 | Lars Hjemli <hjemli@gmail.com> |
@@ -1,165 +1,175 @@ | |||
1 | /* cmd.c: the cgit command dispatcher | 1 | /* cmd.c: the cgit command dispatcher |
2 | * | 2 | * |
3 | * Copyright (C) 2008 Lars Hjemli | 3 | * Copyright (C) 2008 Lars Hjemli |
4 | * | 4 | * |
5 | * Licensed under GNU General Public License v2 | 5 | * Licensed under GNU General Public License v2 |
6 | * (see COPYING for full license text) | 6 | * (see COPYING for full license text) |
7 | */ | 7 | */ |
8 | 8 | ||
9 | #include "cgit.h" | 9 | #include "cgit.h" |
10 | #include "cmd.h" | 10 | #include "cmd.h" |
11 | #include "cache.h" | 11 | #include "cache.h" |
12 | #include "ui-shared.h" | 12 | #include "ui-shared.h" |
13 | #include "ui-atom.h" | 13 | #include "ui-atom.h" |
14 | #include "ui-blob.h" | 14 | #include "ui-blob.h" |
15 | #include "ui-clone.h" | 15 | #include "ui-clone.h" |
16 | #include "ui-commit.h" | 16 | #include "ui-commit.h" |
17 | #include "ui-diff.h" | 17 | #include "ui-diff.h" |
18 | #include "ui-log.h" | 18 | #include "ui-log.h" |
19 | #include "ui-patch.h" | 19 | #include "ui-patch.h" |
20 | #include "ui-plain.h" | 20 | #include "ui-plain.h" |
21 | #include "ui-refs.h" | 21 | #include "ui-refs.h" |
22 | #include "ui-repolist.h" | 22 | #include "ui-repolist.h" |
23 | #include "ui-snapshot.h" | 23 | #include "ui-snapshot.h" |
24 | #include "ui-stats.h" | ||
24 | #include "ui-summary.h" | 25 | #include "ui-summary.h" |
25 | #include "ui-tag.h" | 26 | #include "ui-tag.h" |
26 | #include "ui-tree.h" | 27 | #include "ui-tree.h" |
27 | 28 | ||
28 | static void HEAD_fn(struct cgit_context *ctx) | 29 | static void HEAD_fn(struct cgit_context *ctx) |
29 | { | 30 | { |
30 | cgit_clone_head(ctx); | 31 | cgit_clone_head(ctx); |
31 | } | 32 | } |
32 | 33 | ||
33 | static void atom_fn(struct cgit_context *ctx) | 34 | static void atom_fn(struct cgit_context *ctx) |
34 | { | 35 | { |
35 | cgit_print_atom(ctx->qry.head, ctx->qry.path, 10); | 36 | cgit_print_atom(ctx->qry.head, ctx->qry.path, 10); |
36 | } | 37 | } |
37 | 38 | ||
38 | static void about_fn(struct cgit_context *ctx) | 39 | static void about_fn(struct cgit_context *ctx) |
39 | { | 40 | { |
40 | if (ctx->repo) | 41 | if (ctx->repo) |
41 | cgit_print_repo_readme(); | 42 | cgit_print_repo_readme(); |
42 | else | 43 | else |
43 | cgit_print_site_readme(); | 44 | cgit_print_site_readme(); |
44 | } | 45 | } |
45 | 46 | ||
46 | static void blob_fn(struct cgit_context *ctx) | 47 | static void blob_fn(struct cgit_context *ctx) |
47 | { | 48 | { |
48 | cgit_print_blob(ctx->qry.sha1, ctx->qry.path, ctx->qry.head); | 49 | cgit_print_blob(ctx->qry.sha1, ctx->qry.path, ctx->qry.head); |
49 | } | 50 | } |
50 | 51 | ||
51 | static void commit_fn(struct cgit_context *ctx) | 52 | static void commit_fn(struct cgit_context *ctx) |
52 | { | 53 | { |
53 | cgit_print_commit(ctx->qry.sha1); | 54 | cgit_print_commit(ctx->qry.sha1); |
54 | } | 55 | } |
55 | 56 | ||
56 | static void diff_fn(struct cgit_context *ctx) | 57 | static void diff_fn(struct cgit_context *ctx) |
57 | { | 58 | { |
58 | cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path); | 59 | cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path); |
59 | } | 60 | } |
60 | 61 | ||
61 | static void info_fn(struct cgit_context *ctx) | 62 | static void info_fn(struct cgit_context *ctx) |
62 | { | 63 | { |
63 | cgit_clone_info(ctx); | 64 | cgit_clone_info(ctx); |
64 | } | 65 | } |
65 | 66 | ||
66 | static void log_fn(struct cgit_context *ctx) | 67 | static void log_fn(struct cgit_context *ctx) |
67 | { | 68 | { |
68 | cgit_print_log(ctx->qry.sha1, ctx->qry.ofs, ctx->cfg.max_commit_count, | 69 | cgit_print_log(ctx->qry.sha1, ctx->qry.ofs, ctx->cfg.max_commit_count, |
69 | ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1); | 70 | ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1); |
70 | } | 71 | } |
71 | 72 | ||
72 | static void ls_cache_fn(struct cgit_context *ctx) | 73 | static void ls_cache_fn(struct cgit_context *ctx) |
73 | { | 74 | { |
74 | ctx->page.mimetype = "text/plain"; | 75 | ctx->page.mimetype = "text/plain"; |
75 | ctx->page.filename = "ls-cache.txt"; | 76 | ctx->page.filename = "ls-cache.txt"; |
76 | cgit_print_http_headers(ctx); | 77 | cgit_print_http_headers(ctx); |
77 | cache_ls(ctx->cfg.cache_root); | 78 | cache_ls(ctx->cfg.cache_root); |
78 | } | 79 | } |
79 | 80 | ||
80 | static void objects_fn(struct cgit_context *ctx) | 81 | static void objects_fn(struct cgit_context *ctx) |
81 | { | 82 | { |
82 | cgit_clone_objects(ctx); | 83 | cgit_clone_objects(ctx); |
83 | } | 84 | } |
84 | 85 | ||
85 | static void repolist_fn(struct cgit_context *ctx) | 86 | static void repolist_fn(struct cgit_context *ctx) |
86 | { | 87 | { |
87 | cgit_print_repolist(); | 88 | cgit_print_repolist(); |
88 | } | 89 | } |
89 | 90 | ||
90 | static void patch_fn(struct cgit_context *ctx) | 91 | static void patch_fn(struct cgit_context *ctx) |
91 | { | 92 | { |
92 | cgit_print_patch(ctx->qry.sha1); | 93 | cgit_print_patch(ctx->qry.sha1); |
93 | } | 94 | } |
94 | 95 | ||
95 | static void plain_fn(struct cgit_context *ctx) | 96 | static void plain_fn(struct cgit_context *ctx) |
96 | { | 97 | { |
97 | cgit_print_plain(ctx); | 98 | cgit_print_plain(ctx); |
98 | } | 99 | } |
99 | 100 | ||
100 | static void refs_fn(struct cgit_context *ctx) | 101 | static void refs_fn(struct cgit_context *ctx) |
101 | { | 102 | { |
102 | cgit_print_refs(); | 103 | cgit_print_refs(); |
103 | } | 104 | } |
104 | 105 | ||
105 | static void snapshot_fn(struct cgit_context *ctx) | 106 | static void snapshot_fn(struct cgit_context *ctx) |
106 | { | 107 | { |
107 | cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, | 108 | cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, |
108 | cgit_repobasename(ctx->repo->url), ctx->qry.path, | 109 | cgit_repobasename(ctx->repo->url), ctx->qry.path, |
109 | ctx->repo->snapshots, ctx->qry.nohead); | 110 | ctx->repo->snapshots, ctx->qry.nohead); |
110 | } | 111 | } |
111 | 112 | ||
113 | static void stats_fn(struct cgit_context *ctx) | ||
114 | { | ||
115 | if (ctx->repo->enable_stats) | ||
116 | cgit_show_stats(ctx); | ||
117 | else | ||
118 | cgit_print_error("Stats disabled for this repo"); | ||
119 | } | ||
120 | |||
112 | static void summary_fn(struct cgit_context *ctx) | 121 | static void summary_fn(struct cgit_context *ctx) |
113 | { | 122 | { |
114 | cgit_print_summary(); | 123 | cgit_print_summary(); |
115 | } | 124 | } |
116 | 125 | ||
117 | static void tag_fn(struct cgit_context *ctx) | 126 | static void tag_fn(struct cgit_context *ctx) |
118 | { | 127 | { |
119 | cgit_print_tag(ctx->qry.sha1); | 128 | cgit_print_tag(ctx->qry.sha1); |
120 | } | 129 | } |
121 | 130 | ||
122 | static void tree_fn(struct cgit_context *ctx) | 131 | static void tree_fn(struct cgit_context *ctx) |
123 | { | 132 | { |
124 | cgit_print_tree(ctx->qry.sha1, ctx->qry.path); | 133 | cgit_print_tree(ctx->qry.sha1, ctx->qry.path); |
125 | } | 134 | } |
126 | 135 | ||
127 | #define def_cmd(name, want_repo, want_layout) \ | 136 | #define def_cmd(name, want_repo, want_layout) \ |
128 | {#name, name##_fn, want_repo, want_layout} | 137 | {#name, name##_fn, want_repo, want_layout} |
129 | 138 | ||
130 | struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx) | 139 | struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx) |
131 | { | 140 | { |
132 | static struct cgit_cmd cmds[] = { | 141 | static struct cgit_cmd cmds[] = { |
133 | def_cmd(HEAD, 1, 0), | 142 | def_cmd(HEAD, 1, 0), |
134 | def_cmd(atom, 1, 0), | 143 | def_cmd(atom, 1, 0), |
135 | def_cmd(about, 0, 1), | 144 | def_cmd(about, 0, 1), |
136 | def_cmd(blob, 1, 0), | 145 | def_cmd(blob, 1, 0), |
137 | def_cmd(commit, 1, 1), | 146 | def_cmd(commit, 1, 1), |
138 | def_cmd(diff, 1, 1), | 147 | def_cmd(diff, 1, 1), |
139 | def_cmd(info, 1, 0), | 148 | def_cmd(info, 1, 0), |
140 | def_cmd(log, 1, 1), | 149 | def_cmd(log, 1, 1), |
141 | def_cmd(ls_cache, 0, 0), | 150 | def_cmd(ls_cache, 0, 0), |
142 | def_cmd(objects, 1, 0), | 151 | def_cmd(objects, 1, 0), |
143 | def_cmd(patch, 1, 0), | 152 | def_cmd(patch, 1, 0), |
144 | def_cmd(plain, 1, 0), | 153 | def_cmd(plain, 1, 0), |
145 | def_cmd(refs, 1, 1), | 154 | def_cmd(refs, 1, 1), |
146 | def_cmd(repolist, 0, 0), | 155 | def_cmd(repolist, 0, 0), |
147 | def_cmd(snapshot, 1, 0), | 156 | def_cmd(snapshot, 1, 0), |
157 | def_cmd(stats, 1, 1), | ||
148 | def_cmd(summary, 1, 1), | 158 | def_cmd(summary, 1, 1), |
149 | def_cmd(tag, 1, 1), | 159 | def_cmd(tag, 1, 1), |
150 | def_cmd(tree, 1, 1), | 160 | def_cmd(tree, 1, 1), |
151 | }; | 161 | }; |
152 | int i; | 162 | int i; |
153 | 163 | ||
154 | if (ctx->qry.page == NULL) { | 164 | if (ctx->qry.page == NULL) { |
155 | if (ctx->repo) | 165 | if (ctx->repo) |
156 | ctx->qry.page = "summary"; | 166 | ctx->qry.page = "summary"; |
157 | else | 167 | else |
158 | ctx->qry.page = "repolist"; | 168 | ctx->qry.page = "repolist"; |
159 | } | 169 | } |
160 | 170 | ||
161 | for(i = 0; i < sizeof(cmds)/sizeof(*cmds); i++) | 171 | for(i = 0; i < sizeof(cmds)/sizeof(*cmds); i++) |
162 | if (!strcmp(ctx->qry.page, cmds[i].name)) | 172 | if (!strcmp(ctx->qry.page, cmds[i].name)) |
163 | return &cmds[i]; | 173 | return &cmds[i]; |
164 | return NULL; | 174 | return NULL; |
165 | } | 175 | } |
@@ -1,344 +1,345 @@ | |||
1 | /* shared.c: global vars + some callback functions | 1 | /* shared.c: global vars + some callback functions |
2 | * | 2 | * |
3 | * Copyright (C) 2006 Lars Hjemli | 3 | * Copyright (C) 2006 Lars Hjemli |
4 | * | 4 | * |
5 | * Licensed under GNU General Public License v2 | 5 | * Licensed under GNU General Public License v2 |
6 | * (see COPYING for full license text) | 6 | * (see COPYING for full license text) |
7 | */ | 7 | */ |
8 | 8 | ||
9 | #include "cgit.h" | 9 | #include "cgit.h" |
10 | 10 | ||
11 | struct cgit_repolist cgit_repolist; | 11 | struct cgit_repolist cgit_repolist; |
12 | struct cgit_context ctx; | 12 | struct cgit_context ctx; |
13 | int cgit_cmd; | 13 | int cgit_cmd; |
14 | 14 | ||
15 | int chk_zero(int result, char *msg) | 15 | int chk_zero(int result, char *msg) |
16 | { | 16 | { |
17 | if (result != 0) | 17 | if (result != 0) |
18 | die("%s: %s", msg, strerror(errno)); | 18 | die("%s: %s", msg, strerror(errno)); |
19 | return result; | 19 | return result; |
20 | } | 20 | } |
21 | 21 | ||
22 | int chk_positive(int result, char *msg) | 22 | int chk_positive(int result, char *msg) |
23 | { | 23 | { |
24 | if (result <= 0) | 24 | if (result <= 0) |
25 | die("%s: %s", msg, strerror(errno)); | 25 | die("%s: %s", msg, strerror(errno)); |
26 | return result; | 26 | return result; |
27 | } | 27 | } |
28 | 28 | ||
29 | int chk_non_negative(int result, char *msg) | 29 | int chk_non_negative(int result, char *msg) |
30 | { | 30 | { |
31 | if (result < 0) | 31 | if (result < 0) |
32 | die("%s: %s",msg, strerror(errno)); | 32 | die("%s: %s",msg, strerror(errno)); |
33 | return result; | 33 | return result; |
34 | } | 34 | } |
35 | 35 | ||
36 | struct cgit_repo *cgit_add_repo(const char *url) | 36 | struct cgit_repo *cgit_add_repo(const char *url) |
37 | { | 37 | { |
38 | struct cgit_repo *ret; | 38 | struct cgit_repo *ret; |
39 | 39 | ||
40 | if (++cgit_repolist.count > cgit_repolist.length) { | 40 | if (++cgit_repolist.count > cgit_repolist.length) { |
41 | if (cgit_repolist.length == 0) | 41 | if (cgit_repolist.length == 0) |
42 | cgit_repolist.length = 8; | 42 | cgit_repolist.length = 8; |
43 | else | 43 | else |
44 | cgit_repolist.length *= 2; | 44 | cgit_repolist.length *= 2; |
45 | cgit_repolist.repos = xrealloc(cgit_repolist.repos, | 45 | cgit_repolist.repos = xrealloc(cgit_repolist.repos, |
46 | cgit_repolist.length * | 46 | cgit_repolist.length * |
47 | sizeof(struct cgit_repo)); | 47 | sizeof(struct cgit_repo)); |
48 | } | 48 | } |
49 | 49 | ||
50 | ret = &cgit_repolist.repos[cgit_repolist.count-1]; | 50 | ret = &cgit_repolist.repos[cgit_repolist.count-1]; |
51 | ret->url = trim_end(url, '/'); | 51 | ret->url = trim_end(url, '/'); |
52 | ret->name = ret->url; | 52 | ret->name = ret->url; |
53 | ret->path = NULL; | 53 | ret->path = NULL; |
54 | ret->desc = "[no description]"; | 54 | ret->desc = "[no description]"; |
55 | ret->owner = NULL; | 55 | ret->owner = NULL; |
56 | ret->group = ctx.cfg.repo_group; | 56 | ret->group = ctx.cfg.repo_group; |
57 | ret->defbranch = "master"; | 57 | ret->defbranch = "master"; |
58 | ret->snapshots = ctx.cfg.snapshots; | 58 | ret->snapshots = ctx.cfg.snapshots; |
59 | ret->enable_log_filecount = ctx.cfg.enable_log_filecount; | 59 | ret->enable_log_filecount = ctx.cfg.enable_log_filecount; |
60 | ret->enable_log_linecount = ctx.cfg.enable_log_linecount; | 60 | ret->enable_log_linecount = ctx.cfg.enable_log_linecount; |
61 | ret->enable_stats = ctx.cfg.enable_stats; | ||
61 | ret->module_link = ctx.cfg.module_link; | 62 | ret->module_link = ctx.cfg.module_link; |
62 | ret->readme = NULL; | 63 | ret->readme = NULL; |
63 | return ret; | 64 | return ret; |
64 | } | 65 | } |
65 | 66 | ||
66 | struct cgit_repo *cgit_get_repoinfo(const char *url) | 67 | struct cgit_repo *cgit_get_repoinfo(const char *url) |
67 | { | 68 | { |
68 | int i; | 69 | int i; |
69 | struct cgit_repo *repo; | 70 | struct cgit_repo *repo; |
70 | 71 | ||
71 | for (i=0; i<cgit_repolist.count; i++) { | 72 | for (i=0; i<cgit_repolist.count; i++) { |
72 | repo = &cgit_repolist.repos[i]; | 73 | repo = &cgit_repolist.repos[i]; |
73 | if (!strcmp(repo->url, url)) | 74 | if (!strcmp(repo->url, url)) |
74 | return repo; | 75 | return repo; |
75 | } | 76 | } |
76 | return NULL; | 77 | return NULL; |
77 | } | 78 | } |
78 | 79 | ||
79 | void *cgit_free_commitinfo(struct commitinfo *info) | 80 | void *cgit_free_commitinfo(struct commitinfo *info) |
80 | { | 81 | { |
81 | free(info->author); | 82 | free(info->author); |
82 | free(info->author_email); | 83 | free(info->author_email); |
83 | free(info->committer); | 84 | free(info->committer); |
84 | free(info->committer_email); | 85 | free(info->committer_email); |
85 | free(info->subject); | 86 | free(info->subject); |
86 | free(info->msg); | 87 | free(info->msg); |
87 | free(info->msg_encoding); | 88 | free(info->msg_encoding); |
88 | free(info); | 89 | free(info); |
89 | return NULL; | 90 | return NULL; |
90 | } | 91 | } |
91 | 92 | ||
92 | char *trim_end(const char *str, char c) | 93 | char *trim_end(const char *str, char c) |
93 | { | 94 | { |
94 | int len; | 95 | int len; |
95 | char *s, *t; | 96 | char *s, *t; |
96 | 97 | ||
97 | if (str == NULL) | 98 | if (str == NULL) |
98 | return NULL; | 99 | return NULL; |
99 | t = (char *)str; | 100 | t = (char *)str; |
100 | len = strlen(t); | 101 | len = strlen(t); |
101 | while(len > 0 && t[len - 1] == c) | 102 | while(len > 0 && t[len - 1] == c) |
102 | len--; | 103 | len--; |
103 | 104 | ||
104 | if (len == 0) | 105 | if (len == 0) |
105 | return NULL; | 106 | return NULL; |
106 | 107 | ||
107 | c = t[len]; | 108 | c = t[len]; |
108 | t[len] = '\0'; | 109 | t[len] = '\0'; |
109 | s = xstrdup(t); | 110 | s = xstrdup(t); |
110 | t[len] = c; | 111 | t[len] = c; |
111 | return s; | 112 | return s; |
112 | } | 113 | } |
113 | 114 | ||
114 | char *strlpart(char *txt, int maxlen) | 115 | char *strlpart(char *txt, int maxlen) |
115 | { | 116 | { |
116 | char *result; | 117 | char *result; |
117 | 118 | ||
118 | if (!txt) | 119 | if (!txt) |
119 | return txt; | 120 | return txt; |
120 | 121 | ||
121 | if (strlen(txt) <= maxlen) | 122 | if (strlen(txt) <= maxlen) |
122 | return txt; | 123 | return txt; |
123 | result = xmalloc(maxlen + 1); | 124 | result = xmalloc(maxlen + 1); |
124 | memcpy(result, txt, maxlen - 3); | 125 | memcpy(result, txt, maxlen - 3); |
125 | result[maxlen-1] = result[maxlen-2] = result[maxlen-3] = '.'; | 126 | result[maxlen-1] = result[maxlen-2] = result[maxlen-3] = '.'; |
126 | result[maxlen] = '\0'; | 127 | result[maxlen] = '\0'; |
127 | return result; | 128 | return result; |
128 | } | 129 | } |
129 | 130 | ||
130 | char *strrpart(char *txt, int maxlen) | 131 | char *strrpart(char *txt, int maxlen) |
131 | { | 132 | { |
132 | char *result; | 133 | char *result; |
133 | 134 | ||
134 | if (!txt) | 135 | if (!txt) |
135 | return txt; | 136 | return txt; |
136 | 137 | ||
137 | if (strlen(txt) <= maxlen) | 138 | if (strlen(txt) <= maxlen) |
138 | return txt; | 139 | return txt; |
139 | result = xmalloc(maxlen + 1); | 140 | result = xmalloc(maxlen + 1); |
140 | memcpy(result + 3, txt + strlen(txt) - maxlen + 4, maxlen - 3); | 141 | memcpy(result + 3, txt + strlen(txt) - maxlen + 4, maxlen - 3); |
141 | result[0] = result[1] = result[2] = '.'; | 142 | result[0] = result[1] = result[2] = '.'; |
142 | return result; | 143 | return result; |
143 | } | 144 | } |
144 | 145 | ||
145 | void cgit_add_ref(struct reflist *list, struct refinfo *ref) | 146 | void cgit_add_ref(struct reflist *list, struct refinfo *ref) |
146 | { | 147 | { |
147 | size_t size; | 148 | size_t size; |
148 | 149 | ||
149 | if (list->count >= list->alloc) { | 150 | if (list->count >= list->alloc) { |
150 | list->alloc += (list->alloc ? list->alloc : 4); | 151 | list->alloc += (list->alloc ? list->alloc : 4); |
151 | size = list->alloc * sizeof(struct refinfo *); | 152 | size = list->alloc * sizeof(struct refinfo *); |
152 | list->refs = xrealloc(list->refs, size); | 153 | list->refs = xrealloc(list->refs, size); |
153 | } | 154 | } |
154 | list->refs[list->count++] = ref; | 155 | list->refs[list->count++] = ref; |
155 | } | 156 | } |
156 | 157 | ||
157 | struct refinfo *cgit_mk_refinfo(const char *refname, const unsigned char *sha1) | 158 | struct refinfo *cgit_mk_refinfo(const char *refname, const unsigned char *sha1) |
158 | { | 159 | { |
159 | struct refinfo *ref; | 160 | struct refinfo *ref; |
160 | 161 | ||
161 | ref = xmalloc(sizeof (struct refinfo)); | 162 | ref = xmalloc(sizeof (struct refinfo)); |
162 | ref->refname = xstrdup(refname); | 163 | ref->refname = xstrdup(refname); |
163 | ref->object = parse_object(sha1); | 164 | ref->object = parse_object(sha1); |
164 | switch (ref->object->type) { | 165 | switch (ref->object->type) { |
165 | case OBJ_TAG: | 166 | case OBJ_TAG: |
166 | ref->tag = cgit_parse_tag((struct tag *)ref->object); | 167 | ref->tag = cgit_parse_tag((struct tag *)ref->object); |
167 | break; | 168 | break; |
168 | case OBJ_COMMIT: | 169 | case OBJ_COMMIT: |
169 | ref->commit = cgit_parse_commit((struct commit *)ref->object); | 170 | ref->commit = cgit_parse_commit((struct commit *)ref->object); |
170 | break; | 171 | break; |
171 | } | 172 | } |
172 | return ref; | 173 | return ref; |
173 | } | 174 | } |
174 | 175 | ||
175 | int cgit_refs_cb(const char *refname, const unsigned char *sha1, int flags, | 176 | int cgit_refs_cb(const char *refname, const unsigned char *sha1, int flags, |
176 | void *cb_data) | 177 | void *cb_data) |
177 | { | 178 | { |
178 | struct reflist *list = (struct reflist *)cb_data; | 179 | struct reflist *list = (struct reflist *)cb_data; |
179 | struct refinfo *info = cgit_mk_refinfo(refname, sha1); | 180 | struct refinfo *info = cgit_mk_refinfo(refname, sha1); |
180 | 181 | ||
181 | if (info) | 182 | if (info) |
182 | cgit_add_ref(list, info); | 183 | cgit_add_ref(list, info); |
183 | return 0; | 184 | return 0; |
184 | } | 185 | } |
185 | 186 | ||
186 | void cgit_diff_tree_cb(struct diff_queue_struct *q, | 187 | void cgit_diff_tree_cb(struct diff_queue_struct *q, |
187 | struct diff_options *options, void *data) | 188 | struct diff_options *options, void *data) |
188 | { | 189 | { |
189 | int i; | 190 | int i; |
190 | 191 | ||
191 | for (i = 0; i < q->nr; i++) { | 192 | for (i = 0; i < q->nr; i++) { |
192 | if (q->queue[i]->status == 'U') | 193 | if (q->queue[i]->status == 'U') |
193 | continue; | 194 | continue; |
194 | ((filepair_fn)data)(q->queue[i]); | 195 | ((filepair_fn)data)(q->queue[i]); |
195 | } | 196 | } |
196 | } | 197 | } |
197 | 198 | ||
198 | static int load_mmfile(mmfile_t *file, const unsigned char *sha1) | 199 | static int load_mmfile(mmfile_t *file, const unsigned char *sha1) |
199 | { | 200 | { |
200 | enum object_type type; | 201 | enum object_type type; |
201 | 202 | ||
202 | if (is_null_sha1(sha1)) { | 203 | if (is_null_sha1(sha1)) { |
203 | file->ptr = (char *)""; | 204 | file->ptr = (char *)""; |
204 | file->size = 0; | 205 | file->size = 0; |
205 | } else { | 206 | } else { |
206 | file->ptr = read_sha1_file(sha1, &type, | 207 | file->ptr = read_sha1_file(sha1, &type, |
207 | (unsigned long *)&file->size); | 208 | (unsigned long *)&file->size); |
208 | } | 209 | } |
209 | return 1; | 210 | return 1; |
210 | } | 211 | } |
211 | 212 | ||
212 | /* | 213 | /* |
213 | * Receive diff-buffers from xdiff and concatenate them as | 214 | * Receive diff-buffers from xdiff and concatenate them as |
214 | * needed across multiple callbacks. | 215 | * needed across multiple callbacks. |
215 | * | 216 | * |
216 | * This is basically a copy of xdiff-interface.c/xdiff_outf(), | 217 | * This is basically a copy of xdiff-interface.c/xdiff_outf(), |
217 | * ripped from git and modified to use globals instead of | 218 | * ripped from git and modified to use globals instead of |
218 | * a special callback-struct. | 219 | * a special callback-struct. |
219 | */ | 220 | */ |
220 | char *diffbuf = NULL; | 221 | char *diffbuf = NULL; |
221 | int buflen = 0; | 222 | int buflen = 0; |
222 | 223 | ||
223 | int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf) | 224 | int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf) |
224 | { | 225 | { |
225 | int i; | 226 | int i; |
226 | 227 | ||
227 | for (i = 0; i < nbuf; i++) { | 228 | for (i = 0; i < nbuf; i++) { |
228 | if (mb[i].ptr[mb[i].size-1] != '\n') { | 229 | if (mb[i].ptr[mb[i].size-1] != '\n') { |
229 | /* Incomplete line */ | 230 | /* Incomplete line */ |
230 | diffbuf = xrealloc(diffbuf, buflen + mb[i].size); | 231 | diffbuf = xrealloc(diffbuf, buflen + mb[i].size); |
231 | memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); | 232 | memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); |
232 | buflen += mb[i].size; | 233 | buflen += mb[i].size; |
233 | continue; | 234 | continue; |
234 | } | 235 | } |
235 | 236 | ||
236 | /* we have a complete line */ | 237 | /* we have a complete line */ |
237 | if (!diffbuf) { | 238 | if (!diffbuf) { |
238 | ((linediff_fn)priv)(mb[i].ptr, mb[i].size); | 239 | ((linediff_fn)priv)(mb[i].ptr, mb[i].size); |
239 | continue; | 240 | continue; |
240 | } | 241 | } |
241 | diffbuf = xrealloc(diffbuf, buflen + mb[i].size); | 242 | diffbuf = xrealloc(diffbuf, buflen + mb[i].size); |
242 | memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); | 243 | memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); |
243 | ((linediff_fn)priv)(diffbuf, buflen + mb[i].size); | 244 | ((linediff_fn)priv)(diffbuf, buflen + mb[i].size); |
244 | free(diffbuf); | 245 | free(diffbuf); |
245 | diffbuf = NULL; | 246 | diffbuf = NULL; |
246 | buflen = 0; | 247 | buflen = 0; |
247 | } | 248 | } |
248 | if (diffbuf) { | 249 | if (diffbuf) { |
249 | ((linediff_fn)priv)(diffbuf, buflen); | 250 | ((linediff_fn)priv)(diffbuf, buflen); |
250 | free(diffbuf); | 251 | free(diffbuf); |
251 | diffbuf = NULL; | 252 | diffbuf = NULL; |
252 | buflen = 0; | 253 | buflen = 0; |
253 | } | 254 | } |
254 | return 0; | 255 | return 0; |
255 | } | 256 | } |
256 | 257 | ||
257 | int cgit_diff_files(const unsigned char *old_sha1, | 258 | int cgit_diff_files(const unsigned char *old_sha1, |
258 | const unsigned char *new_sha1, | 259 | const unsigned char *new_sha1, |
259 | linediff_fn fn) | 260 | linediff_fn fn) |
260 | { | 261 | { |
261 | mmfile_t file1, file2; | 262 | mmfile_t file1, file2; |
262 | xpparam_t diff_params; | 263 | xpparam_t diff_params; |
263 | xdemitconf_t emit_params; | 264 | xdemitconf_t emit_params; |
264 | xdemitcb_t emit_cb; | 265 | xdemitcb_t emit_cb; |
265 | 266 | ||
266 | if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1)) | 267 | if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1)) |
267 | return 1; | 268 | return 1; |
268 | 269 | ||
269 | diff_params.flags = XDF_NEED_MINIMAL; | 270 | diff_params.flags = XDF_NEED_MINIMAL; |
270 | emit_params.ctxlen = 3; | 271 | emit_params.ctxlen = 3; |
271 | emit_params.flags = XDL_EMIT_FUNCNAMES; | 272 | emit_params.flags = XDL_EMIT_FUNCNAMES; |
272 | emit_params.find_func = NULL; | 273 | emit_params.find_func = NULL; |
273 | emit_cb.outf = filediff_cb; | 274 | emit_cb.outf = filediff_cb; |
274 | emit_cb.priv = fn; | 275 | emit_cb.priv = fn; |
275 | xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb); | 276 | xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb); |
276 | return 0; | 277 | return 0; |
277 | } | 278 | } |
278 | 279 | ||
279 | void cgit_diff_tree(const unsigned char *old_sha1, | 280 | void cgit_diff_tree(const unsigned char *old_sha1, |
280 | const unsigned char *new_sha1, | 281 | const unsigned char *new_sha1, |
281 | filepair_fn fn, const char *prefix) | 282 | filepair_fn fn, const char *prefix) |
282 | { | 283 | { |
283 | struct diff_options opt; | 284 | struct diff_options opt; |
284 | int ret; | 285 | int ret; |
285 | int prefixlen; | 286 | int prefixlen; |
286 | 287 | ||
287 | diff_setup(&opt); | 288 | diff_setup(&opt); |
288 | opt.output_format = DIFF_FORMAT_CALLBACK; | 289 | opt.output_format = DIFF_FORMAT_CALLBACK; |
289 | opt.detect_rename = 1; | 290 | opt.detect_rename = 1; |
290 | opt.rename_limit = ctx.cfg.renamelimit; | 291 | opt.rename_limit = ctx.cfg.renamelimit; |
291 | DIFF_OPT_SET(&opt, RECURSIVE); | 292 | DIFF_OPT_SET(&opt, RECURSIVE); |
292 | opt.format_callback = cgit_diff_tree_cb; | 293 | opt.format_callback = cgit_diff_tree_cb; |
293 | opt.format_callback_data = fn; | 294 | opt.format_callback_data = fn; |
294 | if (prefix) { | 295 | if (prefix) { |
295 | opt.nr_paths = 1; | 296 | opt.nr_paths = 1; |
296 | opt.paths = &prefix; | 297 | opt.paths = &prefix; |
297 | prefixlen = strlen(prefix); | 298 | prefixlen = strlen(prefix); |
298 | opt.pathlens = &prefixlen; | 299 | opt.pathlens = &prefixlen; |
299 | } | 300 | } |
300 | diff_setup_done(&opt); | 301 | diff_setup_done(&opt); |
301 | 302 | ||
302 | if (old_sha1 && !is_null_sha1(old_sha1)) | 303 | if (old_sha1 && !is_null_sha1(old_sha1)) |
303 | ret = diff_tree_sha1(old_sha1, new_sha1, "", &opt); | 304 | ret = diff_tree_sha1(old_sha1, new_sha1, "", &opt); |
304 | else | 305 | else |
305 | ret = diff_root_tree_sha1(new_sha1, "", &opt); | 306 | ret = diff_root_tree_sha1(new_sha1, "", &opt); |
306 | diffcore_std(&opt); | 307 | diffcore_std(&opt); |
307 | diff_flush(&opt); | 308 | diff_flush(&opt); |
308 | } | 309 | } |
309 | 310 | ||
310 | void cgit_diff_commit(struct commit *commit, filepair_fn fn) | 311 | void cgit_diff_commit(struct commit *commit, filepair_fn fn) |
311 | { | 312 | { |
312 | unsigned char *old_sha1 = NULL; | 313 | unsigned char *old_sha1 = NULL; |
313 | 314 | ||
314 | if (commit->parents) | 315 | if (commit->parents) |
315 | old_sha1 = commit->parents->item->object.sha1; | 316 | old_sha1 = commit->parents->item->object.sha1; |
316 | cgit_diff_tree(old_sha1, commit->object.sha1, fn, NULL); | 317 | cgit_diff_tree(old_sha1, commit->object.sha1, fn, NULL); |
317 | } | 318 | } |
318 | 319 | ||
319 | int cgit_parse_snapshots_mask(const char *str) | 320 | int cgit_parse_snapshots_mask(const char *str) |
320 | { | 321 | { |
321 | const struct cgit_snapshot_format *f; | 322 | const struct cgit_snapshot_format *f; |
322 | static const char *delim = " \t,:/|;"; | 323 | static const char *delim = " \t,:/|;"; |
323 | int tl, sl, rv = 0; | 324 | int tl, sl, rv = 0; |
324 | 325 | ||
325 | /* favor legacy setting */ | 326 | /* favor legacy setting */ |
326 | if(atoi(str)) | 327 | if(atoi(str)) |
327 | return 1; | 328 | return 1; |
328 | for(;;) { | 329 | for(;;) { |
329 | str += strspn(str,delim); | 330 | str += strspn(str,delim); |
330 | tl = strcspn(str,delim); | 331 | tl = strcspn(str,delim); |
331 | if (!tl) | 332 | if (!tl) |
332 | break; | 333 | break; |
333 | for (f = cgit_snapshot_formats; f->suffix; f++) { | 334 | for (f = cgit_snapshot_formats; f->suffix; f++) { |
334 | sl = strlen(f->suffix); | 335 | sl = strlen(f->suffix); |
335 | if((tl == sl && !strncmp(f->suffix, str, tl)) || | 336 | if((tl == sl && !strncmp(f->suffix, str, tl)) || |
336 | (tl == sl-1 && !strncmp(f->suffix+1, str, tl-1))) { | 337 | (tl == sl-1 && !strncmp(f->suffix+1, str, tl-1))) { |
337 | rv |= f->bit; | 338 | rv |= f->bit; |
338 | break; | 339 | break; |
339 | } | 340 | } |
340 | } | 341 | } |
341 | str += tl; | 342 | str += tl; |
342 | } | 343 | } |
343 | return rv; | 344 | return rv; |
344 | } | 345 | } |
diff --git a/ui-shared.c b/ui-shared.c index 224e5f3..0e688a0 100644 --- a/ui-shared.c +++ b/ui-shared.c | |||
@@ -260,455 +260,458 @@ static void reporevlink(char *page, char *name, char *title, char *class, | |||
260 | void cgit_summary_link(char *name, char *title, char *class, char *head) | 260 | void cgit_summary_link(char *name, char *title, char *class, char *head) |
261 | { | 261 | { |
262 | reporevlink(NULL, name, title, class, head, NULL, NULL); | 262 | reporevlink(NULL, name, title, class, head, NULL, NULL); |
263 | } | 263 | } |
264 | 264 | ||
265 | void cgit_tag_link(char *name, char *title, char *class, char *head, | 265 | void cgit_tag_link(char *name, char *title, char *class, char *head, |
266 | char *rev) | 266 | char *rev) |
267 | { | 267 | { |
268 | reporevlink("tag", name, title, class, head, rev, NULL); | 268 | reporevlink("tag", name, title, class, head, rev, NULL); |
269 | } | 269 | } |
270 | 270 | ||
271 | void cgit_tree_link(char *name, char *title, char *class, char *head, | 271 | void cgit_tree_link(char *name, char *title, char *class, char *head, |
272 | char *rev, char *path) | 272 | char *rev, char *path) |
273 | { | 273 | { |
274 | reporevlink("tree", name, title, class, head, rev, path); | 274 | reporevlink("tree", name, title, class, head, rev, path); |
275 | } | 275 | } |
276 | 276 | ||
277 | void cgit_plain_link(char *name, char *title, char *class, char *head, | 277 | void cgit_plain_link(char *name, char *title, char *class, char *head, |
278 | char *rev, char *path) | 278 | char *rev, char *path) |
279 | { | 279 | { |
280 | reporevlink("plain", name, title, class, head, rev, path); | 280 | reporevlink("plain", name, title, class, head, rev, path); |
281 | } | 281 | } |
282 | 282 | ||
283 | void cgit_log_link(char *name, char *title, char *class, char *head, | 283 | void cgit_log_link(char *name, char *title, char *class, char *head, |
284 | char *rev, char *path, int ofs, char *grep, char *pattern) | 284 | char *rev, char *path, int ofs, char *grep, char *pattern) |
285 | { | 285 | { |
286 | char *delim; | 286 | char *delim; |
287 | 287 | ||
288 | delim = repolink(title, class, "log", head, path); | 288 | delim = repolink(title, class, "log", head, path); |
289 | if (rev && strcmp(rev, ctx.qry.head)) { | 289 | if (rev && strcmp(rev, ctx.qry.head)) { |
290 | html(delim); | 290 | html(delim); |
291 | html("id="); | 291 | html("id="); |
292 | html_url_arg(rev); | 292 | html_url_arg(rev); |
293 | delim = "&"; | 293 | delim = "&"; |
294 | } | 294 | } |
295 | if (grep && pattern) { | 295 | if (grep && pattern) { |
296 | html(delim); | 296 | html(delim); |
297 | html("qt="); | 297 | html("qt="); |
298 | html_url_arg(grep); | 298 | html_url_arg(grep); |
299 | delim = "&"; | 299 | delim = "&"; |
300 | html(delim); | 300 | html(delim); |
301 | html("q="); | 301 | html("q="); |
302 | html_url_arg(pattern); | 302 | html_url_arg(pattern); |
303 | } | 303 | } |
304 | if (ofs > 0) { | 304 | if (ofs > 0) { |
305 | html(delim); | 305 | html(delim); |
306 | html("ofs="); | 306 | html("ofs="); |
307 | htmlf("%d", ofs); | 307 | htmlf("%d", ofs); |
308 | } | 308 | } |
309 | html("'>"); | 309 | html("'>"); |
310 | html_txt(name); | 310 | html_txt(name); |
311 | html("</a>"); | 311 | html("</a>"); |
312 | } | 312 | } |
313 | 313 | ||
314 | void cgit_commit_link(char *name, char *title, char *class, char *head, | 314 | void cgit_commit_link(char *name, char *title, char *class, char *head, |
315 | char *rev) | 315 | char *rev) |
316 | { | 316 | { |
317 | if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { | 317 | if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { |
318 | name[ctx.cfg.max_msg_len] = '\0'; | 318 | name[ctx.cfg.max_msg_len] = '\0'; |
319 | name[ctx.cfg.max_msg_len - 1] = '.'; | 319 | name[ctx.cfg.max_msg_len - 1] = '.'; |
320 | name[ctx.cfg.max_msg_len - 2] = '.'; | 320 | name[ctx.cfg.max_msg_len - 2] = '.'; |
321 | name[ctx.cfg.max_msg_len - 3] = '.'; | 321 | name[ctx.cfg.max_msg_len - 3] = '.'; |
322 | } | 322 | } |
323 | reporevlink("commit", name, title, class, head, rev, NULL); | 323 | reporevlink("commit", name, title, class, head, rev, NULL); |
324 | } | 324 | } |
325 | 325 | ||
326 | void cgit_refs_link(char *name, char *title, char *class, char *head, | 326 | void cgit_refs_link(char *name, char *title, char *class, char *head, |
327 | char *rev, char *path) | 327 | char *rev, char *path) |
328 | { | 328 | { |
329 | reporevlink("refs", name, title, class, head, rev, path); | 329 | reporevlink("refs", name, title, class, head, rev, path); |
330 | } | 330 | } |
331 | 331 | ||
332 | void cgit_snapshot_link(char *name, char *title, char *class, char *head, | 332 | void cgit_snapshot_link(char *name, char *title, char *class, char *head, |
333 | char *rev, char *archivename) | 333 | char *rev, char *archivename) |
334 | { | 334 | { |
335 | reporevlink("snapshot", name, title, class, head, rev, archivename); | 335 | reporevlink("snapshot", name, title, class, head, rev, archivename); |
336 | } | 336 | } |
337 | 337 | ||
338 | void cgit_diff_link(char *name, char *title, char *class, char *head, | 338 | void cgit_diff_link(char *name, char *title, char *class, char *head, |
339 | char *new_rev, char *old_rev, char *path) | 339 | char *new_rev, char *old_rev, char *path) |
340 | { | 340 | { |
341 | char *delim; | 341 | char *delim; |
342 | 342 | ||
343 | delim = repolink(title, class, "diff", head, path); | 343 | delim = repolink(title, class, "diff", head, path); |
344 | if (new_rev && strcmp(new_rev, ctx.qry.head)) { | 344 | if (new_rev && strcmp(new_rev, ctx.qry.head)) { |
345 | html(delim); | 345 | html(delim); |
346 | html("id="); | 346 | html("id="); |
347 | html_url_arg(new_rev); | 347 | html_url_arg(new_rev); |
348 | delim = "&"; | 348 | delim = "&"; |
349 | } | 349 | } |
350 | if (old_rev) { | 350 | if (old_rev) { |
351 | html(delim); | 351 | html(delim); |
352 | html("id2="); | 352 | html("id2="); |
353 | html_url_arg(old_rev); | 353 | html_url_arg(old_rev); |
354 | } | 354 | } |
355 | html("'>"); | 355 | html("'>"); |
356 | html_txt(name); | 356 | html_txt(name); |
357 | html("</a>"); | 357 | html("</a>"); |
358 | } | 358 | } |
359 | 359 | ||
360 | void cgit_patch_link(char *name, char *title, char *class, char *head, | 360 | void cgit_patch_link(char *name, char *title, char *class, char *head, |
361 | char *rev) | 361 | char *rev) |
362 | { | 362 | { |
363 | reporevlink("patch", name, title, class, head, rev, NULL); | 363 | reporevlink("patch", name, title, class, head, rev, NULL); |
364 | } | 364 | } |
365 | 365 | ||
366 | void cgit_object_link(struct object *obj) | 366 | void cgit_object_link(struct object *obj) |
367 | { | 367 | { |
368 | char *page, *rev, *name; | 368 | char *page, *rev, *name; |
369 | 369 | ||
370 | if (obj->type == OBJ_COMMIT) { | 370 | if (obj->type == OBJ_COMMIT) { |
371 | cgit_commit_link(fmt("commit %s", sha1_to_hex(obj->sha1)), NULL, NULL, | 371 | cgit_commit_link(fmt("commit %s", sha1_to_hex(obj->sha1)), NULL, NULL, |
372 | ctx.qry.head, sha1_to_hex(obj->sha1)); | 372 | ctx.qry.head, sha1_to_hex(obj->sha1)); |
373 | return; | 373 | return; |
374 | } else if (obj->type == OBJ_TREE) | 374 | } else if (obj->type == OBJ_TREE) |
375 | page = "tree"; | 375 | page = "tree"; |
376 | else if (obj->type == OBJ_TAG) | 376 | else if (obj->type == OBJ_TAG) |
377 | page = "tag"; | 377 | page = "tag"; |
378 | else | 378 | else |
379 | page = "blob"; | 379 | page = "blob"; |
380 | rev = sha1_to_hex(obj->sha1); | 380 | rev = sha1_to_hex(obj->sha1); |
381 | name = fmt("%s %s", typename(obj->type), rev); | 381 | name = fmt("%s %s", typename(obj->type), rev); |
382 | reporevlink(page, name, NULL, NULL, ctx.qry.head, rev, NULL); | 382 | reporevlink(page, name, NULL, NULL, ctx.qry.head, rev, NULL); |
383 | } | 383 | } |
384 | 384 | ||
385 | void cgit_print_date(time_t secs, char *format, int local_time) | 385 | void cgit_print_date(time_t secs, char *format, int local_time) |
386 | { | 386 | { |
387 | char buf[64]; | 387 | char buf[64]; |
388 | struct tm *time; | 388 | struct tm *time; |
389 | 389 | ||
390 | if (!secs) | 390 | if (!secs) |
391 | return; | 391 | return; |
392 | if(local_time) | 392 | if(local_time) |
393 | time = localtime(&secs); | 393 | time = localtime(&secs); |
394 | else | 394 | else |
395 | time = gmtime(&secs); | 395 | time = gmtime(&secs); |
396 | strftime(buf, sizeof(buf)-1, format, time); | 396 | strftime(buf, sizeof(buf)-1, format, time); |
397 | html_txt(buf); | 397 | html_txt(buf); |
398 | } | 398 | } |
399 | 399 | ||
400 | void cgit_print_age(time_t t, time_t max_relative, char *format) | 400 | void cgit_print_age(time_t t, time_t max_relative, char *format) |
401 | { | 401 | { |
402 | time_t now, secs; | 402 | time_t now, secs; |
403 | 403 | ||
404 | if (!t) | 404 | if (!t) |
405 | return; | 405 | return; |
406 | time(&now); | 406 | time(&now); |
407 | secs = now - t; | 407 | secs = now - t; |
408 | 408 | ||
409 | if (secs > max_relative && max_relative >= 0) { | 409 | if (secs > max_relative && max_relative >= 0) { |
410 | cgit_print_date(t, format, ctx.cfg.local_time); | 410 | cgit_print_date(t, format, ctx.cfg.local_time); |
411 | return; | 411 | return; |
412 | } | 412 | } |
413 | 413 | ||
414 | if (secs < TM_HOUR * 2) { | 414 | if (secs < TM_HOUR * 2) { |
415 | htmlf("<span class='age-mins'>%.0f min.</span>", | 415 | htmlf("<span class='age-mins'>%.0f min.</span>", |
416 | secs * 1.0 / TM_MIN); | 416 | secs * 1.0 / TM_MIN); |
417 | return; | 417 | return; |
418 | } | 418 | } |
419 | if (secs < TM_DAY * 2) { | 419 | if (secs < TM_DAY * 2) { |
420 | htmlf("<span class='age-hours'>%.0f hours</span>", | 420 | htmlf("<span class='age-hours'>%.0f hours</span>", |
421 | secs * 1.0 / TM_HOUR); | 421 | secs * 1.0 / TM_HOUR); |
422 | return; | 422 | return; |
423 | } | 423 | } |
424 | if (secs < TM_WEEK * 2) { | 424 | if (secs < TM_WEEK * 2) { |
425 | htmlf("<span class='age-days'>%.0f days</span>", | 425 | htmlf("<span class='age-days'>%.0f days</span>", |
426 | secs * 1.0 / TM_DAY); | 426 | secs * 1.0 / TM_DAY); |
427 | return; | 427 | return; |
428 | } | 428 | } |
429 | if (secs < TM_MONTH * 2) { | 429 | if (secs < TM_MONTH * 2) { |
430 | htmlf("<span class='age-weeks'>%.0f weeks</span>", | 430 | htmlf("<span class='age-weeks'>%.0f weeks</span>", |
431 | secs * 1.0 / TM_WEEK); | 431 | secs * 1.0 / TM_WEEK); |
432 | return; | 432 | return; |
433 | } | 433 | } |
434 | if (secs < TM_YEAR * 2) { | 434 | if (secs < TM_YEAR * 2) { |
435 | htmlf("<span class='age-months'>%.0f months</span>", | 435 | htmlf("<span class='age-months'>%.0f months</span>", |
436 | secs * 1.0 / TM_MONTH); | 436 | secs * 1.0 / TM_MONTH); |
437 | return; | 437 | return; |
438 | } | 438 | } |
439 | htmlf("<span class='age-years'>%.0f years</span>", | 439 | htmlf("<span class='age-years'>%.0f years</span>", |
440 | secs * 1.0 / TM_YEAR); | 440 | secs * 1.0 / TM_YEAR); |
441 | } | 441 | } |
442 | 442 | ||
443 | void cgit_print_http_headers(struct cgit_context *ctx) | 443 | void cgit_print_http_headers(struct cgit_context *ctx) |
444 | { | 444 | { |
445 | if (ctx->page.mimetype && ctx->page.charset) | 445 | if (ctx->page.mimetype && ctx->page.charset) |
446 | htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype, | 446 | htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype, |
447 | ctx->page.charset); | 447 | ctx->page.charset); |
448 | else if (ctx->page.mimetype) | 448 | else if (ctx->page.mimetype) |
449 | htmlf("Content-Type: %s\n", ctx->page.mimetype); | 449 | htmlf("Content-Type: %s\n", ctx->page.mimetype); |
450 | if (ctx->page.size) | 450 | if (ctx->page.size) |
451 | htmlf("Content-Length: %ld\n", ctx->page.size); | 451 | htmlf("Content-Length: %ld\n", ctx->page.size); |
452 | if (ctx->page.filename) | 452 | if (ctx->page.filename) |
453 | htmlf("Content-Disposition: inline; filename=\"%s\"\n", | 453 | htmlf("Content-Disposition: inline; filename=\"%s\"\n", |
454 | ctx->page.filename); | 454 | ctx->page.filename); |
455 | htmlf("Last-Modified: %s\n", http_date(ctx->page.modified)); | 455 | htmlf("Last-Modified: %s\n", http_date(ctx->page.modified)); |
456 | htmlf("Expires: %s\n", http_date(ctx->page.expires)); | 456 | htmlf("Expires: %s\n", http_date(ctx->page.expires)); |
457 | html("\n"); | 457 | html("\n"); |
458 | } | 458 | } |
459 | 459 | ||
460 | void cgit_print_docstart(struct cgit_context *ctx) | 460 | void cgit_print_docstart(struct cgit_context *ctx) |
461 | { | 461 | { |
462 | char *host = cgit_hosturl(); | 462 | char *host = cgit_hosturl(); |
463 | html(cgit_doctype); | 463 | html(cgit_doctype); |
464 | html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n"); | 464 | html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n"); |
465 | html("<head>\n"); | 465 | html("<head>\n"); |
466 | html("<title>"); | 466 | html("<title>"); |
467 | html_txt(ctx->page.title); | 467 | html_txt(ctx->page.title); |
468 | html("</title>\n"); | 468 | html("</title>\n"); |
469 | htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version); | 469 | htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version); |
470 | if (ctx->cfg.robots && *ctx->cfg.robots) | 470 | if (ctx->cfg.robots && *ctx->cfg.robots) |
471 | htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots); | 471 | htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots); |
472 | html("<link rel='stylesheet' type='text/css' href='"); | 472 | html("<link rel='stylesheet' type='text/css' href='"); |
473 | html_attr(ctx->cfg.css); | 473 | html_attr(ctx->cfg.css); |
474 | html("'/>\n"); | 474 | html("'/>\n"); |
475 | if (ctx->cfg.favicon) { | 475 | if (ctx->cfg.favicon) { |
476 | html("<link rel='shortcut icon' href='"); | 476 | html("<link rel='shortcut icon' href='"); |
477 | html_attr(ctx->cfg.favicon); | 477 | html_attr(ctx->cfg.favicon); |
478 | html("'/>\n"); | 478 | html("'/>\n"); |
479 | } | 479 | } |
480 | if (host && ctx->repo) { | 480 | if (host && ctx->repo) { |
481 | html("<link rel='alternate' title='Atom feed' href='http://"); | 481 | html("<link rel='alternate' title='Atom feed' href='http://"); |
482 | html_attr(cgit_hosturl()); | 482 | html_attr(cgit_hosturl()); |
483 | html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path, | 483 | html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path, |
484 | fmt("h=%s", ctx->qry.head))); | 484 | fmt("h=%s", ctx->qry.head))); |
485 | html("' type='application/atom+xml'/>"); | 485 | html("' type='application/atom+xml'/>"); |
486 | } | 486 | } |
487 | html("</head>\n"); | 487 | html("</head>\n"); |
488 | html("<body>\n"); | 488 | html("<body>\n"); |
489 | } | 489 | } |
490 | 490 | ||
491 | void cgit_print_docend() | 491 | void cgit_print_docend() |
492 | { | 492 | { |
493 | html("</div>"); | 493 | html("</div>"); |
494 | if (ctx.cfg.footer) | 494 | if (ctx.cfg.footer) |
495 | html_include(ctx.cfg.footer); | 495 | html_include(ctx.cfg.footer); |
496 | else { | 496 | else { |
497 | htmlf("<div class='footer'>generated by cgit %s at ", | 497 | htmlf("<div class='footer'>generated by cgit %s at ", |
498 | cgit_version); | 498 | cgit_version); |
499 | cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time); | 499 | cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time); |
500 | html("</div>\n"); | 500 | html("</div>\n"); |
501 | } | 501 | } |
502 | html("</body>\n</html>\n"); | 502 | html("</body>\n</html>\n"); |
503 | } | 503 | } |
504 | 504 | ||
505 | int print_branch_option(const char *refname, const unsigned char *sha1, | 505 | int print_branch_option(const char *refname, const unsigned char *sha1, |
506 | int flags, void *cb_data) | 506 | int flags, void *cb_data) |
507 | { | 507 | { |
508 | char *name = (char *)refname; | 508 | char *name = (char *)refname; |
509 | html_option(name, name, ctx.qry.head); | 509 | html_option(name, name, ctx.qry.head); |
510 | return 0; | 510 | return 0; |
511 | } | 511 | } |
512 | 512 | ||
513 | int print_archive_ref(const char *refname, const unsigned char *sha1, | 513 | int print_archive_ref(const char *refname, const unsigned char *sha1, |
514 | int flags, void *cb_data) | 514 | int flags, void *cb_data) |
515 | { | 515 | { |
516 | struct tag *tag; | 516 | struct tag *tag; |
517 | struct taginfo *info; | 517 | struct taginfo *info; |
518 | struct object *obj; | 518 | struct object *obj; |
519 | char buf[256], *url; | 519 | char buf[256], *url; |
520 | unsigned char fileid[20]; | 520 | unsigned char fileid[20]; |
521 | int *header = (int *)cb_data; | 521 | int *header = (int *)cb_data; |
522 | 522 | ||
523 | if (prefixcmp(refname, "refs/archives")) | 523 | if (prefixcmp(refname, "refs/archives")) |
524 | return 0; | 524 | return 0; |
525 | strncpy(buf, refname+14, sizeof(buf)); | 525 | strncpy(buf, refname+14, sizeof(buf)); |
526 | obj = parse_object(sha1); | 526 | obj = parse_object(sha1); |
527 | if (!obj) | 527 | if (!obj) |
528 | return 1; | 528 | return 1; |
529 | if (obj->type == OBJ_TAG) { | 529 | if (obj->type == OBJ_TAG) { |
530 | tag = lookup_tag(sha1); | 530 | tag = lookup_tag(sha1); |
531 | if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) | 531 | if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) |
532 | return 0; | 532 | return 0; |
533 | hashcpy(fileid, tag->tagged->sha1); | 533 | hashcpy(fileid, tag->tagged->sha1); |
534 | } else if (obj->type != OBJ_BLOB) { | 534 | } else if (obj->type != OBJ_BLOB) { |
535 | return 0; | 535 | return 0; |
536 | } else { | 536 | } else { |
537 | hashcpy(fileid, sha1); | 537 | hashcpy(fileid, sha1); |
538 | } | 538 | } |
539 | if (!*header) { | 539 | if (!*header) { |
540 | html("<h1>download</h1>\n"); | 540 | html("<h1>download</h1>\n"); |
541 | *header = 1; | 541 | *header = 1; |
542 | } | 542 | } |
543 | url = cgit_pageurl(ctx.qry.repo, "blob", | 543 | url = cgit_pageurl(ctx.qry.repo, "blob", |
544 | fmt("id=%s&path=%s", sha1_to_hex(fileid), | 544 | fmt("id=%s&path=%s", sha1_to_hex(fileid), |
545 | buf)); | 545 | buf)); |
546 | html_link_open(url, NULL, "menu"); | 546 | html_link_open(url, NULL, "menu"); |
547 | html_txt(strlpart(buf, 20)); | 547 | html_txt(strlpart(buf, 20)); |
548 | html_link_close(); | 548 | html_link_close(); |
549 | return 0; | 549 | return 0; |
550 | } | 550 | } |
551 | 551 | ||
552 | void add_hidden_formfields(int incl_head, int incl_search, char *page) | 552 | void add_hidden_formfields(int incl_head, int incl_search, char *page) |
553 | { | 553 | { |
554 | char *url; | 554 | char *url; |
555 | 555 | ||
556 | if (!ctx.cfg.virtual_root) { | 556 | if (!ctx.cfg.virtual_root) { |
557 | url = fmt("%s/%s", ctx.qry.repo, page); | 557 | url = fmt("%s/%s", ctx.qry.repo, page); |
558 | if (ctx.qry.path) | 558 | if (ctx.qry.path) |
559 | url = fmt("%s/%s", url, ctx.qry.path); | 559 | url = fmt("%s/%s", url, ctx.qry.path); |
560 | html_hidden("url", url); | 560 | html_hidden("url", url); |
561 | } | 561 | } |
562 | 562 | ||
563 | if (incl_head && ctx.qry.head && ctx.repo->defbranch && | 563 | if (incl_head && ctx.qry.head && ctx.repo->defbranch && |
564 | strcmp(ctx.qry.head, ctx.repo->defbranch)) | 564 | strcmp(ctx.qry.head, ctx.repo->defbranch)) |
565 | html_hidden("h", ctx.qry.head); | 565 | html_hidden("h", ctx.qry.head); |
566 | 566 | ||
567 | if (ctx.qry.sha1) | 567 | if (ctx.qry.sha1) |
568 | html_hidden("id", ctx.qry.sha1); | 568 | html_hidden("id", ctx.qry.sha1); |
569 | if (ctx.qry.sha2) | 569 | if (ctx.qry.sha2) |
570 | html_hidden("id2", ctx.qry.sha2); | 570 | html_hidden("id2", ctx.qry.sha2); |
571 | 571 | ||
572 | if (incl_search) { | 572 | if (incl_search) { |
573 | if (ctx.qry.grep) | 573 | if (ctx.qry.grep) |
574 | html_hidden("qt", ctx.qry.grep); | 574 | html_hidden("qt", ctx.qry.grep); |
575 | if (ctx.qry.search) | 575 | if (ctx.qry.search) |
576 | html_hidden("q", ctx.qry.search); | 576 | html_hidden("q", ctx.qry.search); |
577 | } | 577 | } |
578 | } | 578 | } |
579 | 579 | ||
580 | char *hc(struct cgit_cmd *cmd, const char *page) | 580 | char *hc(struct cgit_cmd *cmd, const char *page) |
581 | { | 581 | { |
582 | return (strcmp(cmd->name, page) ? NULL : "active"); | 582 | return (strcmp(cmd->name, page) ? NULL : "active"); |
583 | } | 583 | } |
584 | 584 | ||
585 | void cgit_print_pageheader(struct cgit_context *ctx) | 585 | void cgit_print_pageheader(struct cgit_context *ctx) |
586 | { | 586 | { |
587 | struct cgit_cmd *cmd = cgit_get_cmd(ctx); | 587 | struct cgit_cmd *cmd = cgit_get_cmd(ctx); |
588 | 588 | ||
589 | html("<table id='header'>\n"); | 589 | html("<table id='header'>\n"); |
590 | html("<tr>\n"); | 590 | html("<tr>\n"); |
591 | html("<td class='logo' rowspan='2'><a href='"); | 591 | html("<td class='logo' rowspan='2'><a href='"); |
592 | if (ctx->cfg.logo_link) | 592 | if (ctx->cfg.logo_link) |
593 | html_attr(ctx->cfg.logo_link); | 593 | html_attr(ctx->cfg.logo_link); |
594 | else | 594 | else |
595 | html_attr(cgit_rooturl()); | 595 | html_attr(cgit_rooturl()); |
596 | html("'><img src='"); | 596 | html("'><img src='"); |
597 | html_attr(ctx->cfg.logo); | 597 | html_attr(ctx->cfg.logo); |
598 | html("' alt='cgit logo'/></a></td>\n"); | 598 | html("' alt='cgit logo'/></a></td>\n"); |
599 | 599 | ||
600 | html("<td class='main'>"); | 600 | html("<td class='main'>"); |
601 | if (ctx->repo) { | 601 | if (ctx->repo) { |
602 | cgit_index_link("index", NULL, NULL, NULL, 0); | 602 | cgit_index_link("index", NULL, NULL, NULL, 0); |
603 | html(" : "); | 603 | html(" : "); |
604 | cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL); | 604 | cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL); |
605 | html("</td><td class='form'>"); | 605 | html("</td><td class='form'>"); |
606 | html("<form method='get' action=''>\n"); | 606 | html("<form method='get' action=''>\n"); |
607 | add_hidden_formfields(0, 1, ctx->qry.page); | 607 | add_hidden_formfields(0, 1, ctx->qry.page); |
608 | html("<select name='h' onchange='this.form.submit();'>\n"); | 608 | html("<select name='h' onchange='this.form.submit();'>\n"); |
609 | for_each_branch_ref(print_branch_option, ctx->qry.head); | 609 | for_each_branch_ref(print_branch_option, ctx->qry.head); |
610 | html("</select> "); | 610 | html("</select> "); |
611 | html("<input type='submit' name='' value='switch'/>"); | 611 | html("<input type='submit' name='' value='switch'/>"); |
612 | html("</form>"); | 612 | html("</form>"); |
613 | } else | 613 | } else |
614 | html_txt(ctx->cfg.root_title); | 614 | html_txt(ctx->cfg.root_title); |
615 | html("</td></tr>\n"); | 615 | html("</td></tr>\n"); |
616 | 616 | ||
617 | html("<tr><td class='sub'>"); | 617 | html("<tr><td class='sub'>"); |
618 | if (ctx->repo) { | 618 | if (ctx->repo) { |
619 | html_txt(ctx->repo->desc); | 619 | html_txt(ctx->repo->desc); |
620 | html("</td><td class='sub right'>"); | 620 | html("</td><td class='sub right'>"); |
621 | html_txt(ctx->repo->owner); | 621 | html_txt(ctx->repo->owner); |
622 | } else { | 622 | } else { |
623 | if (ctx->cfg.root_desc) | 623 | if (ctx->cfg.root_desc) |
624 | html_txt(ctx->cfg.root_desc); | 624 | html_txt(ctx->cfg.root_desc); |
625 | else if (ctx->cfg.index_info) | 625 | else if (ctx->cfg.index_info) |
626 | html_include(ctx->cfg.index_info); | 626 | html_include(ctx->cfg.index_info); |
627 | } | 627 | } |
628 | html("</td></tr></table>\n"); | 628 | html("</td></tr></table>\n"); |
629 | 629 | ||
630 | html("<table class='tabs'><tr><td>\n"); | 630 | html("<table class='tabs'><tr><td>\n"); |
631 | if (ctx->repo) { | 631 | if (ctx->repo) { |
632 | cgit_summary_link("summary", NULL, hc(cmd, "summary"), | 632 | cgit_summary_link("summary", NULL, hc(cmd, "summary"), |
633 | ctx->qry.head); | 633 | ctx->qry.head); |
634 | cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, | 634 | cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, |
635 | ctx->qry.sha1, NULL); | 635 | ctx->qry.sha1, NULL); |
636 | cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head, | 636 | cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head, |
637 | NULL, NULL, 0, NULL, NULL); | 637 | NULL, NULL, 0, NULL, NULL); |
638 | cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head, | 638 | cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head, |
639 | ctx->qry.sha1, NULL); | 639 | ctx->qry.sha1, NULL); |
640 | cgit_commit_link("commit", NULL, hc(cmd, "commit"), | 640 | cgit_commit_link("commit", NULL, hc(cmd, "commit"), |
641 | ctx->qry.head, ctx->qry.sha1); | 641 | ctx->qry.head, ctx->qry.sha1); |
642 | cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head, | 642 | cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head, |
643 | ctx->qry.sha1, ctx->qry.sha2, NULL); | 643 | ctx->qry.sha1, ctx->qry.sha2, NULL); |
644 | if (ctx->repo->enable_stats) | ||
645 | reporevlink("stats", "stats", NULL, hc(cmd, "stats"), | ||
646 | ctx->qry.head, NULL, NULL); | ||
644 | if (ctx->repo->readme) | 647 | if (ctx->repo->readme) |
645 | reporevlink("about", "about", NULL, | 648 | reporevlink("about", "about", NULL, |
646 | hc(cmd, "about"), ctx->qry.head, NULL, | 649 | hc(cmd, "about"), ctx->qry.head, NULL, |
647 | NULL); | 650 | NULL); |
648 | html("</td><td class='form'>"); | 651 | html("</td><td class='form'>"); |
649 | html("<form class='right' method='get' action='"); | 652 | html("<form class='right' method='get' action='"); |
650 | if (ctx->cfg.virtual_root) | 653 | if (ctx->cfg.virtual_root) |
651 | html_url_path(cgit_fileurl(ctx->qry.repo, "log", | 654 | html_url_path(cgit_fileurl(ctx->qry.repo, "log", |
652 | ctx->qry.path, NULL)); | 655 | ctx->qry.path, NULL)); |
653 | html("'>\n"); | 656 | html("'>\n"); |
654 | add_hidden_formfields(1, 0, "log"); | 657 | add_hidden_formfields(1, 0, "log"); |
655 | html("<select name='qt'>\n"); | 658 | html("<select name='qt'>\n"); |
656 | html_option("grep", "log msg", ctx->qry.grep); | 659 | html_option("grep", "log msg", ctx->qry.grep); |
657 | html_option("author", "author", ctx->qry.grep); | 660 | html_option("author", "author", ctx->qry.grep); |
658 | html_option("committer", "committer", ctx->qry.grep); | 661 | html_option("committer", "committer", ctx->qry.grep); |
659 | html("</select>\n"); | 662 | html("</select>\n"); |
660 | html("<input class='txt' type='text' size='10' name='q' value='"); | 663 | html("<input class='txt' type='text' size='10' name='q' value='"); |
661 | html_attr(ctx->qry.search); | 664 | html_attr(ctx->qry.search); |
662 | html("'/>\n"); | 665 | html("'/>\n"); |
663 | html("<input type='submit' value='search'/>\n"); | 666 | html("<input type='submit' value='search'/>\n"); |
664 | html("</form>\n"); | 667 | html("</form>\n"); |
665 | } else { | 668 | } else { |
666 | site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0); | 669 | site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0); |
667 | if (ctx->cfg.root_readme) | 670 | if (ctx->cfg.root_readme) |
668 | site_link("about", "about", NULL, hc(cmd, "about"), | 671 | site_link("about", "about", NULL, hc(cmd, "about"), |
669 | NULL, 0); | 672 | NULL, 0); |
670 | html("</td><td class='form'>"); | 673 | html("</td><td class='form'>"); |
671 | html("<form method='get' action='"); | 674 | html("<form method='get' action='"); |
672 | html_attr(cgit_rooturl()); | 675 | html_attr(cgit_rooturl()); |
673 | html("'>\n"); | 676 | html("'>\n"); |
674 | html("<input type='text' name='q' size='10' value='"); | 677 | html("<input type='text' name='q' size='10' value='"); |
675 | html_attr(ctx->qry.search); | 678 | html_attr(ctx->qry.search); |
676 | html("'/>\n"); | 679 | html("'/>\n"); |
677 | html("<input type='submit' value='search'/>\n"); | 680 | html("<input type='submit' value='search'/>\n"); |
678 | html("</form>"); | 681 | html("</form>"); |
679 | } | 682 | } |
680 | html("</td></tr></table>\n"); | 683 | html("</td></tr></table>\n"); |
681 | html("<div class='content'>"); | 684 | html("<div class='content'>"); |
682 | } | 685 | } |
683 | 686 | ||
684 | void cgit_print_filemode(unsigned short mode) | 687 | void cgit_print_filemode(unsigned short mode) |
685 | { | 688 | { |
686 | if (S_ISDIR(mode)) | 689 | if (S_ISDIR(mode)) |
687 | html("d"); | 690 | html("d"); |
688 | else if (S_ISLNK(mode)) | 691 | else if (S_ISLNK(mode)) |
689 | html("l"); | 692 | html("l"); |
690 | else if (S_ISGITLINK(mode)) | 693 | else if (S_ISGITLINK(mode)) |
691 | html("m"); | 694 | html("m"); |
692 | else | 695 | else |
693 | html("-"); | 696 | html("-"); |
694 | html_fileperm(mode >> 6); | 697 | html_fileperm(mode >> 6); |
695 | html_fileperm(mode >> 3); | 698 | html_fileperm(mode >> 3); |
696 | html_fileperm(mode); | 699 | html_fileperm(mode); |
697 | } | 700 | } |
698 | 701 | ||
699 | void cgit_print_snapshot_links(const char *repo, const char *head, | 702 | void cgit_print_snapshot_links(const char *repo, const char *head, |
700 | const char *hex, int snapshots) | 703 | const char *hex, int snapshots) |
701 | { | 704 | { |
702 | const struct cgit_snapshot_format* f; | 705 | const struct cgit_snapshot_format* f; |
703 | char *filename; | 706 | char *filename; |
704 | 707 | ||
705 | for (f = cgit_snapshot_formats; f->suffix; f++) { | 708 | for (f = cgit_snapshot_formats; f->suffix; f++) { |
706 | if (!(snapshots & f->bit)) | 709 | if (!(snapshots & f->bit)) |
707 | continue; | 710 | continue; |
708 | filename = fmt("%s-%s%s", cgit_repobasename(repo), hex, | 711 | filename = fmt("%s-%s%s", cgit_repobasename(repo), hex, |
709 | f->suffix); | 712 | f->suffix); |
710 | cgit_snapshot_link(filename, NULL, NULL, (char *)head, | 713 | cgit_snapshot_link(filename, NULL, NULL, (char *)head, |
711 | (char *)hex, filename); | 714 | (char *)hex, filename); |
712 | html("<br/>"); | 715 | html("<br/>"); |
713 | } | 716 | } |
714 | } | 717 | } |
diff --git a/ui-stats.c b/ui-stats.c new file mode 100644 index 0000000..9150840 --- a/dev/null +++ b/ui-stats.c | |||
@@ -0,0 +1,380 @@ | |||
1 | #include "cgit.h" | ||
2 | #include "html.h" | ||
3 | #include <string-list.h> | ||
4 | |||
5 | #define MONTHS 6 | ||
6 | |||
7 | struct Period { | ||
8 | const char code; | ||
9 | const char *name; | ||
10 | int max_periods; | ||
11 | int count; | ||
12 | |||
13 | /* Convert a tm value to the first day in the period */ | ||
14 | void (*trunc)(struct tm *tm); | ||
15 | |||
16 | /* Update tm value to start of next/previous period */ | ||
17 | void (*dec)(struct tm *tm); | ||
18 | void (*inc)(struct tm *tm); | ||
19 | |||
20 | /* Pretty-print a tm value */ | ||
21 | char *(*pretty)(struct tm *tm); | ||
22 | }; | ||
23 | |||
24 | struct authorstat { | ||
25 | long total; | ||
26 | struct string_list list; | ||
27 | }; | ||
28 | |||
29 | #define DAY_SECS (60 * 60 * 24) | ||
30 | #define WEEK_SECS (DAY_SECS * 7) | ||
31 | |||
32 | static void trunc_week(struct tm *tm) | ||
33 | { | ||
34 | time_t t = timegm(tm); | ||
35 | t -= ((tm->tm_wday + 6) % 7) * DAY_SECS; | ||
36 | gmtime_r(&t, tm); | ||
37 | } | ||
38 | |||
39 | static void dec_week(struct tm *tm) | ||
40 | { | ||
41 | time_t t = timegm(tm); | ||
42 | t -= WEEK_SECS; | ||
43 | gmtime_r(&t, tm); | ||
44 | } | ||
45 | |||
46 | static void inc_week(struct tm *tm) | ||
47 | { | ||
48 | time_t t = timegm(tm); | ||
49 | t += WEEK_SECS; | ||
50 | gmtime_r(&t, tm); | ||
51 | } | ||
52 | |||
53 | static char *pretty_week(struct tm *tm) | ||
54 | { | ||
55 | static char buf[10]; | ||
56 | |||
57 | strftime(buf, sizeof(buf), "W%V %G", tm); | ||
58 | return buf; | ||
59 | } | ||
60 | |||
61 | static void trunc_month(struct tm *tm) | ||
62 | { | ||
63 | tm->tm_mday = 1; | ||
64 | } | ||
65 | |||
66 | static void dec_month(struct tm *tm) | ||
67 | { | ||
68 | tm->tm_mon--; | ||
69 | if (tm->tm_mon < 0) { | ||
70 | tm->tm_year--; | ||
71 | tm->tm_mon = 11; | ||
72 | } | ||
73 | } | ||
74 | |||
75 | static void inc_month(struct tm *tm) | ||
76 | { | ||
77 | tm->tm_mon++; | ||
78 | if (tm->tm_mon > 11) { | ||
79 | tm->tm_year++; | ||
80 | tm->tm_mon = 0; | ||
81 | } | ||
82 | } | ||
83 | |||
84 | static char *pretty_month(struct tm *tm) | ||
85 | { | ||
86 | static const char *months[] = { | ||
87 | "Jan", "Feb", "Mar", "Apr", "May", "Jun", | ||
88 | "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" | ||
89 | }; | ||
90 | return fmt("%s %d", months[tm->tm_mon], tm->tm_year + 1900); | ||
91 | } | ||
92 | |||
93 | static void trunc_quarter(struct tm *tm) | ||
94 | { | ||
95 | trunc_month(tm); | ||
96 | while(tm->tm_mon % 3 != 0) | ||
97 | dec_month(tm); | ||
98 | } | ||
99 | |||
100 | static void dec_quarter(struct tm *tm) | ||
101 | { | ||
102 | dec_month(tm); | ||
103 | dec_month(tm); | ||
104 | dec_month(tm); | ||
105 | } | ||
106 | |||
107 | static void inc_quarter(struct tm *tm) | ||
108 | { | ||
109 | inc_month(tm); | ||
110 | inc_month(tm); | ||
111 | inc_month(tm); | ||
112 | } | ||
113 | |||
114 | static char *pretty_quarter(struct tm *tm) | ||
115 | { | ||
116 | return fmt("Q%d %d", tm->tm_mon / 3 + 1, tm->tm_year + 1900); | ||
117 | } | ||
118 | |||
119 | static void trunc_year(struct tm *tm) | ||
120 | { | ||
121 | trunc_month(tm); | ||
122 | tm->tm_mon = 0; | ||
123 | } | ||
124 | |||
125 | static void dec_year(struct tm *tm) | ||
126 | { | ||
127 | tm->tm_year--; | ||
128 | } | ||
129 | |||
130 | static void inc_year(struct tm *tm) | ||
131 | { | ||
132 | tm->tm_year++; | ||
133 | } | ||
134 | |||
135 | static char *pretty_year(struct tm *tm) | ||
136 | { | ||
137 | return fmt("%d", tm->tm_year + 1900); | ||
138 | } | ||
139 | |||
140 | struct Period periods[] = { | ||
141 | {'w', "week", 12, 4, trunc_week, dec_week, inc_week, pretty_week}, | ||
142 | {'m', "month", 12, 4, trunc_month, dec_month, inc_month, pretty_month}, | ||
143 | {'q', "quarter", 12, 4, trunc_quarter, dec_quarter, inc_quarter, pretty_quarter}, | ||
144 | {'y', "year", 12, 4, trunc_year, dec_year, inc_year, pretty_year}, | ||
145 | }; | ||
146 | |||
147 | static void add_commit(struct string_list *authors, struct commit *commit, | ||
148 | struct Period *period) | ||
149 | { | ||
150 | struct commitinfo *info; | ||
151 | struct string_list_item *author, *item; | ||
152 | struct authorstat *authorstat; | ||
153 | struct string_list *items; | ||
154 | char *tmp; | ||
155 | struct tm *date; | ||
156 | time_t t; | ||
157 | |||
158 | info = cgit_parse_commit(commit); | ||
159 | tmp = xstrdup(info->author); | ||
160 | author = string_list_insert(tmp, authors); | ||
161 | if (!author->util) | ||
162 | author->util = xcalloc(1, sizeof(struct authorstat)); | ||
163 | else | ||
164 | free(tmp); | ||
165 | authorstat = author->util; | ||
166 | items = &authorstat->list; | ||
167 | t = info->committer_date; | ||
168 | date = gmtime(&t); | ||
169 | period->trunc(date); | ||
170 | tmp = xstrdup(period->pretty(date)); | ||
171 | item = string_list_insert(tmp, items); | ||
172 | if (item->util) | ||
173 | free(tmp); | ||
174 | item->util++; | ||
175 | authorstat->total++; | ||
176 | cgit_free_commitinfo(info); | ||
177 | } | ||
178 | |||
179 | static int cmp_total_commits(const void *a1, const void *a2) | ||
180 | { | ||
181 | const struct string_list_item *i1 = a1; | ||
182 | const struct string_list_item *i2 = a2; | ||
183 | const struct authorstat *auth1 = i1->util; | ||
184 | const struct authorstat *auth2 = i2->util; | ||
185 | |||
186 | return auth2->total - auth1->total; | ||
187 | } | ||
188 | |||
189 | /* Walk the commit DAG and collect number of commits per author per | ||
190 | * timeperiod into a nested string_list collection. | ||
191 | */ | ||
192 | struct string_list collect_stats(struct cgit_context *ctx, | ||
193 | struct Period *period) | ||
194 | { | ||
195 | struct string_list authors; | ||
196 | struct rev_info rev; | ||
197 | struct commit *commit; | ||
198 | const char *argv[] = {NULL, ctx->qry.head, NULL, NULL}; | ||
199 | time_t now; | ||
200 | long i; | ||
201 | struct tm *tm; | ||
202 | char tmp[11]; | ||
203 | |||
204 | time(&now); | ||
205 | tm = gmtime(&now); | ||
206 | period->trunc(tm); | ||
207 | for (i = 1; i < period->count; i++) | ||
208 | period->dec(tm); | ||
209 | strftime(tmp, sizeof(tmp), "%Y-%m-%d", tm); | ||
210 | argv[2] = xstrdup(fmt("--since=%s", tmp)); | ||
211 | init_revisions(&rev, NULL); | ||
212 | rev.abbrev = DEFAULT_ABBREV; | ||
213 | rev.commit_format = CMIT_FMT_DEFAULT; | ||
214 | rev.no_merges = 1; | ||
215 | rev.verbose_header = 1; | ||
216 | rev.show_root_diff = 0; | ||
217 | setup_revisions(3, argv, &rev, NULL); | ||
218 | prepare_revision_walk(&rev); | ||
219 | memset(&authors, 0, sizeof(authors)); | ||
220 | while ((commit = get_revision(&rev)) != NULL) { | ||
221 | add_commit(&authors, commit, period); | ||
222 | free(commit->buffer); | ||
223 | free_commit_list(commit->parents); | ||
224 | } | ||
225 | return authors; | ||
226 | } | ||
227 | |||
228 | void print_combined_authorrow(struct string_list *authors, int from, int to, | ||
229 | const char *name, const char *leftclass, const char *centerclass, | ||
230 | const char *rightclass, struct Period *period) | ||
231 | { | ||
232 | struct string_list_item *author; | ||
233 | struct authorstat *authorstat; | ||
234 | struct string_list *items; | ||
235 | struct string_list_item *date; | ||
236 | time_t now; | ||
237 | long i, j, total, subtotal; | ||
238 | struct tm *tm; | ||
239 | char *tmp; | ||
240 | |||
241 | time(&now); | ||
242 | tm = gmtime(&now); | ||
243 | period->trunc(tm); | ||
244 | for (i = 1; i < period->count; i++) | ||
245 | period->dec(tm); | ||
246 | |||
247 | total = 0; | ||
248 | htmlf("<tr><td class='%s'>%s</td>", leftclass, | ||
249 | fmt(name, to - from + 1)); | ||
250 | for (j = 0; j < period->count; j++) { | ||
251 | tmp = period->pretty(tm); | ||
252 | period->inc(tm); | ||
253 | subtotal = 0; | ||
254 | for (i = from; i <= to; i++) { | ||
255 | author = &authors->items[i]; | ||
256 | authorstat = author->util; | ||
257 | items = &authorstat->list; | ||
258 | date = string_list_lookup(tmp, items); | ||
259 | if (date) | ||
260 | subtotal += (size_t)date->util; | ||
261 | } | ||
262 | htmlf("<td class='%s'>%d</td>", centerclass, subtotal); | ||
263 | total += subtotal; | ||
264 | } | ||
265 | htmlf("<td class='%s'>%d</td></tr>", rightclass, total); | ||
266 | } | ||
267 | |||
268 | void print_authors(struct string_list *authors, int top, struct Period *period) | ||
269 | { | ||
270 | struct string_list_item *author; | ||
271 | struct authorstat *authorstat; | ||
272 | struct string_list *items; | ||
273 | struct string_list_item *date; | ||
274 | time_t now; | ||
275 | long i, j, total; | ||
276 | struct tm *tm; | ||
277 | char *tmp; | ||
278 | |||
279 | time(&now); | ||
280 | tm = gmtime(&now); | ||
281 | period->trunc(tm); | ||
282 | for (i = 1; i < period->count; i++) | ||
283 | period->dec(tm); | ||
284 | |||
285 | html("<table class='stats'><tr><th>Author</th>"); | ||
286 | for (j = 0; j < period->count; j++) { | ||
287 | tmp = period->pretty(tm); | ||
288 | htmlf("<th>%s</th>", tmp); | ||
289 | period->inc(tm); | ||
290 | } | ||
291 | html("<th>Total</th></tr>\n"); | ||
292 | |||
293 | if (top <= 0 || top > authors->nr) | ||
294 | top = authors->nr; | ||
295 | |||
296 | for (i = 0; i < top; i++) { | ||
297 | author = &authors->items[i]; | ||
298 | html("<tr><td class='left'>"); | ||
299 | html_txt(author->string); | ||
300 | html("</td>"); | ||
301 | authorstat = author->util; | ||
302 | items = &authorstat->list; | ||
303 | total = 0; | ||
304 | for (j = 0; j < period->count; j++) | ||
305 | period->dec(tm); | ||
306 | for (j = 0; j < period->count; j++) { | ||
307 | tmp = period->pretty(tm); | ||
308 | period->inc(tm); | ||
309 | date = string_list_lookup(tmp, items); | ||
310 | if (!date) | ||
311 | html("<td>0</td>"); | ||
312 | else { | ||
313 | htmlf("<td>%d</td>", date->util); | ||
314 | total += (size_t)date->util; | ||
315 | } | ||
316 | } | ||
317 | htmlf("<td class='sum'>%d</td></tr>", total); | ||
318 | } | ||
319 | |||
320 | if (top < authors->nr) | ||
321 | print_combined_authorrow(authors, top, authors->nr - 1, | ||
322 | "Others (%d)", "left", "", "sum", period); | ||
323 | |||
324 | print_combined_authorrow(authors, 0, authors->nr - 1, "Total", | ||
325 | "total", "sum", "sum", period); | ||
326 | html("</table>"); | ||
327 | } | ||
328 | |||
329 | /* Create a sorted string_list with one entry per author. The util-field | ||
330 | * for each author is another string_list which is used to calculate the | ||
331 | * number of commits per time-interval. | ||
332 | */ | ||
333 | void cgit_show_stats(struct cgit_context *ctx) | ||
334 | { | ||
335 | struct string_list authors; | ||
336 | struct Period *period; | ||
337 | int top, i; | ||
338 | |||
339 | period = &periods[0]; | ||
340 | if (ctx->qry.period) { | ||
341 | for (i = 0; i < sizeof(periods) / sizeof(periods[0]); i++) | ||
342 | if (periods[i].code == ctx->qry.period[0]) { | ||
343 | period = &periods[i]; | ||
344 | break; | ||
345 | } | ||
346 | } | ||
347 | authors = collect_stats(ctx, period); | ||
348 | qsort(authors.items, authors.nr, sizeof(struct string_list_item), | ||
349 | cmp_total_commits); | ||
350 | |||
351 | top = ctx->qry.ofs; | ||
352 | if (!top) | ||
353 | top = 10; | ||
354 | htmlf("<h2>Commits per author per %s</h2>", period->name); | ||
355 | |||
356 | html("<form method='get' action='.' style='float: right; text-align: right;'>"); | ||
357 | if (strcmp(ctx->qry.head, ctx->repo->defbranch)) | ||
358 | htmlf("<input type='hidden' name='h' value='%s'/>", ctx->qry.head); | ||
359 | html("Period: "); | ||
360 | html("<select name='period' onchange='this.form.submit();'>"); | ||
361 | for (i = 0; i < sizeof(periods) / sizeof(periods[0]); i++) | ||
362 | htmlf("<option value='%c'%s>%s</option>", | ||
363 | periods[i].code, | ||
364 | period == &periods[i] ? " selected" : "", | ||
365 | periods[i].name); | ||
366 | html("</select><br/><br/>"); | ||
367 | html("Authors: "); | ||
368 | html(""); | ||
369 | html("<select name='ofs' onchange='this.form.submit();'>"); | ||
370 | htmlf("<option value='10'%s>10</option>", top == 10 ? " selected" : ""); | ||
371 | htmlf("<option value='25'%s>25</option>", top == 25 ? " selected" : ""); | ||
372 | htmlf("<option value='50'%s>50</option>", top == 50 ? " selected" : ""); | ||
373 | htmlf("<option value='100'%s>100</option>", top == 100 ? " selected" : ""); | ||
374 | htmlf("<option value='-1'%s>All</option>", top == -1 ? " selected" : ""); | ||
375 | html("</select>"); | ||
376 | html("<noscript> <input type='submit' value='Reload'/></noscript>"); | ||
377 | html("</form>"); | ||
378 | print_authors(&authors, top, period); | ||
379 | } | ||
380 | |||
diff --git a/ui-stats.h b/ui-stats.h new file mode 100644 index 0000000..f1d744c --- a/dev/null +++ b/ui-stats.h | |||
@@ -0,0 +1,8 @@ | |||
1 | #ifndef UI_STATS_H | ||
2 | #define UI_STATS_H | ||
3 | |||
4 | #include "cgit.h" | ||
5 | |||
6 | extern void cgit_show_stats(struct cgit_context *ctx); | ||
7 | |||
8 | #endif /* UI_STATS_H */ | ||