{"thread":{"id":"27952","subject":"[PATCH/RFC 0/6] gitweb: Improve project search","startedAt":"2011-07-29T11:52:53Z","lastAt":"2011-07-30T14:57:47Z","messageCount":8,"participants":["Jakub Narebski"],"isPatch":true,"patchVersion":1,"patchTotal":6},"messages":[{"id":"172325","messageId":"1311940379-9608-1-git-send-email-jnareb@gmail.com","threadId":"27952","inReplyTo":null,"subject":"[PATCH/RFC 0/6] gitweb: Improve project search","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2011-07-29T11:52:53Z","receivedAt":"2011-07-29T11:52:53Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"This patch series started a bit strange, by noticing that I have had\ngitweb-Project-search patch in my unapplied StGit stack for gitweb/web\nbranch.  I tried to re-do it on top of current codebase... only to\nrealize that gitweb already has project search :-)\n\nSo this series turned into enhancing project search to the state that\nit was in said patch, splitting it into smaller parts (into separate\npatches), and further improving.\n\nThe first patch is not really necessary, at least for now.  It was\nintended for the situation where we could configure gitweb to show\n_only_ project search form (like on http://repo.or.cz), to be able to\nshow all projects via \"search type\" show_all... which might be added\nin the future.\n\nThe last two patches are new, and probably should be taken together.\nThe issue with them is that the second patch turns off shortening of\nproject description in project search view.  Fixing that would require\nmore complicated code... but perhaps current situation is all right.\n\n\nCC-ing John 'Warthog9' Hawley of git.kernel.org, and Petr 'Pasky' Baudis\nof repo.or.cz\n\n\nJakub Narebski (6):\n  gitweb: Allow underscore in $searchtype ('st')\n  gitweb: Improve projects search form\n  gitweb: Option for filling only specified info in\n    fill_project_list_info\n  gitweb: Faster project search\n  gitweb: Highlight matched part of project name when searching\n    projects\n  gitweb: Highlight matched part of project description when searching\n    projects\n\n gitweb/gitweb.perl       |  110 +++++++++++++++++++++++++++++++++++++--------\n gitweb/static/gitweb.css |    7 +++-\n 2 files changed, 96 insertions(+), 21 deletions(-)\n\n-- \n1.7.5\n"},{"id":"172326","messageId":"1311940379-9608-2-git-send-email-jnareb@gmail.com","threadId":"27952","inReplyTo":"1311940379-9608-1-git-send-email-jnareb@gmail.com","subject":"[PATCH/RFC 1/6] gitweb: Allow underscore in $searchtype ('st')","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2011-07-29T11:52:54Z","receivedAt":"2011-07-29T11:52:54Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"This makes searchtype containing underscore '_' to pass parameter\nvalidation in evaluate_and_validate_params().\n\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\n---\nThis is not necessary in this series, and could be left for later when\nwe need it.\n\n gitweb/gitweb.perl |    2 +-\n 1 files changed, 1 insertions(+), 1 deletions(-)\n\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex f858d1b..7ec1621 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -1057,7 +1057,7 @@ sub evaluate_and_validate_params {\n \n \tour $searchtype = $input_params{'searchtype'};\n \tif (defined $searchtype) {\n-\t\tif ($searchtype =~ m/[^a-z]/) {\n+\t\tif ($searchtype =~ m/[^a-z_]/) {\n \t\t\tdie_error(400, \"Invalid searchtype parameter\");\n \t\t}\n \t}\n-- \n1.7.5\n"},{"id":"172331","messageId":"1311940379-9608-3-git-send-email-jnareb@gmail.com","threadId":"27952","inReplyTo":"1311940379-9608-1-git-send-email-jnareb@gmail.com","subject":"[PATCH/RFC 2/6] gitweb: Improve projects search form","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2011-07-29T11:52:55Z","receivedAt":"2011-07-29T11:52:55Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Refactor generating project search form into git_project_search_form(),\nmake text field wider and add on mouse over explanation (via \"title\"\nattribute), add an option to use regular expressions, and replace\n'Search:' label with [Search] button.\n\nAlso add \"List all projects\" link to make it easier to go back from\nsearch result to list of all projects.\n\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\n---\nThe most useful part is the user interface for turning on regular\nexpression search (it was always there, but you had to hand-edit URL),\nI think.\n\nBy the way, I was reminded (on #perl IRC channel on FreeNode) that\nallowing regular expression search is not safe against DoS because of\nrecursive backtracking regexps.  If one worries about that, one can\nalways change regexp engine to more safe one, for example putting the\nfollowing in gitweb config file:\n\n  use re::engine::RE2 -strict => 1;\n\nto use RE2 fast regexp engine from Google (http://code.google.com/p/re2).\n\nThere was some concern about extended pattern '(?{ code })' that\nallows running arbitrary Perl code... but re(3pm) says that \"no re\n'eval';\" is default, which means that such construct is disallowed\nwhen regular expression contains variable interpolation.\n\n\nNote that if there is no match, you would get 'list all project' link\ntwice, once from search form, one from search result... but I don't\nthink it is something to worry about.\n\n gitweb/gitweb.perl       |   27 ++++++++++++++++++++++-----\n gitweb/static/gitweb.css |    7 ++++++-\n 2 files changed, 28 insertions(+), 6 deletions(-)\n\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex 7ec1621..3e69705 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -4968,6 +4968,26 @@ sub git_patchset_body {\n \n # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n \n+sub git_project_search_form {\n+\tmy ($searchtext, $search_use_regexp);\n+\n+\tprint \"<div class=\\\"projsearch\\\">\\n\";\n+\tprint $cgi->startform(-method => 'get', -action => $my_uri) .\n+\t      $cgi->hidden(-name => 'a', -value => 'project_list') . \"\\n\" .\n+\t      $cgi->textfield(-name => 's', -value => $searchtext,\n+\t                      -title => 'Search project by name and description',\n+\t                      -size => 60) . \"\\n\" .\n+\t      \"<span title=\\\"Extended regular expression\\\">\" .\n+\t      $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',\n+\t                     -checked => $search_use_regexp) .\n+\t      \"</span>\\n\" .\n+\t      $cgi->submit(-name => 'btnS', -value => 'Search') .\n+\t      $cgi->end_form() . \"\\n\" .\n+\t      $cgi->a({-href => href(project => undef, searchtext => undef)},\n+\t              'List all projects') . \"<br />\\n\";\n+\tprint \"</div>\\n\";\n+}\n+\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@@ -5835,11 +5855,8 @@ sub git_project_list {\n \t\tinsert_file($home_text);\n \t\tprint \"</div>\\n\";\n \t}\n-\tprint $cgi->startform(-method => \"get\") .\n-\t      \"<p class=\\\"projsearch\\\">Search:\\n\" .\n-\t      $cgi->textfield(-name => \"s\", -value => $searchtext) . \"\\n\" .\n-\t      \"</p>\" .\n-\t      $cgi->end_form() . \"\\n\";\n+\n+\tgit_project_search_form($searchtext, $search_use_regexp);\n \tgit_project_list_body(\\@list, $order);\n \tgit_footer_html();\n }\ndiff --git a/gitweb/static/gitweb.css b/gitweb/static/gitweb.css\nindex 7d88509..3cdaaa3 100644\n--- a/gitweb/static/gitweb.css\n+++ b/gitweb/static/gitweb.css\n@@ -490,8 +490,13 @@ div.search {\n \tright: 12px\n }\n \n-p.projsearch {\n+div.projsearch {\n \ttext-align: center;\n+\tmargin: 20px 0px;\n+}\n+\n+div.projsearch form {\n+\tmargin-bottom: 2px;\n }\n \n td.linenr {\n-- \n1.7.5\n"},{"id":"172327","messageId":"1311940379-9608-4-git-send-email-jnareb@gmail.com","threadId":"27952","inReplyTo":"1311940379-9608-1-git-send-email-jnareb@gmail.com","subject":"[PATCH/RFC 3/6] gitweb: Option for filling only specified info in fill_project_list_info","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2011-07-29T11:52:56Z","receivedAt":"2011-07-29T11:52:56Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Signed-off-by: Jakub Narebski <jnareb@gmail.com>\n---\nThe API was tweaked to better fit the following patch.\n\nThis patch probably needs longer commit message... or squashing with\nthe following commit.\n\n gitweb/gitweb.perl |   39 +++++++++++++++++++++++++++++----------\n 1 files changed, 29 insertions(+), 10 deletions(-)\n\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex 3e69705..4c66307 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -4988,35 +4988,54 @@ sub git_project_search_form {\n \tprint \"</div>\\n\";\n }\n \n+# entry for given $key doesn't need filling if either $key already exists\n+# in $project_info hash, or we are interested only in subset of keys\n+# and given key is not among @fill_only.\n+sub project_info_needs_filling {\n+\tmy ($project_info, $key, @fill_only) = @_;\n+\n+\tif (!@fill_only ||            # we are interested in everything\n+\t    grep { $key eq $_ } @fill_only) { # or key is in @fill_only\n+\t\treturn !exists $project_info->{$key};\n+\t}\n+\treturn 0;\n+}\n+\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+# returned list, or fill only specified info (removing invalid projects\n+# only when filling 'age').\n+#\n # NOTE: modifies $projlist, but does not remove entries from it\n sub fill_project_list_info {\n-\tmy $projlist = shift;\n+\tmy ($projlist, @fill_only) = @_;\n \tmy @projects;\n \n \tmy $show_ctags = gitweb_check_feature('ctags');\n  PROJECT:\n \tforeach my $pr (@$projlist) {\n-\t\tmy (@activity) = git_get_last_activity($pr->{'path'});\n-\t\tunless (@activity) {\n-\t\t\tnext PROJECT;\n+\t\tif (project_info_needs_filling($pr, 'age', @fill_only)) {\n+\t\t\tmy (@activity) = git_get_last_activity($pr->{'path'});\n+\t\t\tunless (@activity) {\n+\t\t\t\tnext PROJECT;\n+\t\t\t}\n+\t\t\t($pr->{'age'}, $pr->{'age_string'}) = @activity;\n \t\t}\n-\t\t($pr->{'age'}, $pr->{'age_string'}) = @activity;\n-\t\tif (!defined $pr->{'descr'}) {\n+\t\tif (project_info_needs_filling($pr, 'descr', @fill_only)) {\n \t\t\tmy $descr = git_get_project_description($pr->{'path'}) || \"\";\n \t\t\t$descr = to_utf8($descr);\n \t\t\t$pr->{'descr_long'} = $descr;\n \t\t\t$pr->{'descr'} = chop_str($descr, $projects_list_description_width, 5);\n \t\t}\n-\t\tif (!defined $pr->{'owner'}) {\n+\t\tif (project_info_needs_filling($pr, 'owner', @fill_only)) {\n \t\t\t$pr->{'owner'} = git_get_project_owner(\"$pr->{'path'}\") || \"\";\n \t\t}\n-\t\tif ($show_ctags) {\n+\t\tif ($show_ctags &&\n+\t\t    project_info_needs_filling($pr, 'ctags', @fill_only)) {\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\tif ($projects_list_group_categories &&\n+\t\t    project_info_needs_filling($pr, 'category', @fill_only)) {\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-- \n1.7.5\n"},{"id":"172329","messageId":"1311940379-9608-5-git-send-email-jnareb@gmail.com","threadId":"27952","inReplyTo":"1311940379-9608-1-git-send-email-jnareb@gmail.com","subject":"[PATCH/RFC 4/6] gitweb: Faster project search","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2011-07-29T11:52:57Z","receivedAt":"2011-07-29T11:52:57Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Before searching by some field the information we search for must be\nfilled in.  For this fill_project_list_info() was enhanced in previous\ncommit to take additional parameters which part of projects info to\nfill.  This way we can limit doing expensive calculations (like\nrunning git-for-each-ref to get 'age' / \"Last changed\" info) only to\nprojects which we will show as search results.\n\nWith this commit the number of git commands used to generate search\nresults is 2*<matched projects> + 1, and depends on number of matched\nprojects rather than number of all projects (all repositories).\n\nNote: this is 'git for-each-ref' to find last activity, and 'git config'\nfor each project, and 'git --version' once.\n\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\n---\nI have checked it by running gitweb with 'timed' feature enabled, but\nI didn't benchmark it.  \n\nThis is a win especially for hosting sites with many projects, though\nthey either need to have caching, or not show full project list but\nonly search form by default, or paginate projects list.  \n\nBTW. I am thinking about adding configuration which would make gitweb\nshow only search form if number of project is higher than some\nconfigured threshold.  What do you think of this idea?\n\n gitweb/gitweb.perl |    9 +++++++--\n 1 files changed, 7 insertions(+), 2 deletions(-)\n\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex 4c66307..9c82d79 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -2933,6 +2933,10 @@ sub search_projects_list {\n \treturn @$projlist\n \t\tunless ($tagfilter || $searchtext);\n \n+\t# searching projects require filling to be run before it;\n+\tfill_project_list_info($projlist,\n+\t                       $tagfilter  ? 'ctags' : (),\n+\t                       $searchtext ? ('path', 'descr') : ());\n \tmy @projects;\n  PROJECT:\n \tforeach my $pr (@$projlist) {\n@@ -5171,12 +5175,13 @@ sub git_project_list_body {\n \t# filtering out forks before filling info allows to do less work\n \t@projects = filter_forks_from_projects_list(\\@projects)\n \t\tif ($check_forks);\n-\t@projects = fill_project_list_info(\\@projects);\n-\t# searching projects require filling to be run before it\n+\t# search_projects_list pre-fills required info\n \t@projects = search_projects_list(\\@projects,\n \t                                 'searchtext' => $searchtext,\n \t                                 'tagfilter'  => $tagfilter)\n \t\tif ($tagfilter || $searchtext);\n+\t# fill the rest\n+\t@projects = fill_project_list_info(\\@projects);\n \n \t$order ||= $default_projects_order;\n \t$from = 0 unless defined $from;\n-- \n1.7.5\n"},{"id":"172328","messageId":"1311940379-9608-6-git-send-email-jnareb@gmail.com","threadId":"27952","inReplyTo":"1311940379-9608-1-git-send-email-jnareb@gmail.com","subject":"[PATCH/RFC 5/6] gitweb: Highlight matched part of project name when searching projects","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2011-07-29T11:52:58Z","receivedAt":"2011-07-29T11:52:58Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Use newly introduced esc_html_match_hl() to escape HTML and mark match\nwith span element with 'match' class.  Currently only 'path' part\n(i.e. project name) is highlighted; match might be on the project\ndescription.\n\nThe code makes use of the fact that defined $search_regexp means that\nthere was search going on.\n\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\n---\nThe esc_html_match_hl() subroutine (I really need a better name for\nit) should probably be used in more places, everywhere where we do\nhighlight match in non-shortened text.  It is safe wrt. codes that\nneed HTML escaping both in non-matched and in matched part, and with\nregular expression containing characters that would be escaped, for\nexample \"foo>?\" (i.e. \"foo|foo>\").\n\n gitweb/gitweb.perl |   28 +++++++++++++++++++++++++++-\n 1 files changed, 27 insertions(+), 1 deletions(-)\n\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex 9c82d79..692a6bb 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -1690,6 +1690,30 @@ sub chop_and_escape_str {\n \t}\n }\n \n+# highlight match (if any), and escape HTML\n+sub esc_html_match_hl {\n+\tmy ($str, $regexp) = @_;\n+\treturn esc_html($str) unless defined $regexp;\n+\n+\tmy @matches;\n+\twhile ($str =~ /$regexp/g) {\n+\t\tpush @matches, [$-[0], $+[0]];\n+\t}\n+\treturn esc_html($str) unless @matches;\n+\n+\tmy $out = '';\n+\tmy $pos = 0;\n+\tfor my $m (@matches) {\n+\t\t$out .= esc_html(substr $str, $pos, $m->[0] - $pos);\n+\t\t$out .= $cgi->span({-class => 'match'},\n+\t\t                   esc_html(substr $str, $m->[0], $m->[1] - $m->[0]));\n+\t\t$pos = $m->[1];\n+\t}\n+\t$out .= esc_html(substr $str, $pos);\n+\n+\treturn $out;\n+}\n+\n ## ----------------------------------------------------------------------\n ## functions returning short strings\n \n@@ -5143,7 +5167,9 @@ sub git_project_list_rows {\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                        -class => \"list\"},\n+\t\t                       esc_html_match_hl($pr->{'path'}, $search_regexp)) .\n+\t\t      \"</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-- \n1.7.5\n"},{"id":"172330","messageId":"1311940379-9608-7-git-send-email-jnareb@gmail.com","threadId":"27952","inReplyTo":"1311940379-9608-1-git-send-email-jnareb@gmail.com","subject":"[RFC/PATCH 6/6] gitweb: Highlight matched part of project description when searching projects","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2011-07-29T11:52:59Z","receivedAt":"2011-07-29T11:52:59Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Use esc_html_match_hl() from previous commit to mark match in the\n_whole_ description when searching projects.\n\nCurrently, with this commit, when searching projects there is always\nshown full description of a project, and not a shortened one (like for\nordinary projects list view), even if the match is on project name and\nnot project description.\n\nShowing full description when there is match on it is useful to avoid\nsituation where match is in shortened, invisible part... well, perhaps\nthat could be solved (showing shortened description), but it would\nrequire much more complicated code.\n\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\n---\nThis is slightly controversial part.\n\nNote: some of changes is just reindent.\n\n gitweb/gitweb.perl |    5 ++++-\n 1 files changed, 4 insertions(+), 1 deletions(-)\n\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex 692a6bb..6bc68c5 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -5172,7 +5172,10 @@ sub git_project_list_rows {\n \t\t      \"</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                        $search_regexp\n+\t\t                        ? esc_html_match_hl($pr->{'descr_long'}, $search_regexp)\n+\t\t                        : esc_html($pr->{'descr'})) .\n+\t\t      \"</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-- \n1.7.5\n"},{"id":"172397","messageId":"201107301657.48678.jnareb@gmail.com","threadId":"27952","inReplyTo":"1311940379-9608-1-git-send-email-jnareb@gmail.com","subject":"[PATCH/RFC 7/6] gitweb: Use esc_html_match_hl in 'grep' search","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2011-07-30T14:57:47Z","receivedAt":"2011-07-30T14:57:47Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Use newly introduced esc_html_match_hl() subroutine in\ngit_search_files() ('grep' search), instead of handcrafted code using\ncaptures, which highlighted only first match in line.\n\nThis required to enhance esc_html_match_hl() to be able to pass-thru\noptions for esc_html like -nbsp=>1.\n\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\n---\nNote: because untabify() is run before match, some regexp might not\nshow match; this is not regression, because earlier versions had the\nsame behavior.\n\nCode is shorter, and behavior is improved.\n\n gitweb/gitweb.perl |   22 +++++++---------------\n 1 files changed, 7 insertions(+), 15 deletions(-)\n\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex 6bc68c5..d489640 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -1692,24 +1692,24 @@ sub chop_and_escape_str {\n \n # highlight match (if any), and escape HTML\n sub esc_html_match_hl {\n-\tmy ($str, $regexp) = @_;\n-\treturn esc_html($str) unless defined $regexp;\n+\tmy ($str, $regexp, @opts) = @_;\n+\treturn esc_html($str, @opts) unless defined $regexp;\n \n \tmy @matches;\n \twhile ($str =~ /$regexp/g) {\n \t\tpush @matches, [$-[0], $+[0]];\n \t}\n-\treturn esc_html($str) unless @matches;\n+\treturn esc_html($str, @opts) unless @matches;\n \n \tmy $out = '';\n \tmy $pos = 0;\n \tfor my $m (@matches) {\n-\t\t$out .= esc_html(substr $str, $pos, $m->[0] - $pos);\n+\t\t$out .= esc_html(substr($str, $pos, $m->[0] - $pos), @opts);\n \t\t$out .= $cgi->span({-class => 'match'},\n-\t\t                   esc_html(substr $str, $m->[0], $m->[1] - \n$m->[0]));\n+\t\t                   esc_html(substr($str, $m->[0], $m->[1] - $m->[0]), \n@opts));\n \t\t$pos = $m->[1];\n \t}\n-\t$out .= esc_html(substr $str, $pos);\n+\t$out .= esc_html(substr($str, $pos), @opts);\n \n \treturn $out;\n }\n@@ -5796,15 +5796,7 @@ sub git_search_files {\n \t\t\tprint \"<div class=\\\"binary\\\">Binary file</div>\\n\";\n \t\t} else {\n \t\t\t$ltext = untabify($ltext);\n-\t\t\tif ($ltext =~ m/^(.*)($search_regexp)(.*)$/i) {\n-\t\t\t\t$ltext = esc_html($1, -nbsp=>1);\n-\t\t\t\t$ltext .= '<span class=\"match\">';\n-\t\t\t\t$ltext .= esc_html($2, -nbsp=>1);\n-\t\t\t\t$ltext .= '</span>';\n-\t\t\t\t$ltext .= esc_html($3, -nbsp=>1);\n-\t\t\t} else {\n-\t\t\t\t$ltext = esc_html($ltext, -nbsp=>1);\n-\t\t\t}\n+\t\t\tesc_html_match_hl($ltext, $search_regexp, -nbsp => 1);\n \t\t\tprint \"<div class=\\\"pre\\\">\" .\n \t\t\t\t$cgi->a({-href => href(action=>\"blob\", hash=>$co{'hash'},\n \t\t\t\t\t\t       file_name=>\"$file\").'#l'.$lno,\n-- \n1.7.5\n"}]}