]> Dogcows Code - chaz/git-codeowners/blob - lib/App/Codeowners.pm
f33dfb820f827841bfab5b18c878086b9943828d
[chaz/git-codeowners] / lib / App / Codeowners.pm
1 package App::Codeowners;
2 # ABSTRACT: A tool for managing CODEOWNERS files
3
4 use v5.10.1; # defined-or
5 use utf8;
6 use warnings;
7 use strict;
8
9 use App::Codeowners::Formatter;
10 use App::Codeowners::Options;
11 use App::Codeowners::Util qw(find_codeowners_in_directory run_git git_ls_files git_toplevel);
12 use Color::ANSI::Util 0.03 qw(ansifg);
13 use File::Codeowners;
14 use Path::Tiny;
15
16 our $VERSION = '0.49'; # VERSION
17
18
19 sub main {
20 my $class = shift;
21 my $self = bless {}, $class;
22
23 my $opts = App::Codeowners::Options->new(@_);
24
25 my $color = $opts->{color};
26 local $ENV{NO_COLOR} = 1 if defined $color && !$color;
27
28 my $command = $opts->command;
29 my $handler = $self->can("_command_$command")
30 or die "Unknown command: $command\n";
31
32 binmode(STDOUT, ':encoding(UTF-8)');
33 $self->$handler($opts);
34
35 exit 0;
36 }
37
38 sub _command_show {
39 my $self = shift;
40 my $opts = shift;
41
42 my $toplevel = git_toplevel('.') or die "Not a git repo\n";
43
44 my $codeowners_path = find_codeowners_in_directory($toplevel)
45 or die "No CODEOWNERS file in $toplevel\n";
46 my $codeowners = File::Codeowners->parse_from_filepath($codeowners_path);
47
48 my ($proc, $cdup) = run_git(qw{rev-parse --show-cdup});
49 $proc->wait and exit 1;
50
51 my $show_projects = $opts->{projects} // scalar @{$codeowners->projects};
52
53 my $formatter = App::Codeowners::Formatter->new(
54 format => $opts->{format} || ' * %-50F %O',
55 handle => *STDOUT,
56 columns => [
57 'File',
58 $opts->{patterns} ? 'Pattern' : (),
59 'Owner',
60 $show_projects ? 'Project' : (),
61 ],
62 );
63
64 my %filter_owners = map { $_ => 1 } @{$opts->{owner}};
65 my %filter_projects = map { $_ => 1 } @{$opts->{project}};
66 my %filter_patterns = map { $_ => 1 } @{$opts->{pattern}};
67
68 $proc = git_ls_files('.', $opts->args);
69 while (my $filepath = $proc->next) {
70 my $match = $codeowners->match(path($filepath)->relative($cdup));
71 if (%filter_owners) {
72 for my $owner (@{$match->{owners}}) {
73 goto ADD_RESULT if $filter_owners{$owner};
74 }
75 next;
76 }
77 if (%filter_patterns) {
78 goto ADD_RESULT if $filter_patterns{$match->{pattern} || ''};
79 next;
80 }
81 if (%filter_projects) {
82 goto ADD_RESULT if $filter_projects{$match->{project} || ''};
83 next;
84 }
85 ADD_RESULT:
86 $formatter->add_result([
87 $filepath,
88 $opts->{patterns} ? $match->{pattern} : (),
89 $match->{owners},
90 $show_projects ? $match->{project} : (),
91 ]);
92 }
93 $proc->wait and exit 1;
94 }
95
96 sub _command_owners {
97 my $self = shift;
98 my $opts = shift;
99
100 my $toplevel = git_toplevel('.') or die "Not a git repo\n";
101
102 my $codeowners_path = find_codeowners_in_directory($toplevel)
103 or die "No CODEOWNERS file in $toplevel\n";
104 my $codeowners = File::Codeowners->parse_from_filepath($codeowners_path);
105
106 my $results = $codeowners->owners($opts->{pattern});
107
108 my $formatter = App::Codeowners::Formatter->new(
109 format => $opts->{format} || '%O',
110 handle => *STDOUT,
111 columns => [qw(Owner)],
112 );
113 $formatter->add_result(map { [$_] } @$results);
114 }
115
116 sub _command_patterns {
117 my $self = shift;
118 my $opts = shift;
119
120 my $toplevel = git_toplevel('.') or die "Not a git repo\n";
121
122 my $codeowners_path = find_codeowners_in_directory($toplevel)
123 or die "No CODEOWNERS file in $toplevel\n";
124 my $codeowners = File::Codeowners->parse_from_filepath($codeowners_path);
125
126 my $results = $codeowners->patterns($opts->{owner});
127
128 my $formatter = App::Codeowners::Formatter->new(
129 format => $opts->{format} || '%T',
130 handle => *STDOUT,
131 columns => [qw(Pattern)],
132 );
133 $formatter->add_result(map { [$_] } @$results);
134 }
135
136 sub _command_projects {
137 my $self = shift;
138 my $opts = shift;
139
140 my $toplevel = git_toplevel('.') or die "Not a git repo\n";
141
142 my $codeowners_path = find_codeowners_in_directory($toplevel)
143 or die "No CODEOWNERS file in $toplevel\n";
144 my $codeowners = File::Codeowners->parse_from_filepath($codeowners_path);
145
146 my $results = $codeowners->projects;
147
148 my $formatter = App::Codeowners::Formatter->new(
149 format => $opts->{format} || '%P',
150 handle => *STDOUT,
151 columns => [qw(Project)],
152 );
153 $formatter->add_result(map { [$_] } @$results);
154 }
155
156 sub _command_create { goto &_command_update }
157 sub _command_update {
158 my $self = shift;
159 my $opts = shift;
160
161 my ($filepath) = $opts->args;
162
163 my $path = path($filepath || '.');
164 my $repopath;
165
166 die "Does not exist: $path\n" if !$path->parent->exists;
167
168 if ($path->is_dir) {
169 $repopath = $path;
170 $path = find_codeowners_in_directory($path) || $repopath->child('CODEOWNERS');
171 }
172
173 my $is_new = !$path->is_file;
174
175 my $codeowners;
176 if ($is_new) {
177 $codeowners = File::Codeowners->new;
178 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 for my $line (split(/\n/, $template)) {
189 $codeowners->append(comment => $line);
190 }
191 }
192 else {
193 $codeowners = File::Codeowners->parse_from_filepath($path);
194 }
195
196 if ($repopath) {
197 # if there is a repo we can try to update the list of unowned files
198 my ($proc, @filepaths) = git_ls_files($repopath);
199 $proc->wait and exit 1;
200 $codeowners->clear_unowned;
201 $codeowners->add_unowned(grep { !$codeowners->match($_) } @filepaths);
202 }
203
204 $codeowners->write_to_filepath($path);
205 print STDERR "Wrote $path\n";
206 }
207
208 1;
209
210 __END__
211
212 =pod
213
214 =encoding UTF-8
215
216 =head1 NAME
217
218 App::Codeowners - A tool for managing CODEOWNERS files
219
220 =head1 VERSION
221
222 version 0.49
223
224 =head1 DESCRIPTION
225
226 This is the implementation of the F<git-codeowners> command.
227
228 See L<git-codeowners> for documentation.
229
230 =head1 METHODS
231
232 =head2 main
233
234 App::Codeowners->main(@ARGV);
235
236 Run the script and exit; does not return.
237
238 =head1 BUGS
239
240 Please report any bugs or feature requests on the bugtracker website
241 L<https://github.com/chazmcgarvey/git-codeowners/issues>
242
243 When submitting a bug or request, please include a test-file or a
244 patch to an existing test-file that illustrates the bug or desired
245 feature.
246
247 =head1 AUTHOR
248
249 Charles McGarvey <chazmcgarvey@brokenzipper.com>
250
251 =head1 COPYRIGHT AND LICENSE
252
253 This software is copyright (c) 2019 by Charles McGarvey.
254
255 This is free software; you can redistribute it and/or modify it under
256 the same terms as the Perl 5 programming language system itself.
257
258 =cut
This page took 0.049916 seconds and 3 git commands to generate.