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.45'; # 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
227 App::Codeowners->main(@ARGV);
229 Run the script and exit; does not return.
233 Please report any bugs or feature requests on the bugtracker website
234 L<https://github.com/chazmcgarvey/git-codeowners/issues>
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
242 Charles McGarvey <chazmcgarvey@brokenzipper.com>
244 =head1 COPYRIGHT AND LICENSE
246 This software is copyright (c) 2019 by Charles McGarvey.
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.