monotone

monotone Mtn Source Tree

Root/contrib/monotone-notify.pl

  • Property mtn:execute set to true
1#! /usr/bin/perl
2
3# Copyright (c) 2005 by Richard Levitte <richard@levitte.org>
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9#
10# 1. Redistributions of source code must retain the above copyright
11# notice, this list of conditions and the following disclaimer.
12#
13# 2. Redistributions in binary form must reproduce the above copyright
14# notice, this list of conditions and the following disclaimer in the
15# documentation and/or other materials provided with the distribution.
16#
17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28#
29use strict;
30use warnings;
31use Getopt::Long;
32use Pod::Usage;
33use MIME::Lite;
34use File::Spec::Functions qw(:ALL);
35
36my $VERSION = '1.0';
37
38######################################################################
39# User options
40#
41my $help = 0;
42my $man = 0;
43my $user_database = undef;
44my $root = undef;
45my @user_branches = ();
46my @user_not_branches = ();
47my $update = -1;
48my $mail = -1;
49my $attachments = 1;
50my $ignore_merges = 1;
51my $from = undef;
52my $difflogs_to = undef;
53my $nodifflogs_to = undef;
54my $before = undef;
55my $since = undef;
56my $workdir = undef;
57my $quiet = 0;
58my $debug = 0;
59my $monotone = "mtn";
60
61GetOptions('help|?' => \$help,
62 'man' => \$man,
63 'db=s' => \$user_database,
64 'root=s' => \$root,
65 'branch=s' => \@user_branches,
66 'not-branch=s' => \@user_not_branches,
67 'update!' => \$update,
68 'mail!' => \$mail,
69 'attachments!' => \$attachments,
70 'ignore-merges!' => \$ignore_merges,
71 'from=s' => \$from,
72 'difflogs-to=s' => \$difflogs_to,
73 'nodifflogs-to=s' => \$nodifflogs_to,
74 'before=s' => \$before,
75 'since=s' => \$since,
76 'workdir=s' => \$workdir,
77 'quiet' => \$quiet,
78 'debug' => \$debug,
79 'monotone=s' => \$monotone) or pod2usage(2);
80
81$SIG{HUP} = \&my_exit;
82$SIG{KILL} = \&my_exit;
83$SIG{TERM} = \&my_exit;
84$SIG{INT} = \&my_exit;
85
86######################################################################
87# Respond to user input
88#
89
90# For starters, output help if requested
91pod2usage(1) if $help;
92pod2usage(-exitstatus => 0, -verbose => 2) if $man;
93
94# Then check for certain conditions:
95 # If --debug was used and --update wasn't, force --noupdate
96$update = 0 if ($debug && $update == -1);
97 # If --debug was used and --mail wasn't, force --nomail
98$mail = 0 if ($debug && $mail == -1);
99 # If --debug was used, refuse to be quiet
100$quiet = 0 if $debug;
101
102# The check for missing mandatory options (oxymoron, I know :-))
103# Actually, they're only mandatory if $mail or $debug is true...
104if ($mail || $debug) {
105 if (!defined $from) {
106my_errlog("You need to specify a From address with --from");
107pod2usage(2);
108 }
109 if (!defined $difflogs_to && !defined $nodifflogs_to) {
110my_errlog("You need to specify a To address with --difflogs-to or --nodifflogs-to");
111pod2usage(2);
112 }
113}
114
115######################################################################
116# Make sure we have a database, and that the file spec is absolute.
117#
118
119# If no database is given, check the monotone options file (_MTN/options).
120# Do NOT use the branch option from there.
121if (!defined $user_database) {
122 $root = rel2abs($root) if defined $root;
123 $root = rootdir() unless defined $root;
124
125 my $curdir = rel2abs(curdir());
126 while(! -f catfile($curdir, "_MTN", "options") && $curdir ne $root) {
127$curdir = updir($curdir);
128 }
129 my $options = catfile($curdir, "_MTN", "options");
130
131 my_debug("found options file $options");
132
133 open OPTIONS, "<$options"
134|| my_error("Couldn't open $options");
135 ($user_database) = grep(/^\s*database\s/, map { chomp; $_ } <OPTIONS>);
136 close OPTIONS;
137 $user_database =~ s/^\s*database\s"(.*)"$/$1/;
138
139 my_log("found the database $user_database in $options.");
140 my_log("the branch option from $options will NOT be used.");
141} elsif (!file_name_is_absolute($user_database)) {
142 $user_database = rel2abs($user_database);
143}
144
145######################################################################
146# Set up internal variables.
147#
148my $database = " --db=$user_database";
149my_debug("using database $user_database");
150
151my $remove_workdir = 0;
152if (!defined $workdir) {
153 $workdir = "/var/tmp/monotone_notify.work.$$";
154 mkdir $workdir;
155 $remove_workdir = 1;
156} elsif (! file_name_is_absolute($workdir)) {
157 $workdir = rel2abs($workdir);
158}
159if (! -d $workdir && ! -w $workdir && ! -r $workdir) {
160 my_error("work directory $workdir not accessible, exiting");
161}
162my_debug("using work directory $workdir");
163my_debug("(to be removed after I'm done)") if $remove_workdir;
164
165my $branches_re = "^.*\$";
166if ($#user_branches >= 0) {
167 $branches_re=
168'^('.join('|', map { s/([^a-zA-Z0-9\[\]\*_])/\\$1/g;
169 s/\*/.\*/g;
170 $_ } @user_branches).')$';
171}
172my $not_branches_re = "^\$";
173if ($#user_not_branches >= 0) {
174 $not_branches_re=
175'^('.join('|', map { s/([^a-zA-Z0-9\[\]\*\?_])/\\$1/g;
176 s/\?/./g;
177 s/\*/.\*/g;
178 $_ } @user_not_branches).')$';
179}
180my_debug("using the regular expression /$branches_re/ to select branches");
181
182my @files_to_clean_up = ();
183
184######################################################################
185# Move to the working directory.
186#
187chdir $workdir;
188my_debug("changed current directory to $workdir");
189
190######################################################################
191# Save all the branches that we want to look at
192#
193my %branches =
194 map { $_ => 1 }
195grep (/$branches_re/,
196 grep (!/$not_branches_re/,
197 map { chomp; $_ }
198my_backtick("$monotone$database list branches")));
199my_debug("collected the following branches:\n",
200 map { " $_\n" } keys %branches);
201
202######################################################################
203# Find all the current leaves, for the branches that we want.
204#
205my_log("finding all current leaves.");
206# Format: revision => { branch1 => 1, branch2 => 1, ... }
207my %current_leaves = ();
208foreach my $branch (keys %branches) {
209 foreach my $revision (my_backtick("$monotone$database automate heads $branch")) {
210chomp $revision;
211$current_leaves{$revision} = {} if !defined $current_leaves{$revision};
212$current_leaves{$revision}->{$branch} = 1;
213my_debug("$revision\@$branch\n");
214 }
215}
216my_debug("found ", list_size(keys %current_leaves)," current leaves");
217
218######################################################################
219# Find the IDs of the leaves from last run.
220#
221my_log("finding all old leaves.");
222my %old_leaves = ();
223foreach my $notify_entry (my_backtick("$monotone$database list vars notify")) {
224 chomp $notify_entry;
225 if ($notify_entry =~ /^notify:\s([0-9a-fA-F]{40})\@([^\s]+)\s1$/) {
226# Found the new format that keeps track of which branches each
227# revision is part of.
228if (defined $branches{$2}) {
229 $old_leaves{$1} = {} if !defined $current_leaves{$1};
230 $old_leaves{$1}->{$2} = 1;
231}
232 } elsif ($notify_entry =~ /^notify:\s([0-9a-fA-F]{40})\s1$/) {
233$old_leaves{$1} = {} if !defined $current_leaves{$1};
234$old_leaves{$1}->{"*"} = 1;
235 }
236}
237
238# We save them in a file as well, to be used with
239# 'automate ancestry_difference', to avoid problems with system
240# that have small command line size limits, in case there were
241# many heads...
242my $old_leaves_file = "old_leaves";
243open OLDLEAVES, ">$old_leaves_file"
244 || my_error("Couldn't write to $old_leaves_file: $!");
245print OLDLEAVES join("\n", keys %old_leaves),"\n";
246close OLDLEAVES;
247
248my_debug("found ", list_size(keys %old_leaves),
249 " previous leaves\n (saved in $old_leaves_file)");
250
251if ($mail || $debug) {
252 ##################################################################
253 # Collect IDs for all revisions we want to log.
254 #
255 # Use the old_leaves file created by the previous collection.
256 #
257 my_log("collecting all revision IDs between current and old leaves.");
258 my %revisions =
259map { chomp; $_ => 1 }
260 map { my_backtick("$monotone$database automate ancestry_difference $_ -@ $old_leaves_file") }
261keys %current_leaves;
262 push @files_to_clean_up, "$old_leaves_file";
263 my @revisions_keys = keys %revisions;
264 my_debug("found ",
265 list_size(keys %revisions),
266 " revisions to log");
267
268 ##################################################################
269 # Collect all the logs.
270 #
271 # Note that if we would discard it, we skip this step and the next,
272 # so as not to waste time...
273 #
274 my_log("collecting the logs for all collected revision IDs.");
275 my %timeline = ();# hash of revision lists, keyed by date.
276 my %revision_data = ();# hash of logs (represented as arrays of
277# strings), keyed by revision id.
278
279 foreach my $revision (keys %revisions) {
280$revision_data{$revision} =
281 [ map { chomp; $_ }
282 my_backtick("$monotone$database log --no-graph --last=1 --from=$revision") ];
283my $date = (split(' ', (grep(/^Date:/, @{$revision_data{$revision}}))[0]))[1];
284
285if (defined $before && $date ge $before) {
286 my_debug("Rejecting $revision because it's too recent ($date >= $before (--before))\n");
287 next;
288}
289if (defined $since && $date lt $since) {
290 my_debug("Rejecting $revision because it's too old ($date < $since (--since))\n");
291 next;
292}
293$timeline{$date} = {} if !defined $timeline{$date};
294$timeline{$date}->{$revision} = 1;
295 }
296
297 ##################################################################
298 # Generate messages.
299 #
300 my_log("generating messages for all collected revision IDs.");
301
302 my $message_count = 0;# It's nice with a little bit of statistics.
303
304 foreach my $date (sort keys %timeline) {
305foreach my $revision (keys %{$timeline{$date}}) {
306 foreach my $sendinfo (([ 1, "Notify.debug-diffs", $difflogs_to ],
307 [ 0, "Notify.debug-nodiffs", $nodifflogs_to ])) {
308my $diffs = $sendinfo->[0];
309my $debugfile = $sendinfo->[1];
310my $to = $sendinfo->[2];
311next if !defined $to;
312
313my @ancestors =
314 map { (split ' ')[1] }
315grep(/^Ancestor:/, @{$revision_data{$revision}});
316
317# If this revision has more than one ancestor, it's the
318# result of a merge. If we have already shown the
319# participating ancestors, let's not show the diffs again.
320if ($ignore_merges && $diffs && $#ancestors > 0) {
321 my $will_ignore = 1;
322 my %revision_branches =
323map { (split ' ')[1] => 1 }
324 grep /^Branch:/, @{$revision_data{$revision}};
325 my_debug("Checking if ${revision}'s ancestor have already been shown (probably).");
326 foreach (@ancestors) {
327if (!revision_is_in_branch($_,
328 { %branches,
329 %revision_branches },
330 { %revision_data })) {
331 my_debug("Not ignoring this one!");
332 $will_ignore = 0;
333}
334 }
335 if ($will_ignore) {
336$diffs = 0;
337my_debug("Not showing diff for revision $revision, because all it's ancestors\nhave already been shown.");
338 }
339}
340
341# If --nodiffs was used, it's silly to use attachments
342my $attach = $attachments;
343$attach = 0 if $diffs == 0;
344
345my $msg;
346my @files = ();# Makes sure we have the files in
347# correctly sorted order.
348my %file_info = (); # Hold information about each file.
349
350# Make sure we have a null ancestor if there are none.
351# generate_diff will do the right thing with it.
352if ($#ancestors < 0) {
353 push @ancestors, "";
354}
355
356######################################################
357# Create the summary.
358#
359my $summary_file = "message.txt";
360open SUMMARY,">$summary_file"
361 || my_error("Notify: couldn't create $summary_file: $!");
362foreach (@{$revision_data{$revision}}) {
363 print SUMMARY "$_\n";
364}
365if (!$diffs) {
366 print SUMMARY "\n";
367}
368close SUMMARY;
369push @files, $summary_file;
370
371# This information is only used when $attachments is true.
372$file_info{$summary_file} = { Title => 'change summary',
373 Disposition => 'inline' };
374
375######################################################
376# Create the diffs.
377#
378if ($attach) {
379 foreach my $ancestor (@ancestors) {
380my $diff_file = "diff.$ancestor.txt";
381generate_diff($database, $ancestor, $revision,
382 ">$diff_file", $diffs, 0);
383push @files, $diff_file;
384
385$file_info{$diff_file} = {
386 Title => "Diff [$ancestor] -> [$revision]",
387 Disposition => 'attachment' };
388 }
389} else {
390 foreach my $ancestor (@ancestors) {
391generate_diff($database, $ancestor, $revision,
392 ">>$summary_file", $diffs, 1);
393 }
394 open SUMMARY,">>$summary_file"
395|| my_error("Notify: couldn't append to $summary_file: $!");
396 print SUMMARY "-" x 70,"\n";
397 close SUMMARY;
398}
399
400######################################################
401# Create the email.
402#
403if ($attach) {
404 $msg = MIME::Lite->new(From => $from,
405 To => $to,
406 Subject => "Revision $revision",
407 Type => 'multipart/mixed');
408 foreach my $file (@files) {
409$msg->attach(Type => 'TEXT',
410 Path => $file,
411 Disposition => $file_info{$file}->{Disposition},
412 Encoding => '8bit');
413 }
414
415 # MIME:Lite has some quircks that we need to deal with
416 foreach my $part ($msg->parts()) {
417my $filename = $part->filename();
418my_debug("message part: $filename: { ",
419 join(', ',
420 map { "$_ => $file_info{$filename}->{$_}" }
421 keys %{$file_info{$filename}}),
422 " }");
423# Hacks to avoid having file names, and to added a
424# description field
425$part->attr("content-disposition.filename" => undef);
426$part->attr("content-type.name" => undef);
427$part->attr("content-description" =>
428 $file_info{$filename}->{Title});
429 }
430} else {
431 $msg = MIME::Lite->new(From => $from,
432 To => $to,
433 Subject => "Revision $revision",
434 Type => 'TEXT',
435 Path => "$summary_file",
436 Encoding => '8bit');
437 # Hacks to avoid having file names
438 $msg->attr("content-disposition.filename" => undef);
439 $msg->attr("content-type.name" => undef);
440}
441
442######################################################
443# Send it or log it (or discard it).
444#
445if ($mail) {
446 $msg->send();
447} elsif ($debug) {
448 open MESSAGEDBG,">>$debugfile"
449|| my_error("Couldn't create $debugfile: $!");
450 print MESSAGEDBG "======================================================================\n";
451 $msg->print(\*MESSAGEDBG);
452 print MESSAGEDBG "======================================================================\n";
453 close MESSAGEDBG;
454}
455$message_count++;
456
457######################################################
458# Clean up the files used to create the message.
459#
460my_debug("cleaning up.");
461unlink @files;
462 }
463}
464 }
465
466 my_log("$message_count messages were sent.");
467}
468
469######################################################################
470# Update the database with new heads
471#
472my %new_notifications =
473 map { my $rev = $_;
474 map { my $key = "$rev\@$_"; $key => 1 }
475 keys %{$current_leaves{$rev}} }
476keys %current_leaves;
477my %old_notifications =
478 map { my $rev = $_;
479 map { my $key = "$rev\@$_"; $key => 1 }
480 keys %{$old_leaves{$rev}} }
481keys %old_leaves;
482
483my_log("updating the table of last logged revisions.");
484
485map { my_conditional_system($update,
486 "$monotone$database set notify $_ 1") }
487 grep { !defined $old_notifications{$_} && $_ !~ /\@\*$/ }
488keys %new_notifications;
489map { s/\@\*$//;
490 my_conditional_system($update,
491 "$monotone$database unset notify $_") }
492 grep { !defined $new_notifications{$_} }
493keys %old_notifications;
494
495######################################################################
496# Clean up.
497#
498my_exit();
499
500######################################################################
501# Subroutines
502#
503
504# generate_diff does just that, including for the case where there is
505# no ancestor. For that latter case, we need to synthesise the diff,
506# since monotone doesn't know how to do that
507sub generate_diff
508{
509 my ($db, $ancestor, $revision, $filespec, $really_show_diffs,
510$decorate_p, @dummy) = @_;
511
512 open OUTPUT, $filespec
513|| my_error("Couldn't write to $filespec: $!");
514 if ($really_show_diffs && $decorate_p) {
515print OUTPUT "-" x 70, "\n";
516print OUTPUT "Diff [$ancestor] -> [$revision]\n";
517 }
518 if ($ancestor eq "") {
519if (!$really_show_diffs) {
520 print OUTPUT "This is the first commit, and there's no easy way to create a diff\n";
521 print OUTPUT "These are the commands to view the individual files of that commit instead:\n";
522 print OUTPUT "\n";
523}
524my @status = my_backtick("$monotone$db automate get_revision $revision");
525my $line;
526$line = shift @status;
527if ($line =~ /^format_version\s+"([0-9]+)"\s*$/) {
528 # New versioned format
529 my $format_version = $1;
530 if ($format_version == 1) {
531while($line =~ /^(\s*
532 |format_version\s+"[0-9]+"
533 |new_manifest\s+\[[0-9a-f]{40}\]
534 |old_revision\s+\[[0-9a-f]{40}\]
535 )\s*$/x) {
536 $line = shift @status;
537}
538 }
539} else {
540 while($line =~ /^(\s*
541 |(new|old)_manifest\s+\[[0-9a-f]{40}\]
542 |old_revision\s+\[[0-9a-f]{40}\]
543 )\s*$/x) {
544$line = shift @status;
545 }
546}
547foreach (@status) {
548 chomp;
549 print OUTPUT ($_ eq "" ? "#" : "# $_"), "\n";
550}
551my $added_file = "";
552foreach my $line (@status) {
553 my $id = undef;
554 $added_file = $1 if $line =~ /^add_file\s+"(.*)"\s*$/;
555 $id = $1 if $line =~ /^\s+content\s\[([0-9a-fA-F]{40})\]\s*$/;
556 # older format had the add_file just name the file, and having
557 # the content IDs come much later, preceded by a patch line
558 $added_file = $1 if $line =~ /^patch\s+"(.*)"\s*$/;
559 $id = $1 if $line =~ /^\s+to\s\[([0-9a-fA-F]{40})\]\s*$/;
560 if (defined $id) {
561if ($really_show_diffs) {
562 my @file = my_backtick("$monotone$db automate get_file $id");
563 print OUTPUT "--- $added_file\n";
564 print OUTPUT "+++ $added_file\n";
565 print OUTPUT "\@\@ -0,0 +1,",list_size(@file)," \@\@\n";
566 map { print OUTPUT "+" . $_ } @file;
567} else {
568 print OUTPUT "monotone --db={your.database} automate get_file $id\n";
569}
570 }
571}
572 } else {
573if ($really_show_diffs) {
574 print OUTPUT my_backtick("$monotone$db diff --revision=$ancestor --revision=$revision");
575} else {
576 print OUTPUT "mtn --db={your.database} diff --revision=$ancestor --revision=$revision\n";
577}
578 }
579 close OUTPUT;
580}
581
582# revision_is_in_branch checks if the given revision is in one of the
583# given branches. The latter is given in form of a hash.
584sub revision_is_in_branch
585{
586 my ($revision, $branches, $revision_data) = @_;
587 my $bool = 0;
588
589 my_debug("Checking if $revision has already been shown in one of
590 these branches:\n ",
591 join("\n ", keys %$branches));
592
593 if (!defined $$revision_data{$revision}) {
594$$revision_data{$revision} =
595 [ map { chomp; $_ }
596 my_backtick("$monotone$database log --no-graph --last=1 --from=$revision") ];
597 }
598
599 map {
600my $branch = (split ' ')[1];
601if (defined $$branches{$branch}) {
602 $bool = 1;
603 my_debug("Found it in $branch");
604}
605 } grep /^Branch:/, @{$$revision_data{$revision}};
606
607 my_debug("Didn't find it in any of the branches...") if !$bool;
608
609 return $bool;
610}
611
612# my_log will simply output all it's arguments, prefixed with "Notify: ",
613# unless $quiet is true.
614sub my_log
615{
616 if (!$quiet && $#_ >= 0) {
617print STDERR "Notify: ", join("\nNotify: ",
618 split("\n",
619 join('', @_))), "\n";
620 }
621}
622
623# my_errlog will simply output all it's arguments, prefixed with "Notify: ".
624sub my_errlog
625{
626 if ($#_ >= 0) {
627print STDERR "Notify: ", join("\nNotify: ",
628 split("\n",
629 join('', @_))), "\n";
630 }
631}
632
633# my_error will output all it's arguments, prefixed with "Notify: ", then die.
634sub my_error
635{
636 my $save_syserr = "$!";
637 if ($#_ >= 0) {
638print STDERR "Notify: ", join("\nNotify: ",
639 split("\n",
640 join('', @_))), "\n";
641 }
642 die "$save_syserr";
643}
644
645# debug will simply output all it's arguments, prefixed with "DEBUG: ",
646# when $debug is true.
647sub my_debug
648{
649 if ($debug && $#_ >= 0) {
650print STDERR "DEBUG: ", join("\nDEBUG: ",
651 split("\n",
652 join('', @_))), "\n";
653 }
654}
655
656# my_system does the same thing as system, but will print a bit of debugging
657# output when $debug is true. It will also die if the subprocess returned
658# an error code.
659sub my_system
660{
661 my $command = shift @_;
662
663 my_debug("'${command}'\n");
664 my $return = system($command);
665 my $exit = $? >> 8;
666 die "'${command}' returned with exit code $exit\n" if ($exit);
667 return $return;
668}
669
670# my_conditional_system does the same thing as system, but will print a bit
671# of debugging output when $debug is true, and will only actually run the
672# command if the condition is true. It will also die if the subprocess
673# returned an error code.
674sub my_conditional_system
675{
676 my $condition = shift @_;
677 my $command = shift @_;
678 my $return = 0;# exit code for 'true'
679
680 my_debug("'${command}'\n");
681 if ($condition) {
682$return = system($command);
683my $exit = $? >> 8;
684die "'${command}' returned with exit code $exit\n" if ($exit);
685 } else {
686my_debug("... not actually executed.\n");
687 }
688 return $return;
689}
690
691# my_exit removes temporary files and then exits.
692sub my_exit
693{
694 my_log("cleaning up.");
695 unlink @files_to_clean_up;
696 rmdir $workdir if $remove_workdir;
697 my_log("all done.");
698 exit(0);
699}
700
701# my_backtick does the same thing as backtick commands, but will print a bit
702# of debugging output when $debug is true. It will also die if the subprocess
703# returned an error code.
704sub my_backtick
705{
706 my $command = shift @_;
707
708 my_debug("\`$command\`\n");
709 my @return = `$command`;
710 my $exit = $? >> 8;
711 if ($exit) {
712my_debug(map { "> ".$_ } @ return);
713die "'${command}' returned with exit code $exit\n";
714 }
715 return @return;
716}
717
718# list_size returns the size of the list. It's better than $#{var}
719# because it doesn't require the input to be a variable, and it
720# doesn't return one less than the size.
721sub list_size
722{
723 return $#_ + 1;
724}
725
726
727__END__
728
729=head1 NAME
730
731monotone-notify.pl - a script to send monotone change notifications by email
732
733=head1 SYNOPSIS
734
735monotone-notify.pl [--help] [--man]
736[--db=database] [--root=path] [--branch=branch ...]
737[--[no]update] [--[no]mail] [--[no]attachments] [--[no]ignore-merges]
738[--from=email-sender]
739[--difflogs-to=email-recipient] [--nodifflogs-to=email-recipient]
740[--workdir=path] [--before=yyyy-mm-ddThh:mm:ss] [--since=yyyy-mm-ddThh:mm:ss]
741[--quiet] [--debug] [--monotone=path]
742
743=head1 DESCRIPTION
744
745B<monotone-notify.pl> is used to generate emails containing monotone
746change logs for recent changes. It uses monotone database variables
747in the domain 'notify' to keep track of the latest revisions already
748logged.
749
750=head1 OPTIONS
751
752=over 4
753
754=item B<--help>
755
756Print a brief help message and exit.
757
758=item B<--man>
759
760Print the manual page and exit.
761
762=item B<--db>=I<database>
763
764Sets which database to use. If not given, the database given in
765_MTN/options is used.
766
767=item B<--root>=I<path>
768
769Stop the search for a working copy (containing the F<_MTN> directory) at
770the specified root directory rather than at the physical root of the
771filesystem.
772
773=item B<--branch>=I<branch>
774
775Sets a branch that should be checked. Can be used multiple times to
776set several branches. If not given at all, all available branches are
777used.
778
779=item B<--update>
780
781Has B<monotone-notify.pl> update the database variables at the end of
782the run. This is the default unless B<--debug> is given.
783
784=item B<--noupdate>
785
786The inverse of B<--update>. This is the default when B<--debug> is
787given.
788
789=item B<--mail>
790
791Has B<monotone-notify.pl> send the constructed logs as emails. This
792is the default unless B<--debug> is given.
793
794=item B<--nomail>
795
796The inverse of B<--mail>. This is the default when B<--debug> is
797given.
798
799=item B<--attachments>
800
801Add the change summary and the output of 'monotone diff' as
802attachments in the emails. This is the default behavior.
803
804=item B<--noattachments>
805
806Have the change summary and the output of 'monotone diff' in the body
807of the email, separated by lines of dashes.
808
809=item B<--ignore-merges>
810
811Do not create difflogs for merges (revisions with more than one
812ancestor), if the ancestors are in at least one of the branches that
813are monitored. This is the default behavior.
814
815=item B<--noignore-merges>
816
817Always create difflogs, even for merges.
818
819=item B<--from>=I<from>
820
821Sets the sender address to be used when creating the emails. There is
822no default, so this is a required option.
823
824=item B<--difflogs-to>=I<to>
825
826Sets the recipient address to be used when creating the emails with
827logs containing diffs. This or B<--nodifflogs-to> MUST be used, and
828both can be given at the same time (thereby generating two emails for
829each log).
830
831=item B<--nodifflogs-to>=I<to>
832
833Sets the recipient address to be used when creating the emails with
834logs without diffs. This or B<--difflogs-to> MUST be used, and both
835can be given at the same time (thereby generating two emails for each
836log).
837
838=item B<--before>=I<yyyy-mm-ddThh:mm:ss>
839
840Only log revisions where the datetime is less than the one given.
841
842=item B<--since>=I<yyyy-mm-ddThh:mm:ss>
843
844Only log revisions where the datetime is greater or equal to than the
845one given.
846
847=item B<--workdir>=I<path>
848
849Sets the working directory to use for temporary files. This working
850directory should be empty to avoid having files overwritten. When
851B<--debug> is used and unless B<--mail> is given, one or both of the
852two files F<Notify.debug-diffs> and F<Notify.debug-nodiffs> will be
853left in the work directory.
854
855The default working directory is F</var/tmp/monotone_notify.work.$$>,
856and will be removed automatically unless F<Notify.debug-diffs> or
857F<Notify.debug-nodiffs> are left in it.
858
859=item B<--debug>
860
861Makes B<monotone-notify.pl> go to debug mode. It means a LOT of extra
862output, and also implies B<--noupdate> and B<--nomail> unless
863specified differently on the command line.
864
865=item B<--quiet>
866
867Makes B<monotone-notify.pl> really silent. It will normally produce a
868small log of it's activities, but with B<--quiet>, it will only output
869error messages. If B<--debug> was given, B<--quiet> is turned off
870unconditionally.
871
872=item B<--monotone>=I<path>
873
874Gives the name or path to mtn(1) or both. The default is simply
875F<mtn>.
876
877=back
878
879=head1 NOTES
880
881You might notice that a series of logs for some branch may span over
882other branches. This is because some development may actually go
883through those other branches by virtue of 'monotone propagate' and
884other means to move changes from one branch to another.
885
886This behavior isn't entirely deterministic, as it depends on when the
887last run of B<monotone-notify.pl> was done, and what head revisions
888were active at that time. It might be seen as a bug, but if
889corrected, it might miss out on development that moves entirely to
890another branch and moves back later in time, thereby creating a hole
891in the branch currently looked at. This has actually happened in the
892development of monotone itself.
893
894For now, it's assumed that a little too much information is better
895than (unjust) lack of information.
896
897=head1 BUGS
898
899Fewer than before.
900
901=head1 SEE ALSO
902
903L<monotone(1)>
904
905=head1 AUTHOR
906
907Richard Levitte, <richard@levitte.org>
908
909=head1 COPYRIGHT AND LICENSE
910
911Copyright (c) 2005 by Richard Levitte <richard@levitte.org>
912All rights reserved.
913
914Redistribution and use in source and binary forms, with or without
915modification, are permitted provided that the following conditions
916are met:
917
918=over 3
919
920=item 1.
921
922Redistributions of source code must retain the above copyright
923notice, this list of conditions and the following disclaimer.
924
925=item 2.
926
927Redistributions in binary form must reproduce the above copyright
928notice, this list of conditions and the following disclaimer in the
929documentation and/or other materials provided with the distribution.
930
931=back
932
933THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
934``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
935LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
936A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
937OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
938SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
939LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
940DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
941THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
942(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
943OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
944
945=cut

Archive Download this file

Branches

Tags

Quick Links:     www.monotone.ca    -     Downloads    -     Documentation    -     Wiki    -     Code Forge    -     Build Status