summaryrefslogtreecommitdiffabout
authorLars Hjemli <hjemli@gmail.com>2009-08-09 11:46:01 (UTC)
committer Lars Hjemli <hjemli@gmail.com>2009-08-09 11:46:01 (UTC)
commitdb6303b58883c4417f5bcc0c1ee34fed6553dca3 (patch) (side-by-side diff)
treeef7775ade9eef57c5a878f9588fe545a7da2c952
parent17e3ff42646f182911fd0e5d872082977538db9e (diff)
parent97b3d252629a8a3b9d356c2532dec7611438e4b9 (diff)
downloadcgit-db6303b58883c4417f5bcc0c1ee34fed6553dca3.zip
cgit-db6303b58883c4417f5bcc0c1ee34fed6553dca3.tar.gz
cgit-db6303b58883c4417f5bcc0c1ee34fed6553dca3.tar.bz2
Merge branch 'lh/plugins'
Conflicts: cgit.c cgit.h
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c23
-rw-r--r--cgit.h16
-rw-r--r--cgitrc.5.txt20
-rw-r--r--shared.c37
-rw-r--r--ui-commit.c8
-rw-r--r--ui-snapshot.c35
-rw-r--r--ui-tree.c18
7 files changed, 125 insertions, 32 deletions
diff --git a/cgit.c b/cgit.c
index aa1107a..dbec196 100644
--- a/cgit.c
+++ b/cgit.c
@@ -20,16 +20,31 @@ const char *cgit_version = CGIT_VERSION;
void add_mimetype(const char *name, const char *value)
{
struct string_list_item *item;
item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes);
item->util = xstrdup(value);
}
+struct cgit_filter *new_filter(const char *cmd, int extra_args)
+{
+ struct cgit_filter *f;
+
+ if (!cmd || !cmd[0])
+ return NULL;
+
+ f = xmalloc(sizeof(struct cgit_filter));
+ f->cmd = xstrdup(cmd);
+ f->argv = xmalloc((2 + extra_args) * sizeof(char *));
+ f->argv[0] = f->cmd;
+ f->argv[1] = NULL;
+ return f;
+}
+
void config_cb(const char *name, const char *value)
{
if (!strcmp(name, "root-title"))
ctx.cfg.root_title = xstrdup(value);
else if (!strcmp(name, "root-desc"))
ctx.cfg.root_desc = xstrdup(value);
else if (!strcmp(name, "root-readme"))
ctx.cfg.root_readme = xstrdup(value);
@@ -80,26 +95,30 @@ void config_cb(const char *name, const char *value)
else if (!strcmp(name, "cache-root-ttl"))
ctx.cfg.cache_root_ttl = atoi(value);
else if (!strcmp(name, "cache-repo-ttl"))
ctx.cfg.cache_repo_ttl = atoi(value);
else if (!strcmp(name, "cache-static-ttl"))
ctx.cfg.cache_static_ttl = atoi(value);
else if (!strcmp(name, "cache-dynamic-ttl"))
ctx.cfg.cache_dynamic_ttl = atoi(value);
+ else if (!strcmp(name, "commit-filter"))
+ ctx.cfg.commit_filter = new_filter(value, 0);
else if (!strcmp(name, "embedded"))
ctx.cfg.embedded = atoi(value);
else if (!strcmp(name, "max-message-length"))
ctx.cfg.max_msg_len = atoi(value);
else if (!strcmp(name, "max-repodesc-length"))
ctx.cfg.max_repodesc_len = atoi(value);
else if (!strcmp(name, "max-repo-count"))
ctx.cfg.max_repo_count = atoi(value);
else if (!strcmp(name, "max-commit-count"))
ctx.cfg.max_commit_count = atoi(value);
+ else if (!strcmp(name, "source-filter"))
+ ctx.cfg.source_filter = new_filter(value, 1);
else if (!strcmp(name, "summary-log"))
ctx.cfg.summary_log = atoi(value);
else if (!strcmp(name, "summary-branches"))
ctx.cfg.summary_branches = atoi(value);
else if (!strcmp(name, "summary-tags"))
ctx.cfg.summary_tags = atoi(value);
else if (!strcmp(name, "agefile"))
ctx.cfg.agefile = xstrdup(value);
@@ -134,16 +153,20 @@ void config_cb(const char *name, const char *value)
else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount"))
ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount"))
ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
else if (ctx.repo && !strcmp(name, "repo.max-stats"))
ctx.repo->max_stats = cgit_find_stats_period(value, NULL);
else if (ctx.repo && !strcmp(name, "repo.module-link"))
ctx.repo->module_link= xstrdup(value);
+ else if (ctx.repo && !strcmp(name, "repo.commit-filter"))
+ ctx.repo->commit_filter = new_filter(value, 0);
+ else if (ctx.repo && !strcmp(name, "repo.source-filter"))
+ ctx.repo->source_filter = new_filter(value, 1);
else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) {
if (*value == '/')
ctx.repo->readme = xstrdup(value);
else
ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value));
} else if (!strcmp(name, "include"))
parse_configfile(value, config_cb);
}
diff --git a/cgit.h b/cgit.h
index 1194eb0..b8557ac 100644
--- a/cgit.h
+++ b/cgit.h
@@ -44,32 +44,43 @@
* 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_filter {
+ char *cmd;
+ char **argv;
+ int old_stdout;
+ int pipe_fh[2];
+ int pid;
+ int exitstatus;
+};
+
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;
int max_stats;
time_t mtime;
+ struct cgit_filter *commit_filter;
+ struct cgit_filter *source_filter;
};
struct cgit_repolist {
int length;
int count;
struct cgit_repo *repos;
};
@@ -172,16 +183,18 @@ struct cgit_config {
int noplainemail;
int noheader;
int renamelimit;
int snapshots;
int summary_branches;
int summary_log;
int summary_tags;
struct string_list mimetypes;
+ struct cgit_filter *commit_filter;
+ struct cgit_filter *source_filter;
};
struct cgit_page {
time_t modified;
time_t expires;
size_t size;
char *mimetype;
char *charset;
@@ -246,10 +259,13 @@ 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);
+extern int cgit_open_filter(struct cgit_filter *filter);
+extern int cgit_close_filter(struct cgit_filter *filter);
+
#endif /* CGIT_H */
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 0412f64..dc63637 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -50,16 +50,22 @@ cache-static-ttl::
"5".
clone-prefix::
Space-separated list of common prefixes which, when combined with a
repository url, generates valid clone urls for the repository. This
setting is only used if `repo.clone-url` is unspecified. Default value:
none.
+commit-filter::
+ Specifies a command which will be invoked to format commit messages.
+ The command will get the message on its STDIN, and the STDOUT from the
+ command will be included verbatim as the commit message, i.e. this can
+ be used to implement bugtracker integration. Default value: none.
+
css::
Url which specifies the css document to include in all cgit pages.
Default value: "/cgit.css".
embedded::
Flag which, when set to "1", will make cgit generate a html fragment
suitable for embedding in other html pages. Default value: none. See
also: "noheader".
@@ -201,16 +207,24 @@ snapshots::
supported by cgit. The value is a space-separated list of zero or more
of the following values:
"tar" uncompressed tar-file
"tar.gz" gzip-compressed tar-file
"tar.bz2" bzip-compressed tar-file
"zip" zip-file
Default value: none.
+source-filter::
+ Specifies a command which will be invoked to format plaintext blobs
+ in the tree view. The command will get the blob content on its STDIN
+ and the name of the blob as its only command line argument. The STDOUT
+ from the command will be included verbatim as the blob contents, i.e.
+ this can be used to implement e.g. syntax highlighting. Default value:
+ none.
+
summary-branches::
Specifies the number of branches to display in the repository "summary"
view. Default value: "10".
summary-log::
Specifies the number of log entries to display in the repository
"summary" view. Default value: "10".
@@ -227,16 +241,19 @@ virtual-root::
same kind of virtual urls, so this option will probably be deprecated.
REPOSITORY SETTINGS
-------------------
repo.clone-url::
A list of space-separated urls which can be used to clone this repo.
Default value: none.
+repo.commit-filter::
+ Override the default commit-filter. Default value: <commit-filter>.
+
repo.defbranch::
The name of the default branch for this repository. If no such branch
exists in the repository, the first branch name (when sorted) is used
as default instead. Default value: "master".
repo.desc::
The value to show as repository description. Default value: none.
@@ -267,16 +284,19 @@ repo.path::
repo.readme::
A path (relative to <repo.path>) which specifies a file to include
verbatim as the "About" page for this repo. Default value: none.
repo.snapshots::
A mask of allowed snapshot-formats for this repo, restricted by the
"snapshots" global setting. Default value: <snapshots>.
+repo.source-filter::
+ Override the default source-filter. Default value: <source-filter>.
+
repo.url::
The relative url used to access the repository. This must be the first
setting specified for each repo. Default value: none.
EXAMPLE CGITRC FILE
-------------------
diff --git a/shared.c b/shared.c
index cce0af4..783604b 100644
--- a/shared.c
+++ b/shared.c
@@ -57,16 +57,18 @@ struct cgit_repo *cgit_add_repo(const char *url)
ret->defbranch = "master";
ret->snapshots = ctx.cfg.snapshots;
ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
ret->max_stats = ctx.cfg.max_stats;
ret->module_link = ctx.cfg.module_link;
ret->readme = NULL;
ret->mtime = -1;
+ ret->commit_filter = ctx.cfg.commit_filter;
+ ret->source_filter = ctx.cfg.source_filter;
return ret;
}
struct cgit_repo *cgit_get_repoinfo(const char *url)
{
int i;
struct cgit_repo *repo;
@@ -350,8 +352,43 @@ int cgit_parse_snapshots_mask(const char *str)
rv |= f->bit;
break;
}
}
str += tl;
}
return rv;
}
+
+int cgit_open_filter(struct cgit_filter *filter)
+{
+
+ filter->old_stdout = chk_positive(dup(STDOUT_FILENO),
+ "Unable to duplicate STDOUT");
+ chk_zero(pipe(filter->pipe_fh), "Unable to create pipe to subprocess");
+ filter->pid = chk_non_negative(fork(), "Unable to create subprocess");
+ if (filter->pid == 0) {
+ close(filter->pipe_fh[1]);
+ chk_non_negative(dup2(filter->pipe_fh[0], STDIN_FILENO),
+ "Unable to use pipe as STDIN");
+ execvp(filter->cmd, filter->argv);
+ die("Unable to exec subprocess %s: %s (%d)", filter->cmd,
+ strerror(errno), errno);
+ }
+ close(filter->pipe_fh[0]);
+ chk_non_negative(dup2(filter->pipe_fh[1], STDOUT_FILENO),
+ "Unable to use pipe as STDOUT");
+ close(filter->pipe_fh[1]);
+ return 0;
+}
+
+int cgit_close_filter(struct cgit_filter *filter)
+{
+ chk_non_negative(dup2(filter->old_stdout, STDOUT_FILENO),
+ "Unable to restore STDOUT");
+ close(filter->old_stdout);
+ if (filter->pid < 0)
+ return 0;
+ waitpid(filter->pid, &filter->exitstatus, 0);
+ if (WIFEXITED(filter->exitstatus) && !WEXITSTATUS(filter->exitstatus))
+ return 0;
+ die("Subprocess %s exited abnormally", filter->cmd);
+}
diff --git a/ui-commit.c b/ui-commit.c
index 9fdb8ee..d6b73ee 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -88,21 +88,29 @@ void cgit_print_commit(char *hex)
if (ctx.repo->snapshots) {
html("<tr><th>download</th><td colspan='2' class='sha1'>");
cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head,
hex, ctx.repo->snapshots);
html("</td></tr>");
}
html("</table>\n");
html("<div class='commit-subject'>");
+ if (ctx.repo->commit_filter)
+ cgit_open_filter(ctx.repo->commit_filter);
html_txt(info->subject);
+ if (ctx.repo->commit_filter)
+ cgit_close_filter(ctx.repo->commit_filter);
show_commit_decorations(commit);
html("</div>");
html("<div class='commit-msg'>");
+ if (ctx.repo->commit_filter)
+ cgit_open_filter(ctx.repo->commit_filter);
html_txt(info->msg);
+ if (ctx.repo->commit_filter)
+ cgit_close_filter(ctx.repo->commit_filter);
html("</div>");
if (parents < 3) {
if (parents)
tmp = sha1_to_hex(commit->parents->item->object.sha1);
else
tmp = NULL;
cgit_print_diff(ctx.qry.sha1, tmp, NULL);
}
diff --git a/ui-snapshot.c b/ui-snapshot.c
index 5372f5d..4136b3e 100644
--- a/ui-snapshot.c
+++ b/ui-snapshot.c
@@ -7,47 +7,26 @@
*/
#include "cgit.h"
#include "html.h"
#include "ui-shared.h"
static int write_compressed_tar_archive(struct archiver_args *args,const char *filter)
{
- int rw[2];
- pid_t gzpid;
- int stdout2;
- int status;
int rv;
+ struct cgit_filter f;
- stdout2 = chk_non_negative(dup(STDIN_FILENO), "Preserving STDOUT before compressing");
- chk_zero(pipe(rw), "Opening pipe from compressor subprocess");
- gzpid = chk_non_negative(fork(), "Forking compressor subprocess");
- if(gzpid==0) {
- /* child */
- chk_zero(close(rw[1]), "Closing write end of pipe in child");
- chk_zero(close(STDIN_FILENO), "Closing STDIN");
- chk_non_negative(dup2(rw[0],STDIN_FILENO), "Redirecting compressor input to stdin");
- execlp(filter,filter,NULL);
- _exit(-1);
- }
- /* parent */
- chk_zero(close(rw[0]), "Closing read end of pipe");
- chk_non_negative(dup2(rw[1],STDOUT_FILENO), "Redirecting output to compressor");
-
+ f.cmd = xstrdup(filter);
+ f.argv = malloc(2 * sizeof(char *));
+ f.argv[0] = f.cmd;
+ f.argv[1] = NULL;
+ cgit_open_filter(&f);
rv = write_tar_archive(args);
-
- chk_zero(close(STDOUT_FILENO), "Closing STDOUT redirected to compressor");
- chk_non_negative(dup2(stdout2,STDOUT_FILENO), "Restoring uncompressed STDOUT");
- chk_zero(close(stdout2), "Closing uncompressed STDOUT");
- chk_zero(close(rw[1]), "Closing write end of pipe in parent");
- chk_positive(waitpid(gzpid,&status,0), "Waiting on compressor process");
- if(! ( WIFEXITED(status) && WEXITSTATUS(status)==0 ) )
- cgit_print_error("Failed to compress archive");
-
+ cgit_close_filter(&f);
return rv;
}
static int write_tar_gzip_archive(struct archiver_args *args)
{
return write_compressed_tar_archive(args,"gzip");
}
diff --git a/ui-tree.c b/ui-tree.c
index 61fcf5a..c608754 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -10,23 +10,33 @@
#include "cgit.h"
#include "html.h"
#include "ui-shared.h"
char *curr_rev;
char *match_path;
int header = 0;
-static void print_text_buffer(char *buf, unsigned long size)
+static void print_text_buffer(const char *name, char *buf, unsigned long size)
{
unsigned long lineno, idx;
const char *numberfmt =
"<a class='no' id='n%1$d' name='n%1$d' href='#n%1$d'>%1$d</a>\n";
html("<table summary='blob content' class='blob'>\n");
+ if (ctx.repo->source_filter) {
+ html("<tr><td class='lines'><pre><code>");
+ ctx.repo->source_filter->argv[1] = xstrdup(name);
+ cgit_open_filter(ctx.repo->source_filter);
+ write(STDOUT_FILENO, buf, size);
+ cgit_close_filter(ctx.repo->source_filter);
+ html("</code></pre></td></tr></table>\n");
+ return;
+ }
+
html("<tr><td class='linenumbers'><pre>");
idx = 0;
lineno = 0;
if (size) {
htmlf(numberfmt, ++lineno);
while(idx < size - 1) { // skip absolute last newline
if (buf[idx] == '\n')
@@ -60,17 +70,17 @@ static void print_binary_buffer(char *buf, unsigned long size)
ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.';
ascii[idx] = '\0';
html_txt(ascii);
html("</td></tr>\n");
}
html("</table>\n");
}
-static void print_object(const unsigned char *sha1, char *path)
+static void print_object(const unsigned char *sha1, char *path, const char *basename)
{
enum object_type type;
char *buf;
unsigned long size;
type = sha1_object_info(sha1, &size);
if (type == OBJ_BAD) {
cgit_print_error(fmt("Bad object name: %s",
@@ -88,17 +98,17 @@ static void print_object(const unsigned char *sha1, char *path)
html(" (");
cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
curr_rev, path);
htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1));
if (buffer_is_binary(buf, size))
print_binary_buffer(buf, size);
else
- print_text_buffer(buf, size);
+ print_text_buffer(basename, buf, size);
}
static int ls_item(const unsigned char *sha1, const char *base, int baselen,
const char *pathname, unsigned int mode, int stage,
void *cbdata)
{
char *name;
@@ -214,17 +224,17 @@ static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
if (strcmp(match_path, buffer))
return READ_TREE_RECURSIVE;
if (S_ISDIR(mode)) {
state = 1;
ls_head();
return READ_TREE_RECURSIVE;
} else {
- print_object(sha1, buffer);
+ print_object(sha1, buffer, pathname);
return 0;
}
}
ls_item(sha1, base, baselen, pathname, mode, stage, NULL);
return 0;
}