premature exits bork cache

2016-02-26 Thread Jason A. Donenfeld
Hi guys,

When caching is turned on, if we exit(0) early from a cmd, the cache
won't be properly finalized (or even sent), and we get a server error
because of blank output. I just fixed this bug with redirect:

https://git.zx2c4.com/cgit/commit/?id=e9cbdf64632fbe64d6b0c4974ac947ef954938e7

This is fine, because it's called only from 1 function (three times),
and that function then returns, so execution doesn't continue after
the redirect. It simply gives control back to the caching layer that
does things properly now.

But I noticed we have this issue with HTTP HEAD method:

https://git.zx2c4.com/cgit/tree/ui-shared.c#n695

Here we're calling exit(0). The motivation, I imagine, was that we
want to retain the same handlers, but just return the headers for the
HEAD method. Unfortunately, this borks the cache layer for the same
reasons as described above. I'm not yet sure the cleanest way to fix
this. Suggestions?

Jason
___
CGit mailing list
CGit@lists.zx2c4.com
http://lists.zx2c4.com/mailman/listinfo/cgit


Re: Killing plaintext git:// in favor of https:// cloning

2016-02-26 Thread Daniel Reichelt
On 02/22/2016 07:16 PM, Jason A. Donenfeld wrote:
> Does anybody have any objections or comments?

git:// should be removed from clone urls

Cheers
Daniel
___
CGit mailing list
CGit@lists.zx2c4.com
http://lists.zx2c4.com/mailman/listinfo/cgit


Re: Killing plaintext git:// in favor of https:// cloning

2016-02-26 Thread Jason A. Donenfeld
Welp, in the last 2 days:

krantz log # grep git-daemon messages | grep 'Connection from' | wc -l
3079

So, I guess git:// will be sticking around, alas.
___
CGit mailing list
CGit@lists.zx2c4.com
http://lists.zx2c4.com/mailman/listinfo/cgit


Re: premature exits bork cache

2016-02-26 Thread John Keeping
On Fri, Feb 26, 2016 at 01:29:21PM +0100, Jason A. Donenfeld wrote:
> When caching is turned on, if we exit(0) early from a cmd, the cache
> won't be properly finalized (or even sent), and we get a server error
> because of blank output. I just fixed this bug with redirect:
> 
> https://git.zx2c4.com/cgit/commit/?id=e9cbdf64632fbe64d6b0c4974ac947ef954938e7
> 
> This is fine, because it's called only from 1 function (three times),
> and that function then returns, so execution doesn't continue after
> the redirect. It simply gives control back to the caching layer that
> does things properly now.
> 
> But I noticed we have this issue with HTTP HEAD method:
> 
> https://git.zx2c4.com/cgit/tree/ui-shared.c#n695
> 
> Here we're calling exit(0). The motivation, I imagine, was that we
> want to retain the same handlers, but just return the headers for the
> HEAD method. Unfortunately, this borks the cache layer for the same
> reasons as described above. I'm not yet sure the cleanest way to fix
> this. Suggestions?

We set cfg.nocache in cgit.c for HEAD requests so I think we're already
okay in this case, although it is a subtle interaction.
___
CGit mailing list
CGit@lists.zx2c4.com
http://lists.zx2c4.com/mailman/listinfo/cgit


Re: premature exits bork cache

2016-02-26 Thread Jason A. Donenfeld
Ahh, good point.

There are a couple more calls to exit that I'm investigating. This is
certainly not the nicest way to handle things...
___
CGit mailing list
CGit@lists.zx2c4.com
http://lists.zx2c4.com/mailman/listinfo/cgit


Jenkins build is back to normal : cgit-master-coverity #13 - origin/master - e9cbdf6

2016-02-26 Thread Pelagic Jenkins (Public)
See 

___
CGit mailing list
CGit@lists.zx2c4.com
http://lists.zx2c4.com/mailman/listinfo/cgit


[PATCH 2/2] ttl: Support different TTL times based on cache-control

2016-02-26 Thread Tim Nordell
Allow the client browser to pass in "max-age=0" and "no-cache"
to control a separate TTL time on the server for each type of
page.  This extends the TTL field to have the additional form
of:

some-ttl=5:1

where 5 is the number of minutes if the user were to simply
load the page, and 1 is the number of minutes if the user were
to reload the page in their web browser (hit refresh).

Signed-off-by: Tim Nordell 

diff --git a/cgit.c b/cgit.c
index 963aee8..0ca1baa 100644
--- a/cgit.c
+++ b/cgit.c
@@ -29,6 +29,30 @@ static void add_mimetype(const char *name, const char *value)
 
 static void process_cached_repolist(const char *path);
 
+/**
+ * \brief Convert TTL field into a TTL structure
+ *
+ * \param value Value from field; should be one of the following forms:
+ *  [normal]
+ *  [normal]:[forced]
+ * \return Parsed TTL value
+ */
+static struct cgit_ttl ttl_value(const char *value)
+{
+   char *param2;
+   struct cgit_ttl ret;
+
+   ret.normal = atoi(value);
+
+   param2 = strchr(value, ':');
+   if(NULL != param2)
+   ret.forced = atoi(¶m2[1]);
+   else
+   ret.forced = -1;
+
+   return ret;
+}
+
 static void repo_config(struct cgit_repo *repo, const char *name, const char 
*value)
 {
struct string_list_item *item;
@@ -187,19 +211,19 @@ static void config_cb(const char *name, const char *value)
else if (!strcmp(name, "cache-root"))
ctx.cfg.cache_root = xstrdup(expand_macros(value));
else if (!strcmp(name, "cache-root-ttl"))
-   ctx.cfg.cache_root_ttl = atoi(value);
+   ctx.cfg.cache_root_ttl = ttl_value(value);
else if (!strcmp(name, "cache-repo-ttl"))
-   ctx.cfg.cache_repo_ttl = atoi(value);
+   ctx.cfg.cache_repo_ttl = ttl_value(value);
else if (!strcmp(name, "cache-scanrc-ttl"))
ctx.cfg.cache_scanrc_ttl = atoi(value);
else if (!strcmp(name, "cache-static-ttl"))
-   ctx.cfg.cache_static_ttl = atoi(value);
+   ctx.cfg.cache_static_ttl = ttl_value(value);
else if (!strcmp(name, "cache-dynamic-ttl"))
-   ctx.cfg.cache_dynamic_ttl = atoi(value);
+   ctx.cfg.cache_dynamic_ttl = ttl_value(value);
else if (!strcmp(name, "cache-about-ttl"))
-   ctx.cfg.cache_about_ttl = atoi(value);
+   ctx.cfg.cache_about_ttl = ttl_value(value);
else if (!strcmp(name, "cache-snapshot-ttl"))
-   ctx.cfg.cache_snapshot_ttl = atoi(value);
+   ctx.cfg.cache_snapshot_ttl = ttl_value(value);
else if (!strcmp(name, "case-sensitive-sort"))
ctx.cfg.case_sensitive_sort = atoi(value);
else if (!strcmp(name, "about-filter"))
@@ -352,13 +376,19 @@ static void prepare_context(void)
ctx.cfg.cache_size = 0;
ctx.cfg.cache_max_create_time = 5;
ctx.cfg.cache_root = CGIT_CACHE_ROOT;
-   ctx.cfg.cache_about_ttl = 15;
-   ctx.cfg.cache_snapshot_ttl = 5;
-   ctx.cfg.cache_repo_ttl = 5;
-   ctx.cfg.cache_root_ttl = 5;
+   ctx.cfg.cache_about_ttl.normal = 15;
+   ctx.cfg.cache_about_ttl.forced = -1;
+   ctx.cfg.cache_snapshot_ttl.normal = 5;
+   ctx.cfg.cache_snapshot_ttl.forced = -1;
+   ctx.cfg.cache_repo_ttl.normal = 5;
+   ctx.cfg.cache_repo_ttl.forced = -1;
+   ctx.cfg.cache_root_ttl.normal = 5;
+   ctx.cfg.cache_root_ttl.forced = -1;
ctx.cfg.cache_scanrc_ttl = 15;
-   ctx.cfg.cache_dynamic_ttl = 5;
-   ctx.cfg.cache_static_ttl = -1;
+   ctx.cfg.cache_dynamic_ttl.normal = 5;
+   ctx.cfg.cache_dynamic_ttl.forced = -1;
+   ctx.cfg.cache_static_ttl.normal = -1;
+   ctx.cfg.cache_static_ttl.forced = -1;
ctx.cfg.case_sensitive_sort = 1;
ctx.cfg.branch_sort = 0;
ctx.cfg.commit_sort = 0;
@@ -405,6 +435,7 @@ static void prepare_context(void)
ctx.env.server_port = getenv("SERVER_PORT");
ctx.env.http_cookie = getenv("HTTP_COOKIE");
ctx.env.http_referer = getenv("HTTP_REFERER");
+   ctx.env.http_cache_control = getenv("HTTP_CACHE_CONTROL");
ctx.env.content_length = getenv("CONTENT_LENGTH") ? 
strtoul(getenv("CONTENT_LENGTH"), NULL, 10) : 0;
ctx.env.authenticated = 0;
ctx.page.mimetype = "text/html";
@@ -1006,7 +1037,7 @@ static void cgit_parse_args(int argc, const char **argv)
 static int calc_ttl(void)
 {
const int ttl_conversion_multiplier = 60;
-   int ttl;
+   struct cgit_ttl ttl;
 
if (!ctx.repo)
ttl = ctx.cfg.cache_root_ttl;
@@ -1023,8 +1054,14 @@ static int calc_ttl(void)
else
ttl = ctx.cfg.cache_repo_ttl;
 
-   if (ttl >= 0)
-   return ttl * ttl_conversion_multiplier;
+   if(ttl.forced >= 0 && NULL != ctx.env.http_cache_control &&
+   (!strcmp(ctx.env.http_cache_control, "max-age=0") ||
+!strcmp(ctx.

[PATCH 1/2] ui-log: Do not always emit decoration span

2016-02-26 Thread Tim Nordell
The decoration span does not need to be emited if there aren't
any decorations to show.  This modification saves slightly
on bandwidth.

Signed-off-by: Tim Nordell 

diff --git a/ui-log.c b/ui-log.c
index 0a3938b..62881ce 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -61,6 +61,8 @@ void show_commit_decorations(struct commit *commit)
 
buf[sizeof(buf) - 1] = 0;
deco = get_name_decoration(&commit->object);
+   if(!deco)
+   return;
html("");
while (deco) {
if (starts_with(deco->name, "refs/heads/")) {
-- 
2.4.9

___
CGit mailing list
CGit@lists.zx2c4.com
http://lists.zx2c4.com/mailman/listinfo/cgit


[PATCH] ui-diff: Add configuration option to limit number of files

2016-02-26 Thread Tim Nordell
If a large commit is made, the generated HTML file can be very
large and take the server a while to generate the HTML for the
diff.  Add a configuration option to limit the maximum number
of files presented within a diff.

Signed-off-by: Tim Nordell 

diff --git a/cgit.c b/cgit.c
index 0ca1baa..87ba811 100644
--- a/cgit.c
+++ b/cgit.c
@@ -248,6 +248,12 @@ static void config_cb(const char *name, const char *value)
ctx.cfg.max_blob_size = atoi(value);
else if (!strcmp(name, "max-repo-count"))
ctx.cfg.max_repo_count = atoi(value);
+   else if (!strcmp(name, "max-diff-files"))
+   {
+   ctx.cfg.max_diff_files = atoi(value);
+   if(ctx.cfg.max_diff_files < 1)
+   ctx.cfg.max_diff_files = INT_MAX;
+   }
else if (!strcmp(name, "max-commit-count"))
ctx.cfg.max_commit_count = atoi(value);
else if (!strcmp(name, "project-list"))
@@ -401,6 +407,7 @@ static void prepare_context(void)
ctx.cfg.enable_tree_linenumbers = 1;
ctx.cfg.enable_git_config = 0;
ctx.cfg.max_repo_count = 50;
+   ctx.cfg.max_diff_files = INT_MAX;
ctx.cfg.max_commit_count = 50;
ctx.cfg.max_lock_attempts = 5;
ctx.cfg.max_msg_len = 80;
diff --git a/cgit.h b/cgit.h
index 350d25d..b2b2ae9 100644
--- a/cgit.h
+++ b/cgit.h
@@ -243,6 +243,7 @@ struct cgit_config {
int enable_git_config;
int local_time;
int max_atom_items;
+   int max_diff_files;
int max_repo_count;
int max_commit_count;
int max_lock_attempts;
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 94901bd..3bca0ab 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -420,6 +420,14 @@ side-by-side-diffs::
If set to "1" shows side-by-side diffs instead of unidiffs per
default. Default value: "0".
 
+max-diff-files::
+   This value controls the maximum number of files presented in a diff
+   view to help control the size of HTML files presented in a web browser.
+   (In a very large commit, a client web browser may choke on a large
+   commit, such as the first commit in the Linux kernel repository.)
+   A value of "0" turns this feature off.
+   Default value: "0".
+
 snapshots::
Text which specifies the default set of snapshot formats that cgit
generates links for. The value is a space-separated list of zero or
diff --git a/ui-diff.c b/ui-diff.c
index 52ed942..65045a4 100644
--- a/ui-diff.c
+++ b/ui-diff.c
@@ -18,6 +18,7 @@ unsigned char new_rev_sha1[20];
 static int files, slots;
 static int total_adds, total_rems, max_changes;
 static int lines_added, lines_removed;
+static int filepair_cnt;
 
 static struct fileinfo {
char status;
@@ -206,8 +207,18 @@ static void cgit_print_diffstat(const unsigned char 
*old_sha1,
max_changes = 0;
cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, prefix,
   ctx.qry.ignorews);
-   for (i = 0; i");
+   html("...");
+   html("...");
+   html("...");
+   html("...");
+   html("");
+   }
html("");
html("");
htmlf("%d files changed, %d insertions, %d deletions",
@@ -302,6 +313,11 @@ static void filepair_cb(struct diff_filepair *pair)
if (!show_filepair(pair))
return;
 
+   if(filepair_cnt++ >= ctx.cfg.max_diff_files)
+   {
+   return;
+   }
+
current_filepair = pair;
if (use_ssdiff) {
cgit_ssdiff_header_begin();
@@ -490,6 +506,7 @@ void cgit_print_diff(const char *new_rev, const char 
*old_rev,
html("");
html("");
}
+filepair_cnt = 0;
cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix,
   ctx.qry.ignorews);
if (!use_ssdiff)
-- 
2.4.9

___
CGit mailing list
CGit@lists.zx2c4.com
http://lists.zx2c4.com/mailman/listinfo/cgit


[PATCH 1/2] ttl: Consolidate TTL calculation

2016-02-26 Thread Tim Nordell
Currently part of the TTL calculation is done outside of the
calc_ttl() function.  Ideally a single variable in main()
would have the total seconds until the cache expires, and
ctx.page.expires would be incremented by this, as well
as the parameter to cache_process getting this value in
seconds.

Signed-off-by: Tim Nordell 

diff --git a/cache.c b/cache.c
index 6736a01..417f1a2 100644
--- a/cache.c
+++ b/cache.c
@@ -126,7 +126,7 @@ static int is_expired(struct cache_slot *slot)
if (slot->ttl < 0)
return 0;
else
-   return slot->cache_st.st_mtime + slot->ttl * 60 < time(NULL);
+   return slot->cache_st.st_mtime + slot->ttl < time(NULL);
 }
 
 /* Check if the slot has been modified since we opened it.
diff --git a/cgit.c b/cgit.c
index fc482be..963aee8 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1005,25 +1005,28 @@ static void cgit_parse_args(int argc, const char **argv)
 
 static int calc_ttl(void)
 {
-   if (!ctx.repo)
-   return ctx.cfg.cache_root_ttl;
-
-   if (!ctx.qry.page)
-   return ctx.cfg.cache_repo_ttl;
-
-   if (!strcmp(ctx.qry.page, "about"))
-   return ctx.cfg.cache_about_ttl;
-
-   if (!strcmp(ctx.qry.page, "snapshot"))
-   return ctx.cfg.cache_snapshot_ttl;
+   const int ttl_conversion_multiplier = 60;
+   int ttl;
 
-   if (ctx.qry.has_sha1)
-   return ctx.cfg.cache_static_ttl;
+   if (!ctx.repo)
+   ttl = ctx.cfg.cache_root_ttl;
+   else if (!ctx.qry.page)
+   ttl = ctx.cfg.cache_repo_ttl;
+   else if (!strcmp(ctx.qry.page, "about"))
+   ttl = ctx.cfg.cache_about_ttl;
+   else if (!strcmp(ctx.qry.page, "snapshot"))
+   ttl = ctx.cfg.cache_snapshot_ttl;
+   else if (ctx.qry.has_sha1)
+   ttl = ctx.cfg.cache_static_ttl;
+   else if (ctx.qry.has_symref)
+   ttl = ctx.cfg.cache_dynamic_ttl;
+   else
+   ttl = ctx.cfg.cache_repo_ttl;
 
-   if (ctx.qry.has_symref)
-   return ctx.cfg.cache_dynamic_ttl;
+   if (ttl >= 0)
+   return ttl * ttl_conversion_multiplier;
 
-   return ctx.cfg.cache_repo_ttl;
+   return 10 * 365 * 24 * 60 * 60; /* 10 years */
 }
 
 int main(int argc, const char **argv)
@@ -1076,10 +1079,7 @@ int main(int argc, const char **argv)
authenticate_cookie();
 
ttl = calc_ttl();
-   if (ttl < 0)
-   ctx.page.expires += 10 * 365 * 24 * 60 * 60; /* 10 years */
-   else
-   ctx.page.expires += ttl * 60;
+   ctx.page.expires += ttl;
if (!ctx.env.authenticated || (ctx.env.request_method && 
!strcmp(ctx.env.request_method, "HEAD")))
ctx.cfg.nocache = 1;
if (ctx.cfg.nocache)
-- 
2.4.9

___
CGit mailing list
CGit@lists.zx2c4.com
http://lists.zx2c4.com/mailman/listinfo/cgit


[PATCH 0/2] TTL updates

2016-02-26 Thread Tim Nordell
This patchset simplifies the TTL calculation into one routine and uses
seconds internally through the cache routines and the routines in
cgit.c.  (The configuration file still uses minutes.)  Additionally,
this patchset adds a new syntax for the cache lines that allows a
different cache lifetime if the user refreshes the page.

Tim Nordell (2):
  ttl: Consolidate TTL calculation
  ttl: Support different TTL times based on cache-control

 cache.c |  2 +-
 cgit.c  | 99 ++---
 cgit.h  | 18 +++
 ui-shared.c |  2 +-
 4 files changed, 82 insertions(+), 39 deletions(-)

-- 
2.4.9

___
CGit mailing list
CGit@lists.zx2c4.com
http://lists.zx2c4.com/mailman/listinfo/cgit


[PATCH 0/2] ui-log: Don't print out unknown decoration types

2016-02-26 Thread Tim Nordell
On our server we have some ref spaces that shouldn't be displayed in
the main log.  The command line tool "git log" does not display these
either.  Add some additional smarts to ui-log so that it uses the
enumeration types returned by the internals of git to determine
whether or not to display a tag, as well as converting the logic
in the log to use these enumerations rather than reinventing the
wheel for these.

Tim Nordell (2):
  ui-log: Do not always emit decoration span
  ui-log: Simplify decoration code

 ui-log.c | 58 +-
 1 file changed, 29 insertions(+), 29 deletions(-)
-- 
2.4.9

___
CGit mailing list
CGit@lists.zx2c4.com
http://lists.zx2c4.com/mailman/listinfo/cgit


[PATCH 2/2] ui-log: Simplify decoration code

2016-02-26 Thread Tim Nordell
The decoration code inside of git returns the decoration type, so
utilize this to create the decoration spans.  Additionally, use
prettify_refname(...) to get the shorter name for the ref.

Signed-off-by: Tim Nordell 

diff --git a/ui-log.c b/ui-log.c
index 62881ce..fc215d6 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -65,36 +65,34 @@ void show_commit_decorations(struct commit *commit)
return;
html("");
while (deco) {
-   if (starts_with(deco->name, "refs/heads/")) {
-   strncpy(buf, deco->name + 11, sizeof(buf) - 1);
-   cgit_log_link(buf, NULL, "branch-deco", buf, NULL,
- ctx.qry.vpath, 0, NULL, NULL,
- ctx.qry.showmsg, 0);
-   }
-   else if (starts_with(deco->name, "tag: refs/tags/")) {
-   strncpy(buf, deco->name + 15, sizeof(buf) - 1);
-   cgit_tag_link(buf, NULL, "tag-deco", buf);
-   }
-   else if (starts_with(deco->name, "refs/tags/")) {
-   strncpy(buf, deco->name + 10, sizeof(buf) - 1);
-   cgit_tag_link(buf, NULL, "tag-deco", buf);
-   }
-   else if (starts_with(deco->name, "refs/remotes/")) {
-   if (!ctx.repo->enable_remote_branches)
-   goto next;
-   strncpy(buf, deco->name + 13, sizeof(buf) - 1);
-   cgit_log_link(buf, NULL, "remote-deco", NULL,
- oid_to_hex(&commit->object.oid),
- ctx.qry.vpath, 0, NULL, NULL,
- ctx.qry.showmsg, 0);
-   }
-   else {
-   strncpy(buf, deco->name, sizeof(buf) - 1);
-   cgit_commit_link(buf, NULL, "deco", ctx.qry.head,
-oid_to_hex(&commit->object.oid),
-ctx.qry.vpath);
+   strncpy(buf, prettify_refname(deco->name), sizeof(buf) - 1);
+   switch(deco->type) {
+   case DECORATION_NONE:
+   /* If the git-core doesn't recognize it,
+* don't display anything. */
+   break;
+   case DECORATION_REF_LOCAL:
+   cgit_log_link(buf, NULL, "branch-deco", buf, 
NULL,
+   ctx.qry.vpath, 0, NULL, NULL,
+   ctx.qry.showmsg, 0);
+   break;
+   case DECORATION_REF_TAG:
+   cgit_tag_link(buf, NULL, "tag-deco", buf);
+   break;
+   case DECORATION_REF_REMOTE:
+   if (!ctx.repo->enable_remote_branches)
+   break;
+   cgit_log_link(buf, NULL, "remote-deco", NULL,
+   oid_to_hex(&commit->object.oid),
+   ctx.qry.vpath, 0, NULL, NULL,
+   ctx.qry.showmsg, 0);
+   break;
+   default:
+   cgit_commit_link(buf, NULL, "deco", 
ctx.qry.head,
+   oid_to_hex(&commit->object.oid),
+   ctx.qry.vpath);
+   break;
}
-next:
deco = deco->next;
}
html("");
-- 
2.4.9

___
CGit mailing list
CGit@lists.zx2c4.com
http://lists.zx2c4.com/mailman/listinfo/cgit


[PATCH 0/3] ui-repolist: Support additional formatting for sections

2016-02-26 Thread Tim Nordell
On our internal server we have over 2,000 repositories, primarily due to
mirroring external repositories locally.  We patched cgit to make navigation
a little easier by doing the following:

 - Make sections clickable (via a section filter)
 - Collapse repository listing to just sections when the repository listing
   is over 1 page worth of repositories

The patchset here contains this modification and allows it to be enabled
(or disabled) via configuration.

You can see an example of this UI over here: http://git.logicpd.com

(Note: That is running an older patched version not containing the changes
here presently but has the same presentation.)

Tim Nordell (3):
  ui-repolist: Add section filter
  ui-repolist: Restructure internal logic to be more extensible
  ui-repolist: Support a trimmed view when several sections are present

 cgit.c  |   2 +
 cgit.h  |   3 +-
 cgitrc.5.txt|  23 +++-
 filter.c|   1 +
 filters/section-example.lua |  33 ++
 ui-repolist.c   | 271 +---
 6 files changed, 260 insertions(+), 73 deletions(-)
 create mode 100644 filters/section-example.lua

-- 
2.4.9

___
CGit mailing list
CGit@lists.zx2c4.com
http://lists.zx2c4.com/mailman/listinfo/cgit


[PATCH 1/3] ui-repolist: Add section filter

2016-02-26 Thread Tim Nordell
This allows custom links to be added into the section headers by
configuring a filter to be applied in the repository list.

Signed-off-by: Tim Nordell 

 create mode 100644 filters/section-example.lua

diff --git a/cgit.c b/cgit.c
index 87ba811..7dac332 100644
--- a/cgit.c
+++ b/cgit.c
@@ -234,6 +234,8 @@ static void config_cb(const char *name, const char *value)
ctx.cfg.email_filter = cgit_new_filter(value, EMAIL);
else if (!strcmp(name, "owner-filter"))
ctx.cfg.owner_filter = cgit_new_filter(value, OWNER);
+   else if (!strcmp(name, "section-filter"))
+   ctx.cfg.section_filter = cgit_new_filter(value, SECTION);
else if (!strcmp(name, "auth-filter"))
ctx.cfg.auth_filter = cgit_new_filter(value, AUTH);
else if (!strcmp(name, "embedded"))
diff --git a/cgit.h b/cgit.h
index b2b2ae9..f0ac36b 100644
--- a/cgit.h
+++ b/cgit.h
@@ -55,7 +55,7 @@ typedef enum {
 } diff_type;
 
 typedef enum {
-   ABOUT, COMMIT, SOURCE, EMAIL, AUTH, OWNER
+   ABOUT, COMMIT, SOURCE, EMAIL, AUTH, OWNER, SECTION
 } filter_type;
 
 struct cgit_filter {
@@ -272,6 +272,7 @@ struct cgit_config {
struct cgit_filter *source_filter;
struct cgit_filter *email_filter;
struct cgit_filter *owner_filter;
+   struct cgit_filter *section_filter;
struct cgit_filter *auth_filter;
 };
 
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 3bca0ab..113b7a0 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -404,6 +404,13 @@ section::
after this option will inherit the current section name. Default value:
none.
 
+section-filter::
+   Specifies a command which will be invoked to format section headings.
+   The command will get the section on its STDIN, and the STDOUT from the
+   command will be included verbatim as the section heading.
+   Default value: none.
+   See also: "FILTER API".
+
 section-sort::
Flag which, when set to "1", will sort the sections on the repository
listing by name. Set this flag to "0" if the order in the cgitrc file 
should
@@ -695,6 +702,11 @@ owner filter::
standard input and the filter is expected to write to standard
output.  The output is included in the Owner column.
 
+section filter::
+   This filter is given no arguments.  The section text is avilable on
+   standard input and the filter is expected to write to standard
+   output.  The output is included in the section heading.
+
 source filter::
This filter is given a single parameter: the filename of the source
file to filter. The filter can use the filename to determine (for
diff --git a/filter.c b/filter.c
index 949c931..952336f 100644
--- a/filter.c
+++ b/filter.c
@@ -436,6 +436,7 @@ struct cgit_filter *cgit_new_filter(const char *cmd, 
filter_type filtertype)
argument_count = 1;
break;
 
+   case SECTION:
case COMMIT:
default:
argument_count = 0;
diff --git a/filters/section-example.lua b/filters/section-example.lua
new file mode 100644
index 000..76076b2
--- /dev/null
+++ b/filters/section-example.lua
@@ -0,0 +1,33 @@
+-- This script is an example of an section-filter.  It replaces the
+-- section title with several links for each subdirectory represented
+-- within the section title.  (It's intended to be used where the section
+-- title is also the same as the path to the repository, similar to
+-- the output from the "section-from-path" option.)  This script may
+-- be used with the section-filter setting in cgitrc with the `lua:`
+-- prefix.
+
+function gen_link(name, path)
+end
+
+function filter_open()
+   buffer = ""
+end
+
+function filter_close()
+   path = "/"
+   cnt = 0
+   for i in string.gmatch(buffer, "[^/]+") do
+   if cnt > 0 then
+   html("/")
+   end
+   path = path .. i .. "/"
+   html(string.format("%s",
+   os.getenv("SCRIPT_NAME"), path, i))
+   cnt = cnt + 1
+   end
+   return 0
+end
+
+function filter_write(str)
+   buffer = buffer .. str
+end
diff --git a/ui-repolist.c b/ui-repolist.c
index 30915df..7af77e0 100644
--- a/ui-repolist.c
+++ b/ui-repolist.c
@@ -322,7 +322,11 @@ void cgit_print_repolist(void)
 strcmp(section, last_section {
htmlf("",
  columns);
+   if (ctx.cfg.section_filter)
+   cgit_open_filter(ctx.cfg.section_filter);
html_txt(section);
+   if (ctx.cfg.section_filter)
+   cgit_close_filter(ctx.cfg.section_filter);
html("");
last_section = section;
}
-- 
2.4.9

__

[PATCH 2/3] ui-repolist: Restructure internal logic to be more extensible

2016-02-26 Thread Tim Nordell
The internal logic has been restructured so that there is a "walking"
routine that filters the repo list based on the visible criteria, and
subsequently calls a given callback for each repo found.  Additionally,
split out generating a table line for a given repo, and a table line
for a given section.  This makes this more loosely coupled and allows
reuse of more logic for changes to the way the repo list is displayed.
This patch does not make any functional changes to the system and is
a reorganization of the existing logic only.

Signed-off-by: Tim Nordell 

diff --git a/ui-repolist.c b/ui-repolist.c
index 7af77e0..6b06a1a 100644
--- a/ui-repolist.c
+++ b/ui-repolist.c
@@ -269,13 +269,135 @@ static int sort_repolist(char *field)
return 0;
 }
 
+struct repolist_ctx
+{
+   /* From outer contex passed to interior */
+   int columns;
+   int sorted;
+
+   /* Used in interior context; should be reset in repolist_walk_visible() 
*/
+   int hits;
+   const char *last_section;
+};
+
+static void html_section(struct cgit_repo *repo, int columns)
+{
+   htmlf("",
+   columns);
+   if (ctx.cfg.section_filter)
+   cgit_open_filter(ctx.cfg.section_filter);
+   html_txt(repo->section);
+   if (ctx.cfg.section_filter)
+   cgit_close_filter(ctx.cfg.section_filter);
+   html("");
+}
+
+static void html_repository(struct cgit_repo *repo, int sorted)
+{
+   bool is_toplevel;
+
+   ctx.repo = repo;
+
+   is_toplevel = (!sorted && NULL != repo->section && repo->section[0] != 
'\0');
+   htmlf("", is_toplevel ? "sublevel-repo" : 
"toplevel-repo");
+   cgit_summary_link(repo->name, repo->name, NULL, NULL);
+   html("");
+   html_link_open(cgit_repourl(repo->url), NULL, NULL);
+   html_ntxt(ctx.cfg.max_repodesc_len, repo->desc);
+   html_link_close();
+   html("");
+   if (ctx.cfg.enable_index_owner) {
+   if (repo->owner_filter) {
+   cgit_open_filter(repo->owner_filter);
+   html_txt(repo->owner);
+   cgit_close_filter(repo->owner_filter);
+   } else {
+   html("");
+   html_txt(repo->owner);
+   html("");
+   }
+   html("");
+   }
+   print_modtime(repo);
+   html("");
+   if (ctx.cfg.enable_index_links) {
+   html("");
+   cgit_summary_link("summary", NULL, "button", NULL);
+   cgit_log_link("log", NULL, "button", NULL, NULL, NULL,
+   0, NULL, NULL, ctx.qry.showmsg, 0);
+   cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL);
+   html("");
+   }
+   html("\n");
+}
+
+static inline bool should_emit_section(struct cgit_repo *repo, struct 
repolist_ctx *c)
+{
+   /* If we're sorted, we will not have a new section emitted. */
+   if (c->sorted)
+   return false;
+
+   /* We need a valid repo section for the rest of the checks */
+   if(NULL == repo->section)
+   return false;
+
+   /* If the section title is blank (e.g. top-level), we never emit
+* a section heading. */
+   if('\0' == repo->section[0])
+   return false;
+
+   /* Finally, compare the last section name to the current.  If they're
+* the same, do not emit a section area. */
+   if(NULL != c->last_section && !strcmp(repo->section, c->last_section))
+   return false;
+
+   c->last_section = repo->section;
+   return true;
+}
+
+static int generate_repolist(struct cgit_repo *repo, struct repolist_ctx *c)
+{
+   c->hits++;
+   if (c->hits <= ctx.qry.ofs)
+   return 0;
+   if (c->hits > ctx.qry.ofs + ctx.cfg.max_repo_count)
+   return 0;
+
+   if(should_emit_section(repo, c))
+   html_section(repo, c->columns);
+   html_repository(repo, c->sorted);
+
+   return 0;
+}
+
+typedef int (*repolist_walk_callback_t)(struct cgit_repo *repo, struct 
repolist_ctx *c);
+static int repolist_walk_visible(repolist_walk_callback_t callback, struct 
repolist_ctx *c)
+{
+   struct cgit_repo *repo;
+   int i;
+
+   c->hits = 0;
+   c->last_section = NULL;
+
+   for (i = 0; i < cgit_repolist.count; i++) {
+   repo = &cgit_repolist.repos[i];
+   if (!is_visible(repo))
+   continue;
+   if(NULL != callback)
+   callback(repo, c);
+   }
+   return 0;
+}
 
 void cgit_print_repolist(void)
 {
-   int i, columns = 3, hits = 0, header = 0;
-   char *last_section = NULL;
-   char *section;
-   int sorted = 0;
+   struct repolist_ctx repolist_ctx = {0};
+
+   repolist_ctx.columns = 3;
 
if (!any_repos_visible()) {
cgit_print_error_page(404, "Not found", "No repositories 
found")

[PATCH 3/3] ui-repolist: Support a trimmed view when several sections are present

2016-02-26 Thread Tim Nordell
Trim the view to just a section list if there are multiple
sections present in the output view.  This is only really useful
if one also has a script placing links on each section heading to
trim the view by path.

Signed-off-by: Tim Nordell 

diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 113b7a0..2658e89 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -413,9 +413,14 @@ section-filter::
 
 section-sort::
Flag which, when set to "1", will sort the sections on the repository
-   listing by name. Set this flag to "0" if the order in the cgitrc file 
should
-   be preserved. Default value: "1". See also: section,
-   case-sensitive-sort, repository-sort.
+   listing by name. When set to "2", this will collapse the listing of
+   repositories (when over the size max-repo-count) into just the list
+   of sections. This is only really useful if section filters are also
+   enabled, if the filter makes links into the URL that the section
+   comprises, and if all section headings are paths. Set this flag to
+   "0" if the order in the cgitrc file should be preserved.
+   Default value: "1". See also: section, case-sensitive-sort,
+   repository-sort, section-filter
 
 section-from-path::
A number which, if defined prior to scan-path, specifies how many
diff --git a/ui-repolist.c b/ui-repolist.c
index 6b06a1a..9045203 100644
--- a/ui-repolist.c
+++ b/ui-repolist.c
@@ -278,6 +278,8 @@ struct repolist_ctx
/* Used in interior context; should be reset in repolist_walk_visible() 
*/
int hits;
const char *last_section;
+   int section_cnt;
+   int section_nested_cnt;
 };
 
 static void html_section(struct cgit_repo *repo, int columns)
@@ -374,6 +376,57 @@ static int generate_repolist(struct cgit_repo *repo, 
struct repolist_ctx *c)
return 0;
 }
 
+static int generate_sectionlist(struct cgit_repo *repo, struct repolist_ctx *c)
+{
+   bool is_toplevel;
+
+   is_toplevel = (NULL == repo->section || repo->section[0] == '\0');
+
+   if(!should_emit_section(repo, c) && !is_toplevel)
+   return 0;
+
+   c->hits++;
+
+   if (c->hits <= ctx.qry.ofs)
+   return 0;
+   if (c->hits > ctx.qry.ofs + ctx.cfg.max_repo_count)
+   return 0;
+
+   if(is_toplevel)
+   html_repository(repo, c->sorted);
+   else
+   html_section(repo, c->columns);
+
+   return 0;
+}
+
+static int count_sections(struct cgit_repo *repo, struct repolist_ctx *c)
+{
+   const char *last_section;
+
+   last_section = c->last_section;
+   if(should_emit_section(repo, c))
+   {
+   c->section_cnt++;
+
+   /* Determine if one section is nested within the other.  This
+* is only accurate if this is a sorted list.
+*/
+   if(NULL != last_section && NULL != repo->section)
+   {
+   int spn = strspn(last_section, repo->section);
+   if(last_section[spn] == '\0')
+   {
+   c->section_nested_cnt++;
+   }
+   }
+   }
+
+   c->hits++;
+
+   return 0;
+}
+
 typedef int (*repolist_walk_callback_t)(struct cgit_repo *repo, struct 
repolist_ctx *c);
 static int repolist_walk_visible(repolist_walk_callback_t callback, struct 
repolist_ctx *c)
 {
@@ -382,6 +435,8 @@ static int repolist_walk_visible(repolist_walk_callback_t 
callback, struct repol
 
c->hits = 0;
c->last_section = NULL;
+   c->section_cnt = 0;
+   c->section_nested_cnt = 0;
 
for (i = 0; i < cgit_repolist.count; i++) {
repo = &cgit_repolist.repos[i];
@@ -395,6 +450,7 @@ static int repolist_walk_visible(repolist_walk_callback_t 
callback, struct repol
 
 void cgit_print_repolist(void)
 {
+   bool section_pages = false;
struct repolist_ctx repolist_ctx = {0};
 
repolist_ctx.columns = 3;
@@ -420,13 +476,24 @@ void cgit_print_repolist(void)
if (ctx.qry.sort)
repolist_ctx.sorted = sort_repolist(ctx.qry.sort);
else if (ctx.cfg.section_sort)
+   {
sort_repolist("section");
+   section_pages = (2 == ctx.cfg.section_sort);
+   }
 
html("");
print_header();
 
-   /* Count visible repositories */
-   repolist_walk_visible(generate_repolist, &repolist_ctx);
+   if(section_pages)
+   repolist_walk_visible(count_sections, &repolist_ctx);
+
+   if(section_pages && repolist_ctx.hits > ctx.cfg.max_repo_count &&
+   repolist_ctx.section_cnt - repolist_ctx.section_nested_cnt > 1)
+   {
+   repolist_walk_visible(generate_sectionlist, &repolist_ctx);
+   } else {
+   repolist_walk_visible(generate_repolist, &repolist_ctx);
+   }
 
html("");
if (repolist_ctx.hits > ctx.cfg.max_repo_co