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
# Perl filter to handle the log messages from the checkin of files in
4
# a directory.  This script will group the lists of files by log
5
# message, and mail a single consolidated log message at the end of
6
# the commit.
7
#
8
# This file assumes a pre-commit checking program that leaves the
9
# names of the first and last commit directories in a temporary file.
10
#
11
# Contributed by David Hampton <hampton@cisco.com>
12
# Roy Fielding removed useless code and added log/mail of new files
13
# Ken Coar added special processing (i.e., no diffs) for binary files
14
# Jon Stevens added a few new features and cleaned up some of the 
15
# output
16
#
17
# David Sitsky modified this slightly so that it also creates a new
18
# codestriker topic automatically.
19
 
20
############################################################
21
#
22
# Setup instructions
23
#
24
############################################################
25
#
26
# Create a directory $CVSROOT/commitlogs and allow
27
# the cvs process to write to it.
28
#
29
# Edit the options below.
30
#
31
############################################################
32
#
33
# Configurable options
34
#
35
############################################################
36
#
37
# Where do you want the RCS ID and delta info?
38
# 0 = none,
39
# 1 = in mail only,
40
# 2 = rcsids in both mail and logs.
41
#
42
$rcsidinfo = 2;
43
 
44
############################################################
45
#
46
# Constants
47
#
48
############################################################
49
$STATE_NONE    = 0;
50
$STATE_CHANGED = 1;
51
$STATE_ADDED   = 2;
52
$STATE_REMOVED = 3;
53
$STATE_LOG     = 4;
54
 
55
$TMPDIR        = $ENV{'TMPDIR'} || '/tmp';
56
$FILE_PREFIX   = '#cvs.';
57
 
58
$LAST_FILE     = "$TMPDIR/${FILE_PREFIX}lastdir";
59
$CHANGED_FILE  = "$TMPDIR/${FILE_PREFIX}files.changed";
60
$ADDED_FILE    = "$TMPDIR/${FILE_PREFIX}files.added";
61
$REMOVED_FILE  = "$TMPDIR/${FILE_PREFIX}files.removed";
62
$LOG_FILE      = "$TMPDIR/${FILE_PREFIX}files.log";
63
$BRANCH_FILE   = "$TMPDIR/${FILE_PREFIX}files.branch";
64
$SUMMARY_FILE  = "$TMPDIR/${FILE_PREFIX}files.summary";
65
 
66
$CVSROOT       = $ENV{'CVSROOT'};
67
 
68
$CVSBIN        = '/usr/bin';
69
$PATH          = "$PATH:/bin:/usr/bin";
70
$MAIL_CMD      = "| /usr/lib/sendmail -i -t";
71
$MAIL_TO       = 'engineering@localhost.localdomain';
72
$MAIL_FROM     = "$ENV{'USER'}\@localhost.localdomain";
73
$SUBJECT_PRE   = 'CVS update:';
74
 
75
# Codestriker-specific imports.
76
use lib '/var/www/codestriker-1.8.4/bin';
77
use CodestrikerClient;
78
 
79
# Codestriker specific parameters for topic creation.
80
$CODESTRIKER_URL = 'http://localhost/codestriker/codestriker.pl';
81
$CODESTRIKER_PROJECT = 'Project CVS';
82
$CODESTRIKER_REPOSITORY = '/var/lib/cvs';
83
$CODESTRIKER_REVIEWERS = 'engineering@localhost.localdomain';
84
$CODESTRIKER_CC = '';
85
 
86
############################################################
87
#
88
# Subroutines
89
#
90
############################################################
91
 
92
sub format_names {
93
    local($dir, @files) = @_;
94
    local(@lines);
95
 
96
    $lines[0] = sprintf(" %-08s", $dir);
97
    foreach $file (@files) {
98
        if (length($lines[$#lines]) + length($file) > 60) {
99
            $lines[++$#lines] = sprintf(" %8s", " ");
100
        }
101
        $lines[$#lines] .= " ".$file;
102
    }
103
    @lines;
104
}
105
 
106
sub cleanup_tmpfiles {
107
    local(@files);
108
 
109
    opendir(DIR, $TMPDIR);
110
    push(@files, grep(/^${FILE_PREFIX}.*\.${id}$/, readdir(DIR)));
111
    closedir(DIR);
112
    foreach (@files) {
113
        unlink "$TMPDIR/$_";
114
    }
115
}
116
 
117
sub write_logfile {
118
    local($filename, @lines) = @_;
119
 
120
    open(FILE, ">$filename") || die ("Cannot open log file $filename: $!\n");
121
    print(FILE join("\n", @lines), "\n");
122
    close(FILE);
123
}
124
 
125
sub append_to_file {
126
    local($filename, $dir, @files) = @_;
127
 
128
    if (@files) {
129
        local(@lines) = &format_names($dir, @files);
130
        open(FILE, ">>$filename") || die ("Cannot open file $filename: $!\n");
131
        print(FILE join("\n", @lines), "\n");
132
        close(FILE);
133
    }
134
}
135
 
136
sub write_line {
137
    local($filename, $line) = @_;
138
 
139
    open(FILE, ">$filename") || die("Cannot open file $filename: $!\n");
140
    print(FILE $line, "\n");
141
    close(FILE);
142
}
143
 
144
sub append_line {
145
    local($filename, $line) = @_;
146
 
147
    open(FILE, ">>$filename") || die("Cannot open file $filename: $!\n");
148
    print(FILE $line, "\n");
149
    close(FILE);
150
}
151
 
152
sub read_line {
153
    local($filename) = @_;
154
    local($line);
155
 
156
    open(FILE, "<$filename") || die("Cannot open file $filename: $!\n");
157
    $line = <FILE>;
158
    close(FILE);
159
    chomp($line);
160
    $line;
161
}
162
 
163
sub read_file {
164
    local($filename, $leader) = @_;
165
    local(@text) = ();
166
 
167
    open(FILE, "<$filename") || return ();
168
    while (<FILE>) {
169
        chomp;
170
        push(@text, sprintf("  %-10s  %s", $leader, $_));
171
        $leader = "";
172
    }
173
    close(FILE);
174
    @text;
175
}
176
 
177
sub read_logfile {
178
    local($filename, $leader) = @_;
179
    local(@text) = ();
180
 
181
    open(FILE, "<$filename") || die ("Cannot open log file $filename: $!\n");
182
    while (<FILE>) {
183
        chomp;
184
        push(@text, $leader.$_);
185
    }
186
    close(FILE);
187
    @text;
188
}
189
 
190
#
191
# do an 'cvs -Qn status' on each file in the arguments, and extract info.
192
#
193
sub change_summary {
194
    local($out, @filenames) = @_;
195
    local(@revline);
196
    local($file, $rev, $rcsfile, $line);
197
 
198
    while (@filenames) {
199
        $file = shift @filenames;
200
 
201
        if ("$file" eq "") {
202
            next;
203
        }
204
 
205
        open(RCS, "-|") || exec "$CVSBIN/cvs", '-Qn', 'status', $file;
206
 
207
        $rev = "";
208
        $delta = "";
209
        $rcsfile = "";
210
 
211
 
212
        while (<RCS>) {
213
            if (/^[ \t]*Repository revision/) {
214
                chomp;
215
                @revline = split(' ', $_);
216
                $rev = $revline[2];
217
                $rcsfile = $revline[3];
218
                $rcsfile =~ s,^$CVSROOT/,,;
219
                $rcsfile =~ s/,v$//;
220
            }
221
        }
222
        close(RCS);
223
 
224
 
225
        if ($rev ne '' && $rcsfile ne '') {
226
            open(RCS, "-|") || exec "$CVSBIN/cvs", '-Qn', 'log', "-r$rev", $file;
227
            while (<RCS>) {
228
                if (/^date:/) {
229
                    chomp;
230
                    $delta = $_;
231
                    $delta =~ s/^.*;//;
232
                    $delta =~ s/^[\s]+lines://;
233
                }
234
            }
235
            close(RCS);
236
        }
237
 
238
        $diff = "\n\n";
239
 
240
	#
241
	# Get the differences between this and the previous revision,
242
	# being aware that new files always have revision '1.1' and
243
	# new branches always end in '.n.1'.
244
	#
245
	if ($rev =~ /^(.*)\.([0-9]+)$/) {
246
	    $prev = $2 - 1;
247
	    $prev_rev = $1 . '.' .  $prev;
248
 
249
	    $prev_rev =~ s/\.[0-9]+\.0$//;# Truncate if first rev on branch
250
 
251
            open(DIFF, "-|")
252
		|| exec "$CVSBIN/cvs", '-Qn', 'diff', '-uN',
253
	                "-r$prev_rev", "-r$rev", $file;
254
 
255
	    while (<DIFF>) {
256
		$diff .= $_;
257
	    }
258
	    close(DIFF);
259
	    $diff .= "\n\n";
260
	}
261
 
262
        &append_line($out, $diff);
263
    }
264
}
265
 
266
 
267
sub build_header {
268
    local($header);
269
    delete $ENV{'TZ'};
270
    local($sec,$min,$hour,$mday,$mon,$year) = localtime(time);
271
 
272
    $header = sprintf("  User: %-8s\n  Date: %02d/%02d/%02d %02d:%02d:%02d",
273
                       $cvs_user, $year%100, $mon+1, $mday,
274
                       $hour, $min, $sec);
275
}
276
 
277
# !!! Mailing-list and history file mappings here !!!
278
sub mlist_map
279
{
280
    local($path) = @_;
281
 
282
    if ($path =~ /^([^\/]+)/) { return $1; }
283
    else                      { return 'apache'; }
284
}    
285
 
286
sub do_changes_file
287
{
288
    local($category, @text) = @_;
289
    local($changes);
290
 
291
    $changes = "$CVSROOT/CVSROOT/commitlogs/$category";
292
    if (open(CHANGES, ">>$changes")) {
293
        print(CHANGES join("\n", @text), "\n\n");
294
        close(CHANGES);
295
    }
296
    else { 
297
        warn "Cannot open $changes: $!\n";
298
    }
299
}
300
 
301
sub mail_notification
302
{
303
    local(@text) = @_;
304
 
305
#    print "Mailing the commit message...\n";
306
 
307
    open(MAIL, $MAIL_CMD);
308
    print MAIL "From: $MAIL_FROM\n";
309
    print MAIL "To: $MAIL_TO\n";
310
    print MAIL "Subject: $SUBJECT_PRE $ARGV[0]\n\n";
311
    print(MAIL join("\n", @text));
312
    close(MAIL);
313
}
314
 
315
# Create a Codestriker topic.  The topic title will be the
316
# first line of the log message prefixed with "CVS commit: ".
317
# The topic description is the entire log message.
318
# Return the URL of the created topic if successful, otherwise
319
# undef.
320
sub codestriker_create_topic
321
{
322
    local($user, $log_ref, $diff_ref) = @_;
323
    local(@log) = @{$log_ref};
324
    local(@diff) = @{$diff_ref};
325
 
326
    my $topic_title = "CVS commit: " .$log[0];
327
    my $topic_description = join("\n", @log);
328
    my $bug_ids = $topic_description;
329
 
330
    # Truncate the title if necessary.
331
    if (length($topic_title) > 57) {
332
        $topic_title = substr($topic_title, 0, 57) . "...";
333
    }
334
 
335
    # Check for any matching Bug id text.
336
    my @bugs = ();
337
    $bug_ids =~ s/.*[Bb][Uu][Gg]:?(\d+)\b.*/$1 /g;
338
    while ($bug_ids =~ /\b[Bb][Uu][Gg]:?\s*(\d+)\b/g) {
339
	push @bugs, $1;
340
    }
341
 
342
    my $client = CodestrikerClient->new($CODESTRIKER_URL);
343
    return $client->create_topic({
344
	topic_title => $topic_title,
345
	topic_description => $topic_description,
346
	project_name => $CODESTRIKER_PROJECT,
347
	repository => $CODESTRIKER_REPOSITORY,
348
	bug_ids => join(", ", @bugs),
349
	email => $MAIL_FROM,
350
	reviewers => $CODESTRIKER_REVIEWERS,
351
	cc => $CODESTRIKER_CC,
352
	topic_text => join("\n", @diff)
353
	});
354
}
355
 
356
## process the command line arguments sent to this script
357
## it returns an array of files, %s, sent from the loginfo
358
## command
359
sub process_argv
360
{
361
    local(@argv) = @_;
362
    local(@files);
363
    local($arg);
364
#    print "Processing log script arguments...\n";
365
 
366
    while (@argv) {
367
        $arg = shift @argv;
368
 
369
        if ($arg eq '-u') {
370
                $cvs_user = shift @argv;
371
        } else {
372
                ($donefiles) && die "Too many arguments!\n";
373
                $donefiles = 1;
374
                $ARGV[0] = $arg;
375
                @files = split(' ', $arg);
376
        }
377
    }
378
    return @files;
379
}
380
 
381
#############################################################
382
#
383
# Main Body
384
#
385
############################################################
386
#
387
# Setup environment
388
#
389
umask (002);
390
 
391
#
392
# Initialize basic variables
393
#
394
$id = getpgrp();
395
$state = $STATE_NONE;
396
$cvs_user = $ENV{'USER'} || getlogin || (getpwuid($<))[0] || sprintf("uid#%d",$<);
397
@files = process_argv(@ARGV);
398
@path = split('/', $files[0]);
399
$repository = $path[0];
400
if ($#path == 0) {
401
    $dir = ".";
402
} else {
403
    $dir = join('/', @path[1..$#path]);
404
}
405
#print("ARGV  - ", join(":", @ARGV), "\n");
406
#print("files - ", join(":", @files), "\n");
407
#print("path  - ", join(":", @path), "\n");
408
#print("dir   - ", $dir, "\n");
409
#print("id    - ", $id, "\n");
410
 
411
#
412
# Map the repository directory to a name for commitlogs.
413
#
414
$mlist = &mlist_map($files[0]);
415
 
416
##########################
417
# Uncomment the following if we ever have per-repository cvs mail
418
 
419
# if (defined($mlist)) {
420
#     $MAIL_TO = $mlist . '-cvs';
421
# }
422
# else { undef $MAIL_TO; }
423
 
424
##########################
425
#
426
# Check for a new directory first.  This will always appear as a
427
# single item in the argument list, and an empty log message.
428
#
429
if ($ARGV[0] =~ /New directory/) {
430
    $header = &build_header;
431
    @text = ();
432
    push(@text, $header);
433
    push(@text, "");
434
    push(@text, "  ".$ARGV[0]);
435
    &do_changes_file($mlist, @text);
436
    &mail_notification(@text) if defined($MAIL_TO);
437
    exit 0;
438
}
439
 
440
#
441
# Iterate over the body of the message collecting information.
442
#
443
while (<STDIN>) {
444
    chomp;                      # Drop the newline
445
 
446
    if (/^Revision\/Branch:/) {
447
        s,^Revision/Branch:,,;
448
        push (@branch_lines, split);
449
        next;
450
    }
451
#    next if (/^[ \t]+Tag:/ && $state != $STATE_LOG);
452
    if (/^Modified Files/) { $state = $STATE_CHANGED; next; }
453
    if (/^Added Files/)    { $state = $STATE_ADDED;   next; }
454
    if (/^Removed Files/)  { $state = $STATE_REMOVED; next; }
455
    if (/^Log Message/)    { $state = $STATE_LOG;     next; }
456
    s/[ \t\n]+$//;              # delete trailing space
457
 
458
    push (@changed_files, split) if ($state == $STATE_CHANGED);
459
    push (@added_files,   split) if ($state == $STATE_ADDED);
460
    push (@removed_files, split) if ($state == $STATE_REMOVED);
461
    if ($state == $STATE_LOG) {
462
        if (/^PR:$/i ||
463
            /^Reviewed by:$/i ||
464
            /^Submitted by:$/i ||
465
            /^Obtained from:$/i) {
466
            next;
467
        }
468
        push (@log_lines,     $_);
469
    }
470
}
471
 
472
#
473
# Strip leading and trailing blank lines from the log message.  Also
474
# compress multiple blank lines in the body of the message down to a
475
# single blank line.
476
# (Note, this only does the mail and changes log, not the rcs log).
477
#
478
while ($#log_lines > -1) {
479
    last if ($log_lines[0] ne "");
480
    shift(@log_lines);
481
}
482
while ($#log_lines > -1) {
483
    last if ($log_lines[$#log_lines] ne "");
484
    pop(@log_lines);
485
}
486
for ($i = $#log_lines; $i > 0; $i--) {
487
    if (($log_lines[$i - 1] eq "") && ($log_lines[$i] eq "")) {
488
        splice(@log_lines, $i, 1);
489
    }
490
}
491
 
492
#
493
# Find the log file that matches this log message
494
#
495
for ($i = 0; ; $i++) {
496
    last if (! -e "$LOG_FILE.$i.$id");
497
    @text = &read_logfile("$LOG_FILE.$i.$id", "");
498
    last if ($#text == -1);
499
    last if (join(" ", @log_lines) eq join(" ", @text));
500
}
501
 
502
#
503
# Spit out the information gathered in this pass.
504
#
505
&write_logfile("$LOG_FILE.$i.$id", @log_lines);
506
&append_to_file("$BRANCH_FILE.$i.$id",  $dir, @branch_lines);
507
&append_to_file("$ADDED_FILE.$i.$id",   $dir, @added_files);
508
&append_to_file("$CHANGED_FILE.$i.$id", $dir, @changed_files);
509
&append_to_file("$REMOVED_FILE.$i.$id", $dir, @removed_files);
510
if ($rcsidinfo) {
511
    &change_summary("$SUMMARY_FILE.$i.$id",
512
		    (@changed_files, @added_files, @removed_files));
513
}
514
 
515
#
516
# Check whether this is the last directory.  If not, quit.
517
#
518
if (-e "$LAST_FILE.$id") {
519
   $_ = &read_line("$LAST_FILE.$id");
520
   $tmpfiles = $files[0];
521
   $tmpfiles =~ s,([^a-zA-Z0-9_/]),\\$1,g;
522
   if (! grep(/$tmpfiles$/, $_)) {
523
        print "More commits to come...\n";
524
        exit 0
525
   }
526
}
527
 
528
#
529
# This is it.  The commits are all finished.  Lump everything together
530
# into a single message, fire a copy off to the mailing list, and drop
531
# it on the end of the Changes file.
532
#
533
$header = &build_header;
534
 
535
#
536
# Produce the final compilation of the log messages
537
#
538
@text = ();
539
@diff_text = ();
540
push(@text, $header);
541
push(@text, "");
542
for ($i = 0; ; $i++) {
543
    last if (! -e "$LOG_FILE.$i.$id");
544
    push(@text, &read_file("$BRANCH_FILE.$i.$id", "Branch:"));
545
    push(@text, &read_file("$CHANGED_FILE.$i.$id", "Modified:"));
546
    push(@text, &read_file("$ADDED_FILE.$i.$id", "Added:"));
547
    push(@text, &read_file("$REMOVED_FILE.$i.$id", "Removed:"));
548
    push(@text, "  Log:");
549
    push(@text, &read_logfile("$LOG_FILE.$i.$id", "  "));
550
    if ($rcsidinfo == 2) {
551
        if (-e "$SUMMARY_FILE.$i.$id") {
552
            push(@text, "  ");
553
            push(@diff_text, &read_logfile("$SUMMARY_FILE.$i.$id", ""));
554
            push(@text, &read_logfile("$SUMMARY_FILE.$i.$id", "  "));
555
        }
556
    }
557
    push(@text, "");
558
}
559
 
560
 
561
#
562
# Append the log message to the commitlogs/<module> file
563
#
564
&do_changes_file($mlist, @text);
565
#
566
# Now generate the extra info for the mail message..
567
#
568
if ($rcsidinfo == 1) {
569
    $revhdr = 0;
570
    for ($i = 0; ; $i++) {
571
        last if (! -e "$SUMMARY_FILE.$i.$id");
572
        if (-e "$SUMMARY_FILE.$i.$id") {
573
            if (!$revhdr++) {
574
                push(@text, "Revision  Changes    Path");
575
            }
576
            push(@text, &read_logfile("$SUMMARY_FILE.$i.$id", ""));
577
            push(@diff_text, &read_logfile("$SUMMARY_FILE.$i.$id", ""));
578
        }
579
    }
580
    if ($revhdr) {
581
        push(@text, "");        # consistancy...
582
    }
583
}
584
 
585
#
586
# Now create the Codestriker topic.
587
#
588
my $topic_url = &codestriker_create_topic($cvs_user, \@log_lines, \@diff_text);
589
 
590
#
591
# Mail out the notification.  Prepend the topic url if it is defined.
592
#
593
if (defined($MAIL_TO)) {
594
    if (defined($topic_url)) {
595
	unshift @text, "";
596
	unshift @text, "  $topic_url";
597
	unshift @text, "  Created Codestriker topic at:";
598
    }
599
    &mail_notification(@text) if defined($MAIL_TO);
600
}
601
 
602
&cleanup_tmpfiles;
603
exit 0;