blob: 8ce4bd2d9c1ba84ed19f145156ea9b662cd3401e [file] [log] [blame]
Andrew Yourtchenko8ffe8f62021-07-14 22:44:05 +02001#!/usr/bin/perl
2
3use Data::Dumper;
4use Env;
5use File::Glob ':glob';
6
7#
8# Use this script to determine the reviewers for a given commit, like so:
9#
10# ayourtch@ayourtch-lnx:~/vpp$ ./extras/scripts/vpp-review HEAD
11# commit 7ea63c597f82412b68b5a565df10e13669b1decb
12# Author: Andrew Yourtchenko <ayourtch@gmail.com>
13# Date: Wed Jul 14 22:44:05 2021 +0200
14#
15# misc: experimental script to get the list of the reviewers for a commit
16#
17# accepts zero or one argument (the commit hash), and outputs
18# the detected components, the component maintainers,
19# and the final suggested reviewer list
20#
21# Change-Id: Ief671fe837c6201bb11fd05d02af881822b0bb33
22# Type: docs
23# Signed-off-by: Andrew Yourtchenko <ayourtch@gmail.com>
24#
25# :000000 100755 000000000 635dde59f A extras/scripts/vpp-review
26#
27#
28# Components of files in the commit:
29# misc: extras/scripts/vpp-review
30#
31# Component misc maintainers:
32# vpp-dev Mailing List <vpp-dev@fd.io>
33#
34#
35# Final reviewer list:
36# vpp-dev Mailing List <vpp-dev@fd.io>
37# ayourtch@ayourtch-lnx:~/vpp$
38#
39
40#
41# Read the maintainers file into a hash, indexed by short component name.
42#
43sub read_maintainers() {
44 open(F, "MAINTAINERS") || die("Could not open MAINTAINERS file");
45 my $short_name = "";
46 my $long_name = "";
47 my $in_component = 0;
48 my $maintainers = [];
49 my $paths = [];
50 my $exclude_paths = [];
51 my $feature_yaml = [];
52 my $comments = [];
53 my $out = {};
54 while (<F>) {
55 chomp();
56 my $aLine = $_;
57 # print ("LINE: $aLine\n");
58 if (/^([A-Z]):\s+(.*)$/) {
59 my $aLetter = $1;
60 my $aValue = $2;
61 # print ("LETTER: $aLetter, VALUE: $aValue\n");
62 if ($aLetter eq "I") {
63 $short_name = $aValue;
64 }
65 elsif ($aLetter eq "M") {
66 push(@{$maintainers}, $aValue);
67 }
68 elsif ($aLetter eq "F") {
69 push(@{$paths}, $aValue);
70 }
71 elsif ($aLetter eq "E") {
72 push(@{$exclude_paths}, $aValue);
73 }
74 elsif ($aLetter eq "Y") {
75 push(@{$feature_yaml}, $aValue);
76 }
77 elsif ($aLetter eq "C") {
78 push(@{$comments}, $aValue);
79 } else {
80 print ("LETTER: $aLetter, VALUE: $aValue\n");
81 }
82 # FIXME: deal with all the other letters here
83 } elsif (/^(\s*)$/) {
84 if ($in_component) {
85 $in_component = 0;
86 if ($short_name ne "") {
87 my $comp = {};
88 $comp->{'short_name'} = $short_name;
89 $comp->{'name'} = $long_name;
90 $comp->{'maintainers'} = $maintainers;
91 $comp->{'paths'} = $paths;
92 $comp->{'comments'} = $comments;
93 $comp->{'exclude_paths'} = $exclude_paths;
94 $comp->{'feature_yaml'} = $feature_yaml;
95 $out->{$short_name} = $comp;
96 # print("FEATURE: $short_name => $long_name\n");
97 $short_name = "";
98 $long_name = "";
99 $maintainers = [];
100 $paths = [];
101 $exclude_paths = [];
102 $comments = [];
103 $feature_yaml = [];
104 }
105 }
106 # print ("END\n");
107 } elsif (/^([^\s].*)$/) {
108 $in_component = 1;
109 $long_name = $1;
110 }
111 }
112
113 if ($in_component) {
114 $in_component = 0;
115 if ($short_name ne "") {
116 my $comp = {};
117 $comp->{'short_name'} = $short_name;
118 $comp->{'name'} = $long_name;
119 $comp->{'maintainers'} = $maintainers;
120 $comp->{'paths'} = $paths;
121 $comp->{'comments'} = $comments;
122 $comp->{'exclude_paths'} = $exclude_paths;
123 $comp->{'feature_yaml'} = $feature_yaml;
124 $out->{$short_name} = $comp;
125 }
126
127 }
128
129 return($out);
130}
131
132sub match_my_path {
133 my $p = $_[0];
134 my $apath = $_[1];
135 my $root = $_[2];
136
137 my $p1 = $p;
138 $p1 =~ s#\*#[^/]*#g;
139
140 my $pattern = "$root$p1";
141
142 if ($apath =~ /$pattern/) {
143 return 1;
144 } else {
145 return 0;
146 }
147}
148
149sub match_path_to_list {
150 my $path_list = $_[0];
151 my $apath = $_[1];
152 my $root = $_[2];
153 foreach $p (@{$path_list}) {
154 if (match_my_path($p, $apath, $root)) {
155 return 1;
156 }
157 }
158 return 0;
159}
160
161sub match_path_to_comp_hash {
162 my $chash = $_[0];
163 my $apath = $_[1];
164 my $root = $_[2];
165 my $is_included = match_path_to_list($chash->{'paths'}, $apath, $root);
166 my $is_excluded = match_path_to_list($chash->{'exclude_paths'}, $apath, $root);
167 return ($is_included && !$is_excluded);
168}
169
170
171sub match_path {
172 my $components = $_[0];
173 my $apath = $_[1];
174 my $root = $_[2];
175 my $out = [];
176
177 foreach $aCompName (keys %{$components}) {
178 my $chash = $components->{$aCompName};
179 if (match_path_to_comp_hash($chash, $apath, $root)) {
180 push(@{$out}, $aCompName);
181 }
182 }
183 my $out1 = $out;
184
185 if (scalar @{$out} > 1) {
186 # not very efficient way to filter out "misc" but oh well.
187 $out1 = [];
188 foreach $aval (@{$out}) {
189 if ($aval ne "misc") {
190 push(@{$out1}, $aval);
191 }
192 }
193 }
194
195 return ($out1);
196}
197
198my $commit_id = $ARGV[0];
199my $commit_log = `git log --raw -n 1 $commit_id`;
200my $files = [];
201foreach my $aLine (split(/[\r\n]+/, $commit_log)) {
202 if ($aLine =~ /^:[0-7]+\s+[0-7]+\s+[0-9a-f]+\s+[0-9a-f]+\s+\S+\s+(\S+)$/) {
203 push(@{$files}, $1);
204 }
205}
206
207my $comp = read_maintainers();
208my $matched_comps = {};
209my $matched_reviewers = {};
210
211print("$commit_log\n\n");
212
213print("Components of files in the commit:\n");
214
215foreach $aFile (@{$files}) {
216 my $matches = match_path($comp, $aFile, '');
217 my $matches_str = join(" ", @{$matches});
218 print (" $matches_str: $aFile\n");
219
220 foreach my $aComp (@{$matches}) {
221 $matched_comps->{$aComp} = 1;
222 }
223}
224
225foreach my $aKey (keys %{$matched_comps}) {
226 print("\nComponent $aKey maintainers:\n");
227 foreach my $aRV (@{$comp->{$aKey}->{'maintainers'}}) {
228 print(" $aRV\n");
229 $matched_reviewers->{$aRV} = 1;
230 }
231}
232
233print("\n\nFinal reviewer list:\n");
234
235foreach my $aRV (keys %{$matched_reviewers}) {
236 print(" $aRV\n");
237}
238# print Dumper(\$comp);
239
240