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