@@ -2651,21 +2651,23 @@ sub git_get_project_url_list {
2651
2651
}
2652
2652
2653
2653
sub git_get_projects_list {
2654
- my ( $filter ) = @_ ;
2654
+ my $filter = shift || ' ' ;
2655
2655
my @list ;
2656
2656
2657
- $filter ||= ' ' ;
2658
2657
$filter =~ s /\. git$// ;
2659
2658
2660
- my $check_forks = gitweb_check_feature(' forks' );
2661
-
2662
2659
if (-d $projects_list ) {
2663
2660
# search in directory
2664
- my $dir = $projects_list . ( $filter ? " / $filter " : ' ' ) ;
2661
+ my $dir = $projects_list ;
2665
2662
# remove the trailing "/"
2666
2663
$dir =~ s ! /+$!! ;
2667
- my $pfxlen = length (" $dir " );
2668
- my $pfxdepth = ($dir =~ tr ! /!! );
2664
+ my $pfxlen = length (" $projects_list " );
2665
+ my $pfxdepth = ($projects_list =~ tr ! /!! );
2666
+ # when filtering, search only given subdirectory
2667
+ if ($filter ) {
2668
+ $dir .= " /$filter " ;
2669
+ $dir =~ s ! /+$!! ;
2670
+ }
2669
2671
2670
2672
File::Find::find({
2671
2673
follow_fast => 1, # follow symbolic links
@@ -2680,14 +2682,14 @@ sub git_get_projects_list {
2680
2682
# only directories can be git repositories
2681
2683
return unless (-d $_ );
2682
2684
# don't traverse too deep (Find is super slow on os x)
2685
+ # $project_maxdepth excludes depth of $projectroot
2683
2686
if (($File::Find::name =~ tr ! /!! ) - $pfxdepth > $project_maxdepth ) {
2684
2687
$File::Find::prune = 1;
2685
2688
return ;
2686
2689
}
2687
2690
2688
- my $subdir = substr ($File::Find::name , $pfxlen + 1);
2691
+ my $path = substr ($File::Find::name , $pfxlen + 1);
2689
2692
# we check related file in $projectroot
2690
- my $path = ($filter ? " $filter /" : ' ' ) . $subdir ;
2691
2693
if (check_export_ok(" $projectroot /$path " )) {
2692
2694
push @list , { path => $path };
2693
2695
$File::Find::prune = 1;
@@ -2700,7 +2702,6 @@ sub git_get_projects_list {
2700
2702
# 'git%2Fgit.git Linus+Torvalds'
2701
2703
# 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
2702
2704
# 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
2703
- my %paths ;
2704
2705
open my $fd , ' <' , $projects_list or return ;
2705
2706
PROJECT:
2706
2707
while (my $line = <$fd >) {
@@ -2711,48 +2712,115 @@ sub git_get_projects_list {
2711
2712
if (!defined $path ) {
2712
2713
next ;
2713
2714
}
2714
- if ($filter ne ' ' ) {
2715
- # looking for forks;
2716
- my $pfx = substr ($path , 0, length ($filter ));
2717
- if ($pfx ne $filter ) {
2718
- next PROJECT;
2719
- }
2720
- my $sfx = substr ($path , length ($filter ));
2721
- if ($sfx !~ / ^\/ .*\. git$ / ) {
2722
- next PROJECT;
2723
- }
2724
- } elsif ($check_forks ) {
2725
- PATH:
2726
- foreach my $filter (keys %paths ) {
2727
- # looking for forks;
2728
- my $pfx = substr ($path , 0, length ($filter ));
2729
- if ($pfx ne $filter ) {
2730
- next PATH;
2731
- }
2732
- my $sfx = substr ($path , length ($filter ));
2733
- if ($sfx !~ / ^\/ .*\. git$ / ) {
2734
- next PATH;
2735
- }
2736
- # is a fork, don't include it in
2737
- # the list
2738
- next PROJECT;
2739
- }
2715
+ # if $filter is rpovided, check if $path begins with $filter
2716
+ if ($filter && $path !~ m ! ^\Q $filter \E /! ) {
2717
+ next ;
2740
2718
}
2741
2719
if (check_export_ok(" $projectroot /$path " )) {
2742
2720
my $pr = {
2743
2721
path => $path ,
2744
2722
owner => to_utf8($owner ),
2745
2723
};
2746
2724
push @list , $pr ;
2747
- (my $forks_path = $path ) =~ s /\. git$// ;
2748
- $paths {$forks_path }++;
2749
2725
}
2750
2726
}
2751
2727
close $fd ;
2752
2728
}
2753
2729
return @list ;
2754
2730
}
2755
2731
2732
+ # written with help of Tree::Trie module (Perl Artistic License, GPL compatibile)
2733
+ # as side effects it sets 'forks' field to list of forks for forked projects
2734
+ sub filter_forks_from_projects_list {
2735
+ my $projects = shift ;
2736
+
2737
+ my %trie ; # prefix tree of directories (path components)
2738
+ # generate trie out of those directories that might contain forks
2739
+ foreach my $pr (@$projects ) {
2740
+ my $path = $pr -> {' path' };
2741
+ $path =~ s /\. git$// ; # forks of 'repo.git' are in 'repo/' directory
2742
+ next if ($path =~ m ! /$ ! ); # skip non-bare repositories, e.g. 'repo/.git'
2743
+ next unless ($path ); # skip '.git' repository: tests, git-instaweb
2744
+ next unless (-d $path ); # containing directory exists
2745
+ $pr -> {' forks' } = []; # there can be 0 or more forks of project
2746
+
2747
+ # add to trie
2748
+ my @dirs = split (' /' , $path );
2749
+ # walk the trie, until either runs out of components or out of trie
2750
+ my $ref = \%trie ;
2751
+ while (scalar @dirs &&
2752
+ exists ($ref -> {$dirs [0]})) {
2753
+ $ref = $ref -> {shift @dirs };
2754
+ }
2755
+ # create rest of trie structure from rest of components
2756
+ foreach my $dir (@dirs ) {
2757
+ $ref = $ref -> {$dir } = {};
2758
+ }
2759
+ # create end marker, store $pr as a data
2760
+ $ref -> {' ' } = $pr if (!exists $ref -> {' ' });
2761
+ }
2762
+
2763
+ # filter out forks, by finding shortest prefix match for paths
2764
+ my @filtered ;
2765
+ PROJECT:
2766
+ foreach my $pr (@$projects ) {
2767
+ # trie lookup
2768
+ my $ref = \%trie ;
2769
+ DIR:
2770
+ foreach my $dir (split (' /' , $pr -> {' path' })) {
2771
+ if (exists $ref -> {' ' }) {
2772
+ # found [shortest] prefix, is a fork - skip it
2773
+ push @{$ref -> {' ' }{' forks' }}, $pr ;
2774
+ next PROJECT;
2775
+ }
2776
+ if (!exists $ref -> {$dir }) {
2777
+ # not in trie, cannot have prefix, not a fork
2778
+ push @filtered , $pr ;
2779
+ next PROJECT;
2780
+ }
2781
+ # If the dir is there, we just walk one step down the trie.
2782
+ $ref = $ref -> {$dir };
2783
+ }
2784
+ # we ran out of trie
2785
+ # (shouldn't happen: it's either no match, or end marker)
2786
+ push @filtered , $pr ;
2787
+ }
2788
+
2789
+ return @filtered ;
2790
+ }
2791
+
2792
+ # note: fill_project_list_info must be run first,
2793
+ # for 'descr_long' and 'ctags' to be filled
2794
+ sub search_projects_list {
2795
+ my ($projlist , %opts ) = @_ ;
2796
+ my $tagfilter = $opts {' tagfilter' };
2797
+ my $searchtext = $opts {' searchtext' };
2798
+
2799
+ return @$projlist
2800
+ unless ($tagfilter || $searchtext );
2801
+
2802
+ my @projects ;
2803
+ PROJECT:
2804
+ foreach my $pr (@$projlist ) {
2805
+
2806
+ if ($tagfilter ) {
2807
+ next unless ref ($pr -> {' ctags' }) eq ' HASH' ;
2808
+ next unless
2809
+ grep { lc ($_ ) eq lc ($tagfilter ) } keys %{$pr -> {' ctags' }};
2810
+ }
2811
+
2812
+ if ($searchtext ) {
2813
+ next unless
2814
+ $pr -> {' path' } =~ / $searchtext / ||
2815
+ $pr -> {' descr_long' } =~ / $searchtext / ;
2816
+ }
2817
+
2818
+ push @projects , $pr ;
2819
+ }
2820
+
2821
+ return @projects ;
2822
+ }
2823
+
2756
2824
our $gitweb_project_owner = undef ;
2757
2825
sub git_get_project_list_from_file {
2758
2826
@@ -4742,7 +4810,7 @@ sub git_patchset_body {
4742
4810
# project in the list, removing invalid projects from returned list
4743
4811
# NOTE: modifies $projlist, but does not remove entries from it
4744
4812
sub fill_project_list_info {
4745
- my ( $projlist , $check_forks ) = @_ ;
4813
+ my $projlist = shift ;
4746
4814
my @projects ;
4747
4815
4748
4816
my $show_ctags = gitweb_check_feature(' ctags' );
@@ -4762,23 +4830,36 @@ sub fill_project_list_info {
4762
4830
if (!defined $pr -> {' owner' }) {
4763
4831
$pr -> {' owner' } = git_get_project_owner(" $pr ->{'path'}" ) || " " ;
4764
4832
}
4765
- if ($check_forks ) {
4766
- my $pname = $pr -> {' path' };
4767
- if (($pname =~ s /\. git$// ) &&
4768
- ($pname !~ / \/ $ / ) &&
4769
- (-d " $projectroot /$pname " )) {
4770
- $pr -> {' forks' } = " -d $projectroot /$pname " ;
4771
- } else {
4772
- $pr -> {' forks' } = 0;
4773
- }
4833
+ if ($show_ctags ) {
4834
+ $pr -> {' ctags' } = git_get_project_ctags($pr -> {' path' });
4774
4835
}
4775
- $show_ctags and $pr -> {' ctags' } = git_get_project_ctags($pr -> {' path' });
4776
4836
push @projects , $pr ;
4777
4837
}
4778
4838
4779
4839
return @projects ;
4780
4840
}
4781
4841
4842
+ sub sort_projects_list {
4843
+ my ($projlist , $order ) = @_ ;
4844
+ my @projects ;
4845
+
4846
+ my %order_info = (
4847
+ project => { key => ' path' , type => ' str' },
4848
+ descr => { key => ' descr_long' , type => ' str' },
4849
+ owner => { key => ' owner' , type => ' str' },
4850
+ age => { key => ' age' , type => ' num' }
4851
+ );
4852
+ my $oi = $order_info {$order };
4853
+ return @$projlist unless defined $oi ;
4854
+ if ($oi -> {' type' } eq ' str' ) {
4855
+ @projects = sort {$a -> {$oi -> {' key' }} cmp $b -> {$oi -> {' key' }}} @$projlist ;
4856
+ } else {
4857
+ @projects = sort {$a -> {$oi -> {' key' }} <=> $b -> {$oi -> {' key' }}} @$projlist ;
4858
+ }
4859
+
4860
+ return @projects ;
4861
+ }
4862
+
4782
4863
# print 'sort by' <th> element, generating 'sort by $name' replay link
4783
4864
# if that order is not selected
4784
4865
sub print_sort_th {
@@ -4805,28 +4886,39 @@ sub format_sort_th {
4805
4886
sub git_project_list_body {
4806
4887
# actually uses global variable $project
4807
4888
my ($projlist , $order , $from , $to , $extra , $no_header ) = @_ ;
4889
+ my @projects = @$projlist ;
4808
4890
4809
4891
my $check_forks = gitweb_check_feature(' forks' );
4810
- my @projects = fill_project_list_info($projlist , $check_forks );
4892
+ my $show_ctags = gitweb_check_feature(' ctags' );
4893
+ my $tagfilter = $show_ctags ? $cgi -> param(' by_tag' ) : undef ;
4894
+ $check_forks = undef
4895
+ if ($tagfilter || $searchtext );
4896
+
4897
+ # filtering out forks before filling info allows to do less work
4898
+ @projects = filter_forks_from_projects_list(\@projects )
4899
+ if ($check_forks );
4900
+ @projects = fill_project_list_info(\@projects );
4901
+ # searching projects require filling to be run before it
4902
+ @projects = search_projects_list(\@projects ,
4903
+ ' searchtext' => $searchtext ,
4904
+ ' tagfilter' => $tagfilter )
4905
+ if ($tagfilter || $searchtext );
4811
4906
4812
4907
$order ||= $default_projects_order ;
4813
4908
$from = 0 unless defined $from ;
4814
4909
$to = $#projects if (!defined $to || $#projects < $to );
4815
4910
4816
- my %order_info = (
4817
- project => { key => ' path' , type => ' str' },
4818
- descr => { key => ' descr_long' , type => ' str' },
4819
- owner => { key => ' owner' , type => ' str' },
4820
- age => { key => ' age' , type => ' num' }
4821
- );
4822
- my $oi = $order_info {$order };
4823
- if ($oi -> {' type' } eq ' str' ) {
4824
- @projects = sort {$a -> {$oi -> {' key' }} cmp $b -> {$oi -> {' key' }}} @projects ;
4825
- } else {
4826
- @projects = sort {$a -> {$oi -> {' key' }} <=> $b -> {$oi -> {' key' }}} @projects ;
4911
+ # short circuit
4912
+ if ($from > $to ) {
4913
+ print " <center>\n " .
4914
+ " <b>No such projects found</b><br />\n " .
4915
+ " Click " .$cgi -> a({-href => href(project => undef )}," here" )." to view all projects<br />\n " .
4916
+ " </center>\n <br />\n " ;
4917
+ return ;
4827
4918
}
4828
4919
4829
- my $show_ctags = gitweb_check_feature(' ctags' );
4920
+ @projects = sort_projects_list(\@projects , $order );
4921
+
4830
4922
if ($show_ctags ) {
4831
4923
my %ctags ;
4832
4924
foreach my $p (@projects ) {
@@ -4852,32 +4944,26 @@ sub git_project_list_body {
4852
4944
" </tr>\n " ;
4853
4945
}
4854
4946
my $alternate = 1;
4855
- my $tagfilter = $cgi -> param(' by_tag' );
4856
4947
for (my $i = $from ; $i <= $to ; $i ++) {
4857
4948
my $pr = $projects [$i ];
4858
4949
4859
- next if $tagfilter and $show_ctags and not grep { lc $_ eq lc $tagfilter } keys %{$pr -> {' ctags' }};
4860
- next if $searchtext and not $pr -> {' path' } =~ / $searchtext /
4861
- and not $pr -> {' descr_long' } =~ / $searchtext / ;
4862
- # Weed out forks or non-matching entries of search
4863
- if ($check_forks ) {
4864
- my $forkbase = $project ; $forkbase ||= ' ' ; $forkbase =~ s #\. git$# /# ;
4865
- $forkbase =" ^$forkbase " if $forkbase ;
4866
- next if not $searchtext and not $tagfilter and $show_ctags
4867
- and $pr -> {' path' } =~ m #$forkbase .*/.*# ; # regexp-safe
4868
- }
4869
-
4870
4950
if ($alternate ) {
4871
4951
print " <tr class=\" dark\" >\n " ;
4872
4952
} else {
4873
4953
print " <tr class=\" light\" >\n " ;
4874
4954
}
4875
4955
$alternate ^= 1;
4956
+
4876
4957
if ($check_forks ) {
4877
4958
print " <td>" ;
4878
4959
if ($pr -> {' forks' }) {
4879
- print " <!-- $pr ->{'forks'} -->\n " ;
4880
- print $cgi -> a({-href => href(project => $pr -> {' path' }, action => " forks" )}, " +" );
4960
+ my $nforks = scalar @{$pr -> {' forks' }};
4961
+ if ($nforks > 0) {
4962
+ print $cgi -> a({-href => href(project => $pr -> {' path' }, action => " forks" ),
4963
+ -title => " $nforks forks" }, " +" );
4964
+ } else {
4965
+ print $cgi -> span({-title => " $nforks forks" }, " +" );
4966
+ }
4881
4967
}
4882
4968
print " </td>\n " ;
4883
4969
}
@@ -5357,7 +5443,10 @@ sub git_forks {
5357
5443
}
5358
5444
5359
5445
sub git_project_index {
5360
- my @projects = git_get_projects_list($project );
5446
+ my @projects = git_get_projects_list();
5447
+ if (!@projects ) {
5448
+ die_error(404, " No projects found" );
5449
+ }
5361
5450
5362
5451
print $cgi -> header(
5363
5452
-type => ' text/plain' ,
@@ -5399,7 +5488,11 @@ sub git_summary {
5399
5488
my $check_forks = gitweb_check_feature(' forks' );
5400
5489
5401
5490
if ($check_forks ) {
5491
+ # find forks of a project
5402
5492
@forklist = git_get_projects_list($project );
5493
+ # filter out forks of forks
5494
+ @forklist = filter_forks_from_projects_list(\@forklist )
5495
+ if (@forklist );
5403
5496
}
5404
5497
5405
5498
git_header_html();
@@ -7319,6 +7412,9 @@ sub git_atom {
7319
7412
7320
7413
sub git_opml {
7321
7414
my @list = git_get_projects_list();
7415
+ if (!@list ) {
7416
+ die_error(404, " No projects found" );
7417
+ }
7322
7418
7323
7419
print $cgi -> header(
7324
7420
-type => ' text/xml' ,
0 commit comments