{"thread":{"id":"26688","subject":"[PATCH 2/3] gitweb: Modularized git_get_project_description to be more generic","startedAt":"2011-03-07T23:13:32Z","lastAt":"2011-03-07T23:13:35Z","messageCount":4,"participants":["Jakub Narebski"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"162943","messageId":"1299539616-19991-1-git-send-email-jnareb@gmail.com","threadId":"26688","inReplyTo":null,"subject":"[RFC/PATCH 0/3] gitweb: Categories support","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2011-03-07T23:13:32Z","receivedAt":"2011-03-07T23:13:32Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"This series is based on\n\n  \"[PATCHv2/RFC] gitweb: Restructure projects list generation\"\n  http://thread.gmane.org/gmane.comp.version-control.git/167996/focus=168321\n\n  http://repo.or.cz/w/git/jnareb-git.git/commit/420071752d13dcecd59e794d82285e7e142ef75f\n  ('gitweb/web' branch in http://repo.or.cz/w/git/jnareb-git.git repository)\n\nwhich is the main reason why this series is marked as an RFC.\n\nThis series is long in wait port of Sebastien Cevey series from\nDecember 2008\n\n  http://thread.gmane.org/gmane.comp.version-control.git/102844\n\nto modern gitweb.  It was waiting for abovementioned restructuring of\nprojects list generation.\n\nUwe, it might be an alternative to use ctags (content tags, aka\nproject labels).\n\nSebastien Cevey (3):\n  gitweb: Split git_project_list_body in two functions\n  gitweb: Modularized git_get_project_description to be more generic\n  gitweb: Optional grouping of projects by category\n\n gitweb/README                          |   16 +++\n gitweb/gitweb.perl                     |  166 +++++++++++++++++++++++---------\n gitweb/static/gitweb.css               |    7 ++\n t/t9500-gitweb-standalone-no-errors.sh |    8 ++\n 4 files changed, 151 insertions(+), 46 deletions(-)\n\n-- \n1.7.3\n"},{"id":"162944","messageId":"1299539616-19991-2-git-send-email-jnareb@gmail.com","threadId":"26688","inReplyTo":"1299539616-19991-1-git-send-email-jnareb@gmail.com","subject":"[PATCH 1/3] gitweb: Split git_project_list_body in two functions","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2011-03-07T23:13:33Z","receivedAt":"2011-03-07T23:13:33Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"From: Sebastien Cevey <seb@cine7.net>\n\nExtract the printing of project rows (body/contents of projects list\ntable) on the 'project_list' page into a separate git_project_list_rows\nfunction. This makes it easier to reuse the code to print different\nsubsets of the whole project list.\n\n[jn: Updated to post restructuring projects list generation]\n\nSigned-off-by: Sebastien Cevey <seb@cine7.net>\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\n---\n gitweb/gitweb.perl |   89 +++++++++++++++++++++++++++++-----------------------\n 1 files changed, 50 insertions(+), 39 deletions(-)\n\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex 996b647..0020b13 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -4868,6 +4868,55 @@ sub format_sort_th {\n \treturn $sort_th;\n }\n \n+sub git_project_list_rows {\n+\tmy ($projlist, $from, $to, $check_forks) = @_;\n+\n+\t$from = 0 unless defined $from;\n+\t$to = $#$projlist if (!defined $to || $#$projlist < $to);\n+\n+\tmy $alternate = 1;\n+\tfor (my $i = $from; $i <= $to; $i++) {\n+\t\tmy $pr = $projlist->[$i];\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+\n+\t\tif ($check_forks) {\n+\t\t\tprint \"<td>\";\n+\t\t\tif ($pr->{'forks'}) {\n+\t\t\t\tmy $nforks = scalar @{$pr->{'forks'}};\n+\t\t\t\tif ($nforks > 0) {\n+\t\t\t\t\tprint $cgi->a({-href => href(project=>$pr->{'path'}, action=>\"forks\"),\n+\t\t\t\t\t               -title => \"$nforks forks\"}, \"+\");\n+\t\t\t\t} else {\n+\t\t\t\t\tprint $cgi->span({-title => \"$nforks forks\"}, \"+\");\n+\t\t\t\t}\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 \t# actually uses global variable $project\n \tmy ($projlist, $order, $from, $to, $extra, $no_header) = @_;\n@@ -4928,47 +4977,9 @@ 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-\tfor (my $i = $from; $i <= $to; $i++) {\n-\t\tmy $pr = $projects[$i];\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+\tgit_project_list_rows(\\@projects, $from, $to, $check_forks);\n \n-\t\tif ($check_forks) {\n-\t\t\tprint \"<td>\";\n-\t\t\tif ($pr->{'forks'}) {\n-\t\t\t\tmy $nforks = scalar @{$pr->{'forks'}};\n-\t\t\t\tif ($nforks > 0) {\n-\t\t\t\t\tprint $cgi->a({-href => href(project=>$pr->{'path'}, action=>\"forks\"),\n-\t\t\t\t\t               -title => \"$nforks forks\"}, \"+\");\n-\t\t\t\t} else {\n-\t\t\t\t\tprint $cgi->span({-title => \"$nforks forks\"}, \"+\");\n-\t\t\t\t}\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 \tif (defined $extra) {\n \t\tprint \"<tr>\\n\";\n \t\tif ($check_forks) {\n-- \n1.7.3\n"},{"id":"162942","messageId":"1299539616-19991-3-git-send-email-jnareb@gmail.com","threadId":"26688","inReplyTo":"1299539616-19991-1-git-send-email-jnareb@gmail.com","subject":"[PATCH 2/3] gitweb: Modularized git_get_project_description to be more generic","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2011-03-07T23:13:34Z","receivedAt":"2011-03-07T23:13:34Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"From: Sebastien Cevey <seb@cine7.net>\n\nIntroduce 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\nThis would be used in next commit to retrieve category for a project,\nwhich is to be stored in the same way as project description.\n\nSigned-off-by: Sebastien Cevey <seb@cine7.net>\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\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 0020b13..c032cd4 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -2550,18 +2550,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 config variable either from file named as the variable\n+# itself in the repository ($GIT_DIR/$name file), or from gitweb.$name\n+# configuration variable in the repository config file.\n+sub git_get_file_or_project_config {\n+\tmy ($path, $name) = @_;\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($path, 'description');\n }\n \n sub git_get_project_ctags {\n-- \n1.7.3\n"},{"id":"162945","messageId":"1299539616-19991-4-git-send-email-jnareb@gmail.com","threadId":"26688","inReplyTo":"1299539616-19991-1-git-send-email-jnareb@gmail.com","subject":"[PATCH 3/3] gitweb: Optional grouping of projects by category","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2011-03-07T23:13:35Z","receivedAt":"2011-03-07T23:13:35Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"From: Sebastien Cevey <seb@cine7.net>\n\nThis 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 'gitweb.category' variable in its configuration file. By default,\nprojects are 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 code. The CSS\nfor categories is inspired from Gustavo Sverzut Barbieri's patch to\ngroup projects by path.\n\nThanks to Florian Ragwitz for Perl tips.\n\n[jn: Updated to post restructuring projects list generation, fixed bugs,\n added very basic test in t9500 that there are no warnings from Perl.]\n\nSigned-off-by: Sebastien Cevey <seb@cine7.net>\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\n---\n gitweb/README                          |   16 ++++++++\n gitweb/gitweb.perl                     |   61 ++++++++++++++++++++++++++++++--\n gitweb/static/gitweb.css               |    7 ++++\n t/t9500-gitweb-standalone-no-errors.sh |    8 ++++\n 4 files changed, 89 insertions(+), 3 deletions(-)\n\ndiff --git a/gitweb/README b/gitweb/README\nindex 4a67393..6cdf51b 100644\n--- a/gitweb/README\n+++ b/gitweb/README\n@@ -207,6 +207,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@@ -314,6 +323,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.perl b/gitweb/gitweb.perl\nindex c032cd4..1b71934 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -115,6 +115,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@@ -2572,6 +2580,11 @@ sub git_get_project_description {\n \treturn git_get_file_or_project_config($path, 'description');\n }\n \n+sub git_get_project_category {\n+\tmy $path = shift;\n+\treturn git_get_file_or_project_config($path, 'category');\n+}\n+\n sub git_get_project_ctags {\n \tmy $path = shift;\n \tmy $ctags = {};\n@@ -4799,8 +4812,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 = shift;\n@@ -4826,6 +4840,12 @@ sub fill_project_list_info {\n \t\tif ($show_ctags) {\n \t\t\t$pr->{'ctags'} = git_get_project_ctags($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+\n \t\tpush @projects, $pr;\n \t}\n \n@@ -4853,6 +4873,23 @@ sub sort_projects_list {\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, $from, $to) = @_;\n+\tmy %categories;\n+\n+\t$from = 0 unless defined $from;\n+\t$to = $#$projlist if (!defined $to || $#$projlist < $to);\n+\n+\tfor (my $i = $from; $i <= $to; $i++) {\n+\t\tmy $pr = $projlist->[$i];\n+\t\tpush @{$categories{ $pr->{'category'} }}, $pr;\n+\t}\n+\n+\treturn wantarray ? %categories : \\%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@@ -4986,7 +5023,25 @@ sub git_project_list_body {\n \t\t      \"</tr>\\n\";\n \t}\n \n-\tgit_project_list_rows(\\@projects, $from, $to, $check_forks);\n+\tif ($projects_list_group_categories) {\n+\t\t# only display categories with projects in the $from-$to window\n+\t\t@projects = sort {$a->{'category'} cmp $b->{'category'}} @projects[$from..$to];\n+\t\tmy %categories = build_projlist_by_category(\\@projects, $from, $to);\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\\\">\".esc_html($cat).\"</td>\\n\";\n+\t\t\t\tprint \"</tr>\\n\";\n+\t\t\t}\n+\n+\t\t\tgit_project_list_rows($categories{$cat}, undef, undef, $check_forks);\n+\t\t}\n+\t} else {\n+\t\tgit_project_list_rows(\\@projects, $from, $to, $check_forks);\n+\t}\n \n \tif (defined $extra) {\n \t\tprint \"<tr>\\n\";\ndiff --git a/gitweb/static/gitweb.css b/gitweb/static/gitweb.css\nindex 79d7eeb..4df2d16 100644\n--- a/gitweb/static/gitweb.css\n+++ b/gitweb/static/gitweb.css\n@@ -295,6 +295,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/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh\nindex 71ef0ac..f5648a6 100755\n--- a/t/t9500-gitweb-standalone-no-errors.sh\n+++ b/t/t9500-gitweb-standalone-no-errors.sh\n@@ -644,4 +644,12 @@ test_expect_success \\\n \t'ctags: search projects by non existent tag' \\\n \t'gitweb_run \"by_tag=non-existent\"'\n \n+# ----------------------------------------------------------------------\n+# categories\n+\n+test_expect_success \\\n+\t'categories: projects list, only default category' \\\n+\t'echo \"\\$projects_list_group_categories = 1;\" >>gitweb_config.perl &&\n+\t gitweb_run'\n+\n test_done\n-- \n1.7.3\n"}]}