Subversion Repositories DevTools

Rev

Rev 1295 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
1293 dpurdie 1
#!/usr/bin/perl
2
 
3
###############################################################################
4
# SubmitCodeReview.pl
5
# A utility for automating the submission of CVS code reviews to Codestriker.
6
#
7
# Copyright (c) 2003 Altona Ed, LLC
8
# Written by Aaron Kardell.
9
# All rights reserved.
10
#
11
# May be redistributed and modified only under the terms of the GPL.
12
# Absolutely no warranty of any kind, express or implied, is granted.
13
###############################################################################
14
 
15
$REVIEWER = '';
16
$BASE_URL = '';
17
$MODULE_NAME = '';
18
 
19
$do_slow_condense = 0;
20
$do_http_transfer = 1;
21
$do_file_output = 0;
22
 
23
$next_is_special = 0;
24
foreach $arg (@ARGV) {
25
  if ($next_is_special) {
26
    if ($next_is_special eq '--file') {
27
      $do_http_transfer = 0;
28
      $do_file_output = $arg;
29
    }
30
    elsif ($next_is_special eq '--url') {
31
      $BASE_URL = $arg;
32
      $do_http_transfer = 1;
33
      $do_file_output = 0;
34
    }
35
    elsif ($next_is_special eq '--reviewer') {
36
      $REVIEWER = $arg;
37
    }
38
    elsif ($next_is_special eq '--module_name') {
39
      $MODULE_NAME = $arg;
40
    }
41
    $next_is_special = 0;
42
  }
43
  elsif ($arg eq '--condense') {
44
    $do_slow_condense = 1;
45
  }
46
  elsif ($arg eq '--file') {
47
    $next_is_special = '--file';
48
  }
49
  elsif ($arg eq '--url') {
50
    $next_is_special = '--url';
51
  }
52
  elsif ($arg eq '--reviewer') {
53
    $next_is_special = '--reviewer';
54
  }
55
  elsif ($arg eq '--module-name') {
56
    $next_is_special = '--module-name';
57
  }
58
  elsif ($arg eq '--help' || $arg eq '-h' || $arg eq '-H') {
59
    &print_help;
60
  }
61
}
62
 
63
$addl_diff_switches = '';
64
if ($do_slow_condense) {
65
  $addl_diff_switches = ' -d ';
66
}
67
 
68
if (! -e ($ENV{HOME}."/.crcache")) {
69
  mkdir($ENV{HOME}."/.crcache");
70
}
71
 
72
if ($do_http_transfer) {
73
  $lwp_avail = eval {
74
    no warnings 'all';
75
    require LWP::UserAgent;
76
    require HTTP::Request;
77
    require HTTP::Request::Common;
78
    1;
79
  };
80
 
81
  if (!$lwp_avail) {
82
    print "LWP::UserAgent not installed, so direct posting not available.\nTry --file instead.\n\n";
83
    &print_help;
84
  }
85
 
86
  require LWP::UserAgent;
87
  require HTTP::Request;
88
  require HTTP::Request::Common;
89
 
90
  if ($BASE_URL =~ m|^https?://([^/]+)/|) {
91
    $HOST_PORT = $1;
92
    if ($HOST_PORT !~ /:/) {
93
      $HOST_PORT .= ':80';
94
    }
95
  }
96
 
97
  if (! -e ($ENV{HOME}."/.cremail")) {
98
    print "Please enter your e-mail address: ";
99
    $MY_EMAIL = &getinputnotblank;
100
    chomp($MY_EMAIL);
101
    open (SETEMAIL, ">".$ENV{HOME}."/.cremail");
102
    print SETEMAIL "$MY_EMAIL\n";
103
    close (SETEMAIL);
104
    print "Thanks.\n";
105
  }
106
 
107
  $ua = new LWP::UserAgent;
108
  $req = HTTP::Request->new(GET => "$BASE_URL");
109
  $res = $ua->request($req);
110
  $first_check = 1;
111
  if ($res->code ne '401' && (!$res->is_success || $res->content eq '')) {
112
    print "Unable to contact host specified.  Edit this file or specify a different host.\nAlternatively, try --file instead.\n\n";
113
    &print_help;
114
  }
115
  while ($res->code eq '401') {
116
    if ($first_check) {
117
      print "A password is required when submitting from offsite.\n";
118
    }
119
    else {
120
      print "Invalid password, please try again.\n";
121
    }
122
    ($username, $password) = &get_credentials;
123
    $ua = new LWP::UserAgent;
124
    $REALM = $res->header('WWW-Authenticate');
125
    $REALM =~ s/^[^"]*"//;
126
    $REALM =~ s/"[^"]*$//;
127
    $ua->credentials($HOST_PORT, $REALM, $username, $password);
128
    $req = HTTP::Request->new(GET => "$BASE_URL");
129
    $res = $ua->request($req);
130
    $first_check = 0;
131
  }
132
 
133
  open (GETEMAIL, $ENV{HOME}."/.cremail");
134
  $MY_EMAIL = <GETEMAIL>;
135
  chomp($MY_EMAIL);
136
  close (GETEMAIL);
137
}
138
 
139
$current_dir = `pwd`;
140
$current_dir =~ s/[\r\n]//sg;
141
 
142
print "Inspecting files in: $current_dir\n";
143
 
144
open (CVSROOT, "CVS/Root");
145
$cvsroot = <CVSROOT>;
146
close (CVSROOT);
147
 
148
$cvsroot =~ s/\015//g;
149
$cvsroot =~ s/\012//g;
150
$cvsroot =~ s/^.*:([^:]*)$/$1/g;
151
$cvsroot =~ s|/$||;
152
 
153
open (CVSREPOSITORY, "CVS/Repository");
154
$cvsrepository = <CVSREPOSITORY>;
155
close (CVSREPOSITORY);
156
 
157
$cvsrepository =~ s/\015//g;
158
$cvsrepository =~ s/\012//g;
159
 
160
$MODULE_NAME =~ s|/$||;
161
 
162
if ($MODULE_NAME) {
163
  $cvsrepository =~ s|^$MODULE_NAME/?||;
164
}
165
 
166
$cvsrootandmodule = $cvsroot.'/'.$MODULE_NAME;
167
 
168
$cvs_status_out = `cvs status 2>/dev/null`;
169
$cvs_status_out =~ s/\015//sg;
170
 
171
@file_infos = split(/^=+$/m,$cvs_status_out);
172
splice(@file_infos,0,1);
173
 
174
@modified_files = ();
175
 
176
foreach $file_info (@file_infos) {
177
  $version = 'NV';
178
  if ($file_info =~ /Working revision:\s+(.*?)$/m) {
179
    $version = $1;
180
    if ($version eq 'New file!') {
181
      $version = 'NF';
182
    }
183
  }
184
  if ($file_info =~ /Status: Needs Merge/m || $file_info =~ /Status: Needs Patch/m || $file_info =~ /Status: Needs Checkout/m) {
185
    die "You must do a CVS update before submitting a code review.\n";
186
  }
187
  elsif ($file_info =~ /Status: Locally Modified/m || $file_info =~ /Status: File had conflicts on merge/m) {
188
    if ($file_info =~ m|Repository revision:[^/]+(/.*)$|m) {
189
      $file = $1;
190
      $file =~ s/,v$//;
191
      $file =~ s|^$cvsrootandmodule/||;
192
      if ($file_info =~ /Status: File had conflicts on merge/m) {
193
        print "WARNING: $file had conflicts on merge, which may or may not have been fixed yet.\n";
194
      }
195
      $file =~ s|/Attic/|/|;
196
      push (@modified_files, $file);
197
      $file_version{$file} = $version;
198
    }
199
  }
200
  elsif ($file_info =~ /Status: Locally Added/m) {
201
    if ($file_info =~ m|File: *([^ ]+) |m) {
202
      $file = $1;
203
      $file = `find . -name "$file"`;
204
      foreach $f (split(/\n/,$file)) {
205
        if ($f) {
206
          $f =~ s|^\./||;
207
	  if ($cvsrepository) {
208
	    $f = $cvsrepository . '/' . $f;
209
	  }
210
          push (@modified_files, $f);
211
          $file_version{$f} = $version;
212
        }
213
      }
214
    }
215
  }
216
}
217
 
218
if (@modified_files == 0) {
219
  die "No files have been changed, so no code review is needed.\n";
220
}
221
 
222
$all_selection = 0;
223
 
224
while ($all_selection == 0) {
225
  print "\nThe following files have changed:\n";
226
  foreach $file (@modified_files) {
227
    print "  $file\n";
228
  }
229
  print "Include Code Review for 1) All; 2) Only files I will select? ";
230
  $line = &getinput;
231
  if ($line == 1 || $line == 2) {
232
    $all_selection = $line;
233
  }
234
}
235
 
236
if ($all_selection == 2) {
237
  $done = 0;
238
  %include_these = {};
239
  while (!$done) {
240
    print "\nInclude the following files with a *:\n";
241
    $counter = 1;
242
    foreach $file (@modified_files) {
243
      print "  $counter) ";
244
      if ($counter < 10) { print "  "; }
245
      elsif ($counter < 100) { print " "; }
246
      if ($include_these{$counter}) { print "* "; }
247
      else { print "  "; }
248
      print "$file\n";
249
      $counter++;
250
    }
251
    print "Enter number(s) to toggle, ALL, NONE, or DONE? ";
252
    $line = &getinput;
253
    if ($line =~ /done/i) {
254
      $done = 1;
255
    }
256
    elsif ($line =~ /(all|none)/i) {
257
      $toggle_to = ($line =~ /all/i);
258
      for ($i=1; $i<=@modified_files; $i++) {
259
        $include_these{$i} = $toggle_to;
260
      }
261
    }
262
    else {
263
      @nums = split(/[^\d\-]+/, $line);
264
      foreach $num (@nums) {
265
        if ($num =~ /^(\d+)-(\d+)$/) {
266
          $begin = $1;
267
          $end = $2;
268
          if ($end < $begin) { $t = $end; $end = $begin; $begin = $t; }
269
          for ($num=$begin; $num<=$end; $num++) {
270
            $include_these{$num} = ($include_these{$num} ? 0 : 1);
271
          }
272
        }
273
        else {
274
          $include_these{$num} = ($include_these{$num} ? 0 : 1);
275
        }
276
      }
277
    }
278
  }
279
  for ($i=@modified_files; $i>=1; $i--) {
280
    if ($include_these{$i} == 0) {
281
      splice(@modified_files, $i-1, 1);
282
    }
283
  }
284
}
285
 
286
print "\nThe following will be included in the Code Review:\n";
287
@incremental_eligible = ();
288
foreach $file (@modified_files) {
289
  $file_no_slash = $file;
290
  $file_no_slash =~ s|/|__|g;
291
  $file_no_slash = $ENV{HOME}."/.crcache/$file_no_slash--".$file_version{$file};
292
  if (-e $file_no_slash) {
293
    push(@incremental_eligible, $file);
294
    $incremental_locs{$file} = $file_no_slash;
295
  }
296
  print "  $file\n";
297
}
298
 
299
@modified_incremental = ();
300
 
301
if (@incremental_eligible > 0) {
302
  $done = 0;
303
  $doinc = '';
304
  while (!$done) {
305
    print "\nYou are eligible for an 'incremental' code review. Do incremental? [Y]/N: ";
306
    $doinc = lc(&getinput);
307
    if ($doinc eq 'y' || $doinc eq '') {
308
      $doinc = 1;
309
      $done = 1;
310
    }
311
    elsif ($doinc eq 'n') {
312
      $doinc = 0;
313
      $done = 1;
314
    }
315
  }
316
 
317
  if ($doinc) {
318
    @modified_incremental = @incremental_eligible;
319
    my %tmp_inc;
320
    foreach $file (@modified_incremental) {
321
      $tmp_inc{$file} = 1;
322
    }
323
 
324
    for ($i=@modified_files-1; $i>=0; $i--) {
325
      if ($tmp_inc{$modified_files[$i]}) {
326
        splice(@modified_files,$i,1);
327
      }
328
    }
329
  }
330
}
331
 
332
if ($do_http_transfer) {
333
  if (!$REVIEWER) {
334
    print "\nEnter the e-mail address of the reviewer: ";
335
    $REVIEWER = &getinputnotblank;
336
  }
337
 
338
  print "\nEnter a title: ";
339
  $title = &getinputnotblank;
340
 
341
  print "\nEnter a description (End with new-line,Ctrl-D): \n";
342
  $description = &getmultipleinputnotblank;
343
 
344
  print "\nEnter request IDs addressed if any (separate with commas): ";
345
  $bug_ids = &getinput;
346
 
347
  $project_id = 0;
348
  while (!$project_id) {
349
    %projects_hash = &getprojects;
350
    @projects = sort keys %projects_hash;
351
    $counter = 1;
352
    print "\nPlease choose a Project Category: \n";
353
    foreach $project (@projects) {
354
      print "  $counter) ";
355
      if ($counter < 10) { print " "; }
356
      print "$project\n";
357
      $counter++;
358
    }
359
    print "  $counter) ";
360
    if ($counter < 10) { print " "; }
361
    print "Add A Category\n";
362
    print "Choose One: ";
363
    $line = &getinput;
364
    if ($line <= 0 || @projects+1 < $line) { next; }
365
    elsif (@projects+1 == $line) {
366
      print "Enter New Project Category: ";
367
      $line = &getinput;
368
      &addproject($line);
369
    }
370
    else {
371
      $project_id = $projects_hash{$projects[$line-1]};
372
    }
373
  }
374
}
375
 
376
if ($cvsrepository) {
377
  # TODO: Rethink this, it is not robust enough yet for situations where only a portion of a repository is checked out; use appropriate --module-name parameter to get around this
378
  $nodirs = ($cvsrepository =~ m|/|g);
379
  $nodirs++;
380
  for ($i=0; $i<$nodirs; $i++) {
381
    chdir('..');
382
  }
383
}
384
 
385
$diff_res_cvs = '';
386
if (@modified_files > 0) {
387
  $files = join(' ',@modified_files);
388
  $diff_res_cvs = `cvs diff -uNbB$addl_diff_switches --show-c-function --show-function-line="p[ur][boi][lvt]" --ignore-all-space --ignore-blank-lines --ignore-space-change $files`;
389
}
390
$diff_res_inc = '';
391
 
392
foreach $file (@modified_incremental) {
393
  $file_to_diff = $incremental_locs{$file};
394
  $this_file_diff = `diff -uNbB$addl_diff_switches --show-c-function --show-function-line="p[ur][boi][lvt]" --ignore-all-space --ignore-blank-lines --ignore-space-change $file_to_diff $file`;
395
  if (length($this_file_diff) > 0) {
396
    if ($diff_res_inc) {
397
      $diff_res_inc .= "\n";
398
    }
399
    $version = $file_version{$file};
400
    $diff_res_inc .= <<"STOP";
401
Index: $file
402
===================================================================
403
RCS file: $cvsrootandmodule/$file,v
404
retrieving revision $version
405
diff -u -b -B -b$addl_diff_switches -r$version $file
406
STOP
407
    $diff_res_inc .= $this_file_diff;
408
  }
409
}
410
 
411
$diff_res = '';
412
if ($diff_res_cvs) {
413
  $diff_res = $diff_res_cvs;
414
}
415
if ($diff_res_inc) {
416
  if ($diff_res) {
417
    $diff_res .= "\n";
418
  }
419
  $diff_res .= $diff_res_inc;
420
}
421
 
422
foreach $file ((@modified_files,@modified_incremental)) {
423
  $file_no_slash = $file;
424
  $file_no_slash =~ s|/|__|g;
425
  $file_no_slash = $ENV{HOME}."/.crcache/$file_no_slash--";
426
  `rm -f $file_no_slash*`;
427
  $file_no_slash .= $file_version{$file};
428
  `cp "$file" "$file_no_slash"`;
429
}
430
 
431
if ($do_http_transfer) {
432
  $tmp_file = "/tmp/codereview$$";
433
 
434
  open (TEMP,">$tmp_file");
435
  print TEMP "$diff_res";
436
  close (TEMP);
437
 
438
  $ua = new LWP::UserAgent;
439
  if ($username && $password) { $ua->credentials($HOST_PORT, $REALM, $username, $password); }
440
  $res = $ua->request(HTTP::Request::Common::POST($BASE_URL, Content_Type => 'form-data', Content => [action=>'submit_topic',topic_title=>$title,topic_description=>$description,projectid=>$project_id,bug_ids=>$bug_ids,email=>$MY_EMAIL,reviewers=>$REVIEWER,cc=>'',topic_file=>[$tmp_file]]));
441
 
442
  unlink ($tmp_file);
443
}
444
else {
445
  open (TEMP,">$do_file_output");
446
  print TEMP "$diff_res";
447
  close (TEMP);
448
}
449
 
450
sub getinput {
451
  my ($line);
452
  $line = <STDIN>;
453
  $line =~ s/\015//sg;
454
  chomp ($line);
455
  $line =~ s/^\s+//;
456
  $line =~ s/\s+$//;
457
  return $line;
458
}
459
 
460
sub getinputnotblank {
461
  my ($line) = '';
462
  while (!$line) {
463
    $line = &getinput;
464
  }
465
  return $line;
466
}
467
 
468
sub getmultipleinput {
469
  my (@lines);
470
  @lines = <STDIN>;
471
  chomp (@lines);
472
  my ($retval);
473
  $retval = join("\n", @lines);
474
  $retval =~ s/\015//sg;
475
  return $retval;
476
}
477
 
478
sub getmultipleinputnotblank {
479
  my ($line) = '';
480
  while (!$line) {
481
    $line = &getmultipleinput;
482
    $line2 = $line;
483
    $line2 =~ s/^[\r\n]+//s;
484
    $line2 =~ s/[\r\n]+$//s;
485
    $line2 =~ s/^\s+//s;
486
    $line2 =~ s/\s+$//s;
487
    $line2 =~ s/^[\r\n]+//s;
488
    $line2 =~ s/[\r\n]+$//s;
489
    $line2 =~ s/^\s+//s;
490
    $line2 =~ s/\s+$//s;
491
    if (!$line2) { $line = $line2; }
492
  }
493
  return $line;
494
}
495
 
496
sub getprojects {
497
  my ($ua,$req,$res,$content,%response,@links);
498
  $ua = new LWP::UserAgent;
499
  if ($username && $password) { $ua->credentials($HOST_PORT, $REALM, $username, $password); }
500
  $req = HTTP::Request->new(GET => "$BASE_URL?action=list_projects");
501
  $res = $ua->request($req);
502
  $content = $res->content;
503
  if ($content =~ /<body.*?Project list(.*?)<HR>/si) {
504
    $content = $1;
505
    @links = ($content =~ m|(<A.*?>.*?</A>)|sig);
506
    foreach $content (@links) {
507
      if ($content =~ m|projectid=(\d+).*?>(.*?)</|) {
508
        $response{$2} = $1;
509
      }
510
    }
511
  }
512
  return %response;
513
}
514
 
515
sub addproject {
516
  my ($project_name) = @_;
517
  my ($ua,$req,$res);
518
  $ua = new LWP::UserAgent;
519
  if ($username && $password) { $ua->credentials($HOST_PORT, $REALM, $username, $password); }
520
  $req = POST $BASE_URL, [action=>'submit_project',project_name=>$project_name,project_description=>$project_name];
521
  $res = $ua->request($req);
522
}
523
 
524
sub get_credentials {
525
  print "Username: ";
526
  my ($username) = &getinputnotblank;
527
  print "Password (will show up on screen): ";
528
  my ($password) = &getinputnotblank;
529
  return ($username, $password);
530
}
531
 
532
sub print_help {
533
  print "SubmitCodeReview.pl [--help] [--condense] [--file output-file] [--url url]\n";
534
  print "  [--reviewer email] [--module-name module]\n";
535
  print "A utility for automating the submission of CVS code reviews to Codestriker.\n";
536
  print "  --condense will include -d in the diff command used.\n";
537
  print "  --reviewer specifies the e-mail address of the reviewer.\n";
538
  print "  --module-name specifies the module name to strip from directory the path.\n";
539
  print "  --file specifies to output code review to the specified file.\n";
540
  print "  --url specifies to send code review to the given codestriker.pl URL.\n";
541
  print "  If neither --file or --url is specified, the URL hardcoded in\n";
542
  print "    SubmitCodeReview.pl is used.\n";
543
  exit 0;
544
}