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