]> Dogcows Code - chaz/git-codeowners/blob - lib/App/Codeowners.pm
Version 0.46
[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.46'; # 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.46
222
223 =head1 DESCRIPTION
224
225 This is the implementation of the F<git-codeowners> command.
226
227 See L<git-codeowners> for documentation.
228
229 =head1 METHODS
230
231 =head2 main
232
233 App::Codeowners->main(@ARGV);
234
235 Run the script and exit; does not return.
236
237 =head1 BUGS
238
239 Please report any bugs or feature requests on the bugtracker website
240 L<https://github.com/chazmcgarvey/git-codeowners/issues>
241
242 When submitting a bug or request, please include a test-file or a
243 patch to an existing test-file that illustrates the bug or desired
244 feature.
245
246 =head1 AUTHOR
247
248 Charles McGarvey <chazmcgarvey@brokenzipper.com>
249
250 =head1 COPYRIGHT AND LICENSE
251
252 This software is copyright (c) 2019 by Charles McGarvey.
253
254 This is free software; you can redistribute it and/or modify it under
255 the same terms as the Perl 5 programming language system itself.
256
257 =cut
This page took 0.051658 seconds and 4 git commands to generate.