File Coverage

blib/lib/App/Codeowners.pm
Criterion Covered Total %
statement 88 127 69.2
branch 20 54 37.0
condition 6 22 27.2
subroutine 14 17 82.3
pod 1 1 100.0
total 129 221 58.3


line stmt bran cond sub pod time code
1             package App::Codeowners;
2             # ABSTRACT: A tool for managing CODEOWNERS files
3              
4 1     1   527 use v5.10.1; # defined-or
  1         3  
5 1     1   569 use utf8;
  1         15  
  1         5  
6 1     1   32 use warnings;
  1         2  
  1         28  
7 1     1   5 use strict;
  1         2  
  1         20  
8              
9 1     1   423 use App::Codeowners::Formatter;
  1         3  
  1         41  
10 1     1   430 use App::Codeowners::Options;
  1         4  
  1         41  
11 1     1   7 use App::Codeowners::Util qw(find_codeowners_in_directory run_git git_ls_files git_toplevel);
  1         2  
  1         74  
12 1     1   543 use Color::ANSI::Util 0.03 qw(ansifg);
  1         7188  
  1         79  
13 1     1   467 use File::Codeowners;
  1         3  
  1         32  
14 1     1   8 use Path::Tiny;
  1         2  
  1         1364  
15              
16             our $VERSION = '0.49'; # VERSION
17              
18              
19             sub main {
20 7     7 1 92589 my $class = shift;
21 7         25 my $self = bless {}, $class;
22              
23 7         173 my $opts = App::Codeowners::Options->new(@_);
24              
25 4         22 my $color = $opts->{color};
26 4 50 33     111 local $ENV{NO_COLOR} = 1 if defined $color && !$color;
27              
28 4         28 my $command = $opts->command;
29 4 50       84 my $handler = $self->can("_command_$command")
30             or die "Unknown command: $command\n";
31              
32 4         58 binmode(STDOUT, ':encoding(UTF-8)');
33 4         290 $self->$handler($opts);
34              
35 4         181 exit 0;
36             }
37              
38             sub _command_show {
39 3     3   5 my $self = shift;
40 3         11 my $opts = shift;
41              
42 3 50       36 my $toplevel = git_toplevel('.') or die "Not a git repo\n";
43              
44 3 50       46 my $codeowners_path = find_codeowners_in_directory($toplevel)
45             or die "No CODEOWNERS file in $toplevel\n";
46 3         197 my $codeowners = File::Codeowners->parse_from_filepath($codeowners_path);
47              
48 3         57 my ($proc, $cdup) = run_git(qw{rev-parse --show-cdup});
49 3 50       50 $proc->wait and exit 1;
50              
51 3   100     45 my $show_projects = $opts->{projects} // scalar @{$codeowners->projects};
  2         43  
52              
53             my $formatter = App::Codeowners::Formatter->new(
54             format => $opts->{format} || ' * %-50F %O',
55             handle => *STDOUT,
56             columns => [
57             'File',
58 3 50 50     133 $opts->{patterns} ? 'Pattern' : (),
    100          
59             'Owner',
60             $show_projects ? 'Project' : (),
61             ],
62             );
63              
64 3         8 my %filter_owners = map { $_ => 1 } @{$opts->{owner}};
  0         0  
  3         32  
65 3         9 my %filter_projects = map { $_ => 1 } @{$opts->{project}};
  0         0  
  3         9  
66 3         20 my %filter_patterns = map { $_ => 1 } @{$opts->{pattern}};
  0         0  
  3         10  
67              
68 3         54 $proc = git_ls_files('.', $opts->args);
69 3         57 while (my $filepath = $proc->next) {
70 9         89 my $match = $codeowners->match(path($filepath)->relative($cdup));
71 9 50       57 if (%filter_owners) {
72 0         0 for my $owner (@{$match->{owners}}) {
  0         0  
73 0 0       0 goto ADD_RESULT if $filter_owners{$owner};
74             }
75 0         0 next;
76             }
77 9 50       26 if (%filter_patterns) {
78 0 0 0     0 goto ADD_RESULT if $filter_patterns{$match->{pattern} || ''};
79 0         0 next;
80             }
81 9 50       19 if (%filter_projects) {
82 0 0 0     0 goto ADD_RESULT if $filter_projects{$match->{project} || ''};
83 0         0 next;
84             }
85             ADD_RESULT:
86             $formatter->add_result([
87             $filepath,
88             $opts->{patterns} ? $match->{pattern} : (),
89             $match->{owners},
90 9 50       146 $show_projects ? $match->{project} : (),
    100          
91             ]);
92             }
93 3 50       15 $proc->wait and exit 1;
94             }
95              
96             sub _command_owners {
97 0     0   0 my $self = shift;
98 0         0 my $opts = shift;
99              
100 0 0       0 my $toplevel = git_toplevel('.') or die "Not a git repo\n";
101              
102 0 0       0 my $codeowners_path = find_codeowners_in_directory($toplevel)
103             or die "No CODEOWNERS file in $toplevel\n";
104 0         0 my $codeowners = File::Codeowners->parse_from_filepath($codeowners_path);
105              
106 0         0 my $results = $codeowners->owners($opts->{pattern});
107              
108             my $formatter = App::Codeowners::Formatter->new(
109 0   0     0 format => $opts->{format} || '%O',
110             handle => *STDOUT,
111             columns => [qw(Owner)],
112             );
113 0         0 $formatter->add_result(map { [$_] } @$results);
  0         0  
114             }
115              
116             sub _command_patterns {
117 0     0   0 my $self = shift;
118 0         0 my $opts = shift;
119              
120 0 0       0 my $toplevel = git_toplevel('.') or die "Not a git repo\n";
121              
122 0 0       0 my $codeowners_path = find_codeowners_in_directory($toplevel)
123             or die "No CODEOWNERS file in $toplevel\n";
124 0         0 my $codeowners = File::Codeowners->parse_from_filepath($codeowners_path);
125              
126 0         0 my $results = $codeowners->patterns($opts->{owner});
127              
128             my $formatter = App::Codeowners::Formatter->new(
129 0   0     0 format => $opts->{format} || '%T',
130             handle => *STDOUT,
131             columns => [qw(Pattern)],
132             );
133 0         0 $formatter->add_result(map { [$_] } @$results);
  0         0  
134             }
135              
136             sub _command_projects {
137 0     0   0 my $self = shift;
138 0         0 my $opts = shift;
139              
140 0 0       0 my $toplevel = git_toplevel('.') or die "Not a git repo\n";
141              
142 0 0       0 my $codeowners_path = find_codeowners_in_directory($toplevel)
143             or die "No CODEOWNERS file in $toplevel\n";
144 0         0 my $codeowners = File::Codeowners->parse_from_filepath($codeowners_path);
145              
146 0         0 my $results = $codeowners->projects;
147              
148             my $formatter = App::Codeowners::Formatter->new(
149 0   0     0 format => $opts->{format} || '%P',
150             handle => *STDOUT,
151             columns => [qw(Project)],
152             );
153 0         0 $formatter->add_result(map { [$_] } @$results);
  0         0  
154             }
155              
156 1     1   18 sub _command_create { goto &_command_update }
157             sub _command_update {
158 1     1   9 my $self = shift;
159 1         11 my $opts = shift;
160              
161 1         17 my ($filepath) = $opts->args;
162              
163 1   50     32 my $path = path($filepath || '.');
164 1         42 my $repopath;
165              
166 1 50       26 die "Does not exist: $path\n" if !$path->parent->exists;
167              
168 1 50       122 if ($path->is_dir) {
169 1         18 $repopath = $path;
170 1   33     14 $path = find_codeowners_in_directory($path) || $repopath->child('CODEOWNERS');
171             }
172              
173 1         82 my $is_new = !$path->is_file;
174              
175 1         13 my $codeowners;
176 1 50       5 if ($is_new) {
177 1         23 $codeowners = File::Codeowners->new;
178 1         9 my $template = <<'END';
179             This file shows mappings between subdirs/files and the individuals and
180             teams who own them. You can read this file yourself or use tools to query it,
181             so you can quickly determine who to speak with or send pull requests to.
182              
183             Simply write a gitignore pattern followed by one or more names/emails/groups.
184             Examples:
185             /project_a/** @team1
186             *.js @harry @javascript-cabal
187             END
188 1         19 for my $line (split(/\n/, $template)) {
189 8         30 $codeowners->append(comment => $line);
190             }
191             }
192             else {
193 0         0 $codeowners = File::Codeowners->parse_from_filepath($path);
194             }
195              
196 1 50       10 if ($repopath) {
197             # if there is a repo we can try to update the list of unowned files
198 1         12 my ($proc, @filepaths) = git_ls_files($repopath);
199 1 50       25 $proc->wait and exit 1;
200 1         27 $codeowners->clear_unowned;
201 1         6 $codeowners->add_unowned(grep { !$codeowners->match($_) } @filepaths);
  3         36  
202             }
203              
204 1         25 $codeowners->write_to_filepath($path);
205 1         807 print STDERR "Wrote $path\n";
206             }
207              
208             1;
209              
210             __END__