summaryrefslogtreecommitdiffabout
authorLars Hjemli <hjemli@gmail.com>2007-10-27 08:55:10 (UTC)
committer Lars Hjemli <hjemli@gmail.com>2007-10-27 08:55:10 (UTC)
commitdd0f27eb36e737261b57d6ebcbd9fe20e559470d (patch) (side-by-side diff)
tree4729ecc84e0b24b044131546ff39bdd55a6cd9a6
parent47bae9f58d5ecae437767b8e7835b23ad1804d0b (diff)
parentac1f493b6bbc589327e9ba3303f112fcd323c6b6 (diff)
downloadcgit-dd0f27eb36e737261b57d6ebcbd9fe20e559470d.zip
cgit-dd0f27eb36e737261b57d6ebcbd9fe20e559470d.tar.gz
cgit-dd0f27eb36e737261b57d6ebcbd9fe20e559470d.tar.bz2
Merge branch 'filter-refs'
* filter-refs: Add links to the new refs page from summary page Add support for refs view Make cgit_print_branches()/cgit_print_tags() external Add descriptions of summary-branches and summary-tags to cgitrc Add support for config param summary-branches Move logic for age comparision from cmp_tag_age into cmp_age() Add support for config param summary-tags Sort tags by age Use reflist to print tag info Use reflist to print branch info Add functions and types for ref lists
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--Makefile2
-rw-r--r--cgit.c3
-rw-r--r--cgit.h27
-rw-r--r--cgitrc10
-rw-r--r--shared.c49
-rw-r--r--ui-refs.c30
-rw-r--r--ui-shared.c6
-rw-r--r--ui-summary.c139
8 files changed, 229 insertions, 37 deletions
diff --git a/Makefile b/Makefile
index 8e3da72..36b5ff6 100644
--- a/Makefile
+++ b/Makefile
@@ -1,72 +1,72 @@
CGIT_VERSION = v0.6.3
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.3.3
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
EXTLIBS = git/libgit.a git/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-tree.o ui-commit.o ui-diff.o \
- ui-snapshot.o ui-blob.o ui-tag.o
+ ui-snapshot.o ui-blob.o ui-tag.o ui-refs.o
.PHONY: all git install clean distclean force-version get-git
all: cgit git
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: cgit.c $(OBJECTS)
$(CC) $(CFLAGS) cgit.c -o cgit $(OBJECTS) $(EXTLIBS)
$(OBJECTS): cgit.h git/xdiff/lib.a git/libgit.a VERSION
git/xdiff/lib.a: | git
git/libgit.a: | git
git:
cd git && $(MAKE) xdiff/lib.a
cd git && $(MAKE) libgit.a
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
rm -rf $(DESTDIR)$(CACHE_ROOT)/*
uninstall:
rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
rm -f $(CGIT_SCRIPT_PATH)/cgit.css
rm -rf $(CACHE_ROOT)
clean:
rm -f cgit VERSION *.o
cd git && $(MAKE) clean
distclean: clean
git clean -d -x
cd git && git clean -d -x
get-git:
curl $(GIT_URL) | tar -xj && rm -rf git && mv git-$(GIT_VER) git
diff --git a/cgit.c b/cgit.c
index 1b85b15..cc18ed4 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,256 +1,259 @@
/* 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"
static int cgit_prepare_cache(struct cacheitem *item)
{
if (!cgit_repo && cgit_query_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_repo) {
item->name = xstrdup(fmt("%s/index.html", cgit_cache_root));
item->ttl = cgit_cache_root_ttl;
return 1;
}
if (!cgit_cmd) {
item->name = xstrdup(fmt("%s/%s/index.%s.html", cgit_cache_root,
cache_safe_filename(cgit_repo->url),
cache_safe_filename(cgit_querystring)));
item->ttl = cgit_cache_repo_ttl;
} else {
item->name = xstrdup(fmt("%s/%s/%s/%s.html", cgit_cache_root,
cache_safe_filename(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 (!cgit_query_head)
cgit_query_head = cgit_repo->defbranch;
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_cmd == CMD_SNAPSHOT) && cgit_repo->snapshots) {
cgit_print_snapshot(item, cgit_query_head, cgit_query_sha1,
cgit_repobasename(cgit_repo->url),
cgit_query_path,
cgit_repo->snapshots );
return;
}
if (cgit_cmd == CMD_BLOB) {
cgit_print_blob(item, cgit_query_sha1, cgit_query_path);
return;
}
show_search = (cgit_cmd == CMD_LOG);
cgit_print_docstart(title, item);
if (!cgit_cmd) {
cgit_print_pageheader("summary", show_search);
cgit_print_summary();
cgit_print_docend();
return;
}
cgit_print_pageheader(cgit_query_page, show_search);
switch(cgit_cmd) {
case CMD_LOG:
cgit_print_log(cgit_query_sha1, cgit_query_ofs,
cgit_max_commit_count, cgit_query_search,
cgit_query_path, 1);
break;
case CMD_TREE:
cgit_print_tree(cgit_query_sha1, cgit_query_path);
break;
case CMD_COMMIT:
cgit_print_commit(cgit_query_sha1);
break;
+ case CMD_REFS:
+ cgit_print_refs();
+ break;
case CMD_TAG:
cgit_print_tag(cgit_query_sha1);
break;
case CMD_DIFF:
cgit_print_diff(cgit_query_sha1, cgit_query_sha2, cgit_query_path);
break;
default:
cgit_print_error("Invalid request");
}
cgit_print_docend();
}
static void cgit_fill_cache(struct cacheitem *item, int use_cache)
{
static char buf[PATH_MAX];
int stdout2;
getcwd(buf, sizeof(buf));
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_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, 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, 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;
const char *cgit_config_env = getenv("CGIT_CONFIG");
htmlfd = STDOUT_FILENO;
item.st.st_mtime = time(NULL);
cgit_repolist.length = 0;
cgit_repolist.count = 0;
cgit_repolist.repos = NULL;
cgit_read_config(cgit_config_env ? cgit_config_env : CGIT_CONFIG,
cgit_global_config_cb);
cgit_repo = NULL;
if (getenv("SCRIPT_NAME"))
cgit_script_name = xstrdup(getenv("SCRIPT_NAME"));
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) {
cgit_fill_cache(&item, 0);
} else {
cgit_check_cache(&item);
cgit_print_cache(&item);
}
return 0;
}
diff --git a/cgit.h b/cgit.h
index e96311f..f8f0316 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,249 +1,276 @@
#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>
/*
* The valid cgit repo-commands
*/
#define CMD_LOG 1
#define CMD_COMMIT 2
#define CMD_DIFF 3
#define CMD_TREE 4
#define CMD_BLOB 5
#define CMD_SNAPSHOT 6
#define CMD_TAG 7
+#define CMD_REFS 8
/*
* Dateformats used on misc. pages
*/
#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S"
#define FMT_SHORTDATE "%Y-%m-%d"
/*
* 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)
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 cacheitem {
char *name;
struct stat st;
int ttl;
int fd;
};
struct repoinfo {
char *url;
char *name;
char *path;
char *desc;
char *owner;
char *defbranch;
char *group;
char *module_link;
char *readme;
int snapshots;
int enable_log_filecount;
int enable_log_linecount;
};
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;
};
+struct refinfo {
+ const char *refname;
+ struct object *object;
+ union {
+ struct taginfo *tag;
+ struct commitinfo *commit;
+ };
+};
+
+struct reflist {
+ struct refinfo **refs;
+ int alloc;
+ int count;
+};
+
extern const char *cgit_version;
extern struct repolist cgit_repolist;
extern struct repoinfo *cgit_repo;
extern int cgit_cmd;
extern char *cgit_root_title;
extern char *cgit_css;
extern char *cgit_logo;
extern char *cgit_index_header;
extern char *cgit_logo_link;
extern char *cgit_module_link;
extern char *cgit_agefile;
extern char *cgit_virtual_root;
extern char *cgit_script_name;
extern char *cgit_cache_root;
extern char *cgit_repo_group;
extern int cgit_nocache;
extern int cgit_snapshots;
extern int cgit_enable_index_links;
extern int cgit_enable_log_filecount;
extern int cgit_enable_log_linecount;
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_summary_log;
+extern int cgit_summary_tags;
+extern int cgit_summary_branches;
extern int cgit_max_msg_len;
extern int cgit_max_repodesc_len;
extern int cgit_max_commit_count;
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 int cgit_get_cmd_index(const char *cmd);
extern struct repoinfo *cgit_get_repoinfo(const char *url);
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 chk_non_negative(int result, char *msg);
extern int hextoint(char c);
extern char *trim_end(const char *str, char c);
+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 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 html_include(const char *filename);
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 void cgit_parse_url(const char *url);
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_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 const char *cgit_repobasename(const char *reponame);
extern void cgit_tree_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);
extern void cgit_commit_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);
extern void cgit_print_age(time_t t, time_t max_relative, char *format);
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_branches(int maxcount);
+extern void cgit_print_tags(int maxcount);
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, char *path, int pager);
extern void cgit_print_blob(struct cacheitem *item, const char *hex, char *path);
extern void cgit_print_tree(const char *rev, char *path);
extern void cgit_print_commit(char *hex);
+extern void cgit_print_refs();
extern void cgit_print_tag(char *revname);
extern void cgit_print_diff(const char *new_hex, const char *old_hex, const char *prefix);
extern void cgit_print_snapshot(struct cacheitem *item, const char *head,
const char *hex, const char *prefix,
const char *filename, int snapshot);
extern void cgit_print_snapshot_links(const char *repo, const char *head,
const char *hex, int snapshots);
extern int cgit_parse_snapshots_mask(const char *str);
#endif /* CGIT_H */
diff --git a/cgitrc b/cgitrc
index 796a62c..2b09e01 100644
--- a/cgitrc
+++ b/cgitrc
@@ -1,167 +1,177 @@
##
## cgitrc: template for /etc/cgitrc
##
## Uncomment and set to 1 to deactivate caching of generated pages. Mostly
## usefull for testing.
#nocache=0
## 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
+
+
## 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
## If specified, the file at this path will be included as HTML in the index
## of repositories
#index-header=
## Link to css file
#css=/cgit/cgit.css
## 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
## 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
## Additional repositories grouped under "mirrors"
#repo.group=mirrors
#repo.url=git
#repo.path=/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
diff --git a/shared.c b/shared.c
index 3d4feea..7eb2b0e 100644
--- a/shared.c
+++ b/shared.c
@@ -1,425 +1,472 @@
/* 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;
int cgit_cmd;
const char *cgit_version = CGIT_VERSION;
char *cgit_root_title = "Git repository browser";
char *cgit_css = "/cgit.css";
char *cgit_logo = "/git-logo.png";
char *cgit_index_header = NULL;
char *cgit_logo_link = "http://www.kernel.org/pub/software/scm/git/docs/";
char *cgit_module_link = "./?repo=%s&page=commit&id=%s";
char *cgit_agefile = "info/web/last-modified";
char *cgit_virtual_root = NULL;
char *cgit_script_name = CGIT_SCRIPT_NAME;
char *cgit_cache_root = CGIT_CACHE_ROOT;
char *cgit_repo_group = NULL;
int cgit_nocache = 0;
int cgit_snapshots = 0;
int cgit_enable_index_links = 0;
int cgit_enable_log_filecount = 0;
int cgit_enable_log_linecount = 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_summary_log = 0;
+int cgit_summary_tags = 0;
+int cgit_summary_branches = 0;
int cgit_renamelimit = -1;
int cgit_max_msg_len = 60;
int cgit_max_repodesc_len = 60;
int cgit_max_commit_count = 50;
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 cgit_get_cmd_index(const char *cmd)
{
static char *cmds[] = {"log", "commit", "diff", "tree", "blob",
- "snapshot", "tag", NULL};
+ "snapshot", "tag", "refs", NULL};
int i;
for(i = 0; cmds[i]; i++)
if (!strcmp(cmd, cmds[i]))
return i + 1;
return 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;
}
int chk_non_negative(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 = trim_end(url, '/');
ret->name = ret->url;
ret->path = NULL;
ret->desc = NULL;
ret->owner = NULL;
ret->group = cgit_repo_group;
ret->defbranch = "master";
ret->snapshots = cgit_snapshots;
ret->enable_log_filecount = cgit_enable_log_filecount;
ret->enable_log_linecount = cgit_enable_log_linecount;
ret->module_link = cgit_module_link;
ret->readme = NULL;
return ret;
}
struct repoinfo *cgit_get_repoinfo(const 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;
}
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, "index-header"))
cgit_index_header = xstrdup(value);
else if (!strcmp(name, "logo-link"))
cgit_logo_link = xstrdup(value);
else if (!strcmp(name, "module-link"))
cgit_module_link = xstrdup(value);
else if (!strcmp(name, "virtual-root"))
cgit_virtual_root = trim_end(value, '/');
else if (!strcmp(name, "nocache"))
cgit_nocache = atoi(value);
else if (!strcmp(name, "snapshots"))
cgit_snapshots = cgit_parse_snapshots_mask(value);
else if (!strcmp(name, "enable-index-links"))
cgit_enable_index_links = atoi(value);
else if (!strcmp(name, "enable-log-filecount"))
cgit_enable_log_filecount = atoi(value);
else if (!strcmp(name, "enable-log-linecount"))
cgit_enable_log_linecount = 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, "max-repodesc-length"))
cgit_max_repodesc_len = atoi(value);
else if (!strcmp(name, "max-commit-count"))
cgit_max_commit_count = atoi(value);
else if (!strcmp(name, "summary-log"))
cgit_summary_log = atoi(value);
+ else if (!strcmp(name, "summary-branches"))
+ cgit_summary_branches = atoi(value);
+ else if (!strcmp(name, "summary-tags"))
+ cgit_summary_tags = atoi(value);
else if (!strcmp(name, "agefile"))
cgit_agefile = xstrdup(value);
else if (!strcmp(name, "renamelimit"))
cgit_renamelimit = atoi(value);
else if (!strcmp(name, "repo.group"))
cgit_repo_group = xstrdup(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 = trim_end(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);
else if (cgit_repo && !strcmp(name, "repo.defbranch"))
cgit_repo->defbranch = xstrdup(value);
else if (cgit_repo && !strcmp(name, "repo.snapshots"))
cgit_repo->snapshots = cgit_snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */
else if (cgit_repo && !strcmp(name, "repo.enable-log-filecount"))
cgit_repo->enable_log_filecount = cgit_enable_log_filecount * atoi(value);
else if (cgit_repo && !strcmp(name, "repo.enable-log-linecount"))
cgit_repo->enable_log_linecount = cgit_enable_log_linecount * atoi(value);
else if (cgit_repo && !strcmp(name, "repo.module-link"))
cgit_repo->module_link= xstrdup(value);
else if (cgit_repo && !strcmp(name, "repo.readme") && value != NULL) {
if (*value == '/')
cgit_repo->readme = xstrdup(value);
else
cgit_repo->readme = xstrdup(fmt("%s/%s", cgit_repo->path, value));
} else if (!strcmp(name, "include"))
cgit_read_config(value, cgit_global_config_cb);
}
void cgit_querystring_cb(const char *name, const char *value)
{
if (!strcmp(name,"r")) {
cgit_query_repo = xstrdup(value);
cgit_repo = cgit_get_repoinfo(value);
} else if (!strcmp(name, "p")) {
cgit_query_page = xstrdup(value);
cgit_cmd = cgit_get_cmd_index(value);
} else if (!strcmp(name, "url")) {
cgit_parse_url(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 = trim_end(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;
}
char *trim_end(const char *str, char c)
{
int len;
char *s, *t;
if (str == NULL)
return NULL;
t = (char *)str;
len = strlen(t);
while(len > 0 && t[len - 1] == c)
len--;
if (len == 0)
return NULL;
c = t[len];
t[len] = '\0';
s = xstrdup(t);
t[len] = c;
return s;
}
+void cgit_add_ref(struct reflist *list, struct refinfo *ref)
+{
+ size_t size;
+
+ if (list->count >= list->alloc) {
+ list->alloc += (list->alloc ? list->alloc : 4);
+ size = list->alloc * sizeof(struct refinfo *);
+ list->refs = xrealloc(list->refs, size);
+ }
+ list->refs[list->count++] = ref;
+}
+
+struct refinfo *cgit_mk_refinfo(const char *refname, const unsigned char *sha1)
+{
+ struct refinfo *ref;
+
+ ref = xmalloc(sizeof (struct refinfo));
+ ref->refname = xstrdup(refname);
+ ref->object = parse_object(sha1);
+ switch (ref->object->type) {
+ case OBJ_TAG:
+ ref->tag = cgit_parse_tag((struct tag *)ref->object);
+ break;
+ case OBJ_COMMIT:
+ ref->commit = cgit_parse_commit((struct commit *)ref->object);
+ break;
+ }
+ return ref;
+}
+
+int cgit_refs_cb(const char *refname, const unsigned char *sha1, int flags,
+ void *cb_data)
+{
+ struct reflist *list = (struct reflist *)cb_data;
+ struct refinfo *info = cgit_mk_refinfo(refname, sha1);
+
+ if (info)
+ cgit_add_ref(list, info);
+ return 0;
+}
+
void cgit_diff_tree_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;
((filepair_fn)data)(q->queue[i]);
}
}
static int load_mmfile(mmfile_t *file, const unsigned char *sha1)
{
enum object_type type;
if (is_null_sha1(sha1)) {
file->ptr = (char *)"";
file->size = 0;
} else {
file->ptr = read_sha1_file(sha1, &type,
(unsigned long *)&file->size);
}
return 1;
}
/*
* Receive diff-buffers from xdiff and concatenate them as
* needed across multiple callbacks.
*
* This is basically a copy of xdiff-interface.c/xdiff_outf(),
* ripped from git and modified to use globals instead of
* a special callback-struct.
*/
char *diffbuf = NULL;
int buflen = 0;
int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf)
{
int i;
for (i = 0; i < nbuf; i++) {
if (mb[i].ptr[mb[i].size-1] != '\n') {
/* Incomplete line */
diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
buflen += mb[i].size;
continue;
}
/* we have a complete line */
if (!diffbuf) {
((linediff_fn)priv)(mb[i].ptr, mb[i].size);
continue;
}
diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
((linediff_fn)priv)(diffbuf, buflen + mb[i].size);
free(diffbuf);
diffbuf = NULL;
buflen = 0;
}
if (diffbuf) {
((linediff_fn)priv)(diffbuf, buflen);
free(diffbuf);
diffbuf = NULL;
buflen = 0;
}
return 0;
}
int cgit_diff_files(const unsigned char *old_sha1,
const unsigned char *new_sha1,
linediff_fn fn)
{
mmfile_t file1, file2;
xpparam_t diff_params;
xdemitconf_t emit_params;
xdemitcb_t emit_cb;
if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1))
return 1;
diff_params.flags = XDF_NEED_MINIMAL;
emit_params.ctxlen = 3;
emit_params.flags = XDL_EMIT_FUNCNAMES;
emit_params.find_func = NULL;
emit_cb.outf = filediff_cb;
emit_cb.priv = fn;
xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb);
return 0;
}
void cgit_diff_tree(const unsigned char *old_sha1,
const unsigned char *new_sha1,
filepair_fn fn, const char *prefix)
{
struct diff_options opt;
int ret;
int prefixlen;
diff_setup(&opt);
opt.output_format = DIFF_FORMAT_CALLBACK;
opt.detect_rename = 1;
opt.rename_limit = cgit_renamelimit;
opt.recursive = 1;
opt.format_callback = cgit_diff_tree_cb;
opt.format_callback_data = fn;
if (prefix) {
opt.nr_paths = 1;
opt.paths = &prefix;
prefixlen = strlen(prefix);
opt.pathlens = &prefixlen;
}
diff_setup_done(&opt);
if (old_sha1 && !is_null_sha1(old_sha1))
ret = diff_tree_sha1(old_sha1, new_sha1, "", &opt);
else
ret = diff_root_tree_sha1(new_sha1, "", &opt);
diffcore_std(&opt);
diff_flush(&opt);
}
void cgit_diff_commit(struct commit *commit, filepair_fn fn)
{
unsigned char *old_sha1 = NULL;
if (commit->parents)
old_sha1 = commit->parents->item->object.sha1;
cgit_diff_tree(old_sha1, commit->object.sha1, fn, NULL);
}
diff --git a/ui-refs.c b/ui-refs.c
new file mode 100644
index 0000000..295f5ba
--- a/dev/null
+++ b/ui-refs.c
@@ -0,0 +1,30 @@
+/* ui-refs.c: browse symbolic refs
+ *
+ * Copyright (C) 2006 Lars Hjemli
+ *
+ * Licensed under GNU General Public License v2
+ * (see COPYING for full license text)
+ */
+
+#include "cgit.h"
+
+
+
+
+void cgit_print_refs()
+{
+
+ html("<table class='list nowrap'>");
+
+ if (cgit_query_path && !strncmp(cgit_query_path, "heads", 5))
+ cgit_print_branches(0);
+ else if (cgit_query_path && !strncmp(cgit_query_path, "tags", 4))
+ cgit_print_tags(0);
+ else {
+ cgit_print_branches(0);
+ html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
+ cgit_print_tags(0);
+ }
+
+ html("</table>");
+}
diff --git a/ui-shared.c b/ui-shared.c
index 5c5bcf3..e4bb98f 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -1,433 +1,439 @@
/* 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_rooturl()
{
if (cgit_virtual_root)
return fmt("%s/", cgit_virtual_root);
else
return cgit_script_name;
}
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_fileurl(const char *reponame, const char *pagename,
const char *filename, const char *query)
{
if (cgit_virtual_root) {
if (query)
return fmt("%s/%s/%s/%s?%s", cgit_virtual_root, reponame,
pagename, filename?filename:"", query);
else
return fmt("%s/%s/%s/", cgit_virtual_root, reponame,
pagename);
} else {
if (query)
return fmt("?r=%s&amp;p=%s&amp;%s", reponame, pagename, query);
else
return fmt("?r=%s&amp;p=%s", reponame, pagename);
}
}
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 (!cgit_virtual_root)
return cgit_script_name;
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);
}
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 (cgit_virtual_root) {
html_attr(cgit_virtual_root);
if (cgit_virtual_root[strlen(cgit_virtual_root) - 1] != '/')
html("/");
html_attr(cgit_repo->url);
if (cgit_repo->url[strlen(cgit_repo->url) - 1] != '/')
html("/");
if (page) {
html(page);
html("/");
if (path)
html_attr(path);
}
} else {
html(cgit_script_name);
html("?url=");
html_attr(cgit_repo->url);
if (cgit_repo->url[strlen(cgit_repo->url) - 1] != '/')
html("/");
if (page) {
html(page);
html("/");
if (path)
html_attr(path);
}
delim = "&amp;";
}
if (head && strcmp(head, cgit_repo->defbranch)) {
html(delim);
html("h=");
html_attr(head);
delim = "&amp;";
}
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, cgit_query_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_log_link(char *name, char *title, char *class, char *head,
char *rev, char *path, int ofs)
{
char *delim;
delim = repolink(title, class, "log", head, path);
if (rev && strcmp(rev, cgit_query_head)) {
html(delim);
html("id=");
html_attr(rev);
delim = "&";
}
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) > cgit_max_msg_len && cgit_max_msg_len >= 15) {
name[cgit_max_msg_len] = '\0';
name[cgit_max_msg_len - 1] = '.';
name[cgit_max_msg_len - 2] = '.';
name[cgit_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, cgit_query_head)) {
html(delim);
html("id=");
html_attr(new_rev);
delim = "&amp;";
}
if (old_rev) {
html(delim);
html("id2=");
html_attr(old_rev);
}
html("'>");
html_txt(name);
html("</a>");
}
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,
cgit_query_head, sha1_to_hex(obj->sha1));
return;
} else if (obj->type == OBJ_TREE) {
page = "tree";
arg = "id";
} else {
page = "blob";
arg = "id";
}
url = cgit_pageurl(cgit_query_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)
{
char buf[64];
struct tm *time;
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;
time(&now);
secs = now - t;
if (secs > max_relative && max_relative >= 0) {
cgit_print_date(t, format);
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_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 %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'>");
html("<tr><td id='header'><a href='");
html_attr(cgit_rooturl());
html("'>");
html_txt(cgit_root_title);
html("</a></td><td id='logo'>");
html("<a href='");
html_attr(cgit_logo_link);
htmlf("'><img src='%s' alt='logo'/></a>", cgit_logo);
html("</td></tr>");
html("<tr><td id='crumb'>");
if (cgit_query_repo) {
html_txt(cgit_repo->name);
html(" (");
html_txt(cgit_query_head);
html(") : &nbsp;");
reporevlink(NULL, "summary", NULL, NULL, cgit_query_head,
NULL, NULL);
html(" ");
cgit_log_link("log", NULL, NULL, cgit_query_head,
cgit_query_sha1, cgit_query_path, 0);
html(" ");
cgit_tree_link("tree", NULL, NULL, cgit_query_head,
cgit_query_sha1, NULL);
html(" ");
cgit_commit_link("commit", NULL, NULL, cgit_query_head,
cgit_query_sha1);
html(" ");
cgit_diff_link("diff", NULL, NULL, cgit_query_head,
cgit_query_sha1, cgit_query_sha2,
cgit_query_path);
} else {
html_txt("Index of repositories");
}
html("</td>");
html("<td id='search'>");
if (show_search) {
html("<form method='get' action='");
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>");
}
html("</td></tr>");
html("<tr><td id='content' colspan='2'>");
}
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");
}
/* vim:set sw=8: */
diff --git a/ui-summary.c b/ui-summary.c
index de8a180..016fea2 100644
--- a/ui-summary.c
+++ b/ui-summary.c
@@ -1,189 +1,258 @@
/* ui-summary.c: functions for generating repo summary page
*
* Copyright (C) 2006 Lars Hjemli
*
* Licensed under GNU General Public License v2
* (see COPYING for full license text)
*/
#include "cgit.h"
static int header;
-static int cgit_print_branch_cb(const char *refname, const unsigned char *sha1,
- int flags, void *cb_data)
+static int cmp_age(int age1, int age2)
+{
+ if (age1 != 0 && age2 != 0)
+ return age2 - age1;
+
+ if (age1 == 0 && age2 == 0)
+ return 0;
+
+ if (age1 == 0)
+ return +1;
+
+ return -1;
+}
+
+static int cmp_ref_name(const void *a, const void *b)
+{
+ struct refinfo *r1 = *(struct refinfo **)a;
+ struct refinfo *r2 = *(struct refinfo **)b;
+
+ return strcmp(r1->refname, r2->refname);
+}
+
+static int cmp_branch_age(const void *a, const void *b)
+{
+ struct refinfo *r1 = *(struct refinfo **)a;
+ struct refinfo *r2 = *(struct refinfo **)b;
+
+ return cmp_age(r1->commit->committer_date, r2->commit->committer_date);
+}
+
+static int cmp_tag_age(const void *a, const void *b)
+{
+ struct refinfo *r1 = *(struct refinfo **)a;
+ struct refinfo *r2 = *(struct refinfo **)b;
+
+ return cmp_age(r1->tag->tagger_date, r2->tag->tagger_date);
+}
+
+static void cgit_print_branch(struct refinfo *ref)
{
struct commit *commit;
struct commitinfo *info;
- char buf[256];
- char *ref;
+ char *name = (char *)ref->refname;
- ref = xstrdup(refname);
- strncpy(buf, refname, sizeof(buf));
- commit = lookup_commit(sha1);
+ commit = lookup_commit(ref->object->sha1);
// object is not really parsed at this point, because of some fallout
// from previous calls to git functions in cgit_print_log()
commit->object.parsed = 0;
if (commit && !parse_commit(commit)){
info = cgit_parse_commit(commit);
html("<tr><td>");
- cgit_log_link(ref, NULL, NULL, ref, NULL, NULL, 0);
+ cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0);
html("</td><td>");
cgit_print_age(commit->date, -1, NULL);
html("</td><td>");
html_txt(info->author);
html("</td><td>");
- cgit_commit_link(info->subject, NULL, NULL, ref, NULL);
+ cgit_commit_link(info->subject, NULL, NULL, name, NULL);
html("</td></tr>\n");
cgit_free_commitinfo(info);
} else {
html("<tr><td>");
- html_txt(buf);
+ html_txt(name);
html("</td><td colspan='3'>");
- htmlf("*** bad ref %s ***", sha1_to_hex(sha1));
+ htmlf("*** bad ref %s ***", sha1_to_hex(ref->object->sha1));
html("</td></tr>\n");
}
- free(ref);
- return 0;
}
static void print_tag_header()
{
html("<tr class='nohover'><th class='left'>Tag</th>"
"<th class='left'>Age</th>"
"<th class='left'>Author</th>"
"<th class='left'>Reference</th></tr>\n");
header = 1;
}
-static int cgit_print_tag_cb(const char *refname, const unsigned char *sha1,
- int flags, void *cb_data)
+static int print_tag(struct refinfo *ref)
{
struct tag *tag;
struct taginfo *info;
- struct object *obj;
- char buf[256], *url;
+ char *url, *name = (char *)ref->refname;
- strncpy(buf, refname, sizeof(buf));
- obj = parse_object(sha1);
- if (!obj)
- return 1;
- if (obj->type == OBJ_TAG) {
- tag = lookup_tag(sha1);
+ if (ref->object->type == OBJ_TAG) {
+ tag = lookup_tag(ref->object->sha1);
if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag)))
return 2;
- if (!header)
- print_tag_header();
html("<tr><td>");
url = cgit_pageurl(cgit_query_repo, "tag",
- fmt("id=%s", refname));
+ fmt("id=%s", name));
html_link_open(url, NULL, NULL);
- html_txt(buf);
+ html_txt(name);
html_link_close();
html("</td><td>");
if (info->tagger_date > 0)
cgit_print_age(info->tagger_date, -1, NULL);
html("</td><td>");
if (info->tagger)
html(info->tagger);
html("</td><td>");
cgit_object_link(tag->tagged);
html("</td></tr>\n");
} else {
if (!header)
print_tag_header();
html("<tr><td>");
- html_txt(buf);
+ html_txt(name);
html("</td><td colspan='2'/><td>");
- cgit_object_link(obj);
+ cgit_object_link(ref->object);
html("</td></tr>\n");
}
return 0;
}
static int cgit_print_archive_cb(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];
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("<table id='downloads'>");
html("<tr><th>Downloads</th></tr>");
header = 1;
}
html("<tr><td>");
url = cgit_pageurl(cgit_query_repo, "blob",
fmt("id=%s&amp;path=%s", sha1_to_hex(fileid),
buf));
html_link_open(url, NULL, NULL);
html_txt(buf);
html_link_close();
html("</td></tr>");
return 0;
}
-static void cgit_print_branches()
+static void print_refs_link(char *path)
{
+ html("<tr class='nohover'><td colspan='4'>");
+ cgit_refs_link("[...]", NULL, NULL, cgit_query_head, NULL, path);
+ html("</td></tr>");
+}
+
+void cgit_print_branches(int maxcount)
+{
+ struct reflist list;
+ int i;
+
html("<tr class='nohover'><th class='left'>Branch</th>"
"<th class='left'>Idle</th>"
"<th class='left'>Author</th>"
"<th class='left'>Head commit</th></tr>\n");
- for_each_branch_ref(cgit_print_branch_cb, NULL);
+
+ list.refs = NULL;
+ list.alloc = list.count = 0;
+ for_each_branch_ref(cgit_refs_cb, &list);
+
+ if (maxcount == 0 || maxcount > list.count)
+ maxcount = list.count;
+
+ if (maxcount < list.count) {
+ qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age);
+ qsort(list.refs, maxcount, sizeof(*list.refs), cmp_ref_name);
+ }
+
+ for(i=0; i<maxcount; i++)
+ cgit_print_branch(list.refs[i]);
+
+ if (maxcount < list.count)
+ print_refs_link("heads");
}
-static void cgit_print_tags()
+void cgit_print_tags(int maxcount)
{
+ struct reflist list;
+ int i;
+
header = 0;
- for_each_tag_ref(cgit_print_tag_cb, NULL);
+ list.refs = NULL;
+ list.alloc = list.count = 0;
+ for_each_tag_ref(cgit_refs_cb, &list);
+ if (list.count == 0)
+ return;
+ qsort(list.refs, list.count, sizeof(*list.refs), cmp_tag_age);
+ if (!maxcount)
+ maxcount = list.count;
+ else if (maxcount > list.count)
+ maxcount = list.count;
+ print_tag_header();
+ for(i=0; i<maxcount; i++)
+ print_tag(list.refs[i]);
+
+ if (maxcount < list.count)
+ print_refs_link("tags");
}
static void cgit_print_archives()
{
header = 0;
for_each_ref(cgit_print_archive_cb, NULL);
if (header)
html("</table>");
}
void cgit_print_summary()
{
html("<div id='summary'>");
cgit_print_archives();
html("<h2>");
html_txt(cgit_repo->name);
html(" - ");
html_txt(cgit_repo->desc);
html("</h2>");
if (cgit_repo->readme)
html_include(cgit_repo->readme);
html("</div>");
if (cgit_summary_log > 0)
cgit_print_log(cgit_query_head, 0, cgit_summary_log, NULL, NULL, 0);
html("<table class='list nowrap'>");
if (cgit_summary_log > 0)
html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
- cgit_print_branches();
+ cgit_print_branches(cgit_summary_branches);
html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
- cgit_print_tags();
+ cgit_print_tags(cgit_summary_tags);
html("</table>");
}