author | Lars Hjemli <hjemli@gmail.com> | 2009-08-09 11:46:01 (UTC) |
---|---|---|
committer | Lars Hjemli <hjemli@gmail.com> | 2009-08-09 11:46:01 (UTC) |
commit | db6303b58883c4417f5bcc0c1ee34fed6553dca3 (patch) (side-by-side diff) | |
tree | ef7775ade9eef57c5a878f9588fe545a7da2c952 | |
parent | 17e3ff42646f182911fd0e5d872082977538db9e (diff) | |
parent | 97b3d252629a8a3b9d356c2532dec7611438e4b9 (diff) | |
download | cgit-db6303b58883c4417f5bcc0c1ee34fed6553dca3.zip cgit-db6303b58883c4417f5bcc0c1ee34fed6553dca3.tar.gz cgit-db6303b58883c4417f5bcc0c1ee34fed6553dca3.tar.bz2 |
Merge branch 'lh/plugins'
Conflicts:
cgit.c
cgit.h
-rw-r--r-- | cgit.c | 23 | ||||
-rw-r--r-- | cgit.h | 16 | ||||
-rw-r--r-- | cgitrc.5.txt | 20 | ||||
-rw-r--r-- | shared.c | 37 | ||||
-rw-r--r-- | ui-commit.c | 8 | ||||
-rw-r--r-- | ui-snapshot.c | 35 | ||||
-rw-r--r-- | ui-tree.c | 18 |
7 files changed, 125 insertions, 32 deletions
@@ -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); } @@ -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 ------------------- @@ -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"); } @@ -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; } |