1 package App
::Codeowners
;
2 # ABSTRACT: A tool for managing CODEOWNERS files
4 use v5
.10
.1; # defined-or
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);
16 our $VERSION = '0.49'; # VERSION
21 my $self = bless {}, $class;
23 my $opts = App
::Codeowners
::Options-
>new(@_);
25 my $color = $opts->{color
};
26 local $ENV{NO_COLOR
} = 1 if defined $color && !$color;
28 my $command = $opts->command;
29 my $handler = $self->can("_command_$command")
30 or die "Unknown command: $command\n";
32 binmode(STDOUT
, ':encoding(UTF-8)');
33 $self->$handler($opts);
42 my $toplevel = git_toplevel
('.') or die "Not a git repo\n";
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);
48 my ($proc, $cdup) = run_git
(qw{rev-parse --show-cdup});
49 $proc->wait and exit 1;
51 my $show_projects = $opts->{projects
} // scalar @{$codeowners->projects};
53 my $formatter = App
::Codeowners
::Formatter-
>new(
54 format
=> $opts->{format
} || ' * %-50F %O',
58 $opts->{patterns
} ? 'Pattern' : (),
60 $show_projects ? 'Project' : (),
64 my %filter_owners = map { $_ => 1 } @{$opts->{owner
}};
65 my %filter_projects = map { $_ => 1 } @{$opts->{project
}};
66 my %filter_patterns = map { $_ => 1 } @{$opts->{pattern
}};
68 $proc = git_ls_files
('.', $opts->args);
69 while (my $filepath = $proc->next) {
70 my $match = $codeowners->match(path
($filepath)->relative($cdup));
72 for my $owner (@{$match->{owners
}}) {
73 goto ADD_RESULT
if $filter_owners{$owner};
77 if (%filter_patterns) {
78 goto ADD_RESULT
if $filter_patterns{$match->{pattern
} || ''};
81 if (%filter_projects) {
82 goto ADD_RESULT
if $filter_projects{$match->{project
} || ''};
86 $formatter->add_result([
88 $opts->{patterns
} ? $match->{pattern
} : (),
90 $show_projects ? $match->{project
} : (),
93 $proc->wait and exit 1;
100 my $toplevel = git_toplevel
('.') or die "Not a git repo\n";
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);
106 my $results = $codeowners->owners($opts->{pattern
});
108 my $formatter = App
::Codeowners
::Formatter-
>new(
109 format
=> $opts->{format
} || '%O',
111 columns
=> [qw(Owner)],
113 $formatter->add_result(map { [$_] } @$results);
116 sub _command_patterns
{
120 my $toplevel = git_toplevel
('.') or die "Not a git repo\n";
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);
126 my $results = $codeowners->patterns($opts->{owner
});
128 my $formatter = App
::Codeowners
::Formatter-
>new(
129 format
=> $opts->{format
} || '%T',
131 columns
=> [qw(Pattern)],
133 $formatter->add_result(map { [$_] } @$results);
136 sub _command_projects
{
140 my $toplevel = git_toplevel
('.') or die "Not a git repo\n";
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);
146 my $results = $codeowners->projects;
148 my $formatter = App
::Codeowners
::Formatter-
>new(
149 format
=> $opts->{format
} || '%P',
151 columns
=> [qw(Project)],
153 $formatter->add_result(map { [$_] } @$results);
156 sub _command_create
{ goto &_command_update
}
157 sub _command_update
{
161 my ($filepath) = $opts->args;
163 my $path = path
($filepath || '.');
166 die "Does not exist: $path\n" if !$path->parent->exists;
170 $path = find_codeowners_in_directory
($path) || $repopath->child('CODEOWNERS');
173 my $is_new = !$path->is_file;
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.
183 Simply write a gitignore pattern followed by one or more names/emails/groups.
186 *.js @harry @javascript-cabal
188 for my $line (split(/\n/, $template)) {
189 $codeowners->append(comment
=> $line);
193 $codeowners = File
::Codeowners-
>parse_from_filepath($path);
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);
204 $codeowners->write_to_filepath($path);
205 print STDERR
"Wrote $path\n";
218 App::Codeowners - A tool for managing CODEOWNERS files
226 This is the implementation of the F<git-codeowners> command.
228 See L<git-codeowners> for documentation.
234 App::Codeowners->main(@ARGV);
236 Run the script and exit; does not return.
240 Please report any bugs or feature requests on the bugtracker website
241 L<https://github.com/chazmcgarvey/git-codeowners/issues>
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
249 Charles McGarvey <chazmcgarvey@brokenzipper.com>
251 =head1 COPYRIGHT AND LICENSE
253 This software is copyright (c) 2019 by Charles McGarvey.
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.