pick-to-branch: Improve fix of behavior on failed cherry-pick
[tools.git] / review-tools / gitaddrev
1 #!/usr/bin/env perl
2
3 use strict;
4 use warnings;
5
6 use File::Basename;
7 use FindBin;
8
9 use OpenSSL::Query::REST;
10 use Module::Load::Conditional qw(can_load);
11
12 can_load(modules => { 'OpenSSL::Query::DB' => undef });
13
14 my $rmrev = 0;
15 my @commits;
16 my $skip = 0;
17 my $matchstr;
18 my $clatype;
19 my $found = 0;
20 my $num = 0;
21 my $refuse = 0;
22 my $prnum = 0;
23 my $verbose = 0;
24 my $WHAT = 'openssl';
25
26 my $query = OpenSSL::Query->new();
27
28 my @reviewers;
29 my @nocla_reviewers;
30 my @unknown_reviewers;
31 my $skip_reviewer;
32 my $otccount = 0;
33 sub try_add_reviewer {
34     my $id = shift;
35     my $rc = undef;
36     my $id2 = $id =~ /^\@(.*)$/ ? { github => $1 } : $id;
37     my $rev = $query->find_person_tag($id2, 'rev');
38     if ($rev) {
39         my $cla = $query->has_cla(lc $rev);
40         if ($cla) {
41             unless (grep {$_ eq $rev} @reviewers) {
42                 $otccount++ if $query->is_member_of($id2, 'otc');
43                 push @reviewers, $rev;
44             }
45             $rc = $rev;
46         } else {
47             push @nocla_reviewers, $id
48                 unless grep {$_ eq $id} @nocla_reviewers;
49         }
50     } else {
51         push @unknown_reviewers, $id
52             unless grep {$_ eq $id} @unknown_reviewers;
53         unless ($id =~ m|^.+\@.*$| && $query->has_cla(lc $id)) {
54             push @nocla_reviewers, $id
55                 unless grep {$_ eq $id} @nocla_reviewers;
56         }
57     }
58     return $rc;
59 }
60
61 foreach (@ARGV) {
62     if (/^--list$/) {
63         my %list = ();
64         foreach ($query->list_people()) {
65             my $email_id = (grep { ref($_) eq "" && $_ =~ m|\@| } @$_)[0];
66             my $rev = $query->find_person_tag($email_id, 'rev');
67             my $otc = $query->is_member_of($email_id, 'otc');
68             next unless $query->has_cla(lc $rev);
69             next unless $query->is_member_of($email_id, 'commit') || $otc;
70             my @ids =
71                 sort grep { $_ =~ /^[a-z]+$/ || $_ =~ /^\@(?:\w|\w-\w)+$/ }
72                 map {
73                     if (ref($_) eq "HASH") {
74                         my %h = %$_;
75                         map { $_ eq "github" ? '@'.$h{$_} : $h{$_} } keys %h;
76                     } else {
77                         $_;
78                     }
79                 } @$_;
80             foreach (@ids) {
81                 $list{$_} = { tag => $rev, otc => $otc };
82             }
83         }
84         foreach (sort { my $res = $list{$a}->{tag} cmp $list{$b}->{tag};
85                         $res != 0 ? $res : ($a cmp $b) } keys %list) {
86             printf "%-15s %-6s (%s)\n",
87                 $_, $list{$_}->{otc} ? "[OTC]" : "", $list{$_}->{tag};
88         }
89         exit 0;
90     } elsif (/^--reviewer=(.+)$/) {
91         try_add_reviewer($1);
92     } elsif (/^--prnum=(.+)$/) {
93         $prnum = $1;
94     } elsif (/^--commit=(.+)$/) {
95         push @commits, $1;
96         $skip = 1;
97     } elsif (/^--rmreviewers$/) {
98         $rmrev = 1;
99     } elsif (/^--myemail=(.+\@.+)$/) {
100         try_add_reviewer($1);
101     } elsif (/^--verbose$/) {
102         $verbose = 1;
103     } elsif (/^--web$/) {
104         $WHAT = 'web';
105     } elsif (/--tools$/) {
106         $WHAT = 'tools'
107     }
108 }
109
110 my @commit_message = map { (my $x = $_) =~ s|\R$||; $x } <STDIN>;
111 my $author = $ENV{GIT_AUTHOR_EMAIL};
112 my $trivial = !! grep(/^CLA:\s*Trivial\s*$/i, @commit_message);
113
114 # If the author is a registered committer, that identity passes as a reviewer
115 # too.  There is a twist, though...  see next comment
116 if (my $rev = try_add_reviewer($author)) {
117
118     # So here's the deal: We added the commit author because we need to keep
119     # count of the total amount of reviewers, which includes the commit author
120     # if it's a registered committer.  However, the author can "reviewed
121     # themselves", so no Reviewed-by: should be added for that identity.
122     # However, we still need to check the @reviewers count below, so the
123     # solution is to record this rev tag separately and remove it from
124     # @reviewers after the count check.
125     $skip_reviewer = $rev;
126
127 } else {
128
129     # In case the author is unknown to our databases or is lacking a CLA,
130     # we need to be extra careful to check if this is supposed to be a
131     # trivial commit.
132
133     # Note: it really should be enough to check if $author is unknown, since
134     # the databases are supposed to be consistent with each other.  However,
135     # let's be cautious and check both, in case someone has been registered
136     # as a known identity without having a CLA in place.
137     die "Commit author ",$author," has no CLA, and this is a non-trivial commit\n"
138         if !$trivial && grep { $_ eq $author } (@nocla_reviewers);
139
140     # Now that that's cleared, remove the author from anything that could cause
141     # more unnecessary errors (false positives).
142     @nocla_reviewers = grep { $_ ne $author } @nocla_reviewers;
143     @unknown_reviewers = grep { $_ ne $author } @unknown_reviewers;
144     $skip_reviewer = $author;
145 }
146
147 if (@unknown_reviewers) {
148     die "Unknown reviewers: ", join(", ", @unknown_reviewers), "\n";
149 }
150 if (@nocla_reviewers) {
151     die "Reviewers without CLA: ", join(", ", @nocla_reviewers), "\n";
152 }
153
154 print STDERR "Detected trivial marker\n" if $verbose && $trivial;
155
156 if (scalar @reviewers < 2) {
157     die "Too few reviewers (total must be at least 2)\n";
158 }
159 if ($otccount < 1) {
160     die "At least one of the reviewers must be an OTC member\n";
161 }
162 if ($skip_reviewer) {
163     @reviewers = grep { !m/$skip_reviewer/i } @reviewers;
164     @nocla_reviewers = grep { !m/$skip_reviewer/i } @nocla_reviewers;
165     @unknown_reviewers = grep { !m/$skip_reviewer/i } @unknown_reviewers;
166 }
167
168 print STDERR "Going with these reviewers:\n  ", join("\n  ", @reviewers), "\n"
169     if $verbose;
170
171 if ($skip == 1) {
172     my $commit_id = $ENV{GIT_COMMIT};
173     foreach(@commits) {
174         if ($commit_id =~ /^$_/) {
175             $skip = 0;
176             last;
177         }
178     }
179     if ($skip == 1) {
180         while(<STDIN>) {
181             print;
182         }
183     exit(0);
184     }
185 }
186
187 if (scalar @reviewers == 0 && $rmrev == 0) {
188     die "No reviewer set!\n";
189 }
190
191 # Remove blank lines from the end of commit message
192 pop @commit_message while $commit_message[$#commit_message] =~ m|^\s*$|;
193
194 my $last_is_rev = 0;
195 foreach (@commit_message) {
196     # Start each line with assuming it's not a reviewed-by line
197     $last_is_rev = 0;
198     if (/^\(Merged from https:\/\/github\.com\/openssl\/$WHAT\/pull\//) {
199         next if $rmrev == 1;
200         $last_is_rev = 1;
201         next;                   # Because we're rewriting it below
202                                 # (unless --nopr was given in addrev)
203     } elsif (/^Reviewed-by:\s*(\S.*\S)\s*$/) {
204         my $id = $1;
205         next if $rmrev == 1;
206         $last_is_rev = 1;
207         # Remove reviewers that are already in the message from our reviewer list
208         @reviewers = grep { $_ ne $id } @reviewers;
209     }
210     print $_,"\n";
211 }
212 if ($rmrev == 0) {
213     #Add a blank line unless the last one is a review line
214     print "\n" unless $last_is_rev;
215     foreach(@reviewers) {
216         print "Reviewed-by: $_\n";
217     }
218 }
219
220 print "(Merged from https://github.com/openssl/$WHAT/pull/$prnum)\n"
221     if $prnum;