-rw-r--r-- | Makefile | 3 | ||||
-rw-r--r-- | cgit.c | 39 | ||||
-rw-r--r-- | cgit.h | 10 | ||||
-rw-r--r-- | git.h | 27 | ||||
-rw-r--r-- | shared.c | 17 | ||||
-rw-r--r-- | ui-commit.c | 7 | ||||
-rw-r--r-- | ui-shared.c | 11 | ||||
-rw-r--r-- | ui-snapshot.c | 47 |
8 files changed, 153 insertions, 8 deletions
@@ -1,41 +1,42 @@ CGIT_VERSION = 0.2 prefix = /var/www/htdocs/cgit gitsrc = ../git CACHE_ROOT = /var/cache/cgit EXTLIBS = $(gitsrc)/libgit.a $(gitsrc)/xdiff/lib.a -lz -lcrypto OBJECTS = shared.o cache.o parsing.o html.o ui-shared.o ui-repolist.o \ - ui-summary.o ui-log.o ui-view.c ui-tree.c ui-commit.c ui-diff.o + ui-summary.o ui-log.o ui-view.c ui-tree.c ui-commit.c ui-diff.o \ + ui-snapshot.o CFLAGS += -Wall ifdef DEBUG CFLAGS += -g endif all: cgit install: all clean-cache mkdir -p $(prefix) install cgit $(prefix)/cgit.cgi install cgit.css $(prefix)/cgit.css cgit: cgit.c cgit.h git.h $(OBJECTS) $(gitsrc)/libgit.a $(CC) $(CFLAGS) -DCGIT_VERSION='"$(CGIT_VERSION)"' cgit.c -o cgit \ $(OBJECTS) $(EXTLIBS) $(OBJECTS): cgit.h git.h ui-diff.o: xdiff.h $(gitsrc)/libgit.a: $(MAKE) -C $(gitsrc) .PHONY: clean clean: rm -f cgit *.o clean-cache: rm -rf $(CACHE_ROOT)/* @@ -1,219 +1,244 @@ /* cgit.c: cgi for the git scm * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" const char cgit_version[] = CGIT_VERSION; static struct repoinfo *cgit_get_repoinfo(char *url) { int i; struct repoinfo *repo; for (i=0; i<cgit_repolist.count; i++) { repo = &cgit_repolist.repos[i]; if (!strcmp(repo->url, url)) return repo; } return NULL; } static int cgit_prepare_cache(struct cacheitem *item) { if (!cgit_query_repo) { item->name = xstrdup(fmt("%s/index.html", cgit_cache_root)); item->ttl = cgit_cache_root_ttl; return 1; } cgit_repo = cgit_get_repoinfo(cgit_query_repo); if (!cgit_repo) { char *title = fmt("%s - %s", cgit_root_title, "Bad request"); cgit_print_docstart(title, item); cgit_print_pageheader(title, 0); cgit_print_error(fmt("Unknown repo: %s", cgit_query_repo)); cgit_print_docend(); return 0; } if (!cgit_query_page) { item->name = xstrdup(fmt("%s/%s/index.html", cgit_cache_root, cgit_repo->url)); item->ttl = cgit_cache_repo_ttl; } else { item->name = xstrdup(fmt("%s/%s/%s/%s.html", cgit_cache_root, cgit_repo->url, cgit_query_page, cache_safe_filename(cgit_querystring))); if (cgit_query_has_symref) item->ttl = cgit_cache_dynamic_ttl; else if (cgit_query_has_sha1) item->ttl = cgit_cache_static_ttl; else item->ttl = cgit_cache_repo_ttl; } return 1; } static void cgit_print_repo_page(struct cacheitem *item) { char *title; int show_search; if (chdir(cgit_repo->path)) { title = fmt("%s - %s", cgit_root_title, "Bad request"); cgit_print_docstart(title, item); cgit_print_pageheader(title, 0); cgit_print_error(fmt("Unable to scan repository: %s", strerror(errno))); cgit_print_docend(); return; } title = fmt("%s - %s", cgit_repo->name, cgit_repo->desc); show_search = 0; setenv("GIT_DIR", cgit_repo->path, 1); + + if (cgit_query_page && !strcmp(cgit_query_page, "snapshot")) { + cgit_print_snapshot(item, cgit_query_sha1, "zip", + cgit_repo->url, cgit_query_name); + return; + } + if (cgit_query_page && !strcmp(cgit_query_page, "log")) show_search = 1; cgit_print_docstart(title, item); cgit_print_pageheader(title, show_search); if (!cgit_query_page) { cgit_print_summary(); } else if (!strcmp(cgit_query_page, "log")) { - cgit_print_log(cgit_query_head, cgit_query_ofs, 100, cgit_query_search); + cgit_print_log(cgit_query_head, cgit_query_ofs, 100, + cgit_query_search); } else if (!strcmp(cgit_query_page, "tree")) { cgit_print_tree(cgit_query_sha1, cgit_query_path); } else if (!strcmp(cgit_query_page, "commit")) { cgit_print_commit(cgit_query_sha1); } else if (!strcmp(cgit_query_page, "view")) { cgit_print_view(cgit_query_sha1); } else if (!strcmp(cgit_query_page, "diff")) { cgit_print_diff(cgit_query_sha1, cgit_query_sha2); + } else { + cgit_print_error("Invalid request"); } cgit_print_docend(); } -static void cgit_fill_cache(struct cacheitem *item) +static void cgit_fill_cache(struct cacheitem *item, int use_cache) { static char buf[PATH_MAX]; + int stdout2; getcwd(buf, sizeof(buf)); - htmlfd = item->fd; item->st.st_mtime = time(NULL); + + if (use_cache) { + stdout2 = chk_positive(dup(STDOUT_FILENO), + "Preserving STDOUT"); + chk_zero(close(STDOUT_FILENO), "Closing STDOUT"); + chk_positive(dup2(item->fd, STDOUT_FILENO), "Dup2(cachefile)"); + } + if (cgit_query_repo) cgit_print_repo_page(item); else cgit_print_repolist(item); + + if (use_cache) { + chk_zero(close(STDOUT_FILENO), "Close redirected STDOUT"); + chk_positive(dup2(stdout2, STDOUT_FILENO), + "Restoring original STDOUT"); + chk_zero(close(stdout2), "Closing temporary STDOUT"); + } + chdir(buf); } static void cgit_check_cache(struct cacheitem *item) { int i = 0; top: if (++i > cgit_max_lock_attempts) { die("cgit_refresh_cache: unable to lock %s: %s", item->name, strerror(errno)); } if (!cache_exist(item)) { if (!cache_lock(item)) { sleep(1); goto top; } if (!cache_exist(item)) { - cgit_fill_cache(item); + cgit_fill_cache(item, 1); cache_unlock(item); } else { cache_cancel_lock(item); } } else if (cache_expired(item) && cache_lock(item)) { if (cache_expired(item)) { - cgit_fill_cache(item); + cgit_fill_cache(item, 1); cache_unlock(item); } else { cache_cancel_lock(item); } } } static void cgit_print_cache(struct cacheitem *item) { static char buf[4096]; ssize_t i; int fd = open(item->name, O_RDONLY); if (fd<0) die("Unable to open cached file %s", item->name); while((i=read(fd, buf, sizeof(buf))) > 0) write(STDOUT_FILENO, buf, i); close(fd); } static void cgit_parse_args(int argc, const char **argv) { int i; for (i = 1; i < argc; i++) { if (!strncmp(argv[i], "--cache=", 8)) { cgit_cache_root = xstrdup(argv[i]+8); } if (!strcmp(argv[i], "--nocache")) { cgit_nocache = 1; } if (!strncmp(argv[i], "--query=", 8)) { cgit_querystring = xstrdup(argv[i]+8); } if (!strncmp(argv[i], "--repo=", 7)) { cgit_query_repo = xstrdup(argv[i]+7); } if (!strncmp(argv[i], "--page=", 7)) { cgit_query_page = xstrdup(argv[i]+7); } if (!strncmp(argv[i], "--head=", 7)) { cgit_query_head = xstrdup(argv[i]+7); cgit_query_has_symref = 1; } if (!strncmp(argv[i], "--sha1=", 7)) { cgit_query_sha1 = xstrdup(argv[i]+7); cgit_query_has_sha1 = 1; } if (!strncmp(argv[i], "--ofs=", 6)) { cgit_query_ofs = atoi(argv[i]+6); } } } int main(int argc, const char **argv) { struct cacheitem item; htmlfd = STDOUT_FILENO; item.st.st_mtime = time(NULL); cgit_repolist.length = 0; cgit_repolist.count = 0; cgit_repolist.repos = NULL; cgit_read_config("/etc/cgitrc", cgit_global_config_cb); if (getenv("QUERY_STRING")) cgit_querystring = xstrdup(getenv("QUERY_STRING")); cgit_parse_args(argc, argv); cgit_parse_query(cgit_querystring, cgit_querystring_cb); if (!cgit_prepare_cache(&item)) return 0; if (cgit_nocache) { - item.fd = STDOUT_FILENO; - cgit_fill_cache(&item); + cgit_fill_cache(&item, 0); } else { cgit_check_cache(&item); cgit_print_cache(&item); } return 0; } @@ -1,142 +1,152 @@ #ifndef CGIT_H #define CGIT_H #include "git.h" #include <openssl/sha.h> #include <ctype.h> #include <sched.h> typedef void (*configfn)(const char *name, const char *value); struct cacheitem { char *name; struct stat st; int ttl; int fd; }; struct repoinfo { char *url; char *name; char *path; char *desc; char *owner; }; struct repolist { int length; int count; struct repoinfo *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; }; struct taginfo { char *tagger; char *tagger_email; int tagger_date; char *msg; }; extern const char cgit_version[]; extern struct repolist cgit_repolist; extern struct repoinfo *cgit_repo; extern char *cgit_root_title; extern char *cgit_css; extern char *cgit_logo; extern char *cgit_logo_link; extern char *cgit_virtual_root; extern char *cgit_cache_root; extern int cgit_nocache; extern int cgit_max_lock_attempts; extern int cgit_cache_root_ttl; extern int cgit_cache_repo_ttl; extern int cgit_cache_dynamic_ttl; extern int cgit_cache_static_ttl; extern int cgit_cache_max_create_time; extern int cgit_max_msg_len; extern char *cgit_repo_name; extern char *cgit_repo_desc; extern char *cgit_repo_owner; extern int cgit_query_has_symref; extern int cgit_query_has_sha1; extern char *cgit_querystring; extern char *cgit_query_repo; extern char *cgit_query_page; extern char *cgit_query_search; extern char *cgit_query_head; extern char *cgit_query_sha1; extern char *cgit_query_sha2; extern char *cgit_query_path; +extern char *cgit_query_name; extern int cgit_query_ofs; extern int htmlfd; extern void cgit_global_config_cb(const char *name, const char *value); extern void cgit_repo_config_cb(const char *name, const char *value); extern void cgit_querystring_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 hextoint(char c); extern void *cgit_free_commitinfo(struct commitinfo *info); extern char *fmt(const char *format,...); extern void html(const char *txt); extern void htmlf(const char *format,...); extern void html_txt(char *txt); extern void html_ntxt(int len, char *txt); extern void html_attr(char *txt); extern void html_hidden(char *name, char *value); extern void html_link_open(char *url, char *title, char *class); extern void html_link_close(void); extern void html_filemode(unsigned short mode); extern int cgit_read_config(const char *filename, configfn fn); extern int cgit_parse_query(char *txt, configfn fn); extern struct commitinfo *cgit_parse_commit(struct commit *commit); extern struct taginfo *cgit_parse_tag(struct tag *tag); extern char *cache_safe_filename(const char *unsafe); extern int cache_lock(struct cacheitem *item); extern int cache_unlock(struct cacheitem *item); extern int cache_cancel_lock(struct cacheitem *item); extern int cache_exist(struct cacheitem *item); extern int cache_expired(struct cacheitem *item); extern char *cgit_repourl(const char *reponame); extern char *cgit_pageurl(const char *reponame, const char *pagename, const char *query); extern void cgit_print_error(char *msg); extern void cgit_print_date(unsigned long secs); extern void cgit_print_docstart(char *title, struct cacheitem *item); extern void cgit_print_docend(); extern void cgit_print_pageheader(char *title, int show_search); +extern void cgit_print_snapshot_start(const char *mimetype, + const char *filename, + struct cacheitem *item); extern void cgit_print_repolist(struct cacheitem *item); extern void cgit_print_summary(); extern void cgit_print_log(const char *tip, int ofs, int cnt, char *grep); extern void cgit_print_view(const char *hex); extern void cgit_print_tree(const char *hex, char *path); extern void cgit_print_commit(const char *hex); extern void cgit_print_diff(const char *old_hex, const char *new_hex); +extern void cgit_print_snapshot(struct cacheitem *item, const char *hex, + const char *format, const char *prefix, + const char *filename); #endif /* CGIT_H */ @@ -288,385 +288,412 @@ struct object *parse_object(const unsigned char *sha1); struct tree { struct object object; void *buffer; unsigned long size; }; struct tree *lookup_tree(const unsigned char *sha1); int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size); int parse_tree(struct tree *tree); struct tree *parse_tree_indirect(const unsigned char *sha1); typedef int (*read_tree_fn_t)(const unsigned char *, const char *, int, const char *, unsigned int, int); extern int read_tree_recursive(struct tree *tree, const char *base, int baselen, int stage, const char **match, read_tree_fn_t fn); extern int read_tree(struct tree *tree, int stage, const char **paths); /* from git:commit.h */ struct commit_list { struct commit *item; struct commit_list *next; }; struct commit { struct object object; void *util; unsigned long date; struct commit_list *parents; struct tree *tree; char *buffer; }; struct commit *lookup_commit(const unsigned char *sha1); struct commit *lookup_commit_reference(const unsigned char *sha1); struct commit *lookup_commit_reference_gently(const unsigned char *sha1, int quiet); int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size); int parse_commit(struct commit *item); struct commit_list * commit_list_insert(struct commit *item, struct commit_list **list_p); struct commit_list * insert_by_date(struct commit *item, struct commit_list **list); void free_commit_list(struct commit_list *list); void sort_by_date(struct commit_list **list); /* Commit formats */ enum cmit_fmt { CMIT_FMT_RAW, CMIT_FMT_MEDIUM, CMIT_FMT_DEFAULT = CMIT_FMT_MEDIUM, CMIT_FMT_SHORT, CMIT_FMT_FULL, CMIT_FMT_FULLER, CMIT_FMT_ONELINE, CMIT_FMT_EMAIL, CMIT_FMT_UNSPECIFIED, }; extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject, const char *after_subject, int relative_date); typedef void (*topo_sort_set_fn_t)(struct commit*, void *data); typedef void* (*topo_sort_get_fn_t)(struct commit*); /* * from git:tag.h */ extern const char *tag_type; struct tag { struct object object; struct object *tagged; char *tag; char *signature; /* not actually implemented */ }; extern struct tag *lookup_tag(const unsigned char *sha1); extern int parse_tag_buffer(struct tag *item, void *data, unsigned long size); extern int parse_tag(struct tag *item); extern struct object *deref_tag(struct object *, const char *, int); /* * from git:diffcore.h */ struct diff_filespec { unsigned char sha1[20]; char *path; void *data; void *cnt_data; unsigned long size; int xfrm_flags; /* for use by the xfrm */ unsigned short mode; /* file mode */ unsigned sha1_valid : 1; /* if true, use sha1 and trust mode; * if false, use the name and read from * the filesystem. */ #define DIFF_FILE_VALID(spec) (((spec)->mode) != 0) unsigned should_free : 1; /* data should be free()'ed */ unsigned should_munmap : 1; /* data should be munmap()'ed */ }; struct diff_filepair { struct diff_filespec *one; struct diff_filespec *two; unsigned short int score; char status; /* M C R N D U (see Documentation/diff-format.txt) */ unsigned source_stays : 1; /* all of R/C are copies */ unsigned broken_pair : 1; unsigned renamed_pair : 1; }; #define DIFF_PAIR_UNMERGED(p) \ (!DIFF_FILE_VALID((p)->one) && !DIFF_FILE_VALID((p)->two)) #define DIFF_PAIR_RENAME(p) ((p)->renamed_pair) #define DIFF_PAIR_BROKEN(p) \ ( (!DIFF_FILE_VALID((p)->one) != !DIFF_FILE_VALID((p)->two)) && \ ((p)->broken_pair != 0) ) #define DIFF_PAIR_TYPE_CHANGED(p) \ ((S_IFMT & (p)->one->mode) != (S_IFMT & (p)->two->mode)) #define DIFF_PAIR_MODE_CHANGED(p) ((p)->one->mode != (p)->two->mode) extern void diff_free_filepair(struct diff_filepair *); extern int diff_unmodified_pair(struct diff_filepair *); struct diff_queue_struct { struct diff_filepair **queue; int alloc; int nr; }; /* * from git:diff.h */ struct rev_info; struct diff_options; struct diff_queue_struct; typedef void (*change_fn_t)(struct diff_options *options, unsigned old_mode, unsigned new_mode, const unsigned char *old_sha1, const unsigned char *new_sha1, const char *base, const char *path); typedef void (*add_remove_fn_t)(struct diff_options *options, int addremove, unsigned mode, const unsigned char *sha1, const char *base, const char *path); typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, struct diff_options *options, void *data); #define DIFF_FORMAT_RAW 0x0001 #define DIFF_FORMAT_DIFFSTAT 0x0002 #define DIFF_FORMAT_NUMSTAT 0x0004 #define DIFF_FORMAT_SUMMARY 0x0008 #define DIFF_FORMAT_PATCH 0x0010 /* These override all above */ #define DIFF_FORMAT_NAME 0x0100 #define DIFF_FORMAT_NAME_STATUS 0x0200 #define DIFF_FORMAT_CHECKDIFF 0x0400 /* Same as output_format = 0 but we know that -s flag was given * and we should not give default value to output_format. */ #define DIFF_FORMAT_NO_OUTPUT 0x0800 #define DIFF_FORMAT_CALLBACK 0x1000 struct diff_options { const char *filter; const char *orderfile; const char *pickaxe; const char *single_follow; unsigned recursive:1, tree_in_recursive:1, binary:1, text:1, full_index:1, silent_on_remove:1, find_copies_harder:1, color_diff:1, color_diff_words:1; int context; int break_opt; int detect_rename; int line_termination; int output_format; int pickaxe_opts; int rename_score; int reverse_diff; int rename_limit; int setup; int abbrev; const char *msg_sep; const char *stat_sep; long xdl_opts; int stat_width; int stat_name_width; int nr_paths; const char **paths; int *pathlens; change_fn_t change; add_remove_fn_t add_remove; diff_format_fn_t format_callback; void *format_callback_data; }; enum color_diff { DIFF_RESET = 0, DIFF_PLAIN = 1, DIFF_METAINFO = 2, DIFF_FRAGINFO = 3, DIFF_FILE_OLD = 4, DIFF_FILE_NEW = 5, DIFF_COMMIT = 6, DIFF_WHITESPACE = 7, }; extern int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base, struct diff_options *opt); extern int diff_root_tree_sha1(const unsigned char *new, const char *base, struct diff_options *opt); extern int git_diff_ui_config(const char *var, const char *value); extern void diff_setup(struct diff_options *); extern int diff_opt_parse(struct diff_options *, const char **, int); extern int diff_setup_done(struct diff_options *); extern void diffcore_std(struct diff_options *); extern void diff_flush(struct diff_options*); /* diff-raw status letters */ #define DIFF_STATUS_ADDED 'A' #define DIFF_STATUS_COPIED 'C' #define DIFF_STATUS_DELETED 'D' #define DIFF_STATUS_MODIFIED 'M' #define DIFF_STATUS_RENAMED 'R' #define DIFF_STATUS_TYPE_CHANGED 'T' #define DIFF_STATUS_UNKNOWN 'X' #define DIFF_STATUS_UNMERGED 'U' /* * from git:refs.g */ typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data); extern int head_ref(each_ref_fn, void *); extern int for_each_ref(each_ref_fn, void *); extern int for_each_tag_ref(each_ref_fn, void *); extern int for_each_branch_ref(each_ref_fn, void *); extern int for_each_remote_ref(each_ref_fn, void *); /* * from git:revision.h */ struct rev_info; struct log_info; typedef void (prune_fn_t)(struct rev_info *revs, struct commit *commit); struct rev_info { /* Starting list */ struct commit_list *commits; struct object_array pending; /* Basic information */ const char *prefix; void *prune_data; prune_fn_t *prune_fn; /* Traversal flags */ unsigned int dense:1, no_merges:1, no_walk:1, remove_empty_trees:1, simplify_history:1, lifo:1, topo_order:1, tag_objects:1, tree_objects:1, blob_objects:1, edge_hint:1, limited:1, unpacked:1, /* see also ignore_packed below */ boundary:1, parents:1; /* Diff flags */ unsigned int diff:1, full_diff:1, show_root_diff:1, no_commit_id:1, verbose_header:1, ignore_merges:1, combine_merges:1, dense_combined_merges:1, always_show_header:1; /* Format info */ unsigned int shown_one:1, abbrev_commit:1, relative_date:1; const char **ignore_packed; /* pretend objects in these are unpacked */ int num_ignore_packed; unsigned int abbrev; enum cmit_fmt commit_format; struct log_info *loginfo; int nr, total; const char *mime_boundary; const char *message_id; const char *ref_message_id; const char *add_signoff; const char *extra_headers; /* Filter by commit log message */ struct grep_opt *grep_filter; /* special limits */ int max_count; unsigned long max_age; unsigned long min_age; /* diff info for patches and for paths limiting */ struct diff_options diffopt; struct diff_options pruning; topo_sort_set_fn_t topo_setter; topo_sort_get_fn_t topo_getter; }; extern void init_revisions(struct rev_info *revs, const char *prefix); extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def); extern int handle_revision_arg(const char *arg, struct rev_info *revs,int flags,int cant_be_filename); extern void prepare_revision_walk(struct rev_info *revs); extern struct commit *get_revision(struct rev_info *revs); /* from git:log-tree.h */ int log_tree_commit(struct rev_info *, struct commit *); +/* from git:archive.h */ + +struct archiver_args { + const char *base; + struct tree *tree; + const unsigned char *commit_sha1; + time_t time; + const char **pathspec; + unsigned int verbose : 1; + void *extra; +}; + +typedef int (*write_archive_fn_t)(struct archiver_args *); + +typedef void *(*parse_extra_args_fn_t)(int argc, const char **argv); + +struct archiver { + const char *name; + struct archiver_args args; + write_archive_fn_t write_archive; + parse_extra_args_fn_t parse_extra; +}; + +extern int write_tar_archive(struct archiver_args *); +extern int write_zip_archive(struct archiver_args *); +extern void *parse_extra_zip_args(int argc, const char **argv); + #endif /* GIT_H */ @@ -1,168 +1,185 @@ /* shared.c: global vars + some callback functions * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" struct repolist cgit_repolist; struct repoinfo *cgit_repo; char *cgit_root_title = "Git repository browser"; char *cgit_css = "/cgit.css"; char *cgit_logo = "/git-logo.png"; char *cgit_logo_link = "http://www.kernel.org/pub/software/scm/git/docs/"; char *cgit_virtual_root = NULL; char *cgit_cache_root = "/var/cache/cgit"; int cgit_nocache = 0; int cgit_max_lock_attempts = 5; int cgit_cache_root_ttl = 5; int cgit_cache_repo_ttl = 5; int cgit_cache_dynamic_ttl = 5; int cgit_cache_static_ttl = -1; int cgit_cache_max_create_time = 5; int cgit_max_msg_len = 60; char *cgit_repo_name = NULL; char *cgit_repo_desc = NULL; char *cgit_repo_owner = NULL; int cgit_query_has_symref = 0; int cgit_query_has_sha1 = 0; char *cgit_querystring = NULL; char *cgit_query_repo = NULL; char *cgit_query_page = NULL; char *cgit_query_head = NULL; char *cgit_query_search = NULL; char *cgit_query_sha1 = NULL; char *cgit_query_sha2 = NULL; char *cgit_query_path = NULL; +char *cgit_query_name = NULL; int cgit_query_ofs = 0; int htmlfd = 0; +int chk_zero(int result, char *msg) +{ + if (result != 0) + die("%s: %s", msg, strerror(errno)); + return result; +} + +int chk_positive(int result, char *msg) +{ + if (result <= 0) + die("%s: %s", msg, strerror(errno)); + return result; +} + struct repoinfo *add_repo(const char *url) { struct repoinfo *ret; if (++cgit_repolist.count > cgit_repolist.length) { if (cgit_repolist.length == 0) cgit_repolist.length = 8; else cgit_repolist.length *= 2; cgit_repolist.repos = xrealloc(cgit_repolist.repos, cgit_repolist.length * sizeof(struct repoinfo)); } ret = &cgit_repolist.repos[cgit_repolist.count-1]; ret->url = xstrdup(url); ret->name = ret->url; ret->path = NULL; ret->desc = NULL; ret->owner = NULL; return ret; } void cgit_global_config_cb(const char *name, const char *value) { if (!strcmp(name, "root-title")) cgit_root_title = xstrdup(value); else if (!strcmp(name, "css")) cgit_css = xstrdup(value); else if (!strcmp(name, "logo")) cgit_logo = xstrdup(value); else if (!strcmp(name, "logo-link")) cgit_logo_link = xstrdup(value); else if (!strcmp(name, "virtual-root")) cgit_virtual_root = xstrdup(value); else if (!strcmp(name, "nocache")) cgit_nocache = atoi(value); else if (!strcmp(name, "cache-root")) cgit_cache_root = xstrdup(value); else if (!strcmp(name, "cache-root-ttl")) cgit_cache_root_ttl = atoi(value); else if (!strcmp(name, "cache-repo-ttl")) cgit_cache_repo_ttl = atoi(value); else if (!strcmp(name, "cache-static-ttl")) cgit_cache_static_ttl = atoi(value); else if (!strcmp(name, "cache-dynamic-ttl")) cgit_cache_dynamic_ttl = atoi(value); else if (!strcmp(name, "max-message-length")) cgit_max_msg_len = atoi(value); else if (!strcmp(name, "repo.url")) cgit_repo = add_repo(value); else if (!strcmp(name, "repo.name")) cgit_repo->name = xstrdup(value); else if (cgit_repo && !strcmp(name, "repo.path")) cgit_repo->path = xstrdup(value); else if (cgit_repo && !strcmp(name, "repo.desc")) cgit_repo->desc = xstrdup(value); else if (cgit_repo && !strcmp(name, "repo.owner")) cgit_repo->owner = xstrdup(value); } void cgit_repo_config_cb(const char *name, const char *value) { if (!strcmp(name, "name")) cgit_repo_name = xstrdup(value); else if (!strcmp(name, "desc")) cgit_repo_desc = xstrdup(value); else if (!strcmp(name, "owner")) cgit_repo_owner = xstrdup(value); } void cgit_querystring_cb(const char *name, const char *value) { if (!strcmp(name,"r")) { cgit_query_repo = xstrdup(value); } else if (!strcmp(name, "p")) { cgit_query_page = xstrdup(value); } else if (!strcmp(name, "q")) { cgit_query_search = xstrdup(value); } else if (!strcmp(name, "h")) { cgit_query_head = xstrdup(value); cgit_query_has_symref = 1; } else if (!strcmp(name, "id")) { cgit_query_sha1 = xstrdup(value); cgit_query_has_sha1 = 1; } else if (!strcmp(name, "id2")) { cgit_query_sha2 = xstrdup(value); cgit_query_has_sha1 = 1; } else if (!strcmp(name, "ofs")) { cgit_query_ofs = atoi(value); } else if (!strcmp(name, "path")) { cgit_query_path = xstrdup(value); + } else if (!strcmp(name, "name")) { + cgit_query_name = xstrdup(value); } } void *cgit_free_commitinfo(struct commitinfo *info) { free(info->author); free(info->author_email); free(info->committer); free(info->committer_email); free(info->subject); free(info); return NULL; } int hextoint(char c) { if (c >= 'a' && c <= 'f') return 10 + c - 'a'; else if (c >= 'A' && c <= 'F') return 10 + c - 'A'; else if (c >= '0' && c <= '9') return c - '0'; else return -1; } diff --git a/ui-commit.c b/ui-commit.c index 73fa104..de3f2cf 100644 --- a/ui-commit.c +++ b/ui-commit.c @@ -1,185 +1,192 @@ /* ui-commit.c: generate commit view * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" int files = 0; void print_filepair(struct diff_filepair *pair) { char *query; char *class; switch (pair->status) { case DIFF_STATUS_ADDED: class = "add"; break; case DIFF_STATUS_COPIED: class = "cpy"; break; case DIFF_STATUS_DELETED: class = "del"; break; case DIFF_STATUS_MODIFIED: class = "upd"; break; case DIFF_STATUS_RENAMED: class = "mov"; break; case DIFF_STATUS_TYPE_CHANGED: class = "typ"; break; case DIFF_STATUS_UNKNOWN: class = "unk"; break; case DIFF_STATUS_UNMERGED: class = "stg"; break; default: die("bug: unhandled diff status %c", pair->status); } html("<tr>"); htmlf("<td class='mode'>"); if (is_null_sha1(pair->two->sha1)) { html_filemode(pair->one->mode); } else { html_filemode(pair->two->mode); } if (pair->one->mode != pair->two->mode && !is_null_sha1(pair->one->sha1) && !is_null_sha1(pair->two->sha1)) { html("<span class='modechange'>["); html_filemode(pair->one->mode); html("]</span>"); } htmlf("</td><td class='%s'>", class); query = fmt("id=%s&id2=%s", sha1_to_hex(pair->one->sha1), sha1_to_hex(pair->two->sha1)); html_link_open(cgit_pageurl(cgit_query_repo, "diff", query), NULL, NULL); if (pair->status == DIFF_STATUS_COPIED || pair->status == DIFF_STATUS_RENAMED) { html_txt(pair->two->path); htmlf("</a> (%s from ", pair->status == DIFF_STATUS_COPIED ? "copied" : "renamed"); query = fmt("id=%s", sha1_to_hex(pair->one->sha1)); html_link_open(cgit_pageurl(cgit_query_repo, "view", query), NULL, NULL); html_txt(pair->one->path); html("</a>)"); } else { html_txt(pair->two->path); html("</a>"); } html("<td>"); //TODO: diffstat graph html("</td></tr>\n"); files++; } void diff_format_cb(struct diff_queue_struct *q, struct diff_options *options, void *data) { int i; for (i = 0; i < q->nr; i++) { if (q->queue[i]->status == 'U') continue; print_filepair(q->queue[i]); } } void cgit_diffstat(struct commit *commit) { struct diff_options opt; int ret; diff_setup(&opt); opt.output_format = DIFF_FORMAT_CALLBACK; opt.detect_rename = 1; opt.recursive = 1; opt.format_callback = diff_format_cb; diff_setup_done(&opt); if (commit->parents) ret = diff_tree_sha1(commit->parents->item->object.sha1, commit->object.sha1, "", &opt); else ret = diff_root_tree_sha1(commit->object.sha1, "", &opt); diffcore_std(&opt); diff_flush(&opt); } void cgit_print_commit(const char *hex) { struct commit *commit; struct commitinfo *info; struct commit_list *p; unsigned char sha1[20]; char *query; + char *filename; if (get_sha1(hex, sha1)) { cgit_print_error(fmt("Bad object id: %s", hex)); return; } commit = lookup_commit_reference(sha1); if (!commit) { cgit_print_error(fmt("Bad commit reference: %s", hex)); return; } info = cgit_parse_commit(commit); html("<table class='commit-info'>\n"); html("<tr><th>author</th><td>"); html_txt(info->author); html(" "); html_txt(info->author_email); html("</td><td class='right'>"); cgit_print_date(info->author_date); html("</td></tr>\n"); html("<tr><th>committer</th><td>"); html_txt(info->committer); html(" "); html_txt(info->committer_email); html("</td><td class='right'>"); cgit_print_date(info->committer_date); html("</td></tr>\n"); html("<tr><th>tree</th><td colspan='2' class='sha1'><a href='"); query = fmt("id=%s", sha1_to_hex(commit->tree->object.sha1)); html_attr(cgit_pageurl(cgit_query_repo, "tree", query)); htmlf("'>%s</a></td></tr>\n", sha1_to_hex(commit->tree->object.sha1)); for (p = commit->parents; p ; p = p->next) { html("<tr><th>parent</th>" "<td colspan='2' class='sha1'>" "<a href='"); query = fmt("id=%s", sha1_to_hex(p->item->object.sha1)); html_attr(cgit_pageurl(cgit_query_repo, "commit", query)); htmlf("'>%s</a></td></tr>\n", sha1_to_hex(p->item->object.sha1)); } + htmlf("<tr><th>download</th><td colspan='2' class='sha1'><a href='"); + filename = fmt("%s-%s.zip", cgit_query_repo, hex); + html_attr(cgit_pageurl(cgit_query_repo, "snapshot", + fmt("id=%s&name=%s", hex, filename))); + htmlf("'>%s</a></td></tr>", filename); + html("</table>\n"); html("<div class='commit-subject'>"); html_txt(info->subject); html("</div>"); html("<div class='commit-msg'>"); html_txt(info->msg); html("</div>"); html("<table class='diffstat'>"); html("<tr><th colspan='3'>Affected files</tr>\n"); cgit_diffstat(commit); htmlf("<tr><td colspan='3' class='summary'>" "%d file%s changed</td></tr>\n", files, files > 1 ? "s" : ""); html("</table>"); cgit_free_commitinfo(info); } diff --git a/ui-shared.c b/ui-shared.c index 3322561..172499c 100644 --- a/ui-shared.c +++ b/ui-shared.c @@ -1,146 +1,157 @@ /* 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" 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); } static long ttl_seconds(long ttl) { if (ttl<0) return 60 * 60 * 24 * 365; else return ttl * 60; } void cgit_print_error(char *msg) { html("<div class='error'>"); html_txt(msg); html("</div>\n"); } char *cgit_repourl(const char *reponame) { if (cgit_virtual_root) { return fmt("%s/%s/", cgit_virtual_root, reponame); } else { return fmt("?r=%s", reponame); } } char *cgit_pageurl(const char *reponame, const char *pagename, const char *query) { if (cgit_virtual_root) { return fmt("%s/%s/%s/?%s", cgit_virtual_root, reponame, pagename, query); } else { return fmt("?r=%s&p=%s&%s", reponame, pagename, query); } } char *cgit_currurl() { if (!cgit_virtual_root) return "./cgit.cgi"; else if (cgit_query_page) return fmt("%s/%s/%s/", cgit_virtual_root, cgit_query_repo, cgit_query_page); else if (cgit_query_repo) return fmt("%s/%s/", cgit_virtual_root, cgit_query_repo); else return fmt("%s/", cgit_virtual_root); } void cgit_print_date(unsigned long secs) { char buf[32]; struct tm *time; time = gmtime(&secs); strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", time); html_txt(buf); } void cgit_print_docstart(char *title, struct cacheitem *item) { html("Content-Type: text/html; charset=utf-8\n"); htmlf("Last-Modified: %s\n", http_date(item->st.st_mtime)); htmlf("Expires: %s\n", http_date(item->st.st_mtime + ttl_seconds(item->ttl))); html("\n"); html(cgit_doctype); html("<html>\n"); html("<head>\n"); html("<title>"); html_txt(title); html("</title>\n"); htmlf("<meta name='generator' content='cgit v%s'/>\n", cgit_version); html("<link rel='stylesheet' type='text/css' href='"); html_attr(cgit_css); html("'/>\n"); html("</head>\n"); html("<body>\n"); } void cgit_print_docend() { html("</td></tr></table>"); html("</body>\n</html>\n"); } void cgit_print_pageheader(char *title, int show_search) { html("<table id='layout'><tr><td id='header'>"); htmlf("<a href='%s'>", cgit_logo_link); htmlf("<img id='logo' src='%s'/>\n", cgit_logo); htmlf("</a>"); if (show_search) { html("<form method='get' href='"); html_attr(cgit_currurl()); html("'>"); if (!cgit_virtual_root) { if (cgit_query_repo) html_hidden("r", cgit_query_repo); if (cgit_query_page) html_hidden("p", cgit_query_page); } if (cgit_query_head) html_hidden("h", cgit_query_head); if (cgit_query_sha1) html_hidden("id", cgit_query_sha1); if (cgit_query_sha2) html_hidden("id2", cgit_query_sha2); html("<input type='text' name='q' value='"); html_attr(cgit_query_search); html("'/></form>"); } if (cgit_query_repo) htmlf("<a href='%s'>", cgit_repourl(cgit_query_repo)); html_txt(title); if (cgit_query_repo) html("</a>"); html("</td></tr><tr><td id='content'>"); } + +void cgit_print_snapshot_start(const char *mimetype, const char *filename, + struct cacheitem *item) +{ + htmlf("Content-Type: %s\n", mimetype); + htmlf("Content-Disposition: inline; filename=\"%s\"\n", filename); + htmlf("Last-Modified: %s\n", http_date(item->st.st_mtime)); + htmlf("Expires: %s\n", http_date(item->st.st_mtime + + ttl_seconds(item->ttl))); + html("\n"); +} diff --git a/ui-snapshot.c b/ui-snapshot.c new file mode 100644 index 0000000..2257d6b --- a/dev/null +++ b/ui-snapshot.c @@ -0,0 +1,47 @@ +/* ui-snapshot.c: generate snapshot of a commit + * + * Copyright (C) 2006 Lars Hjemli + * + * Licensed under GNU General Public License v2 + * (see COPYING for full license text) + */ + +#include "cgit.h" + +static void cgit_print_zip(struct cacheitem *item, const char *hex, + const char *prefix, const char *filename) +{ + struct archiver_args args; + struct commit *commit; + unsigned char sha1[20]; + + if (get_sha1(hex, sha1)) { + cgit_print_error(fmt("Bad object id: %s", hex)); + return; + } + commit = lookup_commit_reference(sha1); + + if (!commit) { + cgit_print_error(fmt("Not a commit reference: %s", hex)); + return; + } + + memset(&args, 0, sizeof(args)); + args.base = fmt("%s/", prefix); + args.tree = commit->tree; + + cgit_print_snapshot_start("application/x-zip", filename, item); + write_zip_archive(&args); +} + + +void cgit_print_snapshot(struct cacheitem *item, const char *hex, + const char *format, const char *prefix, + const char *filename) +{ + if (!strcmp(format, "zip")) + cgit_print_zip(item, hex, prefix, filename); + else + cgit_print_error(fmt("Unsupported snapshot format: %s", + format)); +} |