{"thread":{"id":"16558","subject":"[PATCH] gitweb: Optional grouping of projects by category","startedAt":"2008-12-02T15:04:10Z","lastAt":"2008-12-12T09:26:06Z","messageCount":24,"participants":["Sebastien Cevey","Jakub Narebski","Sébastien Cevey","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"96947","messageId":"87wsei1uvp.wl%seb@cine7.net","threadId":"16558","inReplyTo":null,"subject":"[PATCH] gitweb: Optional grouping of projects by category","fromName":"Sebastien Cevey","fromEmail":"seb@cine7.net","sentAt":"2008-12-02T15:04:10Z","receivedAt":"2008-12-02T15:04:10Z","isPatch":true,"sender":{"key":"seb@cine7.net","avatar":"https://avatars.githubusercontent.com/u/36964?v=4"},"body":"This adds the GITWEB_GROUP_CATEGORIES option which, if enabled, will\nresult in grouping projects by category on the project list page.  The\ncategory is specified for each project by the $GIT_DIR/category file\nor the 'category' variable in its configuration file.\n\nThe feature is inspired from Sham Chukoury's patch for the XMMS2\ngitweb, but has been rewritten for the current gitweb development\nHEAD.\n\nThanks to Florian Ragwitz for Perl tips.\n\nSigned-off-by: Sebastien Cevey <seb@cine7.net>\n---\n\nI submitted a previous version of this patch on July 27, but was told\nto wait for the end of the feature freeze.  I submitted it again on\nSeptember 5, but didn't get any reply.  Hope to be luckier this time!\n\nThis is a new version of the patch, which has been rebased onto the\ncurrent HEAD of the master branch.\n\n Makefile           |    2 +\n gitweb/README      |   11 +++\n gitweb/gitweb.css  |    5 ++\n gitweb/gitweb.perl |  173 +++++++++++++++++++++++++++++++++++-----------------\n 4 files changed, 135 insertions(+), 56 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex 649cfb8..a8e8bbf 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -211,6 +211,7 @@ GITWEB_EXPORT_OK =\n GITWEB_STRICT_EXPORT =\n GITWEB_BASE_URL =\n GITWEB_LIST =\n+GITWEB_GROUP_CATEGORIES =\n GITWEB_HOMETEXT = indextext.html\n GITWEB_CSS = gitweb.css\n GITWEB_LOGO = git-logo.png\n@@ -1189,6 +1190,7 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl\n \t    -e 's|++GITWEB_STRICT_EXPORT++|$(GITWEB_STRICT_EXPORT)|g' \\\n \t    -e 's|++GITWEB_BASE_URL++|$(GITWEB_BASE_URL)|g' \\\n \t    -e 's|++GITWEB_LIST++|$(GITWEB_LIST)|g' \\\n+\t    -e 's|++GITWEB_GROUP_CATEGORIES++|$(GITWEB_GROUP_CATEGORIES)|g' \\\n \t    -e 's|++GITWEB_HOMETEXT++|$(GITWEB_HOMETEXT)|g' \\\n \t    -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \\\n \t    -e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \\\ndiff --git a/gitweb/README b/gitweb/README\nindex 825162a..a6f82c5 100644\n--- a/gitweb/README\n+++ b/gitweb/README\n@@ -38,6 +38,11 @@ You can specify the following configuration variables when building GIT:\n    using gitweb\" in INSTALL file for gitweb to find out how to generate\n    such file from scan of a directory. [No default, which means use root\n    directory for projects]\n+ * GITWEB_GROUP_CATEGORIES\n+   Groups projects by category on the main projects list page if set\n+   to true.  The category of a project is determined by the\n+   $GIT_DIR/category file or the 'category' variable in its\n+   configuration file.  [No default / Not set]\n  * GITWEB_EXPORT_OK\n    Show repository only if this file exists (in repository).  Only\n    effective if this variable evaluates to true.  [No default / Not set]\n@@ -188,6 +193,12 @@ not include variables usually directly set during build):\n    full description is available as 'title' attribute (usually shown on\n    mouseover).  By default set to 25, which might be too small if you\n    use long project descriptions.\n+ * $projects_list_group_categories\n+   Enables the grouping of projects by category on the project list page.\n+ * $project_list_default_category\n+   Default category for projects for which none is specified.  If set\n+   to the empty string, such projects will remain uncategorized and\n+   listed at the top, above categorized projects.\n  * @git_base_url_list\n    List of git base URLs used for URL to where fetch project from, shown\n    in project summary page.  Full URL is \"$git_base_url/$project\".\ndiff --git a/gitweb/gitweb.css b/gitweb/gitweb.css\nindex a01eac8..2dd45d6 100644\n--- a/gitweb/gitweb.css\n+++ b/gitweb/gitweb.css\n@@ -264,6 +264,11 @@ td.current_head {\n \ttext-decoration: underline;\n }\n \n+td.category {\n+\tpadding-top: 1em;\n+\tfont-weight: bold;\n+}\n+\n table.diff_tree span.file_status.new {\n \tcolor: #008000;\n }\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex 933e137..c1bcd96 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -87,6 +87,13 @@ our $projects_list = \"++GITWEB_LIST++\";\n # the width (in characters) of the projects list \"Description\" column\n our $projects_list_description_width = 25;\n \n+# group projects by category on the projects list\n+our $projects_list_group_categories = \"++GITWEB_GROUP_CATEGORIES++\";\n+\n+# default category if none specified\n+# (leave the empty string for no category)\n+our $project_list_default_category = \"\";\n+\n # default order of projects list\n # valid values are none, project, descr, owner, and age\n our $default_projects_order = \"project\";\n@@ -2001,18 +2008,28 @@ sub git_get_path_by_hash {\n ## ......................................................................\n ## git utility functions, directly accessing git repository\n \n-sub git_get_project_description {\n-\tmy $path = shift;\n+sub git_get_project_config_from_file {\n+\tmy ($name, $path) = @_;\n \n \t$git_dir = \"$projectroot/$path\";\n-\topen my $fd, \"$git_dir/description\"\n-\t\tor return git_get_project_config('description');\n-\tmy $descr = <$fd>;\n+\topen my $fd, \"$git_dir/$name\"\n+\t\tor return git_get_project_config($name);\n+\tmy $conf = <$fd>;\n \tclose $fd;\n-\tif (defined $descr) {\n-\t\tchomp $descr;\n+\tif (defined $conf) {\n+\t\tchomp $conf;\n \t}\n-\treturn $descr;\n+\treturn $conf;\n+}\n+\n+sub git_get_project_description {\n+\tmy $path = shift;\n+\treturn git_get_project_config_from_file('description', $path);\n+}\n+\n+sub git_get_project_category {\n+\tmy $path = shift;\n+\treturn git_get_project_config_from_file('category', $path);\n }\n \n sub git_get_project_ctags {\n@@ -3907,8 +3924,9 @@ sub git_patchset_body {\n \n # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n \n-# fills project list info (age, description, owner, forks) for each\n-# project in the list, removing invalid projects from returned list\n+# fills project list info (age, description, owner, category, forks)\n+# for each project in the list, removing invalid projects from\n+# returned list\n # NOTE: modifies $projlist, but does not remove entries from it\n sub fill_project_list_info {\n \tmy ($projlist, $check_forks) = @_;\n@@ -3931,6 +3949,11 @@ sub fill_project_list_info {\n \t\tif (!defined $pr->{'owner'}) {\n \t\t\t$pr->{'owner'} = git_get_project_owner(\"$pr->{'path'}\") || \"\";\n \t\t}\n+\t\tif ($projects_list_group_categories && !defined $pr->{'cat'}) {\n+\t\t\tmy $cat = git_get_project_category($pr->{'path'}) ||\n+\t\t\t                                   $project_list_default_category;\n+\t\t\t$pr->{'cat'} = to_utf8($cat);\n+\t\t}\n \t\tif ($check_forks) {\n \t\t\tmy $pname = $pr->{'path'};\n \t\t\tif (($pname =~ s/\\.git$//) &&\n@@ -3948,6 +3971,19 @@ sub fill_project_list_info {\n \treturn @projects;\n }\n \n+# returns a hash of categories, containing the list of project\n+# belonging to each category\n+sub build_category_list {\n+\tmy ($projlist) = @_;\n+\tmy %categories;\n+\n+\tfor my $pr (@{ $projlist }) {\n+\t\tpush @{$categories{ $pr->{'cat'} }}, $pr;\n+\t}\n+\n+\treturn %categories;\n+}\n+\n # print 'sort by' <th> element, generating 'sort by $name' replay link\n # if that order is not selected\n sub print_sort_th {\n@@ -3964,59 +4000,17 @@ sub print_sort_th {\n \t}\n }\n \n-sub git_project_list_body {\n+sub print_project_rows {\n \t# actually uses global variable $project\n-\tmy ($projlist, $order, $from, $to, $extra, $no_header) = @_;\n-\n-\tmy ($check_forks) = gitweb_check_feature('forks');\n-\tmy @projects = fill_project_list_info($projlist, $check_forks);\n+\tmy ($projects, $from, $to, $check_forks, $show_ctags) = @_;\n \n-\t$order ||= $default_projects_order;\n \t$from = 0 unless defined $from;\n-\t$to = $#projects if (!defined $to || $#projects < $to);\n+\t$to = $#$projects if (!defined $to || $#$projects < $to);\n \n-\tmy %order_info = (\n-\t\tproject => { key => 'path', type => 'str' },\n-\t\tdescr => { key => 'descr_long', type => 'str' },\n-\t\towner => { key => 'owner', type => 'str' },\n-\t\tage => { key => 'age', type => 'num' }\n-\t);\n-\tmy $oi = $order_info{$order};\n-\tif ($oi->{'type'} eq 'str') {\n-\t\t@projects = sort {$a->{$oi->{'key'}} cmp $b->{$oi->{'key'}}} @projects;\n-\t} else {\n-\t\t@projects = sort {$a->{$oi->{'key'}} <=> $b->{$oi->{'key'}}} @projects;\n-\t}\n-\n-\tmy $show_ctags = gitweb_check_feature('ctags');\n-\tif ($show_ctags) {\n-\t\tmy %ctags;\n-\t\tforeach my $p (@projects) {\n-\t\t\tforeach my $ct (keys %{$p->{'ctags'}}) {\n-\t\t\t\t$ctags{$ct} += $p->{'ctags'}->{$ct};\n-\t\t\t}\n-\t\t}\n-\t\tmy $cloud = git_populate_project_tagcloud(\\%ctags);\n-\t\tprint git_show_project_tagcloud($cloud, 64);\n-\t}\n-\n-\tprint \"<table class=\\\"project_list\\\">\\n\";\n-\tunless ($no_header) {\n-\t\tprint \"<tr>\\n\";\n-\t\tif ($check_forks) {\n-\t\t\tprint \"<th></th>\\n\";\n-\t\t}\n-\t\tprint_sort_th('project', $order, 'Project');\n-\t\tprint_sort_th('descr', $order, 'Description');\n-\t\tprint_sort_th('owner', $order, 'Owner');\n-\t\tprint_sort_th('age', $order, 'Last Change');\n-\t\tprint \"<th></th>\\n\" . # for links\n-\t\t      \"</tr>\\n\";\n-\t}\n \tmy $alternate = 1;\n \tmy $tagfilter = $cgi->param('by_tag');\n \tfor (my $i = $from; $i <= $to; $i++) {\n-\t\tmy $pr = $projects[$i];\n+\t\tmy $pr = $projects->[$i];\n \n \t\tnext if $tagfilter and $show_ctags and not grep { lc $_ eq lc $tagfilter } keys %{$pr->{'ctags'}};\n \t\tnext if $searchtext and not $pr->{'path'} =~ /$searchtext/\n@@ -4060,6 +4054,73 @@ sub git_project_list_body {\n \t\t      \"</td>\\n\" .\n \t\t      \"</tr>\\n\";\n \t}\n+}\n+\n+sub git_project_list_body {\n+\tmy ($projlist, $order, $from, $to, $extra, $no_header) = @_;\n+\n+\tmy ($check_forks) = gitweb_check_feature('forks');\n+\tmy @projects = fill_project_list_info($projlist, $check_forks);\n+\n+\t$order ||= $default_projects_order;\n+\n+\tmy %order_info = (\n+\t\tproject => { key => 'path', type => 'str' },\n+\t\tdescr => { key => 'descr_long', type => 'str' },\n+\t\towner => { key => 'owner', type => 'str' },\n+\t\tage => { key => 'age', type => 'num' }\n+\t);\n+\tmy $oi = $order_info{$order};\n+\tif ($oi->{'type'} eq 'str') {\n+\t\t@projects = sort {$a->{$oi->{'key'}} cmp $b->{$oi->{'key'}}} @projects;\n+\t} else {\n+\t\t@projects = sort {$a->{$oi->{'key'}} <=> $b->{$oi->{'key'}}} @projects;\n+\t}\n+\n+\tmy $show_ctags = gitweb_check_feature('ctags');\n+\tif ($show_ctags) {\n+\t\tmy %ctags;\n+\t\tforeach my $p (@projects) {\n+\t\t\tforeach my $ct (keys %{$p->{'ctags'}}) {\n+\t\t\t\t$ctags{$ct} += $p->{'ctags'}->{$ct};\n+\t\t\t}\n+\t\t}\n+\t\tmy $cloud = git_populate_project_tagcloud(\\%ctags);\n+\t\tprint git_show_project_tagcloud($cloud, 64);\n+\t}\n+\n+\tprint \"<table class=\\\"project_list\\\">\\n\";\n+\tunless ($no_header) {\n+\t\tprint \"<tr>\\n\";\n+\t\tif ($check_forks) {\n+\t\t\tprint \"<th></th>\\n\";\n+\t\t}\n+\t\tprint_sort_th('project', $order, 'Project');\n+\t\tprint_sort_th('descr', $order, 'Description');\n+\t\tprint_sort_th('owner', $order, 'Owner');\n+\t\tprint_sort_th('age', $order, 'Last Change');\n+\t\tprint \"<th></th>\\n\" . # for links\n+\t\t      \"</tr>\\n\";\n+\t}\n+\n+\tif ($projects_list_group_categories) {\n+\t\tmy %categories = build_category_list(\\@projects);\n+\t\tforeach my $cat (sort keys %categories) {\n+\t\t\tunless ($cat eq \"\") {\n+\t\t\t\tprint \"<tr>\\n\";\n+\t\t\t\tif ($check_forks) {\n+\t\t\t\t\tprint \"<td></td>\\n\";\n+\t\t\t\t}\n+\t\t\t\tprint \"<td class=\\\"category\\\" colspan=\\\"5\\\">$cat</td>\\n\";\n+\t\t\t\tprint \"</tr>\\n\";\n+\t\t\t}\n+\n+\t\t\tprint_project_rows($categories{$cat}, $from, $to, $check_forks, $show_ctags);\n+\t\t}\n+\t} else {\n+\t\tprint_project_rows(\\@projects, $from, $to, $check_forks, $show_ctags);\n+\t}\n+\n \tif (defined $extra) {\n \t\tprint \"<tr>\\n\";\n \t\tif ($check_forks) {\n-- \n1.5.6.5\n"},{"id":"96995","messageId":"200812030036.13562.jnareb@gmail.com","threadId":"16558","inReplyTo":"87wsei1uvp.wl%seb@cine7.net","subject":"Re: [PATCH] gitweb: Optional grouping of projects by category","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2008-12-02T23:36:12Z","receivedAt":"2008-12-02T23:36:12Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"On Tue, 2 Dec 2008, Sebastien Cevey wrote:\n\n> This adds the GITWEB_GROUP_CATEGORIES option which, if enabled, will\n> result in grouping projects by category on the project list page.\n\nShould this really be build time configuration (to set default value)?\n\n> The category is specified for each project by the $GIT_DIR/category file\n> or the 'category' variable in its configuration file.\n> \n> The feature is inspired from Sham Chukoury's patch for the XMMS2\n> gitweb, but has been rewritten for the current gitweb development\n> HEAD.\n> \n> Thanks to Florian Ragwitz for Perl tips.\n> \n> Signed-off-by: Sebastien Cevey <seb@cine7.net>\n> ---\n> \n> I submitted a previous version of this patch on July 27, but was told\n> to wait for the end of the feature freeze.  I submitted it again on\n> September 5, but didn't get any reply.  Hope to be luckier this time!\n\nUnfortunately it looks like you hit the edge of feature freeze again.\nIt is not announced yet, as far as I remember, but we are now at \n1.6.1-pre1.  I'll add this patch to my queue to resubmit after 1.6.1\nis released, if it wouldn't be accepted in time.\n\nBy the way, there was alternate patch series by Gustavo Sverzut Barbieri\non 29 July 2008 adding categories support to gitweb:\n  http://thread.gmane.org/gmane.comp.version-control.git/90553\nwith live demo at http://staff.get-e.org/\n \n> This is a new version of the patch, which has been rebased onto the\n> current HEAD of the master branch.\n> \n>  Makefile           |    2 +\n>  gitweb/README      |   11 +++\n>  gitweb/gitweb.css  |    5 ++\n>  gitweb/gitweb.perl |  173 +++++++++++++++++++++++++++++++++++-----------------\n>  4 files changed, 135 insertions(+), 56 deletions(-)\n> \n> diff --git a/Makefile b/Makefile\n> index 649cfb8..a8e8bbf 100644\n> --- a/Makefile\n> +++ b/Makefile\n> @@ -211,6 +211,7 @@ GITWEB_EXPORT_OK =\n>  GITWEB_STRICT_EXPORT =\n>  GITWEB_BASE_URL =\n>  GITWEB_LIST =\n> +GITWEB_GROUP_CATEGORIES =\n>  GITWEB_HOMETEXT = indextext.html\n>  GITWEB_CSS = gitweb.css\n>  GITWEB_LOGO = git-logo.png\n> @@ -1189,6 +1190,7 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl\n>  \t    -e 's|++GITWEB_STRICT_EXPORT++|$(GITWEB_STRICT_EXPORT)|g' \\\n>  \t    -e 's|++GITWEB_BASE_URL++|$(GITWEB_BASE_URL)|g' \\\n>  \t    -e 's|++GITWEB_LIST++|$(GITWEB_LIST)|g' \\\n> +\t    -e 's|++GITWEB_GROUP_CATEGORIES++|$(GITWEB_GROUP_CATEGORIES)|g' \\\n>  \t    -e 's|++GITWEB_HOMETEXT++|$(GITWEB_HOMETEXT)|g' \\\n>  \t    -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \\\n>  \t    -e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \\\n\nO.K. That is just support for build time configuration of default value\n(default configuration).\n\n> diff --git a/gitweb/README b/gitweb/README\n> index 825162a..a6f82c5 100644\n> --- a/gitweb/README\n> +++ b/gitweb/README\n> @@ -38,6 +38,11 @@ You can specify the following configuration variables when building GIT:\n>     using gitweb\" in INSTALL file for gitweb to find out how to generate\n>     such file from scan of a directory. [No default, which means use root\n>     directory for projects]\n> + * GITWEB_GROUP_CATEGORIES\n> +   Groups projects by category on the main projects list page if set\n> +   to true.  The category of a project is determined by the\n> +   $GIT_DIR/category file or the 'category' variable in its\n> +   configuration file.  [No default / Not set]\n>   * GITWEB_EXPORT_OK\n>     Show repository only if this file exists (in repository).  Only\n>     effective if this variable evaluates to true.  [No default / Not set]\n> @@ -188,6 +193,12 @@ not include variables usually directly set during build):\n>     full description is available as 'title' attribute (usually shown on\n>     mouseover).  By default set to 25, which might be too small if you\n>     use long project descriptions.\n> + * $projects_list_group_categories\n> +   Enables the grouping of projects by category on the project list page.\n> + * $project_list_default_category\n> +   Default category for projects for which none is specified.  If set\n> +   to the empty string, such projects will remain uncategorized and\n> +   listed at the top, above categorized projects.\n>   * @git_base_url_list\n>     List of git base URLs used for URL to where fetch project from, shown\n>     in project summary page.  Full URL is \"$git_base_url/$project\".\n\nNice documenting it, but I think you should also update \"Per-repository\ngitweb configuration\" section in gitweb/README and mention category\n(file) or gitweb.category repository configuration variable.\n\n> diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css\n> index a01eac8..2dd45d6 100644\n> --- a/gitweb/gitweb.css\n> +++ b/gitweb/gitweb.css\n> @@ -264,6 +264,11 @@ td.current_head {\n>  \ttext-decoration: underline;\n>  }\n>  \n> +td.category {\n> +\tpadding-top: 1em;\n> +\tfont-weight: bold;\n> +}\n> +\n>  table.diff_tree span.file_status.new {\n>  \tcolor: #008000;\n>  }\n> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\n> index 933e137..c1bcd96 100755\n> --- a/gitweb/gitweb.perl\n> +++ b/gitweb/gitweb.perl\n> @@ -87,6 +87,13 @@ our $projects_list = \"++GITWEB_LIST++\";\n>  # the width (in characters) of the projects list \"Description\" column\n>  our $projects_list_description_width = 25;\n>  \n> +# group projects by category on the projects list\n> +our $projects_list_group_categories = \"++GITWEB_GROUP_CATEGORIES++\";\n> +\n> +# default category if none specified\n> +# (leave the empty string for no category)\n> +our $project_list_default_category = \"\";\n> +\n>  # default order of projects list\n>  # valid values are none, project, descr, owner, and age\n>  our $default_projects_order = \"project\";\n> @@ -2001,18 +2008,28 @@ sub git_get_path_by_hash {\n>  ## ......................................................................\n>  ## git utility functions, directly accessing git repository\n>  \n> -sub git_get_project_description {\n> -\tmy $path = shift;\n> +sub git_get_project_config_from_file {\n> +\tmy ($name, $path) = @_;\n>  \n>  \t$git_dir = \"$projectroot/$path\";\n> -\topen my $fd, \"$git_dir/description\"\n> -\t\tor return git_get_project_config('description');\n> -\tmy $descr = <$fd>;\n> +\topen my $fd, \"$git_dir/$name\"\n> +\t\tor return git_get_project_config($name);\n> +\tmy $conf = <$fd>;\n>  \tclose $fd;\n> -\tif (defined $descr) {\n> -\t\tchomp $descr;\n> +\tif (defined $conf) {\n> +\t\tchomp $conf;\n>  \t}\n> -\treturn $descr;\n> +\treturn $conf;\n> +}\n> +\n> +sub git_get_project_description {\n> +\tmy $path = shift;\n> +\treturn git_get_project_config_from_file('description', $path);\n> +}\n> +\n> +sub git_get_project_category {\n> +\tmy $path = shift;\n> +\treturn git_get_project_config_from_file('category', $path);\n>  }\n\nI see you have resurrected (?) here git_get_project_config_from_file.\nBut I don't think it is correct name for this subroutine: it gets\nconfig from a file in GIT_DIR _or_ from repository config.\n\nNevertheless nice that you avoided code duplication here.\n\n>  \n>  sub git_get_project_ctags {\n> @@ -3907,8 +3924,9 @@ sub git_patchset_body {\n>  \n>  # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n>  \n> -# fills project list info (age, description, owner, forks) for each\n> -# project in the list, removing invalid projects from returned list\n> +# fills project list info (age, description, owner, category, forks)\n> +# for each project in the list, removing invalid projects from\n> +# returned list\n>  # NOTE: modifies $projlist, but does not remove entries from it\n>  sub fill_project_list_info {\n>  \tmy ($projlist, $check_forks) = @_;\n> @@ -3931,6 +3949,11 @@ sub fill_project_list_info {\n>  \t\tif (!defined $pr->{'owner'}) {\n>  \t\t\t$pr->{'owner'} = git_get_project_owner(\"$pr->{'path'}\") || \"\";\n>  \t\t}\n> +\t\tif ($projects_list_group_categories && !defined $pr->{'cat'}) {\n> +\t\t\tmy $cat = git_get_project_category($pr->{'path'}) ||\n> +\t\t\t                                   $project_list_default_category;\n> +\t\t\t$pr->{'cat'} = to_utf8($cat);\n> +\t\t}\n>  \t\tif ($check_forks) {\n>  \t\t\tmy $pname = $pr->{'path'};\n>  \t\t\tif (($pname =~ s/\\.git$//) &&\n\nO.K., although I wonder if we wouldn't spell key name in full, i.e as\n$pr->{'category'}. But I think you follow conventions established\nearlier here...\n\n> @@ -3948,6 +3971,19 @@ sub fill_project_list_info {\n>  \treturn @projects;\n>  }\n>  \n> +# returns a hash of categories, containing the list of project\n> +# belonging to each category\n> +sub build_category_list {\n\nI'm not sure about the name of this subroutine...\n\n> +\tmy ($projlist) = @_;\n\nI would use \n\n\tmy $projlist = shift;\n\nhere (or even $projects).\n\n> +\tmy %categories;\n> +\n> +\tfor my $pr (@{ $projlist }) {\n\nI would use simpler\n\n\tfor my $pr (@$projlist) {\n\nhere\n\n> +\t\tpush @{$categories{ $pr->{'cat'} }}, $pr;\n> +\t}\n> +\n> +\treturn %categories;\n> +}\n> +\n>  # print 'sort by' <th> element, generating 'sort by $name' replay link\n>  # if that order is not selected\n>  sub print_sort_th {\n> @@ -3964,59 +4000,17 @@ sub print_sort_th {\n>  \t}\n>  }\n>  \n\nThis chunk looks worse that it really is by accident...\n\n> -sub git_project_list_body {\n\nIt would be nice to have description what this subroutine does.\n\n> +sub print_project_rows {\n>  \t# actually uses global variable $project\n> -\tmy ($projlist, $order, $from, $to, $extra, $no_header) = @_;\n> -\n> -\tmy ($check_forks) = gitweb_check_feature('forks');\n> -\tmy @projects = fill_project_list_info($projlist, $check_forks);\n> +\tmy ($projects, $from, $to, $check_forks, $show_ctags) = @_;\n>  \n> -\t$order ||= $default_projects_order;\n>  \t$from = 0 unless defined $from;\n> -\t$to = $#projects if (!defined $to || $#projects < $to);\n> +\t$to = $#$projects if (!defined $to || $#$projects < $to);\n>  \n> -\tmy %order_info = (\n> -\t\tproject => { key => 'path', type => 'str' },\n> -\t\tdescr => { key => 'descr_long', type => 'str' },\n> -\t\towner => { key => 'owner', type => 'str' },\n> -\t\tage => { key => 'age', type => 'num' }\n> -\t);\n> -\tmy $oi = $order_info{$order};\n> -\tif ($oi->{'type'} eq 'str') {\n> -\t\t@projects = sort {$a->{$oi->{'key'}} cmp $b->{$oi->{'key'}}} @projects;\n> -\t} else {\n> -\t\t@projects = sort {$a->{$oi->{'key'}} <=> $b->{$oi->{'key'}}} @projects;\n> -\t}\n> -\n> -\tmy $show_ctags = gitweb_check_feature('ctags');\n> -\tif ($show_ctags) {\n> -\t\tmy %ctags;\n> -\t\tforeach my $p (@projects) {\n> -\t\t\tforeach my $ct (keys %{$p->{'ctags'}}) {\n> -\t\t\t\t$ctags{$ct} += $p->{'ctags'}->{$ct};\n> -\t\t\t}\n> -\t\t}\n> -\t\tmy $cloud = git_populate_project_tagcloud(\\%ctags);\n> -\t\tprint git_show_project_tagcloud($cloud, 64);\n> -\t}\n> -\n> -\tprint \"<table class=\\\"project_list\\\">\\n\";\n> -\tunless ($no_header) {\n> -\t\tprint \"<tr>\\n\";\n> -\t\tif ($check_forks) {\n> -\t\t\tprint \"<th></th>\\n\";\n> -\t\t}\n> -\t\tprint_sort_th('project', $order, 'Project');\n> -\t\tprint_sort_th('descr', $order, 'Description');\n> -\t\tprint_sort_th('owner', $order, 'Owner');\n> -\t\tprint_sort_th('age', $order, 'Last Change');\n> -\t\tprint \"<th></th>\\n\" . # for links\n> -\t\t      \"</tr>\\n\";\n> -\t}\n>  \tmy $alternate = 1;\n>  \tmy $tagfilter = $cgi->param('by_tag');\n>  \tfor (my $i = $from; $i <= $to; $i++) {\n> -\t\tmy $pr = $projects[$i];\n> +\t\tmy $pr = $projects->[$i];\n>  \n>  \t\tnext if $tagfilter and $show_ctags and not grep { lc $_ eq lc $tagfilter } keys %{$pr->{'ctags'}};\n>  \t\tnext if $searchtext and not $pr->{'path'} =~ /$searchtext/\n> @@ -4060,6 +4054,73 @@ sub git_project_list_body {\n>  \t\t      \"</td>\\n\" .\n>  \t\t      \"</tr>\\n\";\n>  \t}\n> +}\n> +\n> +sub git_project_list_body {\n> +\tmy ($projlist, $order, $from, $to, $extra, $no_header) = @_;\n> +\n> +\tmy ($check_forks) = gitweb_check_feature('forks');\n> +\tmy @projects = fill_project_list_info($projlist, $check_forks);\n> +\n> +\t$order ||= $default_projects_order;\n> +\n> +\tmy %order_info = (\n> +\t\tproject => { key => 'path', type => 'str' },\n> +\t\tdescr => { key => 'descr_long', type => 'str' },\n> +\t\towner => { key => 'owner', type => 'str' },\n> +\t\tage => { key => 'age', type => 'num' }\n> +\t);\n> +\tmy $oi = $order_info{$order};\n> +\tif ($oi->{'type'} eq 'str') {\n> +\t\t@projects = sort {$a->{$oi->{'key'}} cmp $b->{$oi->{'key'}}} @projects;\n> +\t} else {\n> +\t\t@projects = sort {$a->{$oi->{'key'}} <=> $b->{$oi->{'key'}}} @projects;\n> +\t}\n> +\n> +\tmy $show_ctags = gitweb_check_feature('ctags');\n> +\tif ($show_ctags) {\n> +\t\tmy %ctags;\n> +\t\tforeach my $p (@projects) {\n> +\t\t\tforeach my $ct (keys %{$p->{'ctags'}}) {\n> +\t\t\t\t$ctags{$ct} += $p->{'ctags'}->{$ct};\n> +\t\t\t}\n> +\t\t}\n> +\t\tmy $cloud = git_populate_project_tagcloud(\\%ctags);\n> +\t\tprint git_show_project_tagcloud($cloud, 64);\n> +\t}\n> +\n> +\tprint \"<table class=\\\"project_list\\\">\\n\";\n> +\tunless ($no_header) {\n> +\t\tprint \"<tr>\\n\";\n> +\t\tif ($check_forks) {\n> +\t\t\tprint \"<th></th>\\n\";\n> +\t\t}\n> +\t\tprint_sort_th('project', $order, 'Project');\n> +\t\tprint_sort_th('descr', $order, 'Description');\n> +\t\tprint_sort_th('owner', $order, 'Owner');\n> +\t\tprint_sort_th('age', $order, 'Last Change');\n> +\t\tprint \"<th></th>\\n\" . # for links\n> +\t\t      \"</tr>\\n\";\n> +\t}\n> +\n> +\tif ($projects_list_group_categories) {\n> +\t\tmy %categories = build_category_list(\\@projects);\n> +\t\tforeach my $cat (sort keys %categories) {\n> +\t\t\tunless ($cat eq \"\") {\n> +\t\t\t\tprint \"<tr>\\n\";\n> +\t\t\t\tif ($check_forks) {\n> +\t\t\t\t\tprint \"<td></td>\\n\";\n> +\t\t\t\t}\n> +\t\t\t\tprint \"<td class=\\\"category\\\" colspan=\\\"5\\\">$cat</td>\\n\";\n> +\t\t\t\tprint \"</tr>\\n\";\n> +\t\t\t}\n> +\n> +\t\t\tprint_project_rows($categories{$cat}, $from, $to, $check_forks, $show_ctags);\n> +\t\t}\n\nHere is and important issue when adding categories support: how division\ninto categories interplays with sorting of projects.  I see that you\nchoose to always sort categories alphabetically, and to sort projects\nby given column within categories, instead of (more complicated) sorting\ncategories by first project (first by given order) in a category.\n\nThis I think should be mentioned (at least briefly) in commit message.\n\n\nAnother issue is how categories interplay with limiting number of\nprojects displayed. Currently it is no issue, because projects list\npage is not divided into pages, but I think you didn't address this\nin your patch.\n\n> +\t} else {\n> +\t\tprint_project_rows(\\@projects, $from, $to, $check_forks, $show_ctags);\n> +\t}\n> +\n>  \tif (defined $extra) {\n>  \t\tprint \"<tr>\\n\";\n>  \t\tif ($check_forks) {\n> -- \n> 1.5.6.5\n> \n> \n\n-- \nJakub Narebski\nPoland\n"},{"id":"97070","messageId":"87prk91got.wl%seb@cine7.net","threadId":"16558","inReplyTo":"200812030036.13562.jnareb@gmail.com","subject":"[PATCH v2] gitweb: Optional grouping of projects by category","fromName":"Sébastien Cevey","fromEmail":"seb@cine7.net","sentAt":"2008-12-03T14:22:58Z","receivedAt":"2008-12-03T14:22:58Z","isPatch":true,"sender":{"key":"seb@cine7.net","avatar":"https://avatars.githubusercontent.com/u/36964?v=4"},"body":"This adds the $projects_list_group_categories option which, if enabled,\nwill result in grouping projects by category on the project list page.\nThe category is specified for each project by the $GIT_DIR/category file\nor the 'category' variable in its configuration file. By default, projects\nare put in the $project_list_default_category category.\n\nNote:\n- Categories are always sorted alphabetically, with projects in\n  each category sorted according to the globally selected $order.\n- When displaying a subset of all the projects, only the categories with\n  projects in the chosen subset are displayed.\n\nThe feature is inspired from Sham Chukoury's patch for the XMMS2\ngitweb, but has been rewritten for the current gitweb development\nHEAD. The CSS for categories is inspired from Gustavo Sverzut Barbieri's\npatch to group projects by path.\n\nThanks to Florian Ragwitz for Perl tips.\n\nSigned-off-by: Sebastien Cevey <seb@cine7.net>\n---\n\nVersion 2 of the patch including corrections based on Jakub's comment,\nnotably removed the build time configuration variable, added more doc,\nimproved CSS look, cleaned some code and added support for displaying\na subset of all the projects (using $from/$to).\n\n gitweb/README      |   16 +++++\n gitweb/gitweb.css  |    7 ++\n gitweb/gitweb.perl |  190 +++++++++++++++++++++++++++++++++++++---------------\n 3 files changed, 158 insertions(+), 55 deletions(-)\n\ndiff --git a/gitweb/README b/gitweb/README\nindex 825162a..f8f8872 100644\n--- a/gitweb/README\n+++ b/gitweb/README\n@@ -188,6 +188,15 @@ not include variables usually directly set during build):\n    full description is available as 'title' attribute (usually shown on\n    mouseover).  By default set to 25, which might be too small if you\n    use long project descriptions.\n+ * $projects_list_group_categories\n+   Enables the grouping of projects by category on the project list page.\n+   The category of a project is determined by the $GIT_DIR/category\n+   file or the 'gitweb.category' variable in its repository configuration.\n+   Disabled by default.\n+ * $project_list_default_category\n+   Default category for projects for which none is specified.  If set\n+   to the empty string, such projects will remain uncategorized and\n+   listed at the top, above categorized projects.\n  * @git_base_url_list\n    List of git base URLs used for URL to where fetch project from, shown\n    in project summary page.  Full URL is \"$git_base_url/$project\".\n@@ -269,6 +278,13 @@ You can use the following files in repository:\n    from the template during repository creation. You can use the\n    gitweb.description repo configuration variable, but the file takes\n    precedence.\n+ * category (or gitweb.category)\n+   Singe line category of a project, used to group projects if\n+   $projects_list_group_categories is enabled. By default (file and\n+   configuration variable absent), uncategorized projects are put in\n+   the $project_list_default_category category. You can use the\n+   gitweb.category repo configuration variable, but the file takes\n+   precedence.\n  * cloneurl (or multiple-valued gitweb.url)\n    File with repository URL (used for clone and fetch), one per line.\n    Displayed in the project summary page. You can use multiple-valued\ndiff --git a/gitweb/gitweb.css b/gitweb/gitweb.css\nindex a01eac8..64f2a41 100644\n--- a/gitweb/gitweb.css\n+++ b/gitweb/gitweb.css\n@@ -264,6 +264,13 @@ td.current_head {\n \ttext-decoration: underline;\n }\n \n+td.category {\n+\tbackground-color: #d9d8d1;\n+\tborder-top: 1px solid #000000;\n+\tborder-left: 1px solid #000000;\n+\tfont-weight: bold;\n+}\n+\n table.diff_tree span.file_status.new {\n \tcolor: #008000;\n }\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex 933e137..5b11b66 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -87,6 +87,14 @@ our $projects_list = \"++GITWEB_LIST++\";\n # the width (in characters) of the projects list \"Description\" column\n our $projects_list_description_width = 25;\n \n+# group projects by category on the projects list\n+# (enabled if this variable evaluates to true)\n+our $projects_list_group_categories = 0;\n+\n+# default category if none specified\n+# (leave the empty string for no category)\n+our $project_list_default_category = \"\";\n+\n # default order of projects list\n # valid values are none, project, descr, owner, and age\n our $default_projects_order = \"project\";\n@@ -2001,18 +2009,31 @@ sub git_get_path_by_hash {\n ## ......................................................................\n ## git utility functions, directly accessing git repository\n \n-sub git_get_project_description {\n-\tmy $path = shift;\n+# get the value of a config variable either from a file with the same\n+# name in the repository, or the gitweb.$name value in the repository\n+# config file.\n+sub git_get_project_config_or_file {\n+\tmy ($name, $path) = @_;\n \n \t$git_dir = \"$projectroot/$path\";\n-\topen my $fd, \"$git_dir/description\"\n-\t\tor return git_get_project_config('description');\n-\tmy $descr = <$fd>;\n+\topen my $fd, \"$git_dir/$name\"\n+\t\tor return git_get_project_config($name);\n+\tmy $conf = <$fd>;\n \tclose $fd;\n-\tif (defined $descr) {\n-\t\tchomp $descr;\n+\tif (defined $conf) {\n+\t\tchomp $conf;\n \t}\n-\treturn $descr;\n+\treturn $conf;\n+}\n+\n+sub git_get_project_description {\n+\tmy $path = shift;\n+\treturn git_get_project_config_or_file('description', $path);\n+}\n+\n+sub git_get_project_category {\n+\tmy $path = shift;\n+\treturn git_get_project_config_or_file('category', $path);\n }\n \n sub git_get_project_ctags {\n@@ -3907,8 +3928,9 @@ sub git_patchset_body {\n \n # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n \n-# fills project list info (age, description, owner, forks) for each\n-# project in the list, removing invalid projects from returned list\n+# fills project list info (age, description, owner, category, forks)\n+# for each project in the list, removing invalid projects from\n+# returned list\n # NOTE: modifies $projlist, but does not remove entries from it\n sub fill_project_list_info {\n \tmy ($projlist, $check_forks) = @_;\n@@ -3931,6 +3953,11 @@ sub fill_project_list_info {\n \t\tif (!defined $pr->{'owner'}) {\n \t\t\t$pr->{'owner'} = git_get_project_owner(\"$pr->{'path'}\") || \"\";\n \t\t}\n+\t\tif ($projects_list_group_categories && !defined $pr->{'category'}) {\n+\t\t\tmy $cat = git_get_project_category($pr->{'path'}) ||\n+\t\t\t                                   $project_list_default_category;\n+\t\t\t$pr->{'category'} = to_utf8($cat);\n+\t\t}\n \t\tif ($check_forks) {\n \t\t\tmy $pname = $pr->{'path'};\n \t\t\tif (($pname =~ s/\\.git$//) &&\n@@ -3948,6 +3975,19 @@ sub fill_project_list_info {\n \treturn @projects;\n }\n \n+# returns a hash of categories, containing the list of project\n+# belonging to each category\n+sub build_projlist_by_category {\n+\tmy $projlist = shift;\n+\tmy %categories;\n+\n+\tfor my $pr (@$projlist) {\n+\t\tpush @{$categories{ $pr->{'category'} }}, $pr;\n+\t}\n+\n+\treturn %categories;\n+}\n+\n # print 'sort by' <th> element, generating 'sort by $name' replay link\n # if that order is not selected\n sub print_sort_th {\n@@ -3964,16 +4004,71 @@ sub print_sort_th {\n \t}\n }\n \n-sub git_project_list_body {\n+# print a row for each project in the given list, using the given\n+# range and extra display options\n+sub print_project_rows {\n \t# actually uses global variable $project\n+\tmy ($projects, $from, $to, $check_forks, $show_ctags) = @_;\n+\n+\t$from = 0 unless defined $from;\n+\t$to = $#$projects if (!defined $to || $#$projects < $to);\n+\n+\tmy $alternate = 1;\n+\tmy $tagfilter = $cgi->param('by_tag');\n+\tfor (my $i = $from; $i <= $to; $i++) {\n+\t\tmy $pr = $projects->[$i];\n+\n+\t\tnext if $tagfilter and $show_ctags and not grep { lc $_ eq lc $tagfilter } keys %{$pr->{'ctags'}};\n+\t\tnext if $searchtext and not $pr->{'path'} =~ /$searchtext/\n+\t\t\tand not $pr->{'descr_long'} =~ /$searchtext/;\n+\t\t# Weed out forks or non-matching entries of search\n+\t\tif ($check_forks) {\n+\t\t\tmy $forkbase = $project; $forkbase ||= ''; $forkbase =~ s#\\.git$#/#;\n+\t\t\t$forkbase=\"^$forkbase\" if $forkbase;\n+\t\t\tnext if not $searchtext and not $tagfilter and $show_ctags\n+\t\t\t\tand $pr->{'path'} =~ m#$forkbase.*/.*#; # regexp-safe\n+\t\t}\n+\n+\t\tif ($alternate) {\n+\t\t\tprint \"<tr class=\\\"dark\\\">\\n\";\n+\t\t} else {\n+\t\t\tprint \"<tr class=\\\"light\\\">\\n\";\n+\t\t}\n+\t\t$alternate ^= 1;\n+\t\tif ($check_forks) {\n+\t\t\tprint \"<td>\";\n+\t\t\tif ($pr->{'forks'}) {\n+\t\t\t\tprint \"<!-- $pr->{'forks'} -->\\n\";\n+\t\t\t\tprint $cgi->a({-href => href(project=>$pr->{'path'}, action=>\"forks\")}, \"+\");\n+\t\t\t}\n+\t\t\tprint \"</td>\\n\";\n+\t\t}\n+\t\tprint \"<td>\" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>\"summary\"),\n+\t\t                        -class => \"list\"}, esc_html($pr->{'path'})) . \"</td>\\n\" .\n+\t\t      \"<td>\" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>\"summary\"),\n+\t\t                        -class => \"list\", -title => $pr->{'descr_long'}},\n+\t\t                        esc_html($pr->{'descr'})) . \"</td>\\n\" .\n+\t\t      \"<td><i>\" . chop_and_escape_str($pr->{'owner'}, 15) . \"</i></td>\\n\";\n+\t\tprint \"<td class=\\\"\". age_class($pr->{'age'}) . \"\\\">\" .\n+\t\t      (defined $pr->{'age_string'} ? $pr->{'age_string'} : \"No commits\") . \"</td>\\n\" .\n+\t\t      \"<td class=\\\"link\\\">\" .\n+\t\t      $cgi->a({-href => href(project=>$pr->{'path'}, action=>\"summary\")}, \"summary\")   . \" | \" .\n+\t\t      $cgi->a({-href => href(project=>$pr->{'path'}, action=>\"shortlog\")}, \"shortlog\") . \" | \" .\n+\t\t      $cgi->a({-href => href(project=>$pr->{'path'}, action=>\"log\")}, \"log\") . \" | \" .\n+\t\t      $cgi->a({-href => href(project=>$pr->{'path'}, action=>\"tree\")}, \"tree\") .\n+\t\t      ($pr->{'forks'} ? \" | \" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>\"forks\")}, \"forks\") : '') .\n+\t\t      \"</td>\\n\" .\n+\t\t      \"</tr>\\n\";\n+\t}\n+}\n+\n+sub git_project_list_body {\n \tmy ($projlist, $order, $from, $to, $extra, $no_header) = @_;\n \n \tmy ($check_forks) = gitweb_check_feature('forks');\n \tmy @projects = fill_project_list_info($projlist, $check_forks);\n \n \t$order ||= $default_projects_order;\n-\t$from = 0 unless defined $from;\n-\t$to = $#projects if (!defined $to || $#projects < $to);\n \n \tmy %order_info = (\n \t\tproject => { key => 'path', type => 'str' },\n@@ -4013,53 +4108,38 @@ sub git_project_list_body {\n \t\tprint \"<th></th>\\n\" . # for links\n \t\t      \"</tr>\\n\";\n \t}\n-\tmy $alternate = 1;\n-\tmy $tagfilter = $cgi->param('by_tag');\n-\tfor (my $i = $from; $i <= $to; $i++) {\n-\t\tmy $pr = $projects[$i];\n \n-\t\tnext if $tagfilter and $show_ctags and not grep { lc $_ eq lc $tagfilter } keys %{$pr->{'ctags'}};\n-\t\tnext if $searchtext and not $pr->{'path'} =~ /$searchtext/\n-\t\t\tand not $pr->{'descr_long'} =~ /$searchtext/;\n-\t\t# Weed out forks or non-matching entries of search\n-\t\tif ($check_forks) {\n-\t\t\tmy $forkbase = $project; $forkbase ||= ''; $forkbase =~ s#\\.git$#/#;\n-\t\t\t$forkbase=\"^$forkbase\" if $forkbase;\n-\t\t\tnext if not $searchtext and not $tagfilter and $show_ctags\n-\t\t\t\tand $pr->{'path'} =~ m#$forkbase.*/.*#; # regexp-safe\n-\t\t}\n+\tif ($projects_list_group_categories) {\n+\t\t# only display categories with projects in the $from-$to window\n+\t\tmy %categories = build_projlist_by_category(\\@projects);\n+\t\tforeach my $cat (sort keys %categories) {\n+\t\t\tmy $num_cats = @{$categories{$cat}};\n \n-\t\tif ($alternate) {\n-\t\t\tprint \"<tr class=\\\"dark\\\">\\n\";\n-\t\t} else {\n-\t\t\tprint \"<tr class=\\\"light\\\">\\n\";\n-\t\t}\n-\t\t$alternate ^= 1;\n-\t\tif ($check_forks) {\n-\t\t\tprint \"<td>\";\n-\t\t\tif ($pr->{'forks'}) {\n-\t\t\t\tprint \"<!-- $pr->{'forks'} -->\\n\";\n-\t\t\t\tprint $cgi->a({-href => href(project=>$pr->{'path'}, action=>\"forks\")}, \"+\");\n+\t\t\t# out of the window to display, done\n+\t\t\tlast if defined $to and $to < 0;\n+\n+\t\t\t# in the window to display\n+\t\t\tif (!defined $from or $from < $num_cats) {\n+\t\t\t\tunless ($cat eq \"\") {\n+\t\t\t\t\tprint \"<tr>\\n\";\n+\t\t\t\t\tif ($check_forks) {\n+\t\t\t\t\t\tprint \"<td></td>\\n\";\n+\t\t\t\t\t}\n+\t\t\t\t\tprint \"<td class=\\\"category\\\" colspan=\\\"5\\\">$cat</td>\\n\";\n+\t\t\t\t\tprint \"</tr>\\n\";\n+\t\t\t\t}\n+\n+\t\t\t\tprint_project_rows($categories{$cat}, $from, $to, $check_forks, $show_ctags);\n \t\t\t}\n-\t\t\tprint \"</td>\\n\";\n+\n+\t\t\t# adjust $from/$to offset, keep $from positive\n+\t\t\t$from = ($from > $num_cats) ? $from - $num_cats : 0 if defined $from;\n+\t\t\t$to -= $num_cats if defined $to;\n \t\t}\n-\t\tprint \"<td>\" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>\"summary\"),\n-\t\t                        -class => \"list\"}, esc_html($pr->{'path'})) . \"</td>\\n\" .\n-\t\t      \"<td>\" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>\"summary\"),\n-\t\t                        -class => \"list\", -title => $pr->{'descr_long'}},\n-\t\t                        esc_html($pr->{'descr'})) . \"</td>\\n\" .\n-\t\t      \"<td><i>\" . chop_and_escape_str($pr->{'owner'}, 15) . \"</i></td>\\n\";\n-\t\tprint \"<td class=\\\"\". age_class($pr->{'age'}) . \"\\\">\" .\n-\t\t      (defined $pr->{'age_string'} ? $pr->{'age_string'} : \"No commits\") . \"</td>\\n\" .\n-\t\t      \"<td class=\\\"link\\\">\" .\n-\t\t      $cgi->a({-href => href(project=>$pr->{'path'}, action=>\"summary\")}, \"summary\")   . \" | \" .\n-\t\t      $cgi->a({-href => href(project=>$pr->{'path'}, action=>\"shortlog\")}, \"shortlog\") . \" | \" .\n-\t\t      $cgi->a({-href => href(project=>$pr->{'path'}, action=>\"log\")}, \"log\") . \" | \" .\n-\t\t      $cgi->a({-href => href(project=>$pr->{'path'}, action=>\"tree\")}, \"tree\") .\n-\t\t      ($pr->{'forks'} ? \" | \" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>\"forks\")}, \"forks\") : '') .\n-\t\t      \"</td>\\n\" .\n-\t\t      \"</tr>\\n\";\n+\t} else {\n+\t\tprint_project_rows(\\@projects, $from, $to, $check_forks, $show_ctags);\n \t}\n+\n \tif (defined $extra) {\n \t\tprint \"<tr>\\n\";\n \t\tif ($check_forks) {\n-- \n1.5.6.5\n"},{"id":"97072","messageId":"87oczt1gdj.wl%seb@cine7.net","threadId":"16558","inReplyTo":"200812030036.13562.jnareb@gmail.com","subject":"Re: [PATCH] gitweb: Optional grouping of projects by category","fromName":"Sébastien Cevey","fromEmail":"seb@cine7.net","sentAt":"2008-12-03T14:29:44Z","receivedAt":"2008-12-03T14:29:44Z","isPatch":true,"sender":{"key":"seb@cine7.net","avatar":"https://avatars.githubusercontent.com/u/36964?v=4"},"body":"At Wed, 3 Dec 2008 00:36:12 +0100, Jakub Narebski wrote:\n\nHello,\n\n> > This adds the GITWEB_GROUP_CATEGORIES option which, if enabled, will\n> > result in grouping projects by category on the project list page.\n> \n> Should this really be build time configuration (to set default value)?\n\nProbably not, I removed it.  I corrected my patch using all your other\ncomments and sent it again in another email.\n\n> Unfortunately it looks like you hit the edge of feature freeze again.\n\nDamn, time is always against me :-)\nHope it's gonna be included eventually!\n\n> By the way, there was alternate patch series by Gustavo Sverzut Barbieri\n> on 29 July 2008 adding categories support to gitweb:\n>   http://thread.gmane.org/gmane.comp.version-control.git/90553\n> with live demo at http://staff.get-e.org/\n\nRight, I saw that in July but it's not exactly the same feature\n(arbitrary category in config vs repository path).  I think both are\nuseful independently.. If both get supported, there should be a way to\ndo it modularly to reuse code though.\n\nBtw you can see a demo of my patch here: http://inso.cc/git\n\n> Here is and important issue when adding categories support: how division\n> into categories interplays with sorting of projects.\n\nI kept it how it was but added a comment in the commitlog.\n \n> Another issue is how categories interplay with limiting number of\n> projects displayed. Currently it is no issue, because projects list\n> page is not divided into pages, but I think you didn't address this\n> in your patch.\n\nI think it's now better in v2, I added support for $from/$to also in\nthe categorized project list.\n\nThanks for your in depth comments, and I hope this patch will get\napplied in the near future!\n\n-- \nSébastien Cevey / inso.cc\n\n\" The question of whether a computer can think is no more\n  interesting than the question of whether a submarine can swim. \"\nEdsger W. Dijkstra  \n"},{"id":"97097","messageId":"7vabbdc669.fsf@gitster.siamese.dyndns.org","threadId":"16558","inReplyTo":"200812030036.13562.jnareb@gmail.com","subject":"Re: [PATCH] gitweb: Optional grouping of projects by category","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-12-03T21:14:38Z","receivedAt":"2008-12-03T21:14:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jakub Narebski <jnareb@gmail.com> writes:\n\n> On Tue, 2 Dec 2008, Sebastien Cevey wrote:\n> ...\n>> I submitted a previous version of this patch on July 27, but was told\n>> to wait for the end of the feature freeze.  I submitted it again on\n>> September 5, but didn't get any reply.  Hope to be luckier this time!\n>\n> Unfortunately it looks like you hit the edge of feature freeze again.\n\nFreeze for 1.6.1 by itself does not have to stop you or anybody to review\nthe patch and Ack for queuing for 'next' ;-)\n"},{"id":"97104","messageId":"7viqq0c1pg.fsf@gitster.siamese.dyndns.org","threadId":"16558","inReplyTo":"87prk91got.wl%seb@cine7.net","subject":"Re: [PATCH v2] gitweb: Optional grouping of projects by category","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-12-03T22:51:07Z","receivedAt":"2008-12-03T22:51:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Sébastien Cevey <seb@cine7.net> writes:\n\n> This adds the $projects_list_group_categories option which, if enabled,\n> will result in grouping projects by category on the project list page.\n> The category is specified for each project by the $GIT_DIR/category file\n> or the 'category' variable in its configuration file. By default, projects\n> are put in the $project_list_default_category category.\n>\n> Note:\n> - Categories are always sorted alphabetically, with projects in\n>   each category sorted according to the globally selected $order.\n\nI am not sure if always sorting the categories alphabetically is a severe\nenough restriction, but if it was, you can use @project_list_categories\narray that disables the feature when empty and otherwise enumerates the\ncategories in order.  Just an idle thought.\n\n> - When displaying a subset of all the projects, only the categories with\n>   projects in the chosen subset are displayed.\n\nCould you clarify this bit?\n\nIt is not very clear to me how this subset selection happens.  As far as I\ncan see, the user does not choose the category to view, but lets the usual\npage limiting to decide at which project to start and stop placing on the\npage, and only show the ones in the same category as the one that happened\nto be the first on the page.\n\nWhile I think both the introduction of \"git_get_project_config_or_file\"\nwhich is a generalized interface usable between description and the new\nfeature and the refactoring of project_list_body into a seprate function\n\"print_project_rows\" is a good idea, the patch would have been much easier\nto read if these preparatory refactoring steps (without any new feature)\nwere done as a separate patch followed by the main patch to introduce the\nnew feature.\n"},{"id":"97111","messageId":"200812040012.30885.jnareb@gmail.com","threadId":"16558","inReplyTo":"7viqq0c1pg.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH v2] gitweb: Optional grouping of projects by category","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2008-12-03T23:12:29Z","receivedAt":"2008-12-03T23:12:29Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"On Wed, 3 Dec 2008, Junio C Hamano wrote:\n> Sébastien Cevey <seb@cine7.net> writes:\n\n> > This adds the $projects_list_group_categories option which, if enabled,\n> > will result in grouping projects by category on the project list page.\n> > The category is specified for each project by the $GIT_DIR/category file\n> > or the 'category' variable in its configuration file. By default, projects\n> > are put in the $project_list_default_category category.\n> >\n> > Note:\n> > - Categories are always sorted alphabetically, with projects in\n> >   each category sorted according to the globally selected $order.\n> \n> I am not sure if always sorting the categories alphabetically is a severe\n> enough restriction, but if it was, you can use @project_list_categories\n> array that disables the feature when empty and otherwise enumerates the\n> categories in order.  Just an idle thought.\n\nWell, this way of sorting, i.e. keeping order of categories constant,\nand changing order _within_ categories has the advantage that changing\norder (the column we sort by) doesn't change page layout, i.e. in this\ncase doesn't change categories layout.\n\n\nIt means that if we had:\n\n  project1\n  project2\n  [category A]\n  A/pr1\n  A/pr2\n  [category B]\n  B/pr1\n  B/pr2\n  B/pr3\n\nwhen sorting by project name, we will have similarly looking\n\n  project2\n  project1\n  [category A]\n  A/pr1\n  A/pr2\n  [category B]\n  B/pr3\n  B/pr1\n  B/pr2\n\nfor example if we order by age (not shown here)\n \n> > - When displaying a subset of all the projects, only the categories with\n> >   projects in the chosen subset are displayed.\n> \n> Could you clarify this bit?\n> \n> It is not very clear to me how this subset selection happens.  As far as I\n> can see, the user does not choose the category to view, but lets the usual\n> page limiting to decide at which project to start and stop placing on the\n> page, and only show the ones in the same category as the one that happened\n> to be the first on the page.\n\nCurrently the only selection is by tag, but lets assume that we have\nsome page length limit and only first N projects (first N in given sort\norder) are shown.\n\nIf we have selection by tag, only those categories that have at least\none project tagged with selected tag, and only projects with given tag\nwould be shown. If we have page limit on projects list page, and we\nsort for example by age, then only categories with freshest projects\nwill be shown.\n\nAt least it is how I understand it...\n\n \n> While I think both the introduction of \"git_get_project_config_or_file\"\n\nErrr... I think it is git_get_file_or_project_config ;-))))\n\n> which is a generalized interface usable between description and the new\n> feature and the refactoring of project_list_body into a seprate function\n> \"print_project_rows\" is a good idea, the patch would have been much easier\n> to read if these preparatory refactoring steps (without any new feature)\n> were done as a separate patch followed by the main patch to introduce the\n> new feature.\n \nI agree, especially the second part, as adding print_project_rows patch\nlooks harder than it actually is.\n\n-- \nJakub Narebski\nPoland\n"},{"id":"97116","messageId":"87ljuw22q1.wl%seb@cine7.net","threadId":"16558","inReplyTo":"7viqq0c1pg.fsf@gitster.siamese.dyndns.org","subject":"[PATCH v3 0/3] gitweb: Optional grouping of projects by category","fromName":"Sébastien Cevey","fromEmail":"seb@cine7.net","sentAt":"2008-12-04T00:39:18Z","receivedAt":"2008-12-04T00:39:18Z","isPatch":true,"sender":{"key":"seb@cine7.net","avatar":"https://avatars.githubusercontent.com/u/36964?v=4"},"body":"At Wed, 03 Dec 2008 14:51:07 -0800, Junio C Hamano wrote:\n\nGood evening,\n\n> I am not sure if always sorting the categories alphabetically is a\n> severe enough restriction, but if it was, you can use\n> @project_list_categories array that disables the feature when empty\n> and otherwise enumerates the categories in order.  Just an idle\n> thought.\n\nI could add that if requested to, though Jakub seems to think\nalphabetical order is also fine.  I'll be pushing v3 in a minute with\ncommits split by feature update and the rename to\ngit_get_file_or_project_config.\n\nAll this gymnastics helped me getting more familiar with add -i and\nother fun workflow, thanks for the exercise[1] ;-)\n\n> > - When displaying a subset of all the projects, only the categories with\n> >   projects in the chosen subset are displayed.\n> \n> Could you clarify this bit?\n> \n> It is not very clear to me how this subset selection happens.  As far as I\n> can see, the user does not choose the category to view, but lets the usual\n> page limiting to decide at which project to start and stop placing on the\n> page\n\nRight,\n\n> and only show the ones in the same category as the one that happened\n> to be the first on the page.\n\nI'm not sure I understand what you meant by this.\n\nWhat I meant is that, as you said, the subset of projects to display\nis defined by the usual $from/$to mechanism.  And that the category\nheader for all the displayed projects is present; in other words, the\nheader for some category C is shown iff one or more of the projects in\nC is present on the page.  It's really that simple.\n\nIt also means that we might only see a subset of the projects in the\nfirst and last category but hey, if you asked for range N-M of the\nprojects, that's what you get!\n\nI hope I did not confuse you further.\n\n\n[1] OT: I didn't find a simple command to do this:\n\n    $ git diff ..the-end-state > finish.patch\n    $ patch -p1 < finish.patch\n    $ git commit -a -s\n\n    (where the original HEAD and the-end-state have an older MRCA with\n     rewritten history inbetween, and I don't want to apply that\n     history and solve conflicts, just \"get my tree to the end state\".)\n\n    Any tip?\n\n-- \nSébastien Cevey / inso.cc\n\n\" Rest is for the weak and the dead. \"\n"},{"id":"97119","messageId":"87k5ag22ke.wl%seb@cine7.net","threadId":"16558","inReplyTo":"7viqq0c1pg.fsf@gitster.siamese.dyndns.org","subject":"[PATCH v3 1/3] gitweb: Modularized git_get_project_description to be more generic","fromName":"Sébastien Cevey","fromEmail":"seb@cine7.net","sentAt":"2008-12-04T00:42:41Z","receivedAt":"2008-12-04T00:42:41Z","isPatch":true,"sender":{"key":"seb@cine7.net","avatar":"https://avatars.githubusercontent.com/u/36964?v=4"},"body":"Introduce a git_get_file_or_project_config utility function to\nretrieve a repository variable either from a plain text file in the\n$GIT_DIR or else from 'gitweb.$variable' in the repository config\n(e.g. 'description').\n\nSigned-off-by: Sebastien Cevey <seb@cine7.net>\n---\n gitweb/gitweb.perl |   24 ++++++++++++++++--------\n 1 files changed, 16 insertions(+), 8 deletions(-)\n\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex 933e137..b31274c 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -2001,18 +2001,26 @@ sub git_get_path_by_hash {\n ## ......................................................................\n ## git utility functions, directly accessing git repository\n \n-sub git_get_project_description {\n-\tmy $path = shift;\n+# get the value of a config variable either from a file with the same\n+# name in the repository, or the gitweb.$name value in the repository\n+# config file.\n+sub git_get_file_or_project_config {\n+\tmy ($name, $path) = @_;\n \n \t$git_dir = \"$projectroot/$path\";\n-\topen my $fd, \"$git_dir/description\"\n-\t\tor return git_get_project_config('description');\n-\tmy $descr = <$fd>;\n+\topen my $fd, \"$git_dir/$name\"\n+\t\tor return git_get_project_config($name);\n+\tmy $conf = <$fd>;\n \tclose $fd;\n-\tif (defined $descr) {\n-\t\tchomp $descr;\n+\tif (defined $conf) {\n+\t\tchomp $conf;\n \t}\n-\treturn $descr;\n+\treturn $conf;\n+}\n+\n+sub git_get_project_description {\n+\tmy $path = shift;\n+\treturn git_get_file_or_project_config('description', $path);\n }\n \n sub git_get_project_ctags {\n-- \n1.5.6.5\n"},{"id":"97121","messageId":"7vy6yw7osr.fsf@gitster.siamese.dyndns.org","threadId":"16558","inReplyTo":"87ljuw22q1.wl%seb@cine7.net","subject":"Re: [PATCH v3 0/3] gitweb: Optional grouping of projects by category","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-12-04T00:43:32Z","receivedAt":"2008-12-04T00:43:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Sébastien Cevey <seb@cine7.net> writes:\n\n> [1] OT: I didn't find a simple command to do this:\n>\n>     $ git diff ..the-end-state > finish.patch\n>     $ patch -p1 < finish.patch\n>     $ git commit -a -s\n>\n>     (where the original HEAD and the-end-state have an older MRCA with\n>      rewritten history inbetween, and I don't want to apply that\n>      history and solve conflicts, just \"get my tree to the end state\".)\n>\n>     Any tip?\n\n$ git read-tree -m -u the-end-state\n$ git commit -a -s\n"},{"id":"97120","messageId":"87iqq022gz.wl%seb@cine7.net","threadId":"16558","inReplyTo":"87k5ag22ke.wl%seb@cine7.net","subject":"[PATCH v3 2/3] gitweb: Split git_project_list_body in two functions","fromName":"Sébastien Cevey","fromEmail":"seb@cine7.net","sentAt":"2008-12-04T00:44:44Z","receivedAt":"2008-12-04T00:44:44Z","isPatch":true,"sender":{"key":"seb@cine7.net","avatar":"https://avatars.githubusercontent.com/u/36964?v=4"},"body":"Extract the printing of project rows on the project page into a\nseparate print_project_rows function. This makes it easier to reuse\nthe code to print different subsets of the whole project list.\n\nThe row printing code is merely moved into a separate function, but\nnote that $projects is passed as a reference now.\n\nSigned-off-by: Sebastien Cevey <seb@cine7.net>\n---\n\nBoo, the diff is still quite scarier than it really should be..\n\n gitweb/gitweb.perl |  103 +++++++++++++++++++++++++++++-----------------------\n 1 files changed, 57 insertions(+), 46 deletions(-)\n\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex b31274c..a6bb702 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -3972,59 +3972,19 @@ sub print_sort_th {\n \t}\n }\n \n-sub git_project_list_body {\n+# print a row for each project in the given list, using the given\n+# range and extra display options\n+sub print_project_rows {\n \t# actually uses global variable $project\n-\tmy ($projlist, $order, $from, $to, $extra, $no_header) = @_;\n-\n-\tmy ($check_forks) = gitweb_check_feature('forks');\n-\tmy @projects = fill_project_list_info($projlist, $check_forks);\n+\tmy ($projects, $from, $to, $check_forks, $show_ctags) = @_;\n \n-\t$order ||= $default_projects_order;\n \t$from = 0 unless defined $from;\n-\t$to = $#projects if (!defined $to || $#projects < $to);\n-\n-\tmy %order_info = (\n-\t\tproject => { key => 'path', type => 'str' },\n-\t\tdescr => { key => 'descr_long', type => 'str' },\n-\t\towner => { key => 'owner', type => 'str' },\n-\t\tage => { key => 'age', type => 'num' }\n-\t);\n-\tmy $oi = $order_info{$order};\n-\tif ($oi->{'type'} eq 'str') {\n-\t\t@projects = sort {$a->{$oi->{'key'}} cmp $b->{$oi->{'key'}}} @projects;\n-\t} else {\n-\t\t@projects = sort {$a->{$oi->{'key'}} <=> $b->{$oi->{'key'}}} @projects;\n-\t}\n+\t$to = $#$projects if (!defined $to || $#$projects < $to);\n \n-\tmy $show_ctags = gitweb_check_feature('ctags');\n-\tif ($show_ctags) {\n-\t\tmy %ctags;\n-\t\tforeach my $p (@projects) {\n-\t\t\tforeach my $ct (keys %{$p->{'ctags'}}) {\n-\t\t\t\t$ctags{$ct} += $p->{'ctags'}->{$ct};\n-\t\t\t}\n-\t\t}\n-\t\tmy $cloud = git_populate_project_tagcloud(\\%ctags);\n-\t\tprint git_show_project_tagcloud($cloud, 64);\n-\t}\n-\n-\tprint \"<table class=\\\"project_list\\\">\\n\";\n-\tunless ($no_header) {\n-\t\tprint \"<tr>\\n\";\n-\t\tif ($check_forks) {\n-\t\t\tprint \"<th></th>\\n\";\n-\t\t}\n-\t\tprint_sort_th('project', $order, 'Project');\n-\t\tprint_sort_th('descr', $order, 'Description');\n-\t\tprint_sort_th('owner', $order, 'Owner');\n-\t\tprint_sort_th('age', $order, 'Last Change');\n-\t\tprint \"<th></th>\\n\" . # for links\n-\t\t      \"</tr>\\n\";\n-\t}\n \tmy $alternate = 1;\n \tmy $tagfilter = $cgi->param('by_tag');\n \tfor (my $i = $from; $i <= $to; $i++) {\n-\t\tmy $pr = $projects[$i];\n+\t\tmy $pr = $projects->[$i];\n \n \t\tnext if $tagfilter and $show_ctags and not grep { lc $_ eq lc $tagfilter } keys %{$pr->{'ctags'}};\n \t\tnext if $searchtext and not $pr->{'path'} =~ /$searchtext/\n@@ -4068,6 +4028,57 @@ sub git_project_list_body {\n \t\t      \"</td>\\n\" .\n \t\t      \"</tr>\\n\";\n \t}\n+}\n+\n+sub git_project_list_body {\n+\tmy ($projlist, $order, $from, $to, $extra, $no_header) = @_;\n+\n+\tmy ($check_forks) = gitweb_check_feature('forks');\n+\tmy @projects = fill_project_list_info($projlist, $check_forks);\n+\n+\t$order ||= $default_projects_order;\n+\n+\tmy %order_info = (\n+\t\tproject => { key => 'path', type => 'str' },\n+\t\tdescr => { key => 'descr_long', type => 'str' },\n+\t\towner => { key => 'owner', type => 'str' },\n+\t\tage => { key => 'age', type => 'num' }\n+\t);\n+\tmy $oi = $order_info{$order};\n+\tif ($oi->{'type'} eq 'str') {\n+\t\t@projects = sort {$a->{$oi->{'key'}} cmp $b->{$oi->{'key'}}} @projects;\n+\t} else {\n+\t\t@projects = sort {$a->{$oi->{'key'}} <=> $b->{$oi->{'key'}}} @projects;\n+\t}\n+\n+\tmy $show_ctags = gitweb_check_feature('ctags');\n+\tif ($show_ctags) {\n+\t\tmy %ctags;\n+\t\tforeach my $p (@projects) {\n+\t\t\tforeach my $ct (keys %{$p->{'ctags'}}) {\n+\t\t\t\t$ctags{$ct} += $p->{'ctags'}->{$ct};\n+\t\t\t}\n+\t\t}\n+\t\tmy $cloud = git_populate_project_tagcloud(\\%ctags);\n+\t\tprint git_show_project_tagcloud($cloud, 64);\n+\t}\n+\n+\tprint \"<table class=\\\"project_list\\\">\\n\";\n+\tunless ($no_header) {\n+\t\tprint \"<tr>\\n\";\n+\t\tif ($check_forks) {\n+\t\t\tprint \"<th></th>\\n\";\n+\t\t}\n+\t\tprint_sort_th('project', $order, 'Project');\n+\t\tprint_sort_th('descr', $order, 'Description');\n+\t\tprint_sort_th('owner', $order, 'Owner');\n+\t\tprint_sort_th('age', $order, 'Last Change');\n+\t\tprint \"<th></th>\\n\" . # for links\n+\t\t      \"</tr>\\n\";\n+\t}\n+\n+\tprint_project_rows(\\@projects, $from, $to, $check_forks, $show_ctags);\n+\n \tif (defined $extra) {\n \t\tprint \"<tr>\\n\";\n \t\tif ($check_forks) {\n-- \n1.5.6.5\n"},{"id":"97122","messageId":"87hc5k22dr.wl%seb@cine7.net","threadId":"16558","inReplyTo":"87iqq022gz.wl%seb@cine7.net","subject":"[PATCH v3 3/3] gitweb: Optional grouping of projects by category","fromName":"Sébastien Cevey","fromEmail":"seb@cine7.net","sentAt":"2008-12-04T00:46:40Z","receivedAt":"2008-12-04T00:46:40Z","isPatch":true,"sender":{"key":"seb@cine7.net","avatar":"https://avatars.githubusercontent.com/u/36964?v=4"},"body":"This adds the $projects_list_group_categories option which, if enabled,\nwill result in grouping projects by category on the project list page.\nThe category is specified for each project by the $GIT_DIR/category file\nor the 'category' variable in its configuration file. By default, projects\nare put in the $project_list_default_category category.\n\nNote:\n- Categories are always sorted alphabetically, with projects in\n  each category sorted according to the globally selected $order.\n- When displaying a subset of all the projects (page limiting), the\n  category headers are only displayed for projects present on the page.\n\nThe feature is inspired from Sham Chukoury's patch for the XMMS2\ngitweb, but has been rewritten for the current gitweb development\nHEAD. The CSS for categories is inspired from Gustavo Sverzut Barbieri's\npatch to group projects by path.\n\nThanks to Florian Ragwitz for Perl tips.\n\nSigned-off-by: Sebastien Cevey <seb@cine7.net>\n---\n\nCleaner patch this time indeed.  Still no fancy sorting of categories,\nonly alphabetical.\n\n gitweb/README      |   16 ++++++++++++\n gitweb/gitweb.css  |    7 +++++\n gitweb/gitweb.perl |   67 +++++++++++++++++++++++++++++++++++++++++++++++++--\n 3 files changed, 87 insertions(+), 3 deletions(-)\n\ndiff --git a/gitweb/README b/gitweb/README\nindex 825162a..f8f8872 100644\n--- a/gitweb/README\n+++ b/gitweb/README\n@@ -188,6 +188,15 @@ not include variables usually directly set during build):\n    full description is available as 'title' attribute (usually shown on\n    mouseover).  By default set to 25, which might be too small if you\n    use long project descriptions.\n+ * $projects_list_group_categories\n+   Enables the grouping of projects by category on the project list page.\n+   The category of a project is determined by the $GIT_DIR/category\n+   file or the 'gitweb.category' variable in its repository configuration.\n+   Disabled by default.\n+ * $project_list_default_category\n+   Default category for projects for which none is specified.  If set\n+   to the empty string, such projects will remain uncategorized and\n+   listed at the top, above categorized projects.\n  * @git_base_url_list\n    List of git base URLs used for URL to where fetch project from, shown\n    in project summary page.  Full URL is \"$git_base_url/$project\".\n@@ -269,6 +278,13 @@ You can use the following files in repository:\n    from the template during repository creation. You can use the\n    gitweb.description repo configuration variable, but the file takes\n    precedence.\n+ * category (or gitweb.category)\n+   Singe line category of a project, used to group projects if\n+   $projects_list_group_categories is enabled. By default (file and\n+   configuration variable absent), uncategorized projects are put in\n+   the $project_list_default_category category. You can use the\n+   gitweb.category repo configuration variable, but the file takes\n+   precedence.\n  * cloneurl (or multiple-valued gitweb.url)\n    File with repository URL (used for clone and fetch), one per line.\n    Displayed in the project summary page. You can use multiple-valued\ndiff --git a/gitweb/gitweb.css b/gitweb/gitweb.css\nindex a01eac8..64f2a41 100644\n--- a/gitweb/gitweb.css\n+++ b/gitweb/gitweb.css\n@@ -264,6 +264,13 @@ td.current_head {\n \ttext-decoration: underline;\n }\n \n+td.category {\n+\tbackground-color: #d9d8d1;\n+\tborder-top: 1px solid #000000;\n+\tborder-left: 1px solid #000000;\n+\tfont-weight: bold;\n+}\n+\n table.diff_tree span.file_status.new {\n \tcolor: #008000;\n }\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex a6bb702..97a9b73 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -87,6 +87,14 @@ our $projects_list = \"++GITWEB_LIST++\";\n # the width (in characters) of the projects list \"Description\" column\n our $projects_list_description_width = 25;\n \n+# group projects by category on the projects list\n+# (enabled if this variable evaluates to true)\n+our $projects_list_group_categories = 0;\n+\n+# default category if none specified\n+# (leave the empty string for no category)\n+our $project_list_default_category = \"\";\n+\n # default order of projects list\n # valid values are none, project, descr, owner, and age\n our $default_projects_order = \"project\";\n@@ -2023,6 +2031,11 @@ sub git_get_project_description {\n \treturn git_get_file_or_project_config('description', $path);\n }\n \n+sub git_get_project_category {\n+\tmy $path = shift;\n+\treturn git_get_file_or_project_config('category', $path);\n+}\n+\n sub git_get_project_ctags {\n \tmy $path = shift;\n \tmy $ctags = {};\n@@ -3915,8 +3928,9 @@ sub git_patchset_body {\n \n # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n \n-# fills project list info (age, description, owner, forks) for each\n-# project in the list, removing invalid projects from returned list\n+# fills project list info (age, description, owner, category, forks)\n+# for each project in the list, removing invalid projects from\n+# returned list\n # NOTE: modifies $projlist, but does not remove entries from it\n sub fill_project_list_info {\n \tmy ($projlist, $check_forks) = @_;\n@@ -3939,6 +3953,11 @@ sub fill_project_list_info {\n \t\tif (!defined $pr->{'owner'}) {\n \t\t\t$pr->{'owner'} = git_get_project_owner(\"$pr->{'path'}\") || \"\";\n \t\t}\n+\t\tif ($projects_list_group_categories && !defined $pr->{'category'}) {\n+\t\t\tmy $cat = git_get_project_category($pr->{'path'}) ||\n+\t\t\t                                   $project_list_default_category;\n+\t\t\t$pr->{'category'} = to_utf8($cat);\n+\t\t}\n \t\tif ($check_forks) {\n \t\t\tmy $pname = $pr->{'path'};\n \t\t\tif (($pname =~ s/\\.git$//) &&\n@@ -3956,6 +3975,19 @@ sub fill_project_list_info {\n \treturn @projects;\n }\n \n+# returns a hash of categories, containing the list of project\n+# belonging to each category\n+sub build_projlist_by_category {\n+\tmy $projlist = shift;\n+\tmy %categories;\n+\n+\tfor my $pr (@$projlist) {\n+\t\tpush @{$categories{ $pr->{'category'} }}, $pr;\n+\t}\n+\n+\treturn %categories;\n+}\n+\n # print 'sort by' <th> element, generating 'sort by $name' replay link\n # if that order is not selected\n sub print_sort_th {\n@@ -4077,7 +4109,36 @@ sub git_project_list_body {\n \t\t      \"</tr>\\n\";\n \t}\n \n-\tprint_project_rows(\\@projects, $from, $to, $check_forks, $show_ctags);\n+\tif ($projects_list_group_categories) {\n+\t\t# only display categories with projects in the $from-$to window\n+\t\tmy %categories = build_projlist_by_category(\\@projects);\n+\t\tforeach my $cat (sort keys %categories) {\n+\t\t\tmy $num_cats = @{$categories{$cat}};\n+\n+\t\t\t# out of the window to display, done\n+\t\t\tlast if defined $to and $to < 0;\n+\n+\t\t\t# in the window to display\n+\t\t\tif (!defined $from or $from < $num_cats) {\n+\t\t\t\tunless ($cat eq \"\") {\n+\t\t\t\t\tprint \"<tr>\\n\";\n+\t\t\t\t\tif ($check_forks) {\n+\t\t\t\t\t\tprint \"<td></td>\\n\";\n+\t\t\t\t\t}\n+\t\t\t\t\tprint \"<td class=\\\"category\\\" colspan=\\\"5\\\">$cat</td>\\n\";\n+\t\t\t\t\tprint \"</tr>\\n\";\n+\t\t\t\t}\n+\n+\t\t\t\tprint_project_rows($categories{$cat}, $from, $to, $check_forks, $show_ctags);\n+\t\t\t}\n+\n+\t\t\t# adjust $from/$to offset, keep $from positive\n+\t\t\t$from = ($from > $num_cats) ? $from - $num_cats : 0 if defined $from;\n+\t\t\t$to -= $num_cats if defined $to;\n+\t\t}\n+\t} else {\n+\t\tprint_project_rows(\\@projects, $from, $to, $check_forks, $show_ctags);\n+\t}\n \n \tif (defined $extra) {\n \t\tprint \"<tr>\\n\";\n-- \n1.5.6.5\n"},{"id":"97166","messageId":"7v63lzenpm.fsf@gitster.siamese.dyndns.org","threadId":"16558","inReplyTo":"87hc5k22dr.wl%seb@cine7.net","subject":"Re: [PATCH v3 3/3] gitweb: Optional grouping of projects by category","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-12-04T19:37:25Z","receivedAt":"2008-12-04T19:37:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thanks, will queue so that they won't get lost in the noise.\n"},{"id":"97189","messageId":"200812050238.16018.jnareb@gmail.com","threadId":"16558","inReplyTo":"87k5ag22ke.wl%seb@cine7.net","subject":"Re: [PATCH v3 1/3] gitweb: Modularized git_get_project_description to be more generic","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2008-12-05T01:38:14Z","receivedAt":"2008-12-05T01:38:14Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"On Thu, 4 Dec 2008, at 01:42, Sébastien Cevey wrote:\n\n> Introduce a git_get_file_or_project_config utility function to\n> retrieve a repository variable either from a plain text file in the\n> $GIT_DIR \n\nI would say that we try $GIT_DIR/$variable file.\n\n> or else from 'gitweb.$variable' in the repository config \n> (e.g. 'description').\n\nIt _might_ also be added (just in case) that currently the only user\nof this new subroutine is git_get_project_description, but this is\nto change, and that is why this split was introduced.\n\n> \n> Signed-off-by: Sebastien Cevey <seb@cine7.net>\n\nBut those are minor issues. So, FWIW\n\nAcked-by: Jakub Narebski <jnareb@gmail.com>\n\n> ---\n>  gitweb/gitweb.perl |   24 ++++++++++++++++--------\n>  1 files changed, 16 insertions(+), 8 deletions(-)\n> \n> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\n> index 933e137..b31274c 100755\n> --- a/gitweb/gitweb.perl\n> +++ b/gitweb/gitweb.perl\n> @@ -2001,18 +2001,26 @@ sub git_get_path_by_hash {\n>  ## ......................................................................\n>  ## git utility functions, directly accessing git repository\n>  \n> -sub git_get_project_description {\n> -\tmy $path = shift;\n> +# get the value of a config variable either from a file with the same\n> +# name in the repository, or the gitweb.$name value in the repository\n> +# config file.\n\nIt would probably be better to explicitly say that we use $git_dir/$name\nfile, or if it doesn't exist, gitweb.$name configuration variable.\n\n> +sub git_get_file_or_project_config {\n> +\tmy ($name, $path) = @_;\n\nI think that $project, or $projectpath _might_ be better name for the\nsecond argument to this subroutine.\n\n>  \n>  \t$git_dir = \"$projectroot/$path\";\n> -\topen my $fd, \"$git_dir/description\"\n> -\t\tor return git_get_project_config('description');\n> -\tmy $descr = <$fd>;\n> +\topen my $fd, \"$git_dir/$name\"\n> +\t\tor return git_get_project_config($name);\n> +\tmy $conf = <$fd>;\n>  \tclose $fd;\n> -\tif (defined $descr) {\n> -\t\tchomp $descr;\n> +\tif (defined $conf) {\n> +\t\tchomp $conf;\n>  \t}\n> -\treturn $descr;\n> +\treturn $conf;\n> +}\n> +\n> +sub git_get_project_description {\n> +\tmy $path = shift;\n> +\treturn git_get_file_or_project_config('description', $path);\n>  }\n\nNicely done.\n\n>  \n>  sub git_get_project_ctags {\n> -- \n> 1.5.6.5\n> \n> \n\n-- \nJakub Narębski\nPoland\n"},{"id":"97190","messageId":"200812050252.04298.jnareb@gmail.com","threadId":"16558","inReplyTo":"87iqq022gz.wl%seb@cine7.net","subject":"Re: [PATCH v3 2/3] gitweb: Split git_project_list_body in two functions","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2008-12-05T01:52:03Z","receivedAt":"2008-12-05T01:52:03Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"On Tue, 4 Dec 2008 at 01:44, Sébastien Cevey wrote:\n\n> Extract the printing of project rows on the project page into a\n> separate print_project_rows function. This makes it easier to reuse\n> the code to print different subsets of the whole project list.\n\nErr... it was not obvious for me that 'project rows' are rows of\nprojects list table, i.e. currently the body of projects list table.\n\nBut I think it is a good description.\n\n> \n> The row printing code is merely moved into a separate function, but\n> note that $projects is passed as a reference now.\n> \n> Signed-off-by: Sebastien Cevey <seb@cine7.net>\n\nNicely done.\n\nFor what it is worth:\n\nAcked-by: Jakub Narebski <jnareb@gmail.com>\n\n> ---\n> \n> Boo, the diff is still quite scarier than it really should be...\n\nWell, nothing short of blame -C would make it work; the issue is that\nwe split subroutine into two, extracting from the middle, but with\nsome common/similar header\n\n  2        1\n  1        1\n  2        1\n  2\n  2   ->   2\n  1        2\n  1        2\n  2        2\n  2        2\n           2\n\nAnd diff is forward preferring, i.e. it finds match then second part\nof split as add, rather than first first part of split as add then\nmatch.\n> \n>  gitweb/gitweb.perl |  103 +++++++++++++++++++++++++++++-----------------------\n>  1 files changed, 57 insertions(+), 46 deletions(-)\n> \n> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\n> index b31274c..a6bb702 100755\n> --- a/gitweb/gitweb.perl\n> +++ b/gitweb/gitweb.perl\n> @@ -3972,59 +3972,19 @@ sub print_sort_th {\n>  \t}\n>  }\n>  \n> -sub git_project_list_body {\n> +# print a row for each project in the given list, using the given\n> +# range and extra display options\n> +sub print_project_rows {\n>  \t# actually uses global variable $project\n> -\tmy ($projlist, $order, $from, $to, $extra, $no_header) = @_;\n> -\n> -\tmy ($check_forks) = gitweb_check_feature('forks');\n> -\tmy @projects = fill_project_list_info($projlist, $check_forks);\n> +\tmy ($projects, $from, $to, $check_forks, $show_ctags) = @_;\n\nI see that print_project_rows gets @$projects list already sorted, and\nit passes explicitly $check_forks and $show_ctags from caller.\n\n>  \n> -\t$order ||= $default_projects_order;\n>  \t$from = 0 unless defined $from;\n> -\t$to = $#projects if (!defined $to || $#projects < $to);\n> -\n> -\tmy %order_info = (\n> -\t\tproject => { key => 'path', type => 'str' },\n> -\t\tdescr => { key => 'descr_long', type => 'str' },\n> -\t\towner => { key => 'owner', type => 'str' },\n> -\t\tage => { key => 'age', type => 'num' }\n> -\t);\n> -\tmy $oi = $order_info{$order};\n> -\tif ($oi->{'type'} eq 'str') {\n> -\t\t@projects = sort {$a->{$oi->{'key'}} cmp $b->{$oi->{'key'}}} @projects;\n> -\t} else {\n> -\t\t@projects = sort {$a->{$oi->{'key'}} <=> $b->{$oi->{'key'}}} @projects;\n> -\t}\n> +\t$to = $#$projects if (!defined $to || $#$projects < $to);\n>  \n> -\tmy $show_ctags = gitweb_check_feature('ctags');\n> -\tif ($show_ctags) {\n> -\t\tmy %ctags;\n> -\t\tforeach my $p (@projects) {\n> -\t\t\tforeach my $ct (keys %{$p->{'ctags'}}) {\n> -\t\t\t\t$ctags{$ct} += $p->{'ctags'}->{$ct};\n> -\t\t\t}\n> -\t\t}\n> -\t\tmy $cloud = git_populate_project_tagcloud(\\%ctags);\n> -\t\tprint git_show_project_tagcloud($cloud, 64);\n> -\t}\n> -\n> -\tprint \"<table class=\\\"project_list\\\">\\n\";\n> -\tunless ($no_header) {\n> -\t\tprint \"<tr>\\n\";\n> -\t\tif ($check_forks) {\n> -\t\t\tprint \"<th></th>\\n\";\n> -\t\t}\n> -\t\tprint_sort_th('project', $order, 'Project');\n> -\t\tprint_sort_th('descr', $order, 'Description');\n> -\t\tprint_sort_th('owner', $order, 'Owner');\n> -\t\tprint_sort_th('age', $order, 'Last Change');\n> -\t\tprint \"<th></th>\\n\" . # for links\n> -\t\t      \"</tr>\\n\";\n> -\t}\n>  \tmy $alternate = 1;\n>  \tmy $tagfilter = $cgi->param('by_tag');\n>  \tfor (my $i = $from; $i <= $to; $i++) {\n> -\t\tmy $pr = $projects[$i];\n> +\t\tmy $pr = $projects->[$i];\n>  \n>  \t\tnext if $tagfilter and $show_ctags and not grep { lc $_ eq lc $tagfilter } keys %{$pr->{'ctags'}};\n>  \t\tnext if $searchtext and not $pr->{'path'} =~ /$searchtext/\n> @@ -4068,6 +4028,57 @@ sub git_project_list_body {\n>  \t\t      \"</td>\\n\" .\n>  \t\t      \"</tr>\\n\";\n>  \t}\n> +}\n> +\n> +sub git_project_list_body {\n> +\tmy ($projlist, $order, $from, $to, $extra, $no_header) = @_;\n> +\n> +\tmy ($check_forks) = gitweb_check_feature('forks');\n> +\tmy @projects = fill_project_list_info($projlist, $check_forks);\n> +\n> +\t$order ||= $default_projects_order;\n> +\n> +\tmy %order_info = (\n> +\t\tproject => { key => 'path', type => 'str' },\n> +\t\tdescr => { key => 'descr_long', type => 'str' },\n> +\t\towner => { key => 'owner', type => 'str' },\n> +\t\tage => { key => 'age', type => 'num' }\n> +\t);\n> +\tmy $oi = $order_info{$order};\n> +\tif ($oi->{'type'} eq 'str') {\n> +\t\t@projects = sort {$a->{$oi->{'key'}} cmp $b->{$oi->{'key'}}} @projects;\n> +\t} else {\n> +\t\t@projects = sort {$a->{$oi->{'key'}} <=> $b->{$oi->{'key'}}} @projects;\n> +\t}\n> +\n> +\tmy $show_ctags = gitweb_check_feature('ctags');\n> +\tif ($show_ctags) {\n> +\t\tmy %ctags;\n> +\t\tforeach my $p (@projects) {\n> +\t\t\tforeach my $ct (keys %{$p->{'ctags'}}) {\n> +\t\t\t\t$ctags{$ct} += $p->{'ctags'}->{$ct};\n> +\t\t\t}\n> +\t\t}\n> +\t\tmy $cloud = git_populate_project_tagcloud(\\%ctags);\n> +\t\tprint git_show_project_tagcloud($cloud, 64);\n> +\t}\n> +\n> +\tprint \"<table class=\\\"project_list\\\">\\n\";\n> +\tunless ($no_header) {\n> +\t\tprint \"<tr>\\n\";\n> +\t\tif ($check_forks) {\n> +\t\t\tprint \"<th></th>\\n\";\n> +\t\t}\n> +\t\tprint_sort_th('project', $order, 'Project');\n> +\t\tprint_sort_th('descr', $order, 'Description');\n> +\t\tprint_sort_th('owner', $order, 'Owner');\n> +\t\tprint_sort_th('age', $order, 'Last Change');\n> +\t\tprint \"<th></th>\\n\" . # for links\n> +\t\t      \"</tr>\\n\";\n> +\t}\n> +\n> +\tprint_project_rows(\\@projects, $from, $to, $check_forks, $show_ctags);\n> +\n>  \tif (defined $extra) {\n>  \t\tprint \"<tr>\\n\";\n>  \t\tif ($check_forks) {\n> -- \n> 1.5.6.5\n> \n> \n\n-- \nJakub Narebski\nPoland\n"},{"id":"97192","messageId":"200812050308.52891.jnareb@gmail.com","threadId":"16558","inReplyTo":"87hc5k22dr.wl%seb@cine7.net","subject":"Re: [PATCH v3 3/3] gitweb: Optional grouping of projects by category","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2008-12-05T02:08:52Z","receivedAt":"2008-12-05T02:08:52Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"On Thu, 4 Dec 2008, Sébastien Cevey wrote:\n\n> This adds the $projects_list_group_categories option which, if enabled,\n> will result in grouping projects by category on the project list page.\n> The category is specified for each project by the $GIT_DIR/category file\n> or the 'category' variable in its configuration file. By default, projects\n> are put in the $project_list_default_category category.\n> \n> Note:\n> - Categories are always sorted alphabetically, with projects in\n>   each category sorted according to the globally selected $order.\n> - When displaying a subset of all the projects (page limiting), the\n>   category headers are only displayed for projects present on the page.\n> \n> The feature is inspired from Sham Chukoury's patch for the XMMS2\n> gitweb, but has been rewritten for the current gitweb development\n> HEAD. The CSS for categories is inspired from Gustavo Sverzut Barbieri's\n> patch to group projects by path.\n> \n> Thanks to Florian Ragwitz for Perl tips.\n\nVery nice, and nicely done and thought out, idea.\n\n> \n> Signed-off-by: Sebastien Cevey <seb@cine7.net>\n> ---\n> \n> Cleaner patch this time indeed.  Still no fancy sorting of categories,\n> only alphabetical.\n> \n>  gitweb/README      |   16 ++++++++++++\n>  gitweb/gitweb.css  |    7 +++++\n>  gitweb/gitweb.perl |   67 +++++++++++++++++++++++++++++++++++++++++++++++++--\n>  3 files changed, 87 insertions(+), 3 deletions(-)\n> \n> diff --git a/gitweb/README b/gitweb/README\n> index 825162a..f8f8872 100644\n> --- a/gitweb/README\n> +++ b/gitweb/README\n> @@ -188,6 +188,15 @@ not include variables usually directly set during build):\n>     full description is available as 'title' attribute (usually shown on\n>     mouseover).  By default set to 25, which might be too small if you\n>     use long project descriptions.\n> + * $projects_list_group_categories\n> +   Enables the grouping of projects by category on the project list page.\n> +   The category of a project is determined by the $GIT_DIR/category\n> +   file or the 'gitweb.category' variable in its repository configuration.\n> +   Disabled by default.\n> + * $project_list_default_category\n> +   Default category for projects for which none is specified.  If set\n> +   to the empty string, such projects will remain uncategorized and\n> +   listed at the top, above categorized projects.\n>   * @git_base_url_list\n>     List of git base URLs used for URL to where fetch project from, shown\n>     in project summary page.  Full URL is \"$git_base_url/$project\".\n\nGood.\n\n> @@ -269,6 +278,13 @@ You can use the following files in repository:\n>     from the template during repository creation. You can use the\n>     gitweb.description repo configuration variable, but the file takes\n>     precedence.\n> + * category (or gitweb.category)\n> +   Singe line category of a project, used to group projects if\n> +   $projects_list_group_categories is enabled. By default (file and\n> +   configuration variable absent), uncategorized projects are put in\n> +   the $project_list_default_category category. You can use the\n> +   gitweb.category repo configuration variable, but the file takes\n> +   precedence.\n>   * cloneurl (or multiple-valued gitweb.url)\n>     File with repository URL (used for clone and fetch), one per line.\n>     Displayed in the project summary page. You can use multiple-valued\n\nGood.\n\n> diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css\n> index a01eac8..64f2a41 100644\n> --- a/gitweb/gitweb.css\n> +++ b/gitweb/gitweb.css\n> @@ -264,6 +264,13 @@ td.current_head {\n>  \ttext-decoration: underline;\n>  }\n>  \n> +td.category {\n> +\tbackground-color: #d9d8d1;\n> +\tborder-top: 1px solid #000000;\n> +\tborder-left: 1px solid #000000;\n> +\tfont-weight: bold;\n> +}\n> +\n>  table.diff_tree span.file_status.new {\n>  \tcolor: #008000;\n>  }\n> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\n> index a6bb702..97a9b73 100755\n> --- a/gitweb/gitweb.perl\n> +++ b/gitweb/gitweb.perl\n> @@ -87,6 +87,14 @@ our $projects_list = \"++GITWEB_LIST++\";\n>  # the width (in characters) of the projects list \"Description\" column\n>  our $projects_list_description_width = 25;\n>  \n> +# group projects by category on the projects list\n> +# (enabled if this variable evaluates to true)\n> +our $projects_list_group_categories = 0;\n> +\n> +# default category if none specified\n> +# (leave the empty string for no category)\n> +our $project_list_default_category = \"\";\n> +\n>  # default order of projects list\n>  # valid values are none, project, descr, owner, and age\n>  our $default_projects_order = \"project\";\n\nNice.\n\n> @@ -2023,6 +2031,11 @@ sub git_get_project_description {\n>  \treturn git_get_file_or_project_config('description', $path);\n>  }\n>  \n> +sub git_get_project_category {\n> +\tmy $path = shift;\n> +\treturn git_get_file_or_project_config('category', $path);\n> +}\n> +\n\nGood. Nicely uses earlier patch, which adds this infrastructure.\n\n>  sub git_get_project_ctags {\n>  \tmy $path = shift;\n>  \tmy $ctags = {};\n> @@ -3915,8 +3928,9 @@ sub git_patchset_body {\n>  \n>  # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n>  \n> -# fills project list info (age, description, owner, forks) for each\n> -# project in the list, removing invalid projects from returned list\n> +# fills project list info (age, description, owner, category, forks)\n> +# for each project in the list, removing invalid projects from\n> +# returned list\n>  # NOTE: modifies $projlist, but does not remove entries from it\n>  sub fill_project_list_info {\n>  \tmy ($projlist, $check_forks) = @_;\n> @@ -3939,6 +3953,11 @@ sub fill_project_list_info {\n>  \t\tif (!defined $pr->{'owner'}) {\n>  \t\t\t$pr->{'owner'} = git_get_project_owner(\"$pr->{'path'}\") || \"\";\n>  \t\t}\n> +\t\tif ($projects_list_group_categories && !defined $pr->{'category'}) {\n> +\t\t\tmy $cat = git_get_project_category($pr->{'path'}) ||\n> +\t\t\t                                   $project_list_default_category;\n> +\t\t\t$pr->{'category'} = to_utf8($cat);\n> +\t\t}\n>  \t\tif ($check_forks) {\n>  \t\t\tmy $pname = $pr->{'path'};\n>  \t\t\tif (($pname =~ s/\\.git$//) &&\n\nNice. I see that you choose to go with $pr->{'category'} like existing\n$pr->{'owner'}, rather than $pr->{'cat'} like existing $pr->{'descr'}.\n\n> @@ -3956,6 +3975,19 @@ sub fill_project_list_info {\n>  \treturn @projects;\n>  }\n>  \n> +# returns a hash of categories, containing the list of project\n> +# belonging to each category\n> +sub build_projlist_by_category {\n> +\tmy $projlist = shift;\n> +\tmy %categories;\n> +\n> +\tfor my $pr (@$projlist) {\n> +\t\tpush @{$categories{ $pr->{'category'} }}, $pr;\n> +\t}\n> +\n> +\treturn %categories;\n> +}\n\nThis is very nice and simple way to group by categories, and it works\nquite well with sorting (assuming that @$projlist is already sorted),\nbut I wonder how it works with $from / $to, i.e. with selecting\nprojects.\n\n> +\n>  # print 'sort by' <th> element, generating 'sort by $name' replay link\n>  # if that order is not selected\n>  sub print_sort_th {\n> @@ -4077,7 +4109,36 @@ sub git_project_list_body {\n>  \t\t      \"</tr>\\n\";\n>  \t}\n>  \n> -\tprint_project_rows(\\@projects, $from, $to, $check_forks, $show_ctags);\n> +\tif ($projects_list_group_categories) {\n> +\t\t# only display categories with projects in the $from-$to window\n> +\t\tmy %categories = build_projlist_by_category(\\@projects);\n\nNice... but perhaps it would be better to simply pass $from / $to to\nbuild_projlist_by_category function, and have in %categories only\nprojects which are shown... well, unless filtered out in \nprint_project_rows() by $show_ctags; so I think that there is nonzero\nprobability of empty (no project shown) categories.\n\n> +\t\tforeach my $cat (sort keys %categories) {\n> +\t\t\tmy $num_cats = @{$categories{$cat}};\n> +\n> +\t\t\t# out of the window to display, done\n> +\t\t\tlast if defined $to and $to < 0;\n> +\n> +\t\t\t# in the window to display\n> +\t\t\tif (!defined $from or $from < $num_cats) {\n> +\t\t\t\tunless ($cat eq \"\") {\n> +\t\t\t\t\tprint \"<tr>\\n\";\n> +\t\t\t\t\tif ($check_forks) {\n> +\t\t\t\t\t\tprint \"<td></td>\\n\";\n> +\t\t\t\t\t}\n> +\t\t\t\t\tprint \"<td class=\\\"category\\\" colspan=\\\"5\\\">$cat</td>\\n\";\n> +\t\t\t\t\tprint \"</tr>\\n\";\n> +\t\t\t\t}\n> +\n> +\t\t\t\tprint_project_rows($categories{$cat}, $from, $to, $check_forks, $show_ctags);\n> +\t\t\t}\n> +\n> +\t\t\t# adjust $from/$to offset, keep $from positive\n> +\t\t\t$from = ($from > $num_cats) ? $from - $num_cats : 0 if defined $from;\n> +\t\t\t$to -= $num_cats if defined $to;\n\nI don't think that the games we play with $from / $to would be enough.\nCheck what happens (I think that it wouldn't work correctly) if we have\nsomething like that:\n\n project | category | shown\n --------------------------\n  1      | A        |\n  2      | A        |\n  3      | B        | Y\n  4      | C        | Y\n  5      | B        | Y\n  6      | C        |\n  7      | C        |\n\nIt means that we display for example second page in projects list.\n\n> +\t\t}\n> +\t} else {\n> +\t\tprint_project_rows(\\@projects, $from, $to, $check_forks, $show_ctags);\n> +\t}\n\nNice.\n\n>  \n>  \tif (defined $extra) {\n>  \t\tprint \"<tr>\\n\";\n> -- \n> 1.5.6.5\n> \n> \n\nI'll try to examine the code in more detail later... currently I don't\nknow why but I can't git-am the second patch (this patch) correctly...\n\n-- \nJakub Narębski\nPoland\n"},{"id":"97200","messageId":"87bpvr1hee.wl%seb@cine7.net","threadId":"16558","inReplyTo":"200812050308.52891.jnareb@gmail.com","subject":"Re: [PATCH v3 3/3] gitweb: Optional grouping of projects by category","fromName":"Sébastien Cevey","fromEmail":"seb@cine7.net","sentAt":"2008-12-05T02:32:09Z","receivedAt":"2008-12-05T02:32:09Z","isPatch":true,"sender":{"key":"seb@cine7.net","avatar":"https://avatars.githubusercontent.com/u/36964?v=4"},"body":"At Fri, 5 Dec 2008 03:08:52 +0100, Jakub Narebski wrote:\n\n> Nice... but perhaps it would be better to simply pass $from / $to to\n> build_projlist_by_category function, and have in %categories only\n> projects which are shown...\n\nAh you're right, we could do that, I hadn't thought of it.  Sounds\ncleaner than the $from/$to dance I did in this patch.\n\n> well, unless filtered out in print_project_rows() by $show_ctags; so\n> I think that there is nonzero probability of empty (no project\n> shown) categories.\n\nMh indeed, in fact this could happen any time we reach one of the\n'next' statements in the loop in print_project_rows(), when performing\nsearch, tag filtering, etc...\n\nActually, assuming the project list is split into page, this can also\nlead to empty pages (if no project on the page matches the filter).\nTo avoid empty categories, it's a bit tricky since the header is\nprinted before we determine whether there are matching projects..\n\nI need to read the code more carefully, but it seems that one solution\nwould be to add a function that determines whether a project should be\ndisplayed or not (according to tags and search and forks); then we can\nmap this function on the list of projects.\n\nI could do it if it sounds sane to you.\n\n> I don't think that the games we play with $from / $to would be enough.\n> Check what happens (I think that it wouldn't work correctly) if we have\n> something like that:\n> \n>  project | category | shown\n>  --------------------------\n>   1      | A        |\n>   2      | A        |\n>   3      | B        | Y\n>   4      | C        | Y\n>   5      | B        | Y\n>   6      | C        |\n>   7      | C        |\n\nAFAIK this cannot happen with the current code, since projects are\ngrouped by category (according to the foreach on categories).\n\n> I'll try to examine the code in more detail later... currently I don't\n> know why but I can't git-am the second patch (this patch) correctly...\n\nThis is the third patch, are you sure you applied 1 and 2 before?\n\n\nThanks for your careful and supportive comments!\n\n-- \nSébastien Cevey / inso.cc\n"},{"id":"97201","messageId":"200812051145.09732.jnareb@gmail.com","threadId":"16558","inReplyTo":"87bpvr1hee.wl%seb@cine7.net","subject":"Re: [PATCH v3 3/3] gitweb: Optional grouping of projects by category","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2008-12-05T10:45:08Z","receivedAt":"2008-12-05T10:45:08Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"On Fri, 5 Dec 2008 at 03:32, Sébastien Cevey wrote:\n> At Fri, 5 Dec 2008 03:08:52 +0100, Jakub Narebski wrote:\n> \n> > Nice... but perhaps it would be better to simply pass $from / $to to\n> > build_projlist_by_category function, and have in %categories only\n> > projects which are shown...\n> \n> Ah you're right, we could do that, I hadn't thought of it.  Sounds\n> cleaner than the $from/$to dance I did in this patch.\n\nUsually simpler is better. The more complicated code, the more chances\nfor bugs, and less maintability. I haven't even tried to follow \n$from/$to updating logic, but noticed that we can simply pass $from\nand $to to build_projlist_by_category(), and use loop $from..$to there.\n\n> > well, unless filtered out in print_project_rows() by $show_ctags; so\n> > I think that there is nonzero probability of empty (no project\n> > shown) categories.\n> \n> Mh indeed, in fact this could happen any time we reach one of the\n> 'next' statements in the loop in print_project_rows(), when performing\n> search, tag filtering, etc...\n> \n> Actually, assuming the project list is split into page, this can also\n> lead to empty pages (if no project on the page matches the filter).\n> To avoid empty categories, it's a bit tricky since the header is\n> printed before we determine whether there are matching projects..\n> \n> I need to read the code more carefully, but it seems that one solution\n> would be to add a function that determines whether a project should be\n> displayed or not (according to tags and search and forks); then we can\n> map this function on the list of projects.\n> \n> I could do it if it sounds sane to you.\n\nNah, I think it would be best to postpone dealing with this issue, and\nkeep the code simple at the cost of possibly empty categories. It is\nnot as empty categories are an actual error...\n\nI guess that correct way to deal with this would be to filter projects\nearlier, before grouping by category, and not \"at the last minute\", \nwhen displaying them. It might be even better solution wrt. dividing\nprojects list into pages. But that in my opinion is certainly matter\nfor a separate patch.\n\n> > I'll try to examine the code in more detail later... currently I don't\n> > know why but I can't git-am the second patch (this patch) correctly...\n> \n> This is the third patch, are you sure you applied 1 and 2 before?\n \nSomehow I couldn't apply second patch in series:\n\n $ git am -3 -u \"[PATCH v3 2_3] gitweb: Split git_project_list_body in two functions.txt\"\n Applying: gitweb: Split git_project_list_body in two functions\n error: patch failed: gitweb/gitweb.perl:3972\n error: gitweb/gitweb.perl: patch does not apply\n fatal: sha1 information is lacking or useless (gitweb/gitweb.perl).\n Repository lacks necessary blobs to fall back on 3-way merge.\n Cannot fall back to three-way merge.\n Patch failed at 0001.\n \n> Thanks for your careful and supportive comments!\n\nYou are welcome. Nice to have another gitweb developer :-)\n-- \nJakub Narebski\nPoland\n"},{"id":"97638","messageId":"878wqmz3pl.wl%seb@cine7.net","threadId":"16558","inReplyTo":"200812051145.09732.jnareb@gmail.com","subject":"Re: [PATCH v3 3/3] gitweb: Optional grouping of projects by category","fromName":"Sébastien Cevey","fromEmail":"seb@cine7.net","sentAt":"2008-12-11T23:34:14Z","receivedAt":"2008-12-11T23:34:14Z","isPatch":true,"sender":{"key":"seb@cine7.net","avatar":"https://avatars.githubusercontent.com/u/36964?v=4"},"body":"At Fri, 5 Dec 2008 11:45:08 +0100, Jakub Narebski wrote:\n\n> > > Nice... but perhaps it would be better to simply pass $from / $to to\n> > > build_projlist_by_category function, and have in %categories only\n> > > projects which are shown...\n> > \n> > Ah you're right, we could do that, I hadn't thought of it.  Sounds\n> > cleaner than the $from/$to dance I did in this patch.\n>\n> [...] we can simply pass $from and $to to\n> build_projlist_by_category(), and use loop $from..$to there.\n\nI just tried, it works but we first need to sort @projects by\ncategory.\n\n> Somehow I couldn't apply second patch in series:\n> \n>  $ git am -3 -u \"[PATCH v3 2_3] gitweb: Split git_project_list_body in two functions.txt\"\n>  Applying: gitweb: Split git_project_list_body in two functions\n>  error: patch failed: gitweb/gitweb.perl:3972\n>  error: gitweb/gitweb.perl: patch does not apply\n>  fatal: sha1 information is lacking or useless (gitweb/gitweb.perl).\n>  Repository lacks necessary blobs to fall back on 3-way merge.\n>  Cannot fall back to three-way merge.\n>  Patch failed at 0001.\n\nWhere does it find the reference to the \"necessary blobs\"?  Could it\nbe because I didn't leave the From OBJECTID DATE line in my email\nheader?\n\nSeems like there was a recent change on the line\n\n  my $check_forks = gitweb_check_feature('forks');\n\nso I had to update my patch to apply cleanly on the current master\nHEAD.\n\nI'm gonna re-send the three patches as a new version.\n\n-- \nSébastien Cevey / inso.cc\n"},{"id":"97645","messageId":"200812120113.47051.jnareb@gmail.com","threadId":"16558","inReplyTo":"878wqmz3pl.wl%seb@cine7.net","subject":"Re: [PATCH v3 3/3] gitweb: Optional grouping of projects by category","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2008-12-12T00:13:45Z","receivedAt":"2008-12-12T00:13:45Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"On Fri, 12 Dec 2008, Sébastien Cevey wrote:\n> At Fri, 5 Dec 2008 11:45:08 +0100, Jakub Narebski wrote:\n> \n>>>> Nice... but perhaps it would be better to simply pass $from / $to to\n>>>> build_projlist_by_category function, and have in %categories only\n>>>> projects which are shown...\n>>> \n>>> Ah you're right, we could do that, I hadn't thought of it.  Sounds\n>>> cleaner than the $from/$to dance I did in this patch.\n>>\n>> [...] we can simply pass $from and $to to\n>> build_projlist_by_category(), and use loop $from..$to there.\n> \n> I just tried, it works but we first need to sort @projects by\n> category.\n\nI don't understand. You have the following code:\n\n+# returns a hash of categories, containing the list of project\n+# belonging to each category\n+sub build_projlist_by_category {\n+       my $projlist = shift;\n+       my %categories;\n+\n+       for my $pr (@$projlist) {\n+               push @{$categories{ $pr->{'category'} }}, $pr;\n+       }\n+\n+       return %categories;\n+}\n\nI propose to change it to:\n\n+# returns a hash of categories, containing the list of project\n+# belonging to each category\n+sub build_projlist_by_category {\n+       my ($projlist, $from, $to) = shift;                       # <<<<<<<<<<<<<<<<<<<<<<<\n+\t$from = 0 unless defined $from;                           # +++++++++++++++++++++++\n+\t$to = $#$projlist if (!defined $to || $#$projlist < $to); # +++++++++++++++++++++++\n+       my %categories;\n+\n+       for (my $i = $from; $i <= $to; $i++) {                    # <<<<<<<<<<<<<<<<<<<<<<<\n+\t\t$pr = $projlist->[$i];                            # +++++++++++++++++++++++\n+               push @{$categories{ $pr->{'category'} }}, $pr;\n+       }\n+\n+       return %categories;\n+}\n\nAnd of course update callers to pass $from and $to parameters.\n\nThis code doesn't change _anything_ beside the fact that it can operate\nonly on part of @$projlist.\n\n\nBut as you have noticed some kinds of limiting shown project list size\nmake sense only if done _after_ dividing into categories. Some but not\nall. For example page length limiting after sorting by name, \ndescription, owner or age (sorting which takes place before dividing\ninto categories) makes sense if it is done _before_ categorization:\nwe want to show e.g. 100 freshest projects, and not up to 100 projects\nfrom first categories, with last category showing maybe only freshest\nprojects in this category.\n\n\nBesides making selection of projects to show operate on list of projects\ninstead of being done just before display is IMHO better, more flexible,\nand more robust solution.\n\n> I'm gonna re-send the three patches as a new version.\n\nThanks in advance.\n\n-- \nJakub Narebski\nPoland\n"},{"id":"97648","messageId":"87zlj2xm35.wl%seb@cine7.net","threadId":"16558","inReplyTo":"200812120113.47051.jnareb@gmail.com","subject":"Re: [PATCH v3 3/3] gitweb: Optional grouping of projects by category","fromName":"Sébastien Cevey","fromEmail":"seb@cine7.net","sentAt":"2008-12-12T00:40:14Z","receivedAt":"2008-12-12T00:40:14Z","isPatch":true,"sender":{"key":"seb@cine7.net","avatar":"https://avatars.githubusercontent.com/u/36964?v=4"},"body":"At Fri, 12 Dec 2008 01:13:45 +0100, Jakub Narebski wrote:\n\n> I just tried, it works but we first need to sort @projects by\n> category.\n> \n> I don't understand.\n> [...]\n> I propose to change it to:\n\nWell in my previous iteration of the patch (v3), the printing of\nprojects with categories is done using:\n\n  foreach my $cat (sort keys %categories) {\n\nSo everything was already sorted by category (and then by whichever\nproperty you picked inside each category).  You seemed okay with it,\nbut requested that I documented that behaviour in the commit log.\n\nTo maintain the same result with your proposed change (which is what I\nsubmitted in my patch), we need to sort by categories first (AFAIK\nPerl sort retains the original order inside equivalence classes of\ncomparison key?), otherwise splice(projlist, from, to) doesn't return\nthe expected subset.\n\n-- \nSébastien Cevey / inso.cc\n"},{"id":"97655","messageId":"200812120303.56997.jnareb@gmail.com","threadId":"16558","inReplyTo":"87zlj2xm35.wl%seb@cine7.net","subject":"Re: [PATCH v3 3/3] gitweb: Optional grouping of projects by category","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2008-12-12T02:03:55Z","receivedAt":"2008-12-12T02:03:55Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"On Fri, 12 Dec 2008, Sébastien Cevey wrote:\n> At Fri, 12 Dec 2008 01:13:45 +0100, Jakub Narebski wrote:\n> \n> > I just tried, it works but we first need to sort @projects by\n> > category.\n> > \n> > I don't understand.\n> > [...]\n> > I propose to change it to:\n> \n> Well in my previous iteration of the patch (v3), the printing of\n> projects with categories is done using:\n> \n>   foreach my $cat (sort keys %categories) {\n> \n> So everything was already sorted by category (and then by whichever\n> property you picked inside each category).  You seemed okay with it,\n> but requested that I documented that behaviour in the commit log.\n\nBut this does not mean that sorting by categories is necessary, or\neven wanted (see below). This foreach _sorts_ by categories as primary\nkey using kind of bucket sort algorithm.\n \n> To maintain the same result with your proposed change (which is what I\n> submitted in my patch), we need to sort by categories first (AFAIK\n> Perl sort retains the original order inside equivalence classes of\n> comparison key?), otherwise splice(projlist, from, to) doesn't return \n> the expected subset.\n\nPerl requires \"use sort 'stable';\" pragma to ensure stable sort.\n\nAnd no, we don't need to sort by categories first.  Let me explain\nin more detail a bit.\n\nLet us assume that $from and $to is actually used to divide projects\nlist into categories (which goal is incompatible with searching\nprojects, limiting to given tag/tagset and hiding forked as it is done\nnow, at the display time; it has to be done _before_ pagination).\nThey are used to display first page, i.e. repositories numbered 1..N\nin current ordering, or N..2*N, or 2*N..3*N to show next pages. Let us\nhave the following project list:\n\n  Project       Category\n  ---------------------------\n  1             a\n  2             b\n  3             b\n  4             a\n\nIf categories are not shown, and page limit is 2, then project would\nbe displayed like this:\n\n\n  page 1          page 2\n  ------          ------\n  1               3\n  2               4\n\nNow _without_ sorting by category upfront, those pages would look like\nthe following if grouping by category is enabled:\n\nA.page 1          page 2\n  ------          ------\n  [a]             [a]\n  1               4\n  [b]             [b]\n  2               3\n\nWhat is not visible in this example is that projects inside category\nwould be sorted by given order.\n\n\nNow if you would sort by categories _before_ pagination, like you\n(from what I understand) proposed, you would have (assuming that\nyou used \"use sort 'stable'\" inside block):\n\n  Project       Category\n  ---------------------------\n  1             a\n  4             a\n  2             b\n  3             b\n\nPagination would then look like the following:\n\nB.page 1          page 2\n  ------          ------\n  [a]             [b]\n  1               2\n  4               3\n\n\nNow which result you consider correct depends on the point of view.\nFirst is sort, paginate, sort; second is sort, sort, paginate.\nFirst have first N repositories in given order on first page, perhaps\nreordered a bit by categories, second doesn't have this feature.\nI think that the case A is more correct, but you might disagree.\n\n\nLet us change example a bit:\n\n  Project       Category\n  ---------------------------\n  1             b\n  2             a\n  3             a\n  4             b\n\nA.page 1          page 2\n  ------          ------\n  [a]             [a]\n  2               3\n  [b]             [b]\n  1               4\n\nB.page 1          page 2\n  ------          ------\n  [a]             [b]\n  2               1\n  3               4\n\nP.S. It is IMHO better to use\n\n \tfor (my $i = $from; $i <= $to; $i++) {\n\nthan the style which is not used elsewhere in gitweb, from what\nI remember\n\n \tforeach my $i ($from..$to) {\n\nI might also be inefficient as it generates temporary array which\nmight be quite big; I don't know if Perl 5.8.x, the oldest version\none can sensibly use with gitweb I think, has this bug or not.\n-- \nJakub Narebski\nPoland\n"},{"id":"97679","messageId":"87y6ymxf4k.wl%seb@cine7.net","threadId":"16558","inReplyTo":"200812120303.56997.jnareb@gmail.com","subject":"Re: [PATCH v3 3/3] gitweb: Optional grouping of projects by category","fromName":"Sébastien Cevey","fromEmail":"seb@cine7.net","sentAt":"2008-12-12T03:10:35Z","receivedAt":"2008-12-12T03:10:35Z","isPatch":true,"sender":{"key":"seb@cine7.net","avatar":"https://avatars.githubusercontent.com/u/36964?v=4"},"body":"At Fri, 12 Dec 2008 03:03:55 +0100, Jakub Narebski wrote:\n\n> And no, we don't need to sort by categories first.  Let me explain\n> in more detail a bit.\n\nThanks for the detailed explanation, I understand your preference.\nBut as you said, it's a bit arbitrary, I think none is completely\nobvious.\n\n\nI don't really have a strong opinion about which is best, but just to\nillustrate what made me go for the solution B, let me show another\nexample:\n\nname / date / cat\n1      2006    A\n2      2003    B\n3      2005    B\n4      2003    A\n5      2000    A\n6      2008    C\n7      2007    C\n8      2001    B\n9      2005    A\n\nWe then sort by name and split in pages of N=3 (sorted by cat on each\npage):\n\nA:sort(name) split(max=3) subsort(cat)\n  1  2006  A     4  2003  A     9  2005  A\n  2  2003  B     5  2000  A     8  2001  B\n  3  2005  B     6  2008  C     7  2007  C\n\nB:sort(cat) subsort(name) split(max=4)\n  1  2006  A     9  2005  A     8  2001  B\n  4  2003  A     2  2003  B     6  2008  C\n  5  2000  A     3  2005  B     7  2007  C\n\nWith B, we have the second top-entry (2) relegated to the second page,\nwhich might be surprising because we ordered by name.  But what I find\nweird about A is that because of the per-page category sorting, the\n\"top-sorted entries at the top\" isn't true either (page 3).  We have\n\"reshuffled\" the result of 'sort(name) split(max=3)' on each page.\n\nTo be truly fair to the main sorting, we should not try to group\ncategories and display the header for each consecutive group of\nentries from a distinct category:\n\nA-:sort(name) split(max=3)\n  1  2006  A     4  2003  A     7  2007  C\n  2  2003  B     5  2000  A     8  2001  B\n  3  2005  B     6  2008  C     9  2005  A\n\nWhich in this case is painless as it only affects page 3, but it could\nlead to a mess of interleaved categories, and we kind of lose the\npurpose of category groups in the first place..\n\nThe point is that with A, you cannot determine whether you're on the\nright page to find project P (even if you know its category) by\nchecking whether it's in the interval between the top and bottom\nentries.\n\n\nIt's perhaps even more apparent if we sort by date:\n\nA:sort(year) split(max=3) subsort(cat)\n  1  2006  A     9  2005  A     4  2003  A\n  6  2008  C     3  2005  B     5  2000  A\n  7  2007  C     2  2003  B     8  2001  B\n\nB:sort(cat) subsort(year) split(max=4)\n  1  2006  A     4  2003  A     3  2005  B\n  9  2005  A     8  2001  B     7  2007  C\n  5  2000  A     2  2003  B     6  2008  C\n\nIt feels kind of unnatural that not only projects are not sorted by\ndate on each page (they are inside categories), but moreover\ncategories are spread over all pages.\n\n\nI guess it depends on your use case, and whether categories are\nimportant or known by the user.  I personally don't really care (I\nnever split stuff into pages in the gitweb I use), so I can do a new\nversion of my patch that does A if you prefer, just let me know.  I\njust wanted to clarify that both solutions sort of suck :-)\n\n> P.S. It is IMHO better to use\n> \n>  \tfor (my $i = $from; $i <= $to; $i++) {\n\nAh sorry, I took the words from your earlier email too literally (\"we\ncan [...] use loop $from..$to there\"). That's what happens when\npseudocode is actually valid syntax in a language!\n\nCheers,\n\n-- \nSébastien Cevey / inso.cc\n"},{"id":"97688","messageId":"200812121026.07694.jnareb@gmail.com","threadId":"16558","inReplyTo":"87y6ymxf4k.wl%seb@cine7.net","subject":"Re: [PATCH v3 3/3] gitweb: Optional grouping of projects by category","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2008-12-12T09:26:06Z","receivedAt":"2008-12-12T09:26:06Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"On Fri, 12 Dec 2008, Sébastien Cevey wrote:\n> At Fri, 12 Dec 2008 03:03:55 +0100, Jakub Narebski wrote:\n> \n> > And no, we don't need to sort by categories first.  Let me explain\n> > in more detail a bit.\n> \n> Thanks for the detailed explanation, I understand your preference.\n> But as you said, it's a bit arbitrary, I think none is completely\n> obvious.\n\nFirst, I feel a bit bad for derailing this patch. Currently gitweb\ndoes not do pagination of projects list; it is not even possible in\na sane way with current way project searching/selecting is implemented.\nSo the whole build_projlist_by_category() respecting $from and $to is\nmoot point.\n\nSo if we don't use it, even if it is nice to have for the future, we\ndon't need to pay cost of extra stable sorting.\n\n> \n> I don't really have a strong opinion about which is best, but just to\n> illustrate what made me go for the solution B, let me show another\n> example:\n> \n> name / date / cat\n> 1      2006    A\n> 2      2003    B\n> 3      2005    B\n> 4      2003    A\n> 5      2000    A\n> 6      2008    C\n> 7      2007    C\n> 8      2001    B\n> 9      2005    A\n> \n> We then sort by name and split in pages of N=3 (sorted by cat on each\n> page):\n> \n> A:sort(name) split(max=3) subsort(cat)\n>   1  2006  A     4  2003  A     9  2005  A\n>   2  2003  B     5  2000  A     8  2001  B\n>   3  2005  B     6  2008  C     7  2007  C\n> \n> B:sort(cat) subsort(name) split(max=3)\n>   1  2006  A     9  2005  A     8  2001  B\n>   4  2003  A     2  2003  B     6  2008  C\n>   5  2000  A     3  2005  B     7  2007  C\n> \n> With B, we have the second top-entry (2) relegated to the second page,\n> which might be surprising because we ordered by name.  But what I find\n> weird about A is that because of the per-page category sorting, the\n> \"top-sorted entries at the top\" isn't true either (page 3).  We have\n> \"reshuffled\" the result of 'sort(name) split(max=3)' on each page.\n\n[...]\n> It's perhaps even more apparent if we sort by date:\n> \n> A:sort(year) split(max=3) subsort(cat)\n>   1  2006  A     9  2005  A     4  2003  A\n>   6  2008  C     3  2005  B     5  2000  A\n>   7  2007  C     2  2003  B     8  2001  B\n> \n> B:sort(cat) subsort(year) split(max=4)\n>   1  2006  A     4  2003  A     3  2005  B\n>   9  2005  A     8  2001  B     7  2007  C\n>   5  2000  A     2  2003  B     6  2008  C\n> \n> It feels kind of unnatural that not only projects are not sorted by\n> date on each page (they are inside categories), but moreover\n> categories are spread over all pages.\n> \n> \n> I guess it depends on your use case, and whether categories are\n> important or known by the user.  I personally don't really care (I\n> never split stuff into pages in the gitweb I use), so I can do a new\n> version of my patch that does A if you prefer, just let me know.  I\n> just wanted to clarify that both solutions sort of suck :-)\n\nWell, with version A you can (I think) simply change\n\n  foreach my $cat (sort keys %categories) {\n\nto\n\n  foreach my $cat (sort \n  \t{ cmp_cat($projlist, \\%categories, $oi, $a, $b) } keys %categories) {\n\nto have the following output (see the difference on page 3)\n\nA':sort(name) split(max=3) subsort(sort(cat,name))\n  1  2006  A     4  2003  A     7  2007  C\n  2  2003  B     5  2000  A     8  2001  B\n  3  2005  B     6  2008  C     9  2005  A\n\nwhere sort_cat might be something like (we took advantage that\ncategories in %categories have at least one project):\n\n  sub cmp_cat {\n  \tmy ($projlist, $cats, $oi, $a, $b) = @_;\n  \tmy ($aa, $bb);\n  \t# projects in categories are sorted, so we can compare first\n  \t# project from a category to sort categories in given ordering\n  \t$aa = $projlist->{$cats->{$a}[0]};\n  \t$bb = $projlist->{$cats->{$b}[0]};\n  \tif ($oi->{'type'} eq 'str') {\n\t\treturn $aa->{$oi->{'key'}} cmp $bb->{$oi->{'key'}};\n\t} else {\n\t\treturn $aa->{$oi->{'key'}} <=> $bb->{$oi->{'key'}};\n\t}\n  }\n\n-- \nJakub Narebski\nPoland\n"}]}