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);
13 use Encode
qw(encode);
17 our $VERSION = '0.48'; # VERSION
22 my $self = bless {}, $class;
24 my $opts = App
::Codeowners
::Options-
>new(@_);
26 my $color = $opts->{color
};
27 local $ENV{NO_COLOR
} = 1 if defined $color && !$color;
29 my $command = $opts->command;
30 my $handler = $self->can("_command_$command")
31 or die "Unknown command: $command\n";
32 $self->$handler($opts);
41 my $toplevel = git_toplevel
('.') or die "Not a git repo\n";
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);
47 my ($proc, $cdup) = run_git
(qw{rev-parse --show-cdup});
48 $proc->wait and exit 1;
50 my $show_projects = $opts->{projects
} // scalar @{$codeowners->projects};
52 my $formatter = App
::Codeowners
::Formatter-
>new(
53 format
=> $opts->{format
} || ' * %-50F %O',
57 $opts->{patterns
} ? 'Pattern' : (),
59 $show_projects ? 'Project' : (),
63 my %filter_owners = map { $_ => 1 } @{$opts->{owner
}};
64 my %filter_projects = map { $_ => 1 } @{$opts->{project
}};
65 my %filter_patterns = map { $_ => 1 } @{$opts->{pattern
}};
67 $proc = git_ls_files
('.', $opts->args);
68 while (my $filepath = $proc->next) {
69 my $match = $codeowners->match(path
($filepath)->relative($cdup));
71 for my $owner (@{$match->{owners
}}) {
72 goto ADD_RESULT
if $filter_owners{$owner};
76 if (%filter_patterns) {
77 goto ADD_RESULT
if $filter_patterns{$match->{pattern
} || ''};
80 if (%filter_projects) {
81 goto ADD_RESULT
if $filter_projects{$match->{project
} || ''};
85 $formatter->add_result([
87 $opts->{patterns
} ? $match->{pattern
} : (),
89 $show_projects ? $match->{project
} : (),
92 $proc->wait and exit 1;
99 my $toplevel = git_toplevel
('.') or die "Not a git repo\n";
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);
105 my $results = $codeowners->owners($opts->{pattern
});
107 my $formatter = App
::Codeowners
::Formatter-
>new(
108 format
=> $opts->{format
} || '%O',
110 columns
=> [qw(Owner)],
112 $formatter->add_result(map { [$_] } @$results);
115 sub _command_patterns
{
119 my $toplevel = git_toplevel
('.') or die "Not a git repo\n";
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);
125 my $results = $codeowners->patterns($opts->{owner
});
127 my $formatter = App
::Codeowners
::Formatter-
>new(
128 format
=> $opts->{format
} || '%T',
130 columns
=> [qw(Pattern)],
132 $formatter->add_result(map { [$_] } @$results);
135 sub _command_projects
{
139 my $toplevel = git_toplevel
('.') or die "Not a git repo\n";
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);
145 my $results = $codeowners->projects;
147 my $formatter = App
::Codeowners
::Formatter-
>new(
148 format
=> $opts->{format
} || '%P',
150 columns
=> [qw(Project)],
152 $formatter->add_result(map { [$_] } @$results);
155 sub _command_create
{ goto &_command_update
}
156 sub _command_update
{
160 my ($filepath) = $opts->args;
162 my $path = path
($filepath || '.');
165 die "Does not exist: $path\n" if !$path->parent->exists;
169 $path = find_codeowners_in_directory
($path) || $repopath->child('CODEOWNERS');
172 my $is_new = !$path->is_file;
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.
182 Simply write a gitignore pattern followed by one or more names/emails/groups.
185 *.js @harry @javascript-cabal
187 for my $line (split(/\n/, $template)) {
188 $codeowners->append(comment
=> $line);
192 $codeowners = File
::Codeowners-
>parse_from_filepath($path);
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);
203 $codeowners->write_to_filepath($path);
204 print STDERR
"Wrote $path\n";
217 App::Codeowners - A tool for managing CODEOWNERS files
225 This is the implementation of the F<git-codeowners> command.
227 See L<git-codeowners> for documentation.
233 App::Codeowners->main(@ARGV);
235 Run the script and exit; does not return.
239 Please report any bugs or feature requests on the bugtracker website
240 L<https://github.com/chazmcgarvey/git-codeowners/issues>
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
248 Charles McGarvey <chazmcgarvey@brokenzipper.com>
250 =head1 COPYRIGHT AND LICENSE
252 This software is copyright (c) 2019 by Charles McGarvey.
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.