-rw-r--r-- | Makefile | 11 | ||||
-rw-r--r-- | cache.c | 2 | ||||
-rw-r--r-- | cgit.h | 1 | ||||
-rw-r--r-- | cgitrc | 2 | ||||
-rw-r--r-- | cmd.c | 7 | ||||
m--------- | git | 0 | ||||
-rw-r--r-- | ui-atom.c | 129 | ||||
-rw-r--r-- | ui-atom.h | 6 | ||||
-rw-r--r-- | ui-shared.c | 23 | ||||
-rw-r--r-- | ui-shared.h | 1 |
10 files changed, 175 insertions, 7 deletions
@@ -1,126 +1,125 @@ CGIT_VERSION = v0.7.2 CGIT_SCRIPT_NAME = cgit.cgi CGIT_SCRIPT_PATH = /var/www/htdocs/cgit CGIT_CONFIG = /etc/cgitrc CACHE_ROOT = /var/cache/cgit SHA1_HEADER = <openssl/sha.h> -GIT_VER = 1.5.6 +GIT_VER = 1.6.0.rc1 GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2 # # Let the user override the above settings. # -include cgit.conf # # Define a way to invoke make in subdirs quietly, shamelessly ripped # from git.git # QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir QUIET_SUBDIR1 = ifneq ($(findstring $(MAKEFLAGS),w),w) PRINT_DIR = --no-print-directory else # "make -w" NO_SUBDIR = : endif ifndef V QUIET_CC = @echo ' ' CC $@; QUIET_MM = @echo ' ' MM $@; QUIET_SUBDIR0 = +@subdir= QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ $(MAKE) $(PRINT_DIR) -C $$subdir endif # # Define a pattern rule for automatic dependency building # %.d: %.c $(QUIET_MM)$(CC) $(CFLAGS) -MM $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@ # # Define a pattern rule for silent object building # %.o: %.c $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $< EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lcrypto OBJECTS = OBJECTS += cache.o OBJECTS += cgit.o OBJECTS += cmd.o OBJECTS += configfile.o OBJECTS += html.o OBJECTS += parsing.o OBJECTS += shared.o +OBJECTS += ui-atom.o OBJECTS += ui-blob.o OBJECTS += ui-clone.o OBJECTS += ui-commit.o OBJECTS += ui-diff.o OBJECTS += ui-log.o OBJECTS += ui-patch.o OBJECTS += ui-plain.o OBJECTS += ui-refs.o OBJECTS += ui-repolist.o OBJECTS += ui-shared.o OBJECTS += ui-snapshot.o OBJECTS += ui-summary.o OBJECTS += ui-tag.o OBJECTS += ui-tree.o ifdef NEEDS_LIBICONV EXTLIBS += -liconv endif -.PHONY: all git test install uninstall clean force-version get-git +.PHONY: all libgit test install uninstall clean force-version get-git all: cgit VERSION: force-version @./gen-version.sh "$(CGIT_VERSION)" -include VERSION CFLAGS += -g -Wall -Igit CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)' CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"' CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"' CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"' CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"' -cgit: $(OBJECTS) git/libgit.a git/xdiff/lib.a +cgit: $(OBJECTS) libgit $(QUIET_CC)$(CC) $(CFLAGS) -o cgit $(OBJECTS) $(EXTLIBS) cgit.o: VERSION -include $(OBJECTS:.o=.d) -git/libgit.a: git +libgit: $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) libgit.a - -git/xdiff/lib.a: git $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) xdiff/lib.a test: all $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all install: all mkdir -p $(DESTDIR)$(CGIT_SCRIPT_PATH) install cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) install cgit.css $(DESTDIR)$(CGIT_SCRIPT_PATH)/cgit.css install cgit.png $(DESTDIR)$(CGIT_SCRIPT_PATH)/cgit.png uninstall: rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) rm -f $(CGIT_SCRIPT_PATH)/cgit.css rm -f $(CGIT_SCRIPT_PATH)/cgit.png clean: rm -f cgit VERSION *.o *.d get-git: curl $(GIT_URL) | tar -xj && rm -rf git && mv git-$(GIT_VER) git @@ -1,439 +1,439 @@ /* cache.c: cache management * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) * * * The cache is just a directory structure where each file is a cache slot, * and each filename is based on the hash of some key (e.g. the cgit url). * Each file contains the full key followed by the cached content for that * key. * */ #include "cgit.h" #include "cache.h" #define CACHE_BUFSIZE (1024 * 4) struct cache_slot { const char *key; int keylen; int ttl; cache_fill_fn fn; void *cbdata; int cache_fd; int lock_fd; const char *cache_name; const char *lock_name; int match; struct stat cache_st; struct stat lock_st; int bufsize; char buf[CACHE_BUFSIZE]; }; /* Open an existing cache slot and fill the cache buffer with * (part of) the content of the cache file. Return 0 on success * and errno otherwise. */ static int open_slot(struct cache_slot *slot) { char *bufz; int bufkeylen = -1; slot->cache_fd = open(slot->cache_name, O_RDONLY); if (slot->cache_fd == -1) return errno; if (fstat(slot->cache_fd, &slot->cache_st)) return errno; slot->bufsize = xread(slot->cache_fd, slot->buf, sizeof(slot->buf)); if (slot->bufsize < 0) return errno; bufz = memchr(slot->buf, 0, slot->bufsize); if (bufz) bufkeylen = bufz - slot->buf; slot->match = bufkeylen == slot->keylen && !memcmp(slot->key, slot->buf, bufkeylen + 1); return 0; } /* Close the active cache slot */ static int close_slot(struct cache_slot *slot) { int err = 0; if (slot->cache_fd > 0) { if (close(slot->cache_fd)) err = errno; else slot->cache_fd = -1; } return err; } /* Print the content of the active cache slot (but skip the key). */ static int print_slot(struct cache_slot *slot) { ssize_t i, j; i = lseek(slot->cache_fd, slot->keylen + 1, SEEK_SET); if (i != slot->keylen + 1) return errno; do { i = j = xread(slot->cache_fd, slot->buf, sizeof(slot->buf)); if (i > 0) j = xwrite(STDOUT_FILENO, slot->buf, i); } while (i > 0 && j == i); if (i < 0 || j != i) return errno; else return 0; } /* Check if the slot has expired */ static int is_expired(struct cache_slot *slot) { if (slot->ttl < 0) return 0; else return slot->cache_st.st_mtime + slot->ttl*60 < time(NULL); } /* Check if the slot has been modified since we opened it. * NB: If stat() fails, we pretend the file is modified. */ static int is_modified(struct cache_slot *slot) { struct stat st; if (stat(slot->cache_name, &st)) return 1; return (st.st_ino != slot->cache_st.st_ino || st.st_mtime != slot->cache_st.st_mtime || st.st_size != slot->cache_st.st_size); } /* Close an open lockfile */ static int close_lock(struct cache_slot *slot) { int err = 0; if (slot->lock_fd > 0) { if (close(slot->lock_fd)) err = errno; else slot->lock_fd = -1; } return err; } /* Create a lockfile used to store the generated content for a cache * slot, and write the slot key + \0 into it. * Returns 0 on success and errno otherwise. */ static int lock_slot(struct cache_slot *slot) { slot->lock_fd = open(slot->lock_name, O_RDWR|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR); if (slot->lock_fd == -1) return errno; if (xwrite(slot->lock_fd, slot->key, slot->keylen + 1) < 0) return errno; return 0; } /* Release the current lockfile. If `replace_old_slot` is set the * lockfile replaces the old cache slot, otherwise the lockfile is * just deleted. */ static int unlock_slot(struct cache_slot *slot, int replace_old_slot) { int err; if (replace_old_slot) err = rename(slot->lock_name, slot->cache_name); else err = unlink(slot->lock_name); if (err) return errno; return 0; } /* Generate the content for the current cache slot by redirecting * stdout to the lock-fd and invoking the callback function */ static int fill_slot(struct cache_slot *slot) { int tmp; /* Preserve stdout */ tmp = dup(STDOUT_FILENO); if (tmp == -1) return errno; /* Redirect stdout to lockfile */ if (dup2(slot->lock_fd, STDOUT_FILENO) == -1) return errno; /* Generate cache content */ slot->fn(slot->cbdata); /* Restore stdout */ if (dup2(tmp, STDOUT_FILENO) == -1) return errno; /* Close the temporary filedescriptor */ if (close(tmp)) return errno; return 0; } /* Crude implementation of 32-bit FNV-1 hash algorithm, * see http://www.isthe.com/chongo/tech/comp/fnv/ for details * about the magic numbers. */ #define FNV_OFFSET 0x811c9dc5 #define FNV_PRIME 0x01000193 unsigned long hash_str(const char *str) { unsigned long h = FNV_OFFSET; unsigned char *s = (unsigned char *)str; if (!s) return h; while(*s) { h *= FNV_PRIME; h ^= *s++; } return h; } static int process_slot(struct cache_slot *slot) { int err; err = open_slot(slot); if (!err && slot->match) { if (is_expired(slot)) { if (!lock_slot(slot)) { /* If the cachefile has been replaced between * `open_slot` and `lock_slot`, we'll just * serve the stale content from the original * cachefile. This way we avoid pruning the * newly generated slot. The same code-path * is chosen if fill_slot() fails for some * reason. * * TODO? check if the new slot contains the * same key as the old one, since we would * prefer to serve the newest content. * This will require us to open yet another * file-descriptor and read and compare the * key from the new file, so for now we're * lazy and just ignore the new file. */ if (is_modified(slot) || fill_slot(slot)) { unlock_slot(slot, 0); close_lock(slot); } else { close_slot(slot); unlock_slot(slot, 1); slot->cache_fd = slot->lock_fd; } } } if ((err = print_slot(slot)) != 0) { cache_log("[cgit] error printing cache %s: %s (%d)\n", slot->cache_name, strerror(err), err); } close_slot(slot); return err; } /* If the cache slot does not exist (or its key doesn't match the * current key), lets try to create a new cache slot for this * request. If this fails (for whatever reason), lets just generate * the content without caching it and fool the caller to belive * everything worked out (but print a warning on stdout). */ close_slot(slot); if ((err = lock_slot(slot)) != 0) { cache_log("[cgit] Unable to lock slot %s: %s (%d)\n", slot->lock_name, strerror(err), err); slot->fn(slot->cbdata); return 0; } if ((err = fill_slot(slot)) != 0) { cache_log("[cgit] Unable to fill slot %s: %s (%d)\n", slot->lock_name, strerror(err), err); unlock_slot(slot, 0); close_lock(slot); slot->fn(slot->cbdata); return 0; } // We've got a valid cache slot in the lock file, which // is about to replace the old cache slot. But if we // release the lockfile and then try to open the new cache // slot, we might get a race condition with a concurrent // writer for the same cache slot (with a different key). // Lets avoid such a race by just printing the content of // the lock file. slot->cache_fd = slot->lock_fd; unlock_slot(slot, 1); if ((err = print_slot(slot)) != 0) { cache_log("[cgit] error printing cache %s: %s (%d)\n", slot->cache_name, strerror(err), err); } close_slot(slot); return err; } /* Print cached content to stdout, generate the content if necessary. */ int cache_process(int size, const char *path, const char *key, int ttl, cache_fill_fn fn, void *cbdata) { unsigned long hash; int len, i; char filename[1024]; char lockname[1024 + 5]; /* 5 = ".lock" */ struct cache_slot slot; /* If the cache is disabled, just generate the content */ if (size <= 0) { fn(cbdata); return 0; } /* Verify input, calculate filenames */ if (!path) { cache_log("[cgit] Cache path not specified, caching is disabled\n"); fn(cbdata); return 0; } len = strlen(path); if (len > sizeof(filename) - 10) { /* 10 = "/01234567\0" */ cache_log("[cgit] Cache path too long, caching is disabled: %s\n", path); fn(cbdata); return 0; } if (!key) key = ""; hash = hash_str(key) % size; strcpy(filename, path); if (filename[len - 1] != '/') filename[len++] = '/'; for(i = 0; i < 8; i++) { sprintf(filename + len++, "%x", (unsigned char)(hash & 0xf)); hash >>= 4; } filename[len] = '\0'; strcpy(lockname, filename); strcpy(lockname + len, ".lock"); slot.fn = fn; slot.cbdata = cbdata; slot.ttl = ttl; slot.cache_name = filename; slot.lock_name = lockname; slot.key = key; slot.keylen = strlen(key); return process_slot(&slot); } /* Return a strftime formatted date/time * NB: the result from this function is to shared memory */ char *sprintftime(const char *format, time_t time) { static char buf[64]; struct tm *tm; if (!time) return NULL; tm = gmtime(&time); strftime(buf, sizeof(buf)-1, format, tm); return buf; } int cache_ls(const char *path) { DIR *dir; struct dirent *ent; int err = 0; struct cache_slot slot; char fullname[1024]; char *name; if (!path) { cache_log("[cgit] cache path not specified\n"); return -1; } if (strlen(path) > 1024 - 10) { cache_log("[cgit] cache path too long: %s\n", path); return -1; } dir = opendir(path); if (!dir) { err = errno; cache_log("[cgit] unable to open path %s: %s (%d)\n", path, strerror(err), err); return err; } strcpy(fullname, path); name = fullname + strlen(path); if (*(name - 1) != '/') { *name++ = '/'; *name = '\0'; } slot.cache_name = fullname; while((ent = readdir(dir)) != NULL) { if (strlen(ent->d_name) != 8) continue; strcpy(name, ent->d_name); if ((err = open_slot(&slot)) != 0) { cache_log("[cgit] unable to open path %s: %s (%d)\n", fullname, strerror(err), err); continue; } - printf("%s %s %10lld %s\n", + printf("%s %s %10zd %s\n", name, sprintftime("%Y-%m-%d %H:%M:%S", slot.cache_st.st_mtime), slot.cache_st.st_size, slot.buf); close_slot(&slot); } closedir(dir); return 0; } /* Print a message to stdout */ void cache_log(const char *format, ...) { va_list args; va_start(args, format); vfprintf(stderr, format, args); va_end(args); } @@ -1,240 +1,241 @@ #ifndef CGIT_H #define CGIT_H #include <git-compat-util.h> #include <cache.h> #include <grep.h> #include <object.h> #include <tree.h> #include <commit.h> #include <tag.h> #include <diff.h> #include <diffcore.h> #include <refs.h> #include <revision.h> #include <log-tree.h> #include <archive.h> #include <xdiff/xdiff.h> #include <utf8.h> /* * Dateformats used on misc. pages */ #define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)" #define FMT_SHORTDATE "%Y-%m-%d" +#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ" /* * Limits used for relative dates */ #define TM_MIN 60 #define TM_HOUR (TM_MIN * 60) #define TM_DAY (TM_HOUR * 24) #define TM_WEEK (TM_DAY * 7) #define TM_YEAR (TM_DAY * 365) #define TM_MONTH (TM_YEAR / 12.0) /* * Default encoding */ #define PAGE_ENCODING "UTF-8" typedef void (*configfn)(const char *name, const char *value); typedef void (*filepair_fn)(struct diff_filepair *pair); typedef void (*linediff_fn)(char *line, int len); struct cgit_repo { char *url; char *name; char *path; char *desc; char *owner; char *defbranch; char *group; char *module_link; char *readme; char *clone_url; int snapshots; int enable_log_filecount; int enable_log_linecount; }; struct cgit_repolist { int length; int count; struct cgit_repo *repos; }; struct commitinfo { struct commit *commit; char *author; char *author_email; unsigned long author_date; char *committer; char *committer_email; unsigned long committer_date; char *subject; char *msg; char *msg_encoding; }; struct taginfo { char *tagger; char *tagger_email; int tagger_date; char *msg; }; struct refinfo { const char *refname; struct object *object; union { struct taginfo *tag; struct commitinfo *commit; }; }; struct reflist { struct refinfo **refs; int alloc; int count; }; struct cgit_query { int has_symref; int has_sha1; char *raw; char *repo; char *page; char *search; char *grep; char *head; char *sha1; char *sha2; char *path; char *name; char *mimetype; int ofs; }; struct cgit_config { char *agefile; char *cache_root; char *clone_prefix; char *css; char *favicon; char *footer; char *index_header; char *index_info; char *logo; char *logo_link; char *module_link; char *repo_group; char *robots; char *root_title; char *root_desc; char *root_readme; char *script_name; char *virtual_root; int cache_size; int cache_dynamic_ttl; int cache_max_create_time; int cache_repo_ttl; int cache_root_ttl; int cache_static_ttl; int enable_index_links; int enable_log_filecount; int enable_log_linecount; int local_time; int max_repo_count; int max_commit_count; int max_lock_attempts; int max_msg_len; int max_repodesc_len; int nocache; int renamelimit; int snapshots; int summary_branches; int summary_log; int summary_tags; }; struct cgit_page { time_t modified; time_t expires; size_t size; char *mimetype; char *charset; char *filename; char *title; }; struct cgit_context { struct cgit_query qry; struct cgit_config cfg; struct cgit_repo *repo; struct cgit_page page; }; struct cgit_snapshot_format { const char *suffix; const char *mimetype; write_archive_fn_t write_func; int bit; }; extern const char *cgit_version; extern struct cgit_repolist cgit_repolist; extern struct cgit_context ctx; extern const struct cgit_snapshot_format cgit_snapshot_formats[]; extern struct cgit_repo *cgit_add_repo(const char *url); extern struct cgit_repo *cgit_get_repoinfo(const char *url); extern void cgit_repo_config_cb(const char *name, const char *value); extern int chk_zero(int result, char *msg); extern int chk_positive(int result, char *msg); extern int chk_non_negative(int result, char *msg); extern char *trim_end(const char *str, char c); extern char *strlpart(char *txt, int maxlen); extern char *strrpart(char *txt, int maxlen); extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, int flags, void *cb_data); extern void *cgit_free_commitinfo(struct commitinfo *info); extern int cgit_diff_files(const unsigned char *old_sha1, const unsigned char *new_sha1, linediff_fn fn); extern void cgit_diff_tree(const unsigned char *old_sha1, const unsigned char *new_sha1, filepair_fn fn, const char *prefix); extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); extern char *fmt(const char *format,...); extern struct commitinfo *cgit_parse_commit(struct commit *commit); extern struct taginfo *cgit_parse_tag(struct tag *tag); extern void cgit_parse_url(const char *url); extern const char *cgit_repobasename(const char *reponame); extern int cgit_parse_snapshots_mask(const char *str); /* libgit.a either links against or compiles its own implementation of * strcasestr(), and we'd like to reuse it. Simply re-declaring it * seems to do the trick. */ extern char *strcasestr(const char *haystack, const char *needle); #endif /* CGIT_H */ @@ -1,200 +1,202 @@ ## ## cgitrc: template for /etc/cgitrc ## ## Uncomment and set to 1 to deactivate caching of generated pages. Mostly ## usefull for testing. #nocache=0 ## This variable can be used to override the default value for "robots" ## meta-tag. If unset, the meta-tag isn't generated. #robots=index, nofollow ## Set allowed snapshot types by default. Can be overridden per repo # can be any combination of zip/tar.gz/tar.bz2/tar #snapshots=0 ## Enable/disable extra links to summary/log/tree per repo on index page #enable-index-links=0 ## Enable/disable display of 'number of files changed' in log view #enable-log-filecount=0 ## Enable/disable display of 'number of lines changed' in log view #enable-log-linecount=0 ## Enable/disable display of HEAD shortlog in summary view. Set it to maximum ## number of commits that should be displayed #summary-log=0 ## Restrict the number of branches printed in summary view. Set to 0 to ## print all branches. #summary-branches=0 ## Restrict the number of tags printed in summary view. Set to 0 to ## print all tags. #summary-tags=0 +## Enable/disable display of localtime vs. GMT. Set to 1 for localtime. +#local-time=0 ## The "Idle" column on the repository index page can read a timestamp ## from the specified agefile (if this file cannot be found, the mtime ## of HEAD is used). ## The cgit repo on hjemli.net uses the the following command in it's ## post-receive hook to update the age-file: ## git-for-each-ref --format="%(committerdate)" --sort=-committerdate \ ## --count=1 > $GIT_DIR/info/web/last-modifie ## #agefile=info/web/last-modified ## Git detects renames, but with a limit on the number of files to ## consider. This option can be used to specify another limit (or -1 to ## use the default limit). ## #renamelimit=-1 ## Specify a root for virtual urls. This makes cgit generate urls like ## ## http://localhost/git/repo/log/?h=branch ## ## instead of ## ## http://localhost/cgit/cgit.cgi?url=repo/log&h=branch ## ## For this to work with apache, a rewrite rule must be added to httpd.conf, ## possibly looking something like this: ## ## RewriteRule ^/git/(.*)$ /cgit/cgit.cgi?url=$1 [L,QSA] ## ## For this to work with lighttpd, the rewrite rule should look more like this: ## ## url.rewrite = ( ## "^/git/([^?/]+/[^?]*)?(?:\?(.*))?$" => "/cgit.cgi?url=$1&$2" ## ) ## ## This setting is disabled by default. #virtual-root=/git ## Set the title printed on the root page #root-title=Git repository browser ## Set the description printed on the root page #root-desc=a fast web interface for the git dscm ## If specified, the file at this path will be included as HTML in the ## sidebar on the repository index page #index-info= ## If specified, the file at this path will be included as HTML above ## the repository index #index-header= ## Link to css file #css=/cgit/cgit.css ## Link to favicon #favicon=/favicon.ico ## Link to logo file #logo=/cgit/git-logo.png ## Url loaded when clicking the logo #logo-link=http://www.kernel.org/pub/software/scm/git/docs/ ## Url loaded when clicking a submodule link #module-link=./?repo=%s&page=commit&id=%s ## Shared prefix which, when combined with repo url, becomes the url used ## to clone the repo #clone-prefix= ## Number of chars shown of repo description (in repolist view) #max-repodesc-length=60 ## Number of chars shown of commit subject message (in log view) #max-message-length=60 ## Number of commits per page in log view #max-commit-count=50 ## Root of cached output #cache-root=/var/cache/cgit ## Include another config-file #include=/var/cgit/repolist ## ## Time-To-Live settings: specifies how long (in minutes) different pages ## should be cached (0 for instant expiration, -1 for immortal pages) ## ## ttl for root page #cache-root-ttl=5 ## ttl for repo summary page #cache-repo-ttl=5 ## ttl for other dynamic pages #cache-dynamic-ttl=5 ## ttl for static pages (addressed by SHA-1) #cache-static-ttl=-1 ## Example repository entry. Required values are repo.url and repo.path (each ## repository section must start with repo.url). #repo.url=cgit #repo.name=cgit #repo.desc=the caching cgi for git #repo.path=/pub/git/cgit ## this is the path to $GIT_DIR #repo.owner=Lars Hjemli #repo.defbranch=master ## define a default branch #repo.snapshots=tar.bz2 ## override a sitewide snapshot-setting #repo.enable-log-filecount=0 ## override the default filecount setting #repo.enable-log-linecount=0 ## override the default linecount setting #repo.module-link=/git/%s/commit/?id=%s ## override the standard module-link #repo.readme=info/web/readme ## specify a file to include on summary page #repo.clone-url=git://hjemli.net/pub/git/cgit ## Additional repositories grouped under "mirrors" #repo.group=mirrors #repo.url=git #repo.path=/pub/git/git #repo.clone-url=git://hjemli.net/pub/git/git # #repo.url=linux #repo.path=/pub/git/linux ## A group of private repositories (with a working directory) #repo.group=private #repo.url=larsh/cgit #repo.path=/home/larsh/src/cgit/.git #repo.url=larsh/git #repo.path=/home/larsh/src/git/.git @@ -1,158 +1,165 @@ /* cmd.c: the cgit command dispatcher * * Copyright (C) 2008 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" #include "cmd.h" #include "cache.h" #include "ui-shared.h" +#include "ui-atom.h" #include "ui-blob.h" #include "ui-clone.h" #include "ui-commit.h" #include "ui-diff.h" #include "ui-log.h" #include "ui-patch.h" #include "ui-plain.h" #include "ui-refs.h" #include "ui-repolist.h" #include "ui-snapshot.h" #include "ui-summary.h" #include "ui-tag.h" #include "ui-tree.h" static void HEAD_fn(struct cgit_context *ctx) { cgit_clone_head(ctx); } +static void atom_fn(struct cgit_context *ctx) +{ + cgit_print_atom(ctx->qry.head, ctx->qry.path, 10); +} + static void about_fn(struct cgit_context *ctx) { if (ctx->repo) cgit_print_repo_readme(); else cgit_print_site_readme(); } static void blob_fn(struct cgit_context *ctx) { cgit_print_blob(ctx->qry.sha1, ctx->qry.path, ctx->qry.head); } static void commit_fn(struct cgit_context *ctx) { cgit_print_commit(ctx->qry.sha1); } static void diff_fn(struct cgit_context *ctx) { cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path); } static void info_fn(struct cgit_context *ctx) { cgit_clone_info(ctx); } static void log_fn(struct cgit_context *ctx) { cgit_print_log(ctx->qry.sha1, ctx->qry.ofs, ctx->cfg.max_commit_count, ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1); } static void ls_cache_fn(struct cgit_context *ctx) { ctx->page.mimetype = "text/plain"; ctx->page.filename = "ls-cache.txt"; cgit_print_http_headers(ctx); cache_ls(ctx->cfg.cache_root); } static void objects_fn(struct cgit_context *ctx) { cgit_clone_objects(ctx); } static void repolist_fn(struct cgit_context *ctx) { cgit_print_repolist(); } static void patch_fn(struct cgit_context *ctx) { cgit_print_patch(ctx->qry.sha1); } static void plain_fn(struct cgit_context *ctx) { cgit_print_plain(ctx); } static void refs_fn(struct cgit_context *ctx) { cgit_print_refs(); } static void snapshot_fn(struct cgit_context *ctx) { cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, cgit_repobasename(ctx->repo->url), ctx->qry.path, ctx->repo->snapshots); } static void summary_fn(struct cgit_context *ctx) { cgit_print_summary(); } static void tag_fn(struct cgit_context *ctx) { cgit_print_tag(ctx->qry.sha1); } static void tree_fn(struct cgit_context *ctx) { cgit_print_tree(ctx->qry.sha1, ctx->qry.path); } #define def_cmd(name, want_repo, want_layout) \ {#name, name##_fn, want_repo, want_layout} struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx) { static struct cgit_cmd cmds[] = { def_cmd(HEAD, 1, 0), + def_cmd(atom, 1, 0), def_cmd(about, 0, 1), def_cmd(blob, 1, 0), def_cmd(commit, 1, 1), def_cmd(diff, 1, 1), def_cmd(info, 1, 0), def_cmd(log, 1, 1), def_cmd(ls_cache, 0, 0), def_cmd(objects, 1, 0), def_cmd(patch, 1, 0), def_cmd(plain, 1, 0), def_cmd(refs, 1, 1), def_cmd(repolist, 0, 0), def_cmd(snapshot, 1, 0), def_cmd(summary, 1, 1), def_cmd(tag, 1, 1), def_cmd(tree, 1, 1), }; int i; if (ctx->qry.page == NULL) { if (ctx->repo) ctx->qry.page = "summary"; else ctx->qry.page = "repolist"; } for(i = 0; i < sizeof(cmds)/sizeof(*cmds); i++) if (!strcmp(ctx->qry.page, cmds[i].name)) return &cmds[i]; return NULL; } diff --git a/git b/git -Subproject 93310a40eb022a0e36e7c618921931d8ffc31fd +Subproject 8e1db3871c767cb17b5e0eeb7bea8d967821a05 diff --git a/ui-atom.c b/ui-atom.c new file mode 100644 index 0000000..a6ea3ee --- a/dev/null +++ b/ui-atom.c @@ -0,0 +1,129 @@ +/* ui-atom.c: functions for atom feeds + * + * Copyright (C) 2008 Lars Hjemli + * + * Licensed under GNU General Public License v2 + * (see COPYING for full license text) + */ + +#include "cgit.h" +#include "html.h" +#include "ui-shared.h" + +void add_entry(struct commit *commit, char *host) +{ + char delim = '&'; + char *hex; + char *mail, *t, *t2; + struct commitinfo *info; + + info = cgit_parse_commit(commit); + hex = sha1_to_hex(commit->object.sha1); + html("<entry>\n"); + html("<title>"); + html_txt(info->subject); + html("</title>\n"); + html("<updated>"); + cgit_print_date(info->author_date, FMT_ATOMDATE, ctx.cfg.local_time); + html("</updated>\n"); + html("<author>\n"); + if (info->author) { + html("<name>"); + html_txt(info->author); + html("</name>\n"); + } + if (info->author_email) { + mail = xstrdup(info->author_email); + t = strchr(mail, '<'); + if (t) + t++; + else + t = mail; + t2 = strchr(t, '>'); + if (t2) + *t2 = '\0'; + html("<email>"); + html_txt(t); + html("</email>\n"); + free(mail); + } + html("</author>\n"); + html("<published>"); + cgit_print_date(info->author_date, FMT_ATOMDATE, ctx.cfg.local_time); + html("</published>\n"); + if (host) { + html("<link rel='alternate' type='text/html' href='http://"); + html_attr(host); + html_attr(cgit_pageurl(ctx.repo->url, "commit", NULL)); + if (ctx.cfg.virtual_root) + delim = '?'; + htmlf("%cid=%s", delim, hex); + html("'/>\n"); + } + htmlf("<id>%s</id>\n", hex); + html("<content type='text'>\n"); + html_txt(info->msg); + html("</content>\n"); + html("<content type='xhtml'>\n"); + html("<div xmlns='http://www.w3.org/1999/xhtml'>\n"); + html("<pre>\n"); + html_txt(info->msg); + html("</pre>\n"); + html("</div>\n"); + html("</content>\n"); + html("</entry>\n"); + cgit_free_commitinfo(info); +} + + +void cgit_print_atom(char *tip, char *path, int max_count) +{ + char *host; + const char *argv[] = {NULL, tip, NULL, NULL, NULL}; + struct commit *commit; + struct rev_info rev; + int argc = 2; + + if (!tip) + argv[1] = ctx.qry.head; + + if (path) { + argv[argc++] = "--"; + argv[argc++] = path; + } + + init_revisions(&rev, NULL); + rev.abbrev = DEFAULT_ABBREV; + rev.commit_format = CMIT_FMT_DEFAULT; + rev.verbose_header = 1; + rev.show_root_diff = 0; + rev.max_count = max_count; + setup_revisions(argc, argv, &rev, NULL); + prepare_revision_walk(&rev); + + host = cgit_hosturl(); + ctx.page.mimetype = "text/xml"; + ctx.page.charset = "utf-8"; + cgit_print_http_headers(&ctx); + html("<feed xmlns='http://www.w3.org/2005/Atom'>\n"); + html("<title>"); + html_txt(ctx.repo->name); + html("</title>\n"); + html("<subtitle>"); + html_txt(ctx.repo->desc); + html("</subtitle>\n"); + if (host) { + html("<link rel='alternate' type='text/html' href='http://"); + html_attr(host); + html_attr(cgit_repourl(ctx.repo->url)); + html("'/>\n"); + } + while ((commit = get_revision(&rev)) != NULL) { + add_entry(commit, host); + free(commit->buffer); + commit->buffer = NULL; + free_commit_list(commit->parents); + commit->parents = NULL; + } + html("</feed>\n"); +} diff --git a/ui-atom.h b/ui-atom.h new file mode 100644 index 0000000..749ffd3 --- a/dev/null +++ b/ui-atom.h @@ -0,0 +1,6 @@ +#ifndef UI_ATOM_H +#define UI_ATOM_H + +extern void cgit_print_atom(char *tip, char *path, int max_count); + +#endif diff --git a/ui-shared.c b/ui-shared.c index a2e0dd2..4818e70 100644 --- a/ui-shared.c +++ b/ui-shared.c @@ -1,684 +1,707 @@ /* ui-shared.c: common web output functions * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" #include "cmd.h" #include "html.h" const char cgit_doctype[] = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"; static char *http_date(time_t t) { static char day[][4] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; static char month[][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Now", "Dec"}; struct tm *tm = gmtime(&t); return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday], tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year, tm->tm_hour, tm->tm_min, tm->tm_sec); } void cgit_print_error(char *msg) { html("<div class='error'>"); html_txt(msg); html("</div>\n"); } +char *cgit_hosturl() +{ + char *host, *port; + + host = getenv("SERVER_NAME"); + if (!host) + return NULL; + port = getenv("SERVER_PORT"); + if (port && atoi(port) != 80) + host = xstrdup(fmt("%s:%d", host, atoi(port))); + else + host = xstrdup(host); + return host; +} + char *cgit_rooturl() { if (ctx.cfg.virtual_root) return fmt("%s/", ctx.cfg.virtual_root); else return ctx.cfg.script_name; } char *cgit_repourl(const char *reponame) { if (ctx.cfg.virtual_root) { return fmt("%s/%s/", ctx.cfg.virtual_root, reponame); } else { return fmt("?r=%s", reponame); } } char *cgit_fileurl(const char *reponame, const char *pagename, const char *filename, const char *query) { char *tmp; char *delim; if (ctx.cfg.virtual_root) { tmp = fmt("%s/%s/%s/%s", ctx.cfg.virtual_root, reponame, pagename, (filename ? filename:"")); delim = "?"; } else { tmp = fmt("?url=%s/%s/%s", reponame, pagename, (filename ? filename : "")); delim = "&"; } if (query) tmp = fmt("%s%s%s", tmp, delim, query); return tmp; } char *cgit_pageurl(const char *reponame, const char *pagename, const char *query) { return cgit_fileurl(reponame,pagename,0,query); } const char *cgit_repobasename(const char *reponame) { /* I assume we don't need to store more than one repo basename */ static char rvbuf[1024]; int p; const char *rv; strncpy(rvbuf,reponame,sizeof(rvbuf)); if(rvbuf[sizeof(rvbuf)-1]) die("cgit_repobasename: truncated repository name '%s'", reponame); p = strlen(rvbuf)-1; /* strip trailing slashes */ while(p && rvbuf[p]=='/') rvbuf[p--]=0; /* strip trailing .git */ if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) { p -= 3; rvbuf[p--] = 0; } /* strip more trailing slashes if any */ while( p && rvbuf[p]=='/') rvbuf[p--]=0; /* find last slash in the remaining string */ rv = strrchr(rvbuf,'/'); if(rv) return ++rv; return rvbuf; } char *cgit_currurl() { if (!ctx.cfg.virtual_root) return ctx.cfg.script_name; else if (ctx.qry.page) return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page); else if (ctx.qry.repo) return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo); else return fmt("%s/", ctx.cfg.virtual_root); } static void site_url(char *page, char *search, int ofs) { char *delim = "?"; if (ctx.cfg.virtual_root) { html_attr(ctx.cfg.virtual_root); if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') html("/"); } else html(ctx.cfg.script_name); if (page) { htmlf("?p=%s", page); delim = "&"; } if (search) { html(delim); html("q="); html_attr(search); delim = "&"; } if (ofs) { html(delim); htmlf("ofs=%d", ofs); } } static void site_link(char *page, char *name, char *title, char *class, char *search, int ofs) { html("<a"); if (title) { html(" title='"); html_attr(title); html("'"); } if (class) { html(" class='"); html_attr(class); html("'"); } html(" href='"); site_url(page, search, ofs); html("'>"); html_txt(name); html("</a>"); } void cgit_index_link(char *name, char *title, char *class, char *pattern, int ofs) { site_link(NULL, name, title, class, pattern, ofs); } static char *repolink(char *title, char *class, char *page, char *head, char *path) { char *delim = "?"; html("<a"); if (title) { html(" title='"); html_attr(title); html("'"); } if (class) { html(" class='"); html_attr(class); html("'"); } html(" href='"); if (ctx.cfg.virtual_root) { html_attr(ctx.cfg.virtual_root); if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') html("/"); html_attr(ctx.repo->url); if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') html("/"); if (page) { html(page); html("/"); if (path) html_attr(path); } } else { html(ctx.cfg.script_name); html("?url="); html_attr(ctx.repo->url); if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') html("/"); if (page) { html(page); html("/"); if (path) html_attr(path); } delim = "&"; } if (head && strcmp(head, ctx.repo->defbranch)) { html(delim); html("h="); html_attr(head); delim = "&"; } return fmt("%s", delim); } static void reporevlink(char *page, char *name, char *title, char *class, char *head, char *rev, char *path) { char *delim; delim = repolink(title, class, page, head, path); if (rev && strcmp(rev, ctx.qry.head)) { html(delim); html("id="); html_attr(rev); } html("'>"); html_txt(name); html("</a>"); } void cgit_tree_link(char *name, char *title, char *class, char *head, char *rev, char *path) { reporevlink("tree", name, title, class, head, rev, path); } void cgit_plain_link(char *name, char *title, char *class, char *head, char *rev, char *path) { reporevlink("plain", name, title, class, head, rev, path); } void cgit_log_link(char *name, char *title, char *class, char *head, char *rev, char *path, int ofs, char *grep, char *pattern) { char *delim; delim = repolink(title, class, "log", head, path); if (rev && strcmp(rev, ctx.qry.head)) { html(delim); html("id="); html_attr(rev); delim = "&"; } if (grep && pattern) { html(delim); html("qt="); html_attr(grep); delim = "&"; html(delim); html("q="); html_attr(pattern); } if (ofs > 0) { html(delim); html("ofs="); htmlf("%d", ofs); } html("'>"); html_txt(name); html("</a>"); } void cgit_commit_link(char *name, char *title, char *class, char *head, char *rev) { if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { name[ctx.cfg.max_msg_len] = '\0'; name[ctx.cfg.max_msg_len - 1] = '.'; name[ctx.cfg.max_msg_len - 2] = '.'; name[ctx.cfg.max_msg_len - 3] = '.'; } reporevlink("commit", name, title, class, head, rev, NULL); } void cgit_refs_link(char *name, char *title, char *class, char *head, char *rev, char *path) { reporevlink("refs", name, title, class, head, rev, path); } void cgit_snapshot_link(char *name, char *title, char *class, char *head, char *rev, char *archivename) { reporevlink("snapshot", name, title, class, head, rev, archivename); } void cgit_diff_link(char *name, char *title, char *class, char *head, char *new_rev, char *old_rev, char *path) { char *delim; delim = repolink(title, class, "diff", head, path); if (new_rev && strcmp(new_rev, ctx.qry.head)) { html(delim); html("id="); html_attr(new_rev); delim = "&"; } if (old_rev) { html(delim); html("id2="); html_attr(old_rev); } html("'>"); html_txt(name); html("</a>"); } void cgit_patch_link(char *name, char *title, char *class, char *head, char *rev) { reporevlink("patch", name, title, class, head, rev, NULL); } void cgit_object_link(struct object *obj) { char *page, *arg, *url; if (obj->type == OBJ_COMMIT) { cgit_commit_link(fmt("commit %s", sha1_to_hex(obj->sha1)), NULL, NULL, ctx.qry.head, sha1_to_hex(obj->sha1)); return; } else if (obj->type == OBJ_TREE) { page = "tree"; arg = "id"; } else if (obj->type == OBJ_TAG) { page = "tag"; arg = "id"; } else { page = "blob"; arg = "id"; } url = cgit_pageurl(ctx.qry.repo, page, fmt("%s=%s", arg, sha1_to_hex(obj->sha1))); html_link_open(url, NULL, NULL); htmlf("%s %s", typename(obj->type), sha1_to_hex(obj->sha1)); html_link_close(); } void cgit_print_date(time_t secs, char *format, int local_time) { char buf[64]; struct tm *time; if (!secs) return; if(local_time) time = localtime(&secs); else time = gmtime(&secs); strftime(buf, sizeof(buf)-1, format, time); html_txt(buf); } void cgit_print_age(time_t t, time_t max_relative, char *format) { time_t now, secs; if (!t) return; time(&now); secs = now - t; if (secs > max_relative && max_relative >= 0) { cgit_print_date(t, format, ctx.cfg.local_time); return; } if (secs < TM_HOUR * 2) { htmlf("<span class='age-mins'>%.0f min.</span>", secs * 1.0 / TM_MIN); return; } if (secs < TM_DAY * 2) { htmlf("<span class='age-hours'>%.0f hours</span>", secs * 1.0 / TM_HOUR); return; } if (secs < TM_WEEK * 2) { htmlf("<span class='age-days'>%.0f days</span>", secs * 1.0 / TM_DAY); return; } if (secs < TM_MONTH * 2) { htmlf("<span class='age-weeks'>%.0f weeks</span>", secs * 1.0 / TM_WEEK); return; } if (secs < TM_YEAR * 2) { htmlf("<span class='age-months'>%.0f months</span>", secs * 1.0 / TM_MONTH); return; } htmlf("<span class='age-years'>%.0f years</span>", secs * 1.0 / TM_YEAR); } void cgit_print_http_headers(struct cgit_context *ctx) { if (ctx->page.mimetype && ctx->page.charset) htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype, ctx->page.charset); else if (ctx->page.mimetype) htmlf("Content-Type: %s\n", ctx->page.mimetype); if (ctx->page.size) htmlf("Content-Length: %ld\n", ctx->page.size); if (ctx->page.filename) htmlf("Content-Disposition: inline; filename=\"%s\"\n", ctx->page.filename); htmlf("Last-Modified: %s\n", http_date(ctx->page.modified)); htmlf("Expires: %s\n", http_date(ctx->page.expires)); html("\n"); } void cgit_print_docstart(struct cgit_context *ctx) { + char *host = cgit_hosturl(); html(cgit_doctype); html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n"); html("<head>\n"); html("<title>"); html_txt(ctx->page.title); html("</title>\n"); htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version); if (ctx->cfg.robots && *ctx->cfg.robots) htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots); html("<link rel='stylesheet' type='text/css' href='"); html_attr(ctx->cfg.css); html("'/>\n"); if (ctx->cfg.favicon) { html("<link rel='shortcut icon' href='"); html_attr(ctx->cfg.favicon); html("'/>\n"); } + if (host && ctx->repo) { + html("<link rel='alternate' title='Atom feed' href='http://"); + html_attr(cgit_hosturl()); + html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path, + fmt("h=%s", ctx->qry.head))); + html("' type='application/atom+xml'/>"); + } html("</head>\n"); html("<body>\n"); } void cgit_print_docend() { html("</div>"); if (ctx.cfg.footer) html_include(ctx.cfg.footer); else { html("<div class='footer'>generated "); cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time); htmlf(" by cgit %s", cgit_version); html("</div>\n"); } html("</body>\n</html>\n"); } int print_branch_option(const char *refname, const unsigned char *sha1, int flags, void *cb_data) { char *name = (char *)refname; html_option(name, name, ctx.qry.head); return 0; } int print_archive_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data) { struct tag *tag; struct taginfo *info; struct object *obj; char buf[256], *url; unsigned char fileid[20]; int *header = (int *)cb_data; if (prefixcmp(refname, "refs/archives")) return 0; strncpy(buf, refname+14, sizeof(buf)); obj = parse_object(sha1); if (!obj) return 1; if (obj->type == OBJ_TAG) { tag = lookup_tag(sha1); if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) return 0; hashcpy(fileid, tag->tagged->sha1); } else if (obj->type != OBJ_BLOB) { return 0; } else { hashcpy(fileid, sha1); } if (!*header) { html("<h1>download</h1>\n"); *header = 1; } url = cgit_pageurl(ctx.qry.repo, "blob", fmt("id=%s&path=%s", sha1_to_hex(fileid), buf)); html_link_open(url, NULL, "menu"); html_txt(strlpart(buf, 20)); html_link_close(); return 0; } void add_hidden_formfields(int incl_head, int incl_search, char *page) { char *url; if (!ctx.cfg.virtual_root) { url = fmt("%s/%s", ctx.qry.repo, page); if (ctx.qry.path) url = fmt("%s/%s", url, ctx.qry.path); html_hidden("url", url); } if (incl_head && ctx.qry.head && ctx.repo->defbranch && strcmp(ctx.qry.head, ctx.repo->defbranch)) html_hidden("h", ctx.qry.head); if (ctx.qry.sha1) html_hidden("id", ctx.qry.sha1); if (ctx.qry.sha2) html_hidden("id2", ctx.qry.sha2); if (incl_search) { if (ctx.qry.grep) html_hidden("qt", ctx.qry.grep); if (ctx.qry.search) html_hidden("q", ctx.qry.search); } } char *hc(struct cgit_cmd *cmd, const char *page) { return (strcmp(cmd->name, page) ? NULL : "active"); } void cgit_print_pageheader(struct cgit_context *ctx) { struct cgit_cmd *cmd = cgit_get_cmd(ctx); html("<table id='header'>\n"); html("<tr>\n"); html("<td class='logo' rowspan='2'><a href='"); if (ctx->cfg.logo_link) html_attr(ctx->cfg.logo_link); else html_attr(cgit_rooturl()); html("'><img src='"); html_attr(ctx->cfg.logo); html("' alt='cgit logo'/></a></td>\n"); html("<td class='main'>"); if (ctx->repo) { cgit_index_link("index", NULL, NULL, NULL, 0); html(" : "); reporevlink(NULL, ctx->repo->name, NULL, hc(cmd, "summary"), ctx->qry.head, NULL, NULL); html("</td><td class='form'>"); html("<form method='get' action=''>\n"); add_hidden_formfields(0, 1, ctx->qry.page); html("<select name='h' onchange='this.form.submit();'>\n"); for_each_branch_ref(print_branch_option, ctx->qry.head); html("</select> "); html("<input type='submit' name='' value='switch'/>"); html("</form>"); } else html_txt(ctx->cfg.root_title); html("</td></tr>\n"); html("<tr><td class='sub'>"); if (ctx->repo) { html_txt(ctx->repo->desc); html("</td><td class='sub right'>"); html_txt(ctx->repo->owner); } else { if (ctx->cfg.root_desc) html_txt(ctx->cfg.root_desc); else if (ctx->cfg.index_info) html_include(ctx->cfg.index_info); } html("</td></tr></table>\n"); html("<table class='tabs'><tr><td>\n"); if (ctx->repo) { reporevlink(NULL, "summary", NULL, hc(cmd, "summary"), ctx->qry.head, NULL, NULL); cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, ctx->qry.sha1, NULL); cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head, NULL, NULL, 0, NULL, NULL); cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head, ctx->qry.sha1, NULL); cgit_commit_link("commit", NULL, hc(cmd, "commit"), ctx->qry.head, ctx->qry.sha1); cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head, ctx->qry.sha1, ctx->qry.sha2, NULL); if (ctx->repo->readme) reporevlink("about", "about", NULL, hc(cmd, "about"), ctx->qry.head, NULL, NULL); html("</td><td class='form'>"); html("<form class='right' method='get' action='"); if (ctx->cfg.virtual_root) html_attr(cgit_fileurl(ctx->qry.repo, "log", ctx->qry.path, NULL)); html("'>\n"); add_hidden_formfields(1, 0, "log"); html("<select name='qt'>\n"); html_option("grep", "log msg", ctx->qry.grep); html_option("author", "author", ctx->qry.grep); html_option("committer", "committer", ctx->qry.grep); html("</select>\n"); html("<input class='txt' type='text' size='10' name='q' value='"); html_attr(ctx->qry.search); html("'/>\n"); html("<input type='submit' value='search'/>\n"); html("</form>\n"); } else { site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0); if (ctx->cfg.root_readme) site_link("about", "about", NULL, hc(cmd, "about"), NULL, 0); html("</td><td class='form'>"); html("<form method='get' action='"); html_attr(cgit_rooturl()); html("'>\n"); html("<input type='text' name='q' size='10' value='"); html_attr(ctx->qry.search); html("'/>\n"); html("<input type='submit' value='search'/>\n"); html("</form>"); } html("</td></tr></table>\n"); html("<div class='content'>"); } void cgit_print_filemode(unsigned short mode) { if (S_ISDIR(mode)) html("d"); else if (S_ISLNK(mode)) html("l"); else if (S_ISGITLINK(mode)) html("m"); else html("-"); html_fileperm(mode >> 6); html_fileperm(mode >> 3); html_fileperm(mode); } void cgit_print_snapshot_links(const char *repo, const char *head, const char *hex, int snapshots) { const struct cgit_snapshot_format* f; char *filename; for (f = cgit_snapshot_formats; f->suffix; f++) { if (!(snapshots & f->bit)) continue; filename = fmt("%s-%s%s", cgit_repobasename(repo), hex, f->suffix); cgit_snapshot_link(filename, NULL, NULL, (char *)head, (char *)hex, filename); html("<br/>"); } } diff --git a/ui-shared.h b/ui-shared.h index c5ce056..747f092 100644 --- a/ui-shared.h +++ b/ui-shared.h @@ -1,42 +1,43 @@ #ifndef UI_SHARED_H #define UI_SHARED_H +extern char *cgit_hosturl(); extern char *cgit_repourl(const char *reponame); extern char *cgit_fileurl(const char *reponame, const char *pagename, const char *filename, const char *query); extern char *cgit_pageurl(const char *reponame, const char *pagename, const char *query); extern void cgit_index_link(char *name, char *title, char *class, char *pattern, int ofs); extern void cgit_tree_link(char *name, char *title, char *class, char *head, char *rev, char *path); extern void cgit_plain_link(char *name, char *title, char *class, char *head, char *rev, char *path); extern void cgit_log_link(char *name, char *title, char *class, char *head, char *rev, char *path, int ofs, char *grep, char *pattern); extern void cgit_commit_link(char *name, char *title, char *class, char *head, char *rev); extern void cgit_patch_link(char *name, char *title, char *class, char *head, char *rev); extern void cgit_refs_link(char *name, char *title, char *class, char *head, char *rev, char *path); extern void cgit_snapshot_link(char *name, char *title, char *class, char *head, char *rev, char *archivename); extern void cgit_diff_link(char *name, char *title, char *class, char *head, char *new_rev, char *old_rev, char *path); extern void cgit_object_link(struct object *obj); extern void cgit_print_error(char *msg); extern void cgit_print_date(time_t secs, char *format, int local_time); extern void cgit_print_age(time_t t, time_t max_relative, char *format); extern void cgit_print_http_headers(struct cgit_context *ctx); extern void cgit_print_docstart(struct cgit_context *ctx); extern void cgit_print_docend(); extern void cgit_print_pageheader(struct cgit_context *ctx); extern void cgit_print_filemode(unsigned short mode); extern void cgit_print_snapshot_links(const char *repo, const char *head, const char *hex, int snapshots); #endif /* UI_SHARED_H */ |