File Coverage

blib/lib/App/Codeowners.pm
Criterion Covered Total %
statement 92 131 70.2
branch 21 56 37.5
condition 6 22 27.2
subroutine 15 18 83.3
pod 1 1 100.0
total 135 228 59.2


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