{"thread":{"id":"6029","subject":"[PATCH] gitweb: Support for 'forks'","startedAt":"2006-10-24T03:33:17Z","lastAt":"2006-10-24T03:33:17Z","messageCount":1,"participants":["Petr Baudis"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"29802","messageId":"20061024033317.7549.42852.stgit@rover","threadId":"6029","inReplyTo":null,"subject":"[PATCH] gitweb: Support for 'forks'","fromName":"Petr Baudis","fromEmail":"pasky@suse.cz","sentAt":"2006-10-24T03:33:17Z","receivedAt":"2006-10-24T03:33:17Z","isPatch":true,"sender":{"key":"pasky@ucw.cz","avatar":"https://avatars.githubusercontent.com/u/18439?v=4"},"body":"On repo.or.cz, I want to support project 'forks', which are meant\nfor repositories which are spinoffs of a given project and share\nits objects database through the alternates mechanism. But another\n(and perhaps even greater) incentive for that is that those 'forked\nprojects' do not clutter the main project index but are completely\ngrouped inside of the project view.\n\nA forked project is just like a normal project, but given project\n$projectroot/$projname.git, the forked project resides in directory\n$projectroot/$projname/$forkname.git. This is a somewhat arbitrary\nnaming rule, but I think that for now it's fine; if someone will need\nsomething wildly different, let them submit a patch. The 'forked'\nmode is by default off and can be turned on in runtime gitweb\nconfiguration just like other features.\n\nA project having forks is marked by a '+' (pointing to the list of\nforks) in the project list (this could become some cutesy AJAXy\nDHTML in the future), there is a forks section in the project\nsummary similar to the heads and tags sections, and of course\na forks view which looks the same as the root project list.\n\nForks can be recursive.\n\nSigned-off-by: Petr Baudis <pasky@suse.cz>\n---\n\n gitweb/gitweb.perl |  256 +++++++++++++++++++++++++++++++++++-----------------\n 1 files changed, 174 insertions(+), 82 deletions(-)\n\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex 81adc71..f957aee 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -164,6 +164,21 @@ our %feature = (\n \t'pathinfo' => {\n \t\t'override' => 0,\n \t\t'default' => [0]},\n+\n+\t# Make gitweb consider projects in project root subdirectories\n+\t# to be forks of existing projects. Given project $projname.git,\n+\t# projects matching $projname/*.git will not be shown in the main\n+\t# projects list, instead a '+' mark will be added to $projname\n+\t# there and a 'forks' view will be enabled for the project, listing\n+\t# all the forks. This feature is supported only if project list\n+\t# is taken from a directory, not file.\n+\n+\t# To enable system wide have in $GITWEB_CONFIG\n+\t# $feature{'forks'}{'default'} = [1];\n+\t# Project specific override is not supported.\n+\t'forks' => {\n+\t\t'override' => 0,\n+\t\t'default' => [0]},\n );\n \n sub gitweb_check_feature {\n@@ -409,6 +424,7 @@ my %actions = (\n \t\"commitdiff\" => \\&git_commitdiff,\n \t\"commitdiff_plain\" => \\&git_commitdiff_plain,\n \t\"commit\" => \\&git_commit,\n+\t\"forks\" => \\&git_forks,\n \t\"heads\" => \\&git_heads,\n \t\"history\" => \\&git_history,\n \t\"log\" => \\&git_log,\n@@ -896,13 +912,19 @@ sub git_get_project_url_list {\n }\n \n sub git_get_projects_list {\n+\tmy ($filter) = @_;\n \tmy @list;\n \n+\t$filter ||= '';\n+\t$filter =~ s/\\.git$//;\n+\n \tif (-d $projects_list) {\n \t\t# search in directory\n-\t\tmy $dir = $projects_list;\n+\t\tmy $dir = $projects_list . ($filter ? \"/$filter\" : '');\n \t\tmy $pfxlen = length(\"$dir\");\n \n+\t\tmy $check_forks = gitweb_check_feature('forks');\n+\n \t\tFile::Find::find({\n \t\t\tfollow_fast => 1, # follow symbolic links\n \t\t\tdangling_symlinks => 0, # ignore dangling symlinks, silently\n@@ -914,8 +936,10 @@ sub git_get_projects_list {\n \n \t\t\t\tmy $subdir = substr($File::Find::name, $pfxlen + 1);\n \t\t\t\t# we check related file in $projectroot\n-\t\t\t\tif (check_export_ok(\"$projectroot/$subdir\")) {\n-\t\t\t\t\tpush @list, { path => $subdir };\n+\t\t\t\tif ($check_forks and $subdir =~ m#/.#) {\n+\t\t\t\t\t$File::Find::prune = 1;\n+\t\t\t\t} elsif (check_export_ok(\"$projectroot/$filter/$subdir\")) {\n+\t\t\t\t\tpush @list, { path => ($filter ? \"$filter/\" : '') . $subdir };\n \t\t\t\t\t$File::Find::prune = 1;\n \t\t\t\t}\n \t\t\t},\n@@ -2160,6 +2184,124 @@ sub git_patchset_body {\n \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+\n+\tmy @projects;\n+\tforeach my $pr (@$projlist) {\n+\t\tmy (@aa) = git_get_last_activity($pr->{'path'});\n+\t\tunless (@aa) {\n+\t\t\tnext;\n+\t\t}\n+\t\t($pr->{'age'}, $pr->{'age_string'}) = @aa;\n+\t\tif (!defined $pr->{'descr'}) {\n+\t\t\tmy $descr = git_get_project_description($pr->{'path'}) || \"\";\n+\t\t\t$pr->{'descr'} = chop_str($descr, 25, 5);\n+\t\t}\n+\t\tif (!defined $pr->{'owner'}) {\n+\t\t\t$pr->{'owner'} = get_file_owner(\"$projectroot/$pr->{'path'}\") || \"\";\n+\t\t}\n+\t\tif ($check_forks) {\n+\t\t\tmy $pname = $pr->{'path'};\n+\t\t\t$pname =~ s/\\.git$//;\n+\t\t\t$pr->{'forks'} = -d \"$projectroot/$pname\";\n+\t\t}\n+\t\tpush @projects, $pr;\n+\t}\n+\n+\t$order ||= \"project\";\n+\t$from = 0 unless defined $from;\n+\t$to = $#projects if (!defined $to || $#projects < $to);\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\tif ($order eq \"project\") {\n+\t\t\t@projects = sort {$a->{'path'} cmp $b->{'path'}} @projects;\n+\t\t\tprint \"<th>Project</th>\\n\";\n+\t\t} else {\n+\t\t\tprint \"<th>\" .\n+\t\t\t      $cgi->a({-href => href(project=>undef, order=>'project'),\n+\t\t\t\t       -class => \"header\"}, \"Project\") .\n+\t\t\t      \"</th>\\n\";\n+\t\t}\n+\t\tif ($order eq \"descr\") {\n+\t\t\t@projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects;\n+\t\t\tprint \"<th>Description</th>\\n\";\n+\t\t} else {\n+\t\t\tprint \"<th>\" .\n+\t\t\t      $cgi->a({-href => href(project=>undef, order=>'descr'),\n+\t\t\t\t       -class => \"header\"}, \"Description\") .\n+\t\t\t      \"</th>\\n\";\n+\t\t}\n+\t\tif ($order eq \"owner\") {\n+\t\t\t@projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects;\n+\t\t\tprint \"<th>Owner</th>\\n\";\n+\t\t} else {\n+\t\t\tprint \"<th>\" .\n+\t\t\t      $cgi->a({-href => href(project=>undef, order=>'owner'),\n+\t\t\t\t       -class => \"header\"}, \"Owner\") .\n+\t\t\t      \"</th>\\n\";\n+\t\t}\n+\t\tif ($order eq \"age\") {\n+\t\t\t@projects = sort {$a->{'age'} <=> $b->{'age'}} @projects;\n+\t\t\tprint \"<th>Last Change</th>\\n\";\n+\t\t} else {\n+\t\t\tprint \"<th>\" .\n+\t\t\t      $cgi->a({-href => href(project=>undef, order=>'age'),\n+\t\t\t\t       -class => \"header\"}, \"Last Change\") .\n+\t\t\t      \"</th>\\n\";\n+\t\t}\n+\t\tprint \"<th></th>\\n\" .\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+\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 $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>\" . esc_html($pr->{'descr'}) . \"</td>\\n\" .\n+\t\t      \"<td><i>\" . chop_str($pr->{'owner'}, 15) . \"</i></td>\\n\";\n+\t\tprint \"<td class=\\\"\". age_class($pr->{'age'}) . \"\\\">\" .\n+\t\t      $pr->{'age_string'} . \"</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 => '/git-browser/by-commit.html?r='.$pr->{'path'}}, \"graphiclog\") . \" | \" .\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+\t\t\tprint \"<td></td>\\n\";\n+\t\t}\n+\t\tprint \"<td colspan=\\\"5\\\">$extra</td>\\n\" .\n+\t\t      \"</tr>\\n\";\n+\t}\n+\tprint \"</table>\\n\";\n+}\n+\n sub git_shortlog_body {\n \t# uses global variable $project\n \tmy ($revlist, $from, $to, $refs, $extra) = @_;\n@@ -2378,25 +2520,9 @@ sub git_project_list {\n \t}\n \n \tmy @list = git_get_projects_list();\n-\tmy @projects;\n \tif (!@list) {\n \t\tdie_error(undef, \"No projects found\");\n \t}\n-\tforeach my $pr (@list) {\n-\t\tmy (@aa) = git_get_last_activity($pr->{'path'});\n-\t\tunless (@aa) {\n-\t\t\tnext;\n-\t\t}\n-\t\t($pr->{'age'}, $pr->{'age_string'}) = @aa;\n-\t\tif (!defined $pr->{'descr'}) {\n-\t\t\tmy $descr = git_get_project_description($pr->{'path'}) || \"\";\n-\t\t\t$pr->{'descr'} = chop_str($descr, 25, 5);\n-\t\t}\n-\t\tif (!defined $pr->{'owner'}) {\n-\t\t\t$pr->{'owner'} = get_file_owner(\"$projectroot/$pr->{'path'}\") || \"\";\n-\t\t}\n-\t\tpush @projects, $pr;\n-\t}\n \n \tgit_header_html();\n \tif (-f $home_text) {\n@@ -2406,75 +2532,30 @@ sub git_project_list {\n \t\tclose $fd;\n \t\tprint \"</div>\\n\";\n \t}\n-\tprint \"<table class=\\\"project_list\\\">\\n\" .\n-\t      \"<tr>\\n\";\n-\t$order ||= \"project\";\n-\tif ($order eq \"project\") {\n-\t\t@projects = sort {$a->{'path'} cmp $b->{'path'}} @projects;\n-\t\tprint \"<th>Project</th>\\n\";\n-\t} else {\n-\t\tprint \"<th>\" .\n-\t\t      $cgi->a({-href => href(project=>undef, order=>'project'),\n-\t\t               -class => \"header\"}, \"Project\") .\n-\t\t      \"</th>\\n\";\n-\t}\n-\tif ($order eq \"descr\") {\n-\t\t@projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects;\n-\t\tprint \"<th>Description</th>\\n\";\n-\t} else {\n-\t\tprint \"<th>\" .\n-\t\t      $cgi->a({-href => href(project=>undef, order=>'descr'),\n-\t\t               -class => \"header\"}, \"Description\") .\n-\t\t      \"</th>\\n\";\n-\t}\n-\tif ($order eq \"owner\") {\n-\t\t@projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects;\n-\t\tprint \"<th>Owner</th>\\n\";\n-\t} else {\n-\t\tprint \"<th>\" .\n-\t\t      $cgi->a({-href => href(project=>undef, order=>'owner'),\n-\t\t               -class => \"header\"}, \"Owner\") .\n-\t\t      \"</th>\\n\";\n-\t}\n-\tif ($order eq \"age\") {\n-\t\t@projects = sort {$a->{'age'} <=> $b->{'age'}} @projects;\n-\t\tprint \"<th>Last Change</th>\\n\";\n-\t} else {\n-\t\tprint \"<th>\" .\n-\t\t      $cgi->a({-href => href(project=>undef, order=>'age'),\n-\t\t               -class => \"header\"}, \"Last Change\") .\n-\t\t      \"</th>\\n\";\n+\tgit_project_list_body(\\@list, $order);\n+\tgit_footer_html();\n+}\n+\n+sub git_forks {\n+\tmy $order = $cgi->param('o');\n+\tif (defined $order && $order !~ m/project|descr|owner|age/) {\n+\t\tdie_error(undef, \"Unknown order parameter\");\n \t}\n-\tprint \"<th></th>\\n\" .\n-\t      \"</tr>\\n\";\n-\tmy $alternate = 1;\n-\tforeach my $pr (@projects) {\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\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>\" . esc_html($pr->{'descr'}) . \"</td>\\n\" .\n-\t\t      \"<td><i>\" . chop_str($pr->{'owner'}, 15) . \"</i></td>\\n\";\n-\t\tprint \"<td class=\\\"\". age_class($pr->{'age'}) . \"\\\">\" .\n-\t\t      $pr->{'age_string'} . \"</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      \"</td>\\n\" .\n-\t\t      \"</tr>\\n\";\n+\n+\tmy @list = git_get_projects_list($project);\n+\tif (!@list) {\n+\t\tdie_error(undef, \"No forks found\");\n \t}\n-\tprint \"</table>\\n\";\n+\n+\tgit_header_html();\n+\tgit_print_page_nav('','');\n+\tgit_print_header_div('summary', \"$project forks\");\n+\tgit_project_list_body(\\@list, $order);\n \tgit_footer_html();\n }\n \n sub git_project_index {\n-\tmy @projects = git_get_projects_list();\n+\tmy @projects = git_get_projects_list($project);\n \n \tprint $cgi->header(\n \t\t-type => 'text/plain',\n@@ -2517,6 +2598,10 @@ sub git_summary {\n \t\t\tpush @taglist, $ref;\n \t\t}\n \t}\n+\tmy @forklist;\n+\tif (gitweb_check_feature('forks')) {\n+\t\t@forklist = git_get_projects_list($project);\n+\t}\n \n \tgit_header_html();\n \tgit_print_page_nav('summary','', $head);\n@@ -2567,6 +2652,13 @@ sub git_summary {\n \t\t               $cgi->a({-href => href(action=>\"heads\")}, \"...\"));\n \t}\n \n+\tif (@forklist) {\n+\t\tgit_print_header_div('forks');\n+\t\tgit_project_list_body(\\@forklist, undef, 0, 15,\n+\t\t                      $cgi->a({-href => href(action=>\"forks\")}, \"...\"),\n+\t\t\t\t      'noheader');\n+\t}\n+\n \tgit_footer_html();\n }\n \n"}]}