From: Don Armstrong Date: Sat, 14 Jul 2012 03:54:18 +0000 (-0700) Subject: Merge branch 'control.at.submit' X-Git-Tag: release/2.6.0~367 X-Git-Url: https://git.donarmstrong.com/?a=commitdiff_plain;h=76710dc24f220a16011b2d5c6e5afb5b140fd85c;hp=1ead15d0cda5036b267c81fe9facc92b15cba6d7;p=debbugs.git Merge branch 'control.at.submit' --- diff --git a/Debbugs/Control.pm b/Debbugs/Control.pm index 6851656..c681916 100644 --- a/Debbugs/Control.pm +++ b/Debbugs/Control.pm @@ -99,6 +99,7 @@ BEGIN{ clone => [qw(clone_bug)], archive => [qw(bug_archive bug_unarchive), ], + limit => [qw(check_limit)], log => [qw(append_action_to_log), ], ); @@ -2020,7 +2021,7 @@ sub set_merged { $new_locks += $n_locks; %data = %{$data}; @data = values %data; - if (not __check_limit(data => [@data], + if (not check_limit(data => [@data], exists $param{limit}?(limit => $param{limit}):(), transcript => $transcript, )) { @@ -3618,7 +3619,7 @@ sub __begin_control { } } } - if (not __check_limit(data => \@data, + if (not check_limit(data => \@data, exists $param{limit}?(limit => $param{limit}):(), transcript => $transcript, )) { @@ -3685,9 +3686,9 @@ sub __end_control { } -=head2 __check_limit +=head2 check_limit - __check_limit(data => \@data, limit => $param{limit}); + check_limit(data => \@data, limit => $param{limit}); Checks to make sure that bugs match any limits; each entry of @data @@ -3704,7 +3705,7 @@ limit to succeed. =cut -sub __check_limit{ +sub check_limit{ my %param = validate_with(params => \@_, spec => {data => {type => ARRAYREF|SCALAR, }, @@ -3798,7 +3799,9 @@ sub __message_body_template{ my $hole_var = {'&bugurl' => sub{"$_[0]: ". 'http://'.$config{cgi_domain}.'/'. - Debbugs::CGI::bug_url($_[0]); + Debbugs::CGI::bug_links(bug => $_[0], + links_only => 1, + ); } }; diff --git a/Debbugs/Control/Service.pm b/Debbugs/Control/Service.pm new file mode 100644 index 0000000..239f653 --- /dev/null +++ b/Debbugs/Control/Service.pm @@ -0,0 +1,713 @@ +# This module is part of debbugs, and is released +# under the terms of the GPL version 2, or any later +# version at your option. +# See the file README and COPYING for more information. +# +# [Other people have contributed to this file; their copyrights should +# go here too.] +# Copyright 2007,2008,2009 by Don Armstrong . + +package Debbugs::Control::Service; + +=head1 NAME + +Debbugs::Control::Service -- Handles the modification parts of scripts/service by calling Debbugs::Control + +=head1 SYNOPSIS + +use Debbugs::Control::Service; + + +=head1 DESCRIPTION + +This module contains the code to implement the grammar of control@. It +is abstracted here so that it can be called from process at submit +time. + +All of the public functions take the following options: + +=over + +=item debug -- scalar reference to which debbuging information is +appended + +=item transcript -- scalar reference to which transcript information +is appended + +=item affected_bugs -- hashref which is updated with bugs affected by +this function + + +=back + +Functions which should (probably) append to the .log file take the +following options: + +=over + +=item requester -- Email address of the individual who requested the change + +=item request_addr -- Address to which the request was sent + +=item request_nn -- Name of queue file which caused this request + +=item request_msgid -- Message id of message which caused this request + +=item location -- Optional location; currently ignored but may be +supported in the future for updating archived bugs upon archival + +=item message -- The original message which caused the action to be taken + +=item append_log -- Whether or not to append information to the log. + +=back + +B (for most functions) is a special option. When set to +false, no appending to the log is done at all. When it is not present, +the above information is faked, and appended to the log file. When it +is true, the above options must be present, and their values are used. + + +=head1 GENERAL FUNCTIONS + +=cut + +use warnings; +use strict; +use vars qw($VERSION $DEBUG %EXPORT_TAGS @EXPORT_OK @EXPORT); +use base qw(Exporter); + +BEGIN{ + $VERSION = 1.00; + $DEBUG = 0 unless defined $DEBUG; + + @EXPORT = (); + %EXPORT_TAGS = (control => [qw(control_line valid_control)], + ); + @EXPORT_OK = (); + Exporter::export_ok_tags(keys %EXPORT_TAGS); + $EXPORT_TAGS{all} = [@EXPORT_OK]; +} + +use Debbugs::Config qw(:config); +use Debbugs::Common qw(cleanup_eval_fail); +use Debbugs::Control qw(:all); +use Debbugs::Status qw(splitpackages); +use Params::Validate qw(:types validate_with); +use List::Util qw(first); + +my %control_grammar = + (close => qr/(?i)^close\s+\#?(-?\d+)(?:\s+(\d.*))?$/, + reassign => qr/(?i)^reassign\s+\#?(-?\d+)\s+ # bug and command + (?:(?:((?:src:|source:)?$config{package_name_re}) # new package + (?:\s+((?:$config{package_name_re}\/)? + $config{package_version_re}))?)| # optional version + ((?:src:|source:)?$config{package_name_re} # multiple package form + (?:\s*\,\s*(?:src:|source:)?$config{package_name_re})+)) + \s*$/x, + reopen => qr/(?i)^reopen\s+\#?(-?\d+)(?:\s+([\=\!]|(?:\S.*\S)))?$/, + found => qr{^(?:(?i)found)\s+\#?(-?\d+) + (?:\s+((?:$config{package_name_re}\/)? + $config{package_version_re} + # allow for multiple packages + (?:\s*,\s*(?:$config{package_name_re}\/)? + $config{package_version_re})*) + )?$}x, + notfound => qr{^(?:(?i)notfound)\s+\#?(-?\d+) + \s+((?:$config{package_name_re}\/)? + $config{package_version_re} + # allow for multiple packages + (?:\s*,\s*(?:$config{package_name_re}\/)? + $config{package_version_re})* + )$}x, + fixed => qr{^(?:(?i)fixed)\s+\#?(-?\d+) + \s+((?:$config{package_name_re}\/)? + $config{package_version_re} + # allow for multiple packages + (?:\s*,\s*(?:$config{package_name_re}\/)? + $config{package_version_re})*) + \s*$}x, + notfixed => qr{^(?:(?i)notfixed)\s+\#?(-?\d+) + \s+((?:$config{package_name_re}\/)? + $config{package_version_re} + # allow for multiple packages + (?:\s*,\s*(?:$config{package_name_re}\/)? + $config{package_version_re})*) + \s*$}x, + submitter => qr/(?i)^submitter\s+\#?(-?\d+)\s+(\!|\S.*\S)$/, + forwarded => qr/(?i)^forwarded\s+\#?(-?\d+)\s+(\S.*\S)$/, + notforwarded => qr/(?i)^notforwarded\s+\#?(-?\d+)$/, + severity => qr/(?i)^(?:severity|priority)\s+\#?(-?\d+)\s+([-0-9a-z]+)$/, + tag => qr/(?i)^tags?\s+\#?(-?\d+)\s+(\S.*)$/, + block => qr/(?i)^(un)?block\s+\#?(-?\d+)\s+(?:by|with)\s+(\S.*)?$/, + retitle => qr/(?i)^retitle\s+\#?(-?\d+)\s+(\S.*\S)\s*$/, + unmerge => qr/(?i)^unmerge\s+\#?(-?\d+)$/, + merge => qr/(?i)^merge\s+#?(-?\d+(\s+#?-?\d+)+)\s*$/, + forcemerge => qr/(?i)^forcemerge\s+\#?(-?\d+(?:\s+\#?-?\d+)+)\s*$/, + clone => qr/(?i)^clone\s+#?(\d+)\s+((-\d+\s+)*-\d+)\s*$/, + package => qr/(?i)^package\:?\s+(\S.*\S)?\s*$/, + limit => qr/(?i)^limit\:?\s+(\S.*\S)\s*$/, + affects => qr/(?i)^affects?\s+\#?(-?\d+)(?:\s+((?:[=+-])?)\s*(\S.*)?)?\s*$/, + summary => qr/(?i)^summary\s+\#?(-?\d+)\s*(\d+|)\s*$/, + owner => qr/(?i)^owner\s+\#?(-?\d+)\s+((?:\S.*\S)|\!)\s*$/, + noowner => qr/(?i)^noowner\s+\#?(-?\d+)\s*$/, + unarchive => qr/(?i)^unarchive\s+#?(\d+)$/, + archive => qr/(?i)^archive\s+#?(\d+)$/, + ); + +sub valid_control { + my ($line,$matches) = @_; + my @matches; + for my $ctl (keys %control_grammar) { + if (@matches = $line =~ $control_grammar{$ctl}) { + @{$matches} = @matches if defined $matches and ref($matches) eq 'ARRAY'; + return $ctl; + } + } + @{$matches} = () if defined $matches and ref($matches) eq 'ARRAY'; + return undef; +} + +sub control_line { + my %param = + validate_with(params => \@_, + spec => {line => {type => SCALAR, + }, + clonebugs => {type => HASHREF, + }, + common_control_options => {type => ARRAYREF, + }, + errors => {type => SCALARREF, + }, + transcript => {type => HANDLE, + }, + debug => {type => SCALAR, + default => 0, + }, + ok => {type => SCALARREF, + }, + limit => {type => HASHREF, + }, + }, + ); + my $line = $param{line}; + my @matches; + my $ctl = valid_control($line,\@matches); + my $transcript = $param{transcript}; + my $debug = $param{debug}; + if (not defined $ctl) { + ${$param{errors}}++; + print {$param{transcript}} "Unknown command or invalid options to control\n"; + return; + } + # in almost all cases, the first match is the bug; the exception + # to this is block. + my $ref = $matches[0]; + if (defined $ref) { + $ref = $param{clonebugs}{$ref} if exists $param{clonebugs}{$ref}; + } + ${$param{ok}}++; + my $errors = 0; + my $terminate_control = 0; + + if ($ctl eq 'close') { + if (defined $matches[1]) { + eval { + set_fixed(@{$param{common_control_options}}, + bug => $ref, + fixed => $matches[1], + add => 1, + ); + }; + if ($@) { + $errors++; + print {$transcript} "Failed to add fixed version '$matches[1]' to $ref: ".cleanup_eval_fail($@,$debug)."\n"; + } + } + eval { + set_done(@{$param{common_control_options}}, + done => 1, + bug => $ref, + reopen => 0, + notify_submitter => 1, + clear_fixed => 0, + ); + }; + if ($@) { + $errors++; + print {$transcript} "Failed to mark $ref as done: ".cleanup_eval_fail($@,$debug)."\n"; + } + } elsif ($ctl eq 'reassign') { + my @new_packages; + if (not defined $matches[1]) { + push @new_packages, split /\s*\,\s*/,$matches[3]; + } + else { + push @new_packages, $matches[1]; + } + @new_packages = map {y/A-Z/a-z/; s/^(?:src|source):/src:/; $_;} @new_packages; + my $version= $matches[2]; + eval { + set_package(@{$param{common_control_options}}, + bug => $ref, + package => \@new_packages, + ); + # if there is a version passed, we make an internal call + # to set_found + if (defined($version) && length $version) { + set_found(@{$param{common_control_options}}, + bug => $ref, + found => $version, + ); + } + }; + if ($@) { + $errors++; + print {$transcript} "Failed to clear fixed versions and reopen on $ref: ".cleanup_eval_fail($@,$debug)."\n"; + } + } elsif ($ctl eq 'reopen') { + my $new_submitter = $matches[1]; + if (defined $new_submitter) { + if ($new_submitter eq '=') { + undef $new_submitter; + } + elsif ($new_submitter eq '!') { + $new_submitter = $param{replyto}; + } + } + eval { + set_done(@{$param{common_control_options}}, + bug => $ref, + reopen => 1, + defined $new_submitter? (submitter => $new_submitter):(), + ); + }; + if ($@) { + $errors++; + print {$transcript} "Failed to reopen $ref: ".cleanup_eval_fail($@,$debug)."\n"; + } + } elsif ($ctl eq 'found') { + my @versions; + if (defined $matches[1]) { + @versions = split /\s*,\s*/,$matches[1]; + eval { + set_found(@{$param{common_control_options}}, + bug => $ref, + found => \@versions, + add => 1, + ); + }; + if ($@) { + $errors++; + print {$transcript} "Failed to add found on $ref: ".cleanup_eval_fail($@,$debug)."\n"; + } + } + else { + eval { + set_fixed(@{$param{common_control_options}}, + bug => $ref, + fixed => [], + reopen => 1, + ); + }; + if ($@) { + $errors++; + print {$transcript} "Failed to clear fixed versions and reopen on $ref: ".cleanup_eval_fail($@,$debug)."\n"; + } + } + } + elsif ($ctl eq 'notfound') { + my @versions; + @versions = split /\s*,\s*/,$matches[1]; + eval { + set_found(@{$param{common_control_options}}, + bug => $ref, + found => \@versions, + remove => 1, + ); + }; + if ($@) { + $errors++; + print {$transcript} "Failed to remove found on $ref: ".cleanup_eval_fail($@,$debug)."\n"; + } + } + elsif ($ctl eq 'fixed') { + my @versions; + @versions = split /\s*,\s*/,$matches[1]; + eval { + set_fixed(@{$param{common_control_options}}, + bug => $ref, + fixed => \@versions, + add => 1, + ); + }; + if ($@) { + $errors++; + print {$transcript} "Failed to add fixed on $ref: ".cleanup_eval_fail($@,$debug)."\n"; + } + } + elsif ($ctl eq 'notfixed') { + my @versions; + @versions = split /\s*,\s*/,$matches[1]; + eval { + set_fixed(@{$param{common_control_options}}, + bug => $ref, + fixed => \@versions, + remove => 1, + ); + }; + if ($@) { + $errors++; + print {$transcript} "Failed to remove fixed on $ref: ".cleanup_eval_fail($@,$debug)."\n"; + } + } + elsif ($ctl eq 'submitter') { + my $newsubmitter = $matches[1] eq '!' ? $param{replyto} : $matches[1]; + if (not Mail::RFC822::Address::valid($newsubmitter)) { + print {$transcript} "$newsubmitter is not a valid e-mail address; not changing submitter\n"; + $errors++; + } + else { + eval { + set_submitter(@{$param{common_control_options}}, + bug => $ref, + submitter => $newsubmitter, + ); + }; + if ($@) { + $errors++; + print {$transcript} "Failed to set submitter on $ref: ".cleanup_eval_fail($@,$debug)."\n"; + } + } + } elsif ($ctl eq 'forwarded') { + my $forward_to= $matches[1]; + eval { + set_forwarded(@{$param{common_control_options}}, + bug => $ref, + forwarded => $forward_to, + ); + }; + if ($@) { + $errors++; + print {$transcript} "Failed to set the forwarded-to-address of $ref: ".cleanup_eval_fail($@,$debug)."\n"; + } + } elsif ($ctl eq 'notforwarded') { + eval { + set_forwarded(@{$param{common_control_options}}, + bug => $ref, + forwarded => undef, + ); + }; + if ($@) { + $errors++; + print {$transcript} "Failed to clear the forwarded-to-address of $ref: ".cleanup_eval_fail($@,$debug)."\n"; + } + } elsif ($ctl eq 'severity') { + my $newseverity= $matches[1]; + if (exists $config{obsolete_severities}{$newseverity}) { + print {$transcript} "Severity level \`$newseverity' is obsolete. " . + "Use $config{obsolete_severities}{$newseverity} instead.\n\n"; + $errors++; + } elsif (not defined first {$_ eq $newseverity} + (@{$config{severity_list}}, $config{default_severity})) { + print {$transcript} "Severity level \`$newseverity' is not known.\n". + "Recognized are: $config{show_severities}.\n\n"; + $errors++; + } else { + eval { + set_severity(@{$param{common_control_options}}, + bug => $ref, + severity => $newseverity, + ); + }; + if ($@) { + $errors++; + print {$transcript} "Failed to set severity of $config{bug} $ref to $newseverity: ".cleanup_eval_fail($@,$debug)."\n"; + } + } + } elsif ($ctl eq 'tag') { + my $tags = $matches[1]; + my @tags = map {m/^([+=-])(.+)/ ? ($1,$2):($_)} split /[\s,]+/, $tags; + # this is an array of hashrefs which contain two elements, the + # first of which is the array of tags, the second is the + # option to pass to set_tag (we use a hashref here to make it + # more obvious what is happening) + my @tag_operations; + my @badtags; + for my $tag (@tags) { + if ($tag =~ /^[=+-]$/) { + if ($tag eq '=') { + @tag_operations = {tags => [], + option => [], + }; + } + elsif ($tag eq '-') { + push @tag_operations, + {tags => [], + option => [remove => 1], + }; + } + elsif ($tag eq '+') { + push @tag_operations, + {tags => [], + option => [add => 1], + }; + } + next; + } + if (not defined first {$_ eq $tag} @{$config{tags}}) { + push @badtags, $tag; + next; + } + if (not @tag_operations) { + @tag_operations = {tags => [], + option => [add => 1], + }; + } + push @{$tag_operations[-1]{tags}},$tag; + } + if (@badtags) { + print {$transcript} "Unknown tag/s: ".join(', ', @badtags).".\n". + "Recognized are: ".join(' ', @{$config{tags}}).".\n\n"; + $errors++; + } + eval { + for my $operation (@tag_operations) { + set_tag(@{$param{common_control_options}}, + bug => $ref, + tag => [@{$operation->{tags}}], + warn_on_bad_tags => 0, # don't warn on bad tags, + # 'cause we do that above + @{$operation->{option}}, + ); + } + }; + if ($@) { + # we intentionally have two errors here if there is a bad + # tag and the above fails for some reason + $errors++; + print {$transcript} "Failed to alter tags of $config{bug} $ref: ".cleanup_eval_fail($@,$debug)."\n"; + } + } elsif ($ctl eq 'block') { + my $add_remove = defined $matches[0] && $matches[0] eq 'un'; + $ref = $matches[1]; + $ref = exists $param{clonebugs}{$ref} ? $param{clonebugs}{$ref} : $ref; + my @blockers = map {exists $param{clonebugs}{$_}?$param{clonebugs}{$_}:$_} split /[\s,]+/, $matches[2]; + eval { + set_blocks(@{$param{common_control_options}}, + bug => $ref, + block => \@blockers, + $add_remove ? (remove => 1):(add => 1), + ); + }; + if ($@) { + $errors++; + print {$transcript} "Failed to set blocking bugs of $ref: ".cleanup_eval_fail($@,$debug)."\n"; + } + } elsif ($ctl eq 'retitle') { + my $newtitle= $matches[1]; + eval { + set_title(@{$param{common_control_options}}, + bug => $ref, + title => $newtitle, + ); + }; + if ($@) { + $errors++; + print {$transcript} "Failed to set the title of $ref: ".cleanup_eval_fail($@,$debug)."\n"; + } + } elsif ($ctl eq 'unmerge') { + eval { + set_merged(@{$param{common_control_options}}, + bug => $ref, + ); + }; + if ($@) { + $errors++; + print {$transcript} "Failed to unmerge $ref: ".cleanup_eval_fail($@,$debug)."\n"; + } + } elsif ($ctl eq 'merge') { + my @tomerge; + ($ref,@tomerge) = map {exists $param{clonebugs}{$_}?$param{clonebugs}{$_}:$_} + split(/\s+#?/,$matches[0]); + eval { + set_merged(@{$param{common_control_options}}, + bug => $ref, + merge_with => \@tomerge, + ); + }; + if ($@) { + $errors++; + print {$transcript} "Failed to merge $ref: ".cleanup_eval_fail($@,$debug)."\n"; + } + } elsif ($ctl eq 'forcemerge') { + my @tomerge; + ($ref,@tomerge) = map {exists $param{clonebugs}{$_}?$param{clonebugs}{$_}:$_} + split(/\s+#?/,$matches[0]); + eval { + set_merged(@{$param{common_control_options}}, + bug => $ref, + merge_with => \@tomerge, + force => 1, + masterbug => 1, + ); + }; + if ($@) { + $errors++; + print {$transcript} "Failed to forcibly merge $ref: ".cleanup_eval_fail($@,$debug)."\n"; + } + } elsif ($ctl eq 'clone') { + my $origref = $matches[0]; + my @newclonedids = split /\s+/, $matches[1]; + my $newbugsneeded = scalar(@newclonedids); + + eval { + my %new_clones; + clone_bug(@{$param{common_control_options}}, + bug => $ref, + new_bugs => \@newclonedids, + new_clones => \%new_clones, + ); + %{$param{clonebugs}} = (%{$param{clonebugs}}, + %new_clones); + }; + if ($@) { + $errors++; + print {$transcript} "Failed to clone $ref: ".cleanup_eval_fail($@,$debug)."\n"; + } + } elsif ($ctl eq 'package') { + my @pkgs = split /\s+/, $matches[0]; + if (scalar(@pkgs) > 0) { + $param{limit}{package} = [@pkgs]; + print {$transcript} "Limiting to bugs with field 'package' containing at least one of ".join(', ',map {qq('$_')} @pkgs)."\n"; + print {$transcript} "Limit currently set to"; + for my $limit_field (keys %{$param{limit}}) { + print {$transcript} " '$limit_field':".join(', ',map {qq('$_')} @{$param{limit}{$limit_field}})."\n"; + } + print {$transcript} "\n"; + } else { + $param{limit}{package} = []; + print {$transcript} "Limit cleared.\n\n"; + } + } elsif ($ctl eq 'limit') { + my ($field,@options) = split /\s+/, $matches[0]; + $field = lc($field); + if ($field =~ /^(?:clear|unset|blank)$/) { + %{$param{limit}} = (); + print {$transcript} "Limit cleared.\n\n"; + } + elsif (exists $Debbugs::Status::fields{$field} or $field eq 'source') { + # %{$param{limit}} can actually contain regexes, but because they're + # not evaluated in Safe, DO NOT allow them through without + # fixing this. + $param{limit}{$field} = [@options]; + print {$transcript} "Limiting to bugs with field '$field' containing at least one of ".join(', ',map {qq('$_')} @options)."\n"; + print {$transcript} "Limit currently set to"; + for my $limit_field (keys %{$param{limit}}) { + print {$transcript} " '$limit_field':".join(', ',map {qq('$_')} @{$param{limit}{$limit_field}})."\n"; + } + print {$transcript} "\n"; + } + else { + print {$transcript} "Limit key $field not understood. Stopping processing here.\n\n"; + $errors++; + # this needs to be fixed + syntax error for fixing it + last; + } + } elsif ($ctl eq 'affects') { + my $add_remove = $matches[1]; + my $packages = $matches[2]; + # if there isn't a package given, assume that we should unset + # affects; otherwise default to adding + if (not defined $packages or + not length $packages) { + $packages = ''; + $add_remove ||= '='; + } + elsif (not defined $add_remove or + not length $add_remove) { + $add_remove = '+'; + } + eval { + affects(@{$param{common_control_options}}, + bug => $ref, + package => [splitpackages($packages)], + ($add_remove eq '+'?(add => 1):()), + ($add_remove eq '-'?(remove => 1):()), + ); + }; + if ($@) { + $errors++; + print {$transcript} "Failed to mark $ref as affecting package(s): ".cleanup_eval_fail($@,$debug)."\n"; + } + + } elsif ($ctl eq 'summary') { + my $summary_msg = length($matches[1])?$matches[1]:undef; + eval { + summary(@{$param{common_control_options}}, + bug => $ref, + summary => $summary_msg, + ); + }; + if ($@) { + $errors++; + print {$transcript} "Failed to give $ref a summary: ".cleanup_eval_fail($@,$debug)."\n"; + } + + } elsif ($ctl eq 'owner') { + my $newowner = $matches[1]; + if ($newowner eq '!') { + $newowner = $param{replyto}; + } + eval { + owner(@{$param{common_control_options}}, + bug => $ref, + owner => $newowner, + ); + }; + if ($@) { + $errors++; + print {$transcript} "Failed to mark $ref as having an owner: ".cleanup_eval_fail($@,$debug)."\n"; + } + } elsif ($ctl eq 'noowner') { + eval { + owner(@{$param{common_control_options}}, + bug => $ref, + owner => undef, + ); + }; + if ($@) { + $errors++; + print {$transcript} "Failed to mark $ref as not having an owner: ".cleanup_eval_fail($@,$debug)."\n"; + } + } elsif ($ctl eq 'unarchive') { + eval { + bug_unarchive(@{$param{common_control_options}}, + bug => $ref, + ); + }; + if ($@) { + $errors++; + } + } elsif ($ctl eq 'archive') { + eval { + bug_archive(@{$param{common_control_options}}, + bug => $ref, + ignore_time => 1, + archive_unarchived => 0, + ); + }; + if ($@) { + $errors++; + } + } + if ($errors) { + ${$param{errors}}+=$errors; + } + return($errors,$terminate_control); +} + +1; + +__END__ diff --git a/Debbugs/MIME.pm b/Debbugs/MIME.pm index 05534e3..481be7b 100644 --- a/Debbugs/MIME.pm +++ b/Debbugs/MIME.pm @@ -31,12 +31,21 @@ use warnings; use strict; use base qw(Exporter); -use vars qw($VERSION @EXPORT_OK); +use vars qw($DEBUG $VERSION @EXPORT_OK %EXPORT_TAGS @EXPORT); BEGIN { $VERSION = 1.00; + $DEBUG = 0 unless defined $DEBUG; - @EXPORT_OK = qw(parse decode_rfc1522 encode_rfc1522 convert_to_utf8 create_mime_message getmailbody); + @EXPORT = (); + + %EXPORT_TAGS = (mime => [qw(parse create_mime_message getmailbody)], + rfc1522 => [qw(decode_rfc1522 encode_rfc1522)], + utf8 => [qw(convert_to_utf8)], + ); + @EXPORT_OK=(); + Exporter::export_ok_tags(keys %EXPORT_TAGS); + $EXPORT_TAGS{all} = [@EXPORT_OK]; } use File::Path; diff --git a/scripts/process b/scripts/process index 5687bf3..4236f6b 100755 --- a/scripts/process +++ b/scripts/process @@ -11,15 +11,17 @@ use POSIX qw(strftime); use IO::File; +use Getopt::Long; +use Pod::Usage; use MIME::Parser; use Debbugs::MIME qw(decode_rfc1522 create_mime_message getmailbody); use Debbugs::Mail qw(send_mail_message encode_headers get_addresses); use Debbugs::Packages qw(getpkgsrc binary_to_source); use Debbugs::User qw(read_usertags write_usertags); -use Debbugs::Common qw(:lock get_hashname package_maintainer overwritefile); +use Debbugs::Common qw(:lock get_hashname buglog package_maintainer overwritefile make_list); use Debbugs::Status qw(writebug isstrongseverity lockreadbugmerge lockreadbug new_bug read_bug splitpackages :versions); -use Debbugs::CGI qw(html_escape bug_url); +use Debbugs::CGI qw(html_escape bug_links); use Debbugs::Log qw(:misc :write); @@ -28,53 +30,119 @@ use Debbugs::Text qw(:templates); use Debbugs::Config qw(:globals :config); use Debbugs::Control qw(append_action_to_log); -use Encode qw(encode_utf8); +use Debbugs::Control::Service qw(valid_control control_line); +use Debbugs::Recipients qw(determine_recipients); +use Encode qw(encode_utf8 decode); -chdir( "$gSpoolDir" ) || die "chdir spool: $!\n"; +=head1 NAME + +process - Handle e-mails emails sent to bugs + +=head1 SYNOPSIS + +process nn + + Options: + --debug, -d debugging level (Default 0) + +=head1 OPTIONS + +=over + +=item <--debug,-d> + +Debugging level (default 0) + +=back + +=cut + +use vars qw($DEBUG); + +my %options = (debug => 0, + help => 0, + man => 0, + ); + +GetOptions(\%options, + 'debug|d+','help|h|?','man|m'); + +pod2usage() if $options{help}; +pod2usage({verbose=>2}) if $options{man}; + + +$DEBUG=$options{debug}; +my $debugfh = IO::File->new('/dev/null','w') or + die "Unable to open /dev/null for writing; $!"; +if ($DEBUG > 0) { + $debugfh = \*STDERR; +} + +# these are the valid bug addresses +my %baddress = (B => 'submit', + M => 'maintonly', + Q => 'quiet', + F => 'forwarded', + D => 'done', + S => 'submitter', + L => 'list', + ); +my $valid_codeletters = join('',keys %baddress); + + +chdir($config{spool_dir}) or die "Unable to chdir to spool ($config{spool_dir}): $!"; -#open(DEBUG,"> /tmp/debbugs.debug"); umask(002); -open DEBUG, ">/dev/null"; my $intdate = time or die "failed to get time: $!"; -$_=shift; -m/^([BMQFDUL])(\d*)\.\d+$/ or die "bad argument: $_"; -my $codeletter= $1; -my $tryref= length($2) ? $2 : -1; -my $nn= $_; +my ($nn) = @ARGV; +my ($codeletter,$tryref) = + $nn =~ m/^([$valid_codeletters])(\d*)\.\d+$/ + or die "bad argument: $_"; +$tryref = undef unless length ($tryref) and + $tryref > 0; + +if (!rename("incoming/G$nn","incoming/P$nn")) { + my $error = $!; + $error = '' if not defined $error; + # this is very fragile, but we should probably die here anyway + if ($error =~ m/no such file or directory/i) { + exit 0; + } + die "Unable to rename incoming/G$nn to lock: $error"; +} -if (!rename("incoming/G$nn","incoming/P$nn")) -{ - $_=$!.''; m/no such file or directory/i && exit 0; - die "renaming to lock: $!"; +# die here to avoid continuously processing this mail +if (not exists $baddress{$codeletter}) { + die "bad codeletter $codeletter"; +} + +my $baddress = $baddress{$codeletter}; +if ($baddress eq 'list') { + bug_list_forward($nn) if $codeletter eq 'L'; } -my $baddress= 'submit' if $codeletter eq 'B'; -$baddress= 'maintonly' if $codeletter eq 'M'; -$baddress= 'quiet' if $codeletter eq 'Q'; -$baddress= 'forwarded' if $codeletter eq 'F'; -$baddress= 'done' if $codeletter eq 'D'; -$baddress= 'submitter' if $codeletter eq 'U'; -bug_list_forward($nn) if $codeletter eq 'L'; -$baddress || die "bad codeletter $codeletter"; + my $baddressroot= $baddress; -$baddress= "$tryref-$baddress" if $tryref>=0; +$baddress= "$tryref-$baddress" if defined $tryref; -open(M,"incoming/P$nn"); -my @log=; -close(M); +my $msg; +my @msg; -my @msg = @log; -chomp @msg; +{ + my $log = IO::File->new("incoming/P$nn",'r') or + die "Unable to open 'incoming/P$nn' for reading; $!"; + local $/; + $msg=<$log>; + @msg = split /\n/, $msg; + close($log); +} -print DEBUG "###\n",join("##\n",@msg),"\n###\n"; my $tdate = strftime "%a, %d %h %Y %T +0000", gmtime; -my $fwd= <output_under("$gSpoolDir/mime.tmp"); -my $entity = eval { $parser->parse_data(join('',@log)) }; - -my $i; -if ($entity and $entity->head->tags) { - @headerlines = @{$entity->head->header}; - chomp @headerlines; - - my $entity_body = getmailbody($entity); - @bodylines = map {s/\r?\n$//; $_;} - $entity_body ? $entity_body->as_lines() : (); - - # set $i to beginning of encoded body data, so we can dump it out - # verbatim later - $i = 0; - ++$i while $msg[$i] =~ /./; -} else { - # Legacy pre-MIME code, kept around in case MIME::Parser fails. - for ($i = 0; $i <= $#msg; $i++) { - $_ = $msg[$i]; - last unless length($_); - while ($msg[$i+1] =~ m/^\s/) { - $i++; - $_ .= "\n".$msg[$i]; - } - push @headerlines, $_; - } +my $parser_output = Debbugs::MIME::parse($msg); - @bodylines = @msg[$i..$#msg]; -} +@headerlines = @{$parser_output->{header}}; +@bodylines = @{$parser_output->{body}}; my %header; @@ -135,20 +175,20 @@ for my $hdr (@headerlines) { $hdr = decode_rfc1522($hdr); $_ = $hdr; s/\n\s/ /g; - &finish if m/^x-loop: (\S+)$/i && $1 eq "$gMaintainerEmail"; + finish() if m/^x-loop: (\S+)$/i && $1 eq "$gMaintainerEmail"; my $ins = !m/^subject:/i && !m/^reply-to:/i && !m/^return-path:/i && !m/^From / && !m/^X-Debbugs-/i; $fwd .= $hdr."\n" if $ins; - # print DEBUG ">$_<\n"; + # print {$debugfh} ">$_<\n"; if (s/^(\S+):\s*//) { my $v = lc $1; if ($v eq 'x-loop') { push @common_headers, 'X-Loop',$_; } - print DEBUG ">$v=$_<\n"; + print {$debugfh} ">$v=$_<\n"; $header{$v} = $_; } else { - print DEBUG "!>$_<\n"; + print {$debugfh} "!>$_<\n"; } } $header{'message-id'} = '' if not defined $header{'message-id'}; @@ -173,6 +213,7 @@ if (@bodylines and $bodylines[0] =~ /^-----BEGIN PGP SIGNED/) { #psuedoheaders my %pheader; +my @control_bits; # extract pseudo-headers for my $phline (@bodylines) { @@ -182,12 +223,16 @@ for my $phline (@bodylines) last if $phline !~ m/^([\w-]+):\s*(\S.*)/; my ($fn, $fv) = ($1, $2); $fv =~ s/\s*$//; - print DEBUG ">$fn|$fv|\n"; + print {$debugfh} ">$fn|$fv|\n"; $fn = lc $fn; - # Don't lc owner or forwarded - $fv = lc $fv unless $fn =~ /^(?:owner|forwarded|usertags|version|source-version)$/; - $pheader{$fn} = $fv; - print DEBUG ">$fn~$fv<\n"; + if ($fn =~ /^control$/) { + push @control_bits,$fv; + } else { + # Don't lc owner or forwarded + $fv = lc $fv unless $fn =~ /^(?:owner|forwarded|usertags|version|source-version)$/; + $pheader{$fn} = $fv; + } + print {$debugfh} ">$fn~$fv<\n"; } # Allow pseudo headers to set x-debbugs- stuff [#179340] @@ -195,9 +240,13 @@ for my $key (grep /X-Debbugs-.*/i, keys %pheader) { $header{$key} = $pheader{$key} if not exists $header{$key}; } +# set $i to beginning of encoded body data, so we can dump it out +# verbatim later +my $i = 0; +++$i while $msg[$i] =~ /./; $fwd .= join("\n",@msg[$i..$#msg]); -print DEBUG "***\n$fwd\n***\n"; +print {$debugfh} "***\n$fwd\n***\n"; if (defined $header{'resent-from'} && !defined $header{'from'}) { $header{'from'} = $header{'resent-from'}; @@ -223,12 +272,11 @@ if (!defined($header{'subject'})) my $ref=-1; $subject =~ s/^Re:\s*//i; $_= $subject."\n"; -if ($tryref < 0 && m/^Bug ?\#(\d+)\D/i) { - $tryref= $1+0; +if (not defined $tryref and m/^Bug ?\#(\d+)\D/i) { + $tryref = $1 if $1 > 0; } my $data; -if ($tryref >= 0) -{ +if (defined $tryref) { my $bfound; ($bfound, $data)= &lockreadbugmerge($tryref); if ($bfound and not $data->{archived}) { @@ -252,8 +300,8 @@ if ($tryref >= 0) messageid => $header{'message-id'}, }, )),''); - &appendlog; - &finish; + appendlog($ref,$msg); + finish(); } } else { &filelock('lock/-1'); @@ -289,7 +337,7 @@ if ($codeletter eq 'D' || $codeletter eq 'F') my $generalcc; my $set_done; if ($codeletter eq 'F') { # Forwarded - (&appendlog,&finish) if defined $data->{forwarded} and length($data->{forwarded}); + (appendlog($ref,$msg),finish()) if defined $data->{forwarded} and length($data->{forwarded}); $receivedat= "forwarded\@$gEmailDomain"; $markaswhat= 'forwarded'; $set_forwarded= $header{'to'}; @@ -307,8 +355,8 @@ if ($codeletter eq 'D' || $codeletter eq 'F') if (defined $data->{done} and length($data->{done}) and not defined $pheader{'source-version'} and not defined $pheader{'version'}) { - &appendlog; - &finish; + appendlog($ref,$msg); + finish(); } $receivedat= "done\@$gEmailDomain"; $markaswhat= 'done'; @@ -343,8 +391,8 @@ if ($codeletter eq 'D' || $codeletter eq 'F') messageid => $header{'message-id'}, }, )),''); - &appendlog; - &finish; + appendlog($ref,$msg); + finish(); } &checkmaintainers; @@ -400,8 +448,12 @@ if ($codeletter eq 'D' || $codeletter eq 'F') writebug($ref, $data); my $hash = get_hashname($ref); - open(O,"db-h/$hash/$ref.report") || die "read original report: $!"; - my $orig_report= join('',); close(O); + my $orig_report_fh = IO::File->new("db-h/$hash/$ref.report") or + die "Unable to read original report: $!"; + my $orig_report; + { local $/; $orig_report = <$orig_report_fh>;} + close($orig_report_fh) or + die "Unable to close original report filehandle: $!"; if ($codeletter eq 'F') { &htmllog("Reply","sent",$replyto,"You have marked $gBug as forwarded."); &sendmessage(create_mime_message( @@ -475,9 +527,9 @@ if ($codeletter eq 'D' || $codeletter eq 'F') ), [join("\n",@msg),$orig_report]),'',undef,1); } - &appendlog; + appendlog($ref,$msg); } - &finish; + finish(); } if ($ref<0) { # new bug report @@ -500,8 +552,8 @@ if ($ref<0) { # new bug report messageid => $header{'message-id'}, }, )),''); - &appendlog; - &finish; + appendlog($ref,$msg); + finish(); } $data->{found_versions} = []; @@ -539,8 +591,8 @@ if ($ref<0) { # new bug report baddress => $baddress, }, ),[join("\n", @msg)]), '',undef,1); - &appendlog; - &finish; + appendlog($ref,$msg); + finish(); } if (defined $config{default_package}) { @@ -634,7 +686,7 @@ if ($ref<0) { # new bug report &checkmaintainers; -print DEBUG "maintainers >".join(' ',@maintaddrs)."<\n"; +print {$debugfh} "maintainers >".join(' ',@maintaddrs)."<\n"; my $orgsender= defined($header{'sender'}) ? "Original-Sender: $header{'sender'}\n" : ''; my $newsubject= $subject; $newsubject =~ s/^$gBug#$ref:*\s*//; @@ -903,18 +955,137 @@ if (not exists $header{'x-debbugs-no-ack'} and ],$body,[]), '',undef,1); } -&appendlog; -&finish; +appendlog($ref,$msg); + +## handle control messages at this point, immediately before finishing +my %clonebugs = (-1 => $ref); +my %bug_affected; +if (@control_bits) { + my $transcript_scalar = ''; + open my $transcript, ">:scalar:utf8", \$transcript_scalar or + die "Unable to create transcript scalar: $!"; + print {$transcript} "Processing control commands:\n\n"; + my %affected_packages; + my %recipients; + # this is the hashref which is passed to all control calls + my %limit = (); + my $errors = 0; + my $unknowns = 0; + + my @common_control_options = + (transcript => $transcript, + requester => $header{from}, + request_addr => $baddress.'@'.$config{email_domain}, + request_msgid => $header{'message-id'}, + request_subject => $header{subject}, + request_nn => $nn, + request_replyto => $replyto, + message => $msg, + affected_bugs => \%bug_affected, + affected_packages => \%affected_packages, + recipients => \%recipients, + limit => \%limit, + ); + if (@gExcludeFromControl and grep {$replyto =~ m/\Q$_\E/} @gExcludeFromControl) { + print {$transcript} fill_template('mail/excluded_from_control'); + print {$transcript} "Stopping processing here.\n\n"; + } else { + my %clonebugs = (); + for my $control_bit (@control_bits) { + $control_bit =~ s/\xef\xbb\xbf//g; + next unless $control_bit =~ m/\S/; + eval { + my $temp = decode("utf8",$control_bit,Encode::FB_CROAK); + $control_bit = $temp; + }; + print {$transcript} "> $control_bit\n"; + next if $control_bit =~ /^\s*\#/; + my $action = ''; + my $ok; + if (defined valid_control($control_bit)) { + my ($new_errors,$terminate_control) = + control_line(line => $control_bit, + clonebugs => \%clonebugs, + limit => \%limit, + common_control_options => \@common_control_options, + errors => \$errors, + transcript => $transcript, + debug => 0, + ok => \$ok, + ); + if ($terminate_control) { + last; + } + } + else { + $errors++; + if (++$unknowns >= 5) { + print {$transcript} "Too many unknown commands, stopping here.\n\n"; + last; + } + } + } + } + my $temp_transcript = $transcript_scalar; + eval{ + $temp_transcript = decode("utf8",$temp_transcript,Encode::FB_CROAK); + }; + my @maintccs = determine_recipients(recipients => \%recipients, + address_only => 1, + cc => 1, + ); + my $error_text = $errors > 0 ? " (with $errors errors)":''; + my $reply = + create_mime_message(['X-Loop' => $gMaintainerEmail, + From => "$gMaintainerEmail ($gProject $gBug Tracking System)", + To => $replyto, + @maintccs ? (Cc => join(', ',@maintccs)):(), + Subject => "Processed${error_text}: $header{subject}", + 'Message-ID' => "", + 'In-Reply-To' => $header{'message-id'}, + References => join(' ',grep {defined $_} $header{'message-id'},$data->{msgid}), + Precedence => 'bulk', + keys %affected_packages ?("X-${gProject}-PR-Package" => join(' ',keys %affected_packages)):(), + keys %affected_packages ?("X-${gProject}-PR-Source" => + join(' ', + map {defined $_ ?(ref($_)?@{$_}:$_):()} + binary_to_source(binary => [keys %affected_packages], + source_only => 1))):(), + "X-$gProject-PR-Message" => 'transcript', + @common_headers, + ], + fill_template('mail/message_body', + {body => $temp_transcript}, + )); + + utime(time,time,"db-h"); + + send_mail_message(message => $reply, + recipients => [exists $header{'x-debbugs-no-ack'}?():$replyto, + make_list(values %{{determine_recipients(recipients => \%recipients, + address_only => 1, + )}} + ), + ] + ); + +} + + +finish(); sub appendlog { - my $hash = get_hashname($ref); - if (!open(AP,">>db-h/$hash/$ref.log")) { - print DEBUG "failed open log<\n"; - print DEBUG "failed open log err $!<\n"; - die "opening db-h/$hash/$ref.log (li): $!"; - } - print(AP "\7\n",escape_log(@log),"\n\3\n") || die "writing db-h/$hash/$ref.log (li): $!"; - close(AP) || die "closing db-h/$hash/$ref.log (li): $!"; + my ($ref,$msg) = @_; + my $log_location = buglog($ref); + die "Unable to find .log for $ref" + if not defined $log_location; + my $logfh = IO::File->new(">>$log_location") or + die "Unable to open $log_location for appending: $!"; + write_log_records(logfh => $logfh, + records => [{type => 'incoming-recv', + text => $msg, + }]); + close ($logfh) or die "Unable to close $log_location: $!"; } sub finish { @@ -1049,6 +1220,7 @@ sub fill_template{ my $variables = {config => \%config, defined($ref)?(ref => $ref):(), defined($data)?(data => $data):(), + refs => [map {exists $clonebugs{$_}?$clonebugs{$_}:$_} keys %bug_affected], %{$extra_var}, }; my $hole_var = {'&bugurl' => @@ -1092,7 +1264,7 @@ sub checkmaintainers { # this is utter hackery until we switch to Debbugs::Recipients my @maints = package_maintainer(binary => $p); if (@maints) { - print DEBUG "maintainer add >$p|".join(',',@maints)."<\n"; + print {$debugfh} "maintainer add >$p|".join(',',@maints)."<\n"; my %temp; @temp{@maintaddrs} = @maintaddrs; push(@maintaddrs, @@ -1100,7 +1272,7 @@ sub checkmaintainers { not exists $temp{$_}} @maints); $anymaintfound++; } else { - print DEBUG "maintainer none >$p<\n"; + print {$debugfh} "maintainer none >$p<\n"; push(@maintaddrs,$gUnknownMaintainerEmail) unless $anymaintnotfound; $anymaintnotfound++; last; @@ -1108,7 +1280,7 @@ sub checkmaintainers { } if (defined $data->{owner} and length $data->{owner}) { - print DEBUG "owner add >$data->{package}|$data->{owner}<\n"; + print {$debugfh} "owner add >$data->{package}|$data->{owner}<\n"; my $addmaint = $data->{owner}; push(@maintaddrs, $addmaint) unless $addmaint eq $replyto or grep($_ eq $addmaint, @maintaddrs); @@ -1161,12 +1333,12 @@ sub bug_list_forward{ if defined $data; print STDERR "Tried to loop me with $envelope_from\n" and exit 1 if $envelope_from =~ /\Q$gListDomain\E|\Q$gEmailDomain\E/; - print DEBUG $envelope_from,qq(\n); + print {$debugfh} $envelope_from,qq(\n); # If we don't have a bug address, something has gone horribly wrong. print STDERR "Doesn't match: $bug_address\n" and exit 1 unless defined $bug_address; $bug_address =~ s/\@.+//; - print DEBUG "Sending message to bugs=$bug_address\@$config{bug_subscription_domain}\n"; - print DEBUG $header.qq(\n\n).$body; + print {$debugfh} "Sending message to bugs=$bug_address\@$config{bug_subscription_domain}\n"; + print {$debugfh} $header.qq(\n\n).$body; send_mail_message(message => $header.qq(\n\n).$body, recipients => ["bugs=$bug_address\@$config{bug_subscription_domain}"], envelope_from => $envelope_from, diff --git a/scripts/service b/scripts/service index 29d14af..4d20051 100755 --- a/scripts/service +++ b/scripts/service @@ -31,6 +31,7 @@ use Debbugs::Status qw(splitpackages); use Debbugs::CGI qw(html_escape); use Debbugs::Control qw(:all); +use Debbugs::Control::Service qw(:all); use Debbugs::Log qw(:misc); use Debbugs::Text qw(:templates); @@ -169,11 +170,6 @@ my %clonebugs = (); my %bcc = (); -my @bcc; -sub addbcc { - push @bcc, $_[0] unless grep { $_ eq $_[0] } @bcc; -} - our $data; our $message; our $extramessage; @@ -414,12 +410,13 @@ END print {$transcript} "No valid user selected\n"; $errors++; $indicated_user = 1; - } elsif (&setbug) { + } elsif (check_limit(data => read_bug(bug => $ref), + limit => \%limit, + transcript => $transcript)) { if (not $indicated_user and defined $user) { print {$transcript} "User is $user\n"; $indicated_user = 1; } - &nochangebug; my %ut; Debbugs::User::read_usertags(\%ut, $user); my @oldtags = (); my @newtags = (); my @badtags = (); @@ -465,607 +462,22 @@ Unknown command or malformed arguments to command. (Use control\@$gEmailDomain to manipulate reports.) END - $errors++; - if (++$unknowns >= 3) { - print {$transcript} "Too many unknown commands, stopping here.\n\n"; - last; - } -#### "developer only" ones start here - } elsif (m/^close\s+\#?(-?\d+)(?:\s+(\d.*))?$/i) { - $ok++; - $ref= $1; - $ref = $clonebugs{$ref} if exists $clonebugs{$ref}; - if (defined $2) { - eval { - set_fixed(@common_control_options, - bug => $ref, - fixed => $2, - add => 1, - ); - }; - if ($@) { - $errors++; - print {$transcript} "Failed to add fixed version '$2' to $ref: ".cleanup_eval_fail($@,$debug)."\n"; - } - } - eval { - set_done(@common_control_options, - done => 1, - bug => $ref, - reopen => 0, - notify_submitter => 1, - clear_fixed => 0, - ); - }; - if ($@) { - $errors++; - print {$transcript} "Failed to mark $ref as done: ".cleanup_eval_fail($@,$debug)."\n"; - } - } elsif (m/^reassign\s+\#?(-?\d+)\s+ # bug and command - (?:(?:((?:src:|source:)?$config{package_name_re}) # new package - (?:\s+((?:$config{package_name_re}\/)? - $config{package_version_re}))?)| # optional version - ((?:src:|source:)?$config{package_name_re} # multiple package form - (?:\s*\,\s*(?:src:|source:)?$config{package_name_re})+)) - \s*$/xi) { - $ok++; - $ref= $1; - my @new_packages; - if (not defined $2) { - push @new_packages, split /\s*\,\s*/,$4; - } - else { - push @new_packages, $2; - } - @new_packages = map {y/A-Z/a-z/; s/^(?:src|source):/src:/; $_;} @new_packages; - $ref = $clonebugs{$ref} if exists $clonebugs{$ref}; - my $version= $3; - eval { - set_package(@common_control_options, - bug => $ref, - package => \@new_packages, - ); - # if there is a version passed, we make an internal call - # to set_found - if (defined($version) && length $version) { - set_found(@common_control_options, - bug => $ref, - found => $version, - ); - } - }; - if ($@) { - $errors++; - print {$transcript} "Failed to clear fixed versions and reopen on $ref: ".cleanup_eval_fail($@,$debug)."\n"; - } - } elsif (m/^reopen\s+\#?(-?\d+)(?:\s+([\=\!]|(?:\S.*\S)))?$/i) { - $ok++; - $ref= $1; - $ref = $clonebugs{$ref} if exists $clonebugs{$ref}; - my $new_submitter = $2; - if (defined $new_submitter) { - if ($new_submitter eq '=') { - undef $new_submitter; - } - elsif ($new_submitter eq '!') { - $new_submitter = $replyto; - } - } - eval { - set_done(@common_control_options, - bug => $ref, - reopen => 1, - defined $new_submitter? (submitter => $new_submitter):(), - ); - }; - if ($@) { - $errors++; - print {$transcript} "Failed to reopen $ref: ".cleanup_eval_fail($@,$debug)."\n"; - } - } elsif (m{^(?:(?i)found)\s+\#?(-?\d+) - (?:\s+((?:$config{package_name_re}\/)? - $config{package_version_re} - # allow for multiple packages - (?:\s*,\s*(?:$config{package_name_re}\/)? - $config{package_version_re})*) - )?$}x) { - $ok++; - $ref= $1; - $ref = $clonebugs{$ref} if exists $clonebugs{$ref}; - my @versions; - if (defined $2) { - @versions = split /\s*,\s*/,$2; - eval { - set_found(@common_control_options, - bug => $ref, - found => \@versions, - add => 1, - ); - }; - if ($@) { - $errors++; - print {$transcript} "Failed to add found on $ref: ".cleanup_eval_fail($@,$debug)."\n"; - } - } - else { - eval { - set_fixed(@common_control_options, - bug => $ref, - fixed => [], - reopen => 1, - ); - }; - if ($@) { - $errors++; - print {$transcript} "Failed to clear fixed versions and reopen on $ref: ".cleanup_eval_fail($@,$debug)."\n"; - } - } - } - elsif (m{^(?:(?i)notfound)\s+\#?(-?\d+) - \s+((?:$config{package_name_re}\/)? - $config{package_version_re} - # allow for multiple packages - (?:\s*,\s*(?:$config{package_name_re}\/)? - $config{package_version_re})* - )$}x) { - $ok++; - $ref= $1; - $ref = $clonebugs{$ref} if exists $clonebugs{$ref}; - my @versions; - @versions = split /\s*,\s*/,$2; - eval { - set_found(@common_control_options, - bug => $ref, - found => \@versions, - remove => 1, - ); - }; - if ($@) { - $errors++; - print {$transcript} "Failed to remove found on $ref: ".cleanup_eval_fail($@,$debug)."\n"; - } - } - elsif (m{^(?:(?i)fixed)\s+\#?(-?\d+) - \s+((?:$config{package_name_re}\/)? - $config{package_version_re} - # allow for multiple packages - (?:\s*,\s*(?:$config{package_name_re}\/)? - $config{package_version_re})*) - \s*$}x) { - $ok++; - $ref= $1; - $ref = $clonebugs{$ref} if exists $clonebugs{$ref}; - my @versions; - @versions = split /\s*,\s*/,$2; - eval { - set_fixed(@common_control_options, - bug => $ref, - fixed => \@versions, - add => 1, - ); - }; - if ($@) { - $errors++; - print {$transcript} "Failed to add fixed on $ref: ".cleanup_eval_fail($@,$debug)."\n"; - } - } - elsif (m{^(?:(?i)notfixed)\s+\#?(-?\d+) - \s+((?:$config{package_name_re}\/)? - $config{package_version_re} - # allow for multiple packages - (?:\s*,\s*(?:$config{package_name_re}\/)? - $config{package_version_re})*) - \s*$}x) { - $ok++; - $ref= $1; - $ref = $clonebugs{$ref} if exists $clonebugs{$ref}; - my @versions; - @versions = split /\s*,\s*/,$2; - eval { - set_fixed(@common_control_options, - bug => $ref, - fixed => \@versions, - remove => 1, - ); - }; - if ($@) { - $errors++; - print {$transcript} "Failed to remove fixed on $ref: ".cleanup_eval_fail($@,$debug)."\n"; - } - } - elsif (m/^submitter\s+\#?(-?\d+)\s+(\!|\S.*\S)$/i) { - $ok++; - $ref= $1; - $ref = $clonebugs{$ref} if exists $clonebugs{$ref}; - my $newsubmitter = $2 eq '!' ? $replyto : $2; - if (not Mail::RFC822::Address::valid($newsubmitter)) { - print {$transcript} "$newsubmitter is not a valid e-mail address; not changing submitter\n"; - $errors++; - } - else { - eval { - set_submitter(@common_control_options, - bug => $ref, - submitter => $newsubmitter, - ); - }; - if ($@) { - $errors++; - print {$transcript} "Failed to set submitter on $ref: ".cleanup_eval_fail($@,$debug)."\n"; - } - } - } elsif (m/^forwarded\s+\#?(-?\d+)\s+(\S.*\S)$/i) { - $ok++; - $ref= $1; - my $forward_to= $2; - $ref = $clonebugs{$ref} if exists $clonebugs{$ref}; - eval { - set_forwarded(@common_control_options, - bug => $ref, - forwarded => $forward_to, - ); - }; - if ($@) { - $errors++; - print {$transcript} "Failed to set the forwarded-to-address of $ref: ".cleanup_eval_fail($@,$debug)."\n"; - } - } elsif (m/^notforwarded\s+\#?(-?\d+)$/i) { - $ok++; - $ref= $1; - $ref = $clonebugs{$ref} if exists $clonebugs{$ref}; - eval { - set_forwarded(@common_control_options, - bug => $ref, - forwarded => undef, - ); - }; - if ($@) { - $errors++; - print {$transcript} "Failed to clear the forwarded-to-address of $ref: ".cleanup_eval_fail($@,$debug)."\n"; - } - } elsif (m/^(?:severity|priority)\s+\#?(-?\d+)\s+([-0-9a-z]+)$/i) { - $ok++; - $ref= $1; - $ref = $clonebugs{$ref} if exists $clonebugs{$ref}; - my $newseverity= $2; - if (exists $gObsoleteSeverities{$newseverity}) { - print {$transcript} "Severity level \`$newseverity' is obsolete. " . - "Use $gObsoleteSeverities{$newseverity} instead.\n\n"; - $errors++; - } elsif (not defined first {$_ eq $newseverity} - (@gSeverityList, "$gDefaultSeverity")) { - print {$transcript} "Severity level \`$newseverity' is not known.\n". - "Recognized are: $gShowSeverities.\n\n"; - $errors++; - } else { - eval { - set_severity(@common_control_options, - bug => $ref, - severity => $newseverity, - ); - }; - if ($@) { - $errors++; - print {$transcript} "Failed to set severity of $config{bug} $ref to $newseverity: ".cleanup_eval_fail($@,$debug)."\n"; - } - } - } elsif (m/^tags?\s+\#?(-?\d+)\s+(\S.*)$/i) { - $ok++; - $ref = $1; - $ref = $clonebugs{$ref} if exists $clonebugs{$ref}; - my $tags = $2; - my @tags = map {m/^([+=-])(.+)/ ? ($1,$2):($_)} split /[\s,]+/, $tags; - # this is an array of hashrefs which contain two elements, the - # first of which is the array of tags, the second is the - # option to pass to set_tag (we use a hashref here to make it - # more obvious what is happening) - my @tag_operations; - my @badtags; - for my $tag (@tags) { - if ($tag =~ /^[=+-]$/) { - if ($tag eq '=') { - @tag_operations = {tags => [], - option => [], - }; - } - elsif ($tag eq '-') { - push @tag_operations, - {tags => [], - option => [remove => 1], - }; - } - elsif ($tag eq '+') { - push @tag_operations, - {tags => [], - option => [add => 1], - }; - } - next; - } - if (not defined first {$_ eq $tag} @{$config{tags}}) { - push @badtags, $tag; - next; - } - if (not @tag_operations) { - @tag_operations = {tags => [], - option => [add => 1], - }; - } - push @{$tag_operations[-1]{tags}},$tag; - } - if (@badtags) { - print {$transcript} "Unknown tag/s: ".join(', ', @badtags).".\n". - "Recognized are: ".join(' ', @gTags).".\n\n"; - $errors++; - } - eval { - for my $operation (@tag_operations) { - set_tag(@common_control_options, - bug => $ref, - tag => [@{$operation->{tags}}], - warn_on_bad_tags => 0, # don't warn on bad tags, - # 'cause we do that above - @{$operation->{option}}, - ); - } - }; - if ($@) { - # we intentionally have two errors here if there is a bad - # tag and the above fails for some reason - $errors++; - print {$transcript} "Failed to alter tags of $config{bug} $ref: ".cleanup_eval_fail($@,$debug)."\n"; - } - } elsif (m/^(un)?block\s+\#?(-?\d+)\s+(?:by|with)\s+(\S.*)?$/i) { - $ok++; - $ref= $2; - my $add_remove = defined $1 && $1 eq 'un'; - my @blockers = map {exists $clonebugs{$_}?$clonebugs{$_}:$_} split /[\s,]+/, $3; - $ref = $clonebugs{$ref} if exists $clonebugs{$ref}; - eval { - set_blocks(@common_control_options, - bug => $ref, - block => \@blockers, - $add_remove ? (remove => 1):(add => 1), - ); - }; - if ($@) { - $errors++; - print {$transcript} "Failed to set blocking bugs of $ref: ".cleanup_eval_fail($@,$debug)."\n"; - } - } elsif (m/^retitle\s+\#?(-?\d+)\s+(\S.*\S)\s*$/i) { - $ok++; - $ref= $1; my $newtitle= $2; - $ref = $clonebugs{$ref} if exists $clonebugs{$ref}; - eval { - set_title(@common_control_options, - bug => $ref, - title => $newtitle, - ); - }; - if ($@) { - $errors++; - print {$transcript} "Failed to set the title of $ref: ".cleanup_eval_fail($@,$debug)."\n"; - } - } elsif (m/^unmerge\s+\#?(-?\d+)$/i) { - $ok++; - $ref= $1; - $ref = $clonebugs{$ref} if exists $clonebugs{$ref}; - eval { - set_merged(@common_control_options, - bug => $ref, - ); - }; - if ($@) { - $errors++; - print {$transcript} "Failed to unmerge $ref: ".cleanup_eval_fail($@,$debug)."\n"; - } - } elsif (m/^merge\s+#?(-?\d+(\s+#?-?\d+)+)\s*$/i) { - $ok++; - my @tomerge; - ($ref,@tomerge) = map {exists $clonebugs{$_}?$clonebugs{$_}:$_} - split(/\s+#?/,$1); - eval { - set_merged(@common_control_options, - bug => $ref, - merge_with => \@tomerge, - ); - }; - if ($@) { - $errors++; - print {$transcript} "Failed to merge $ref: ".cleanup_eval_fail($@,$debug)."\n"; - } - } elsif (m/^forcemerge\s+\#?(-?\d+(?:\s+\#?-?\d+)+)\s*$/i) { - $ok++; - my @tomerge; - ($ref,@tomerge) = map {exists $clonebugs{$_}?$clonebugs{$_}:$_} - split(/\s+#?/,$1); - eval { - set_merged(@common_control_options, - bug => $ref, - merge_with => \@tomerge, - force => 1, - masterbug => 1, - ); - }; - if ($@) { - $errors++; - print {$transcript} "Failed to forcibly merge $ref: ".cleanup_eval_fail($@,$debug)."\n"; - } - } elsif (m/^clone\s+#?(\d+)\s+((-\d+\s+)*-\d+)\s*$/i) { - $ok++; - - my $origref = $1; - my @newclonedids = split /\s+/, $2; - my $newbugsneeded = scalar(@newclonedids); - - $ref = $origref; - if (exists $clonebugs{$ref}) { - $ref = $clonebugs{$ref}; - } - $bug_affected{$ref} = 1; - eval { - my %new_clones; - clone_bug(@common_control_options, - bug => $ref, - new_bugs => \@newclonedids, - new_clones => \%new_clones, - ); - %clonebugs = (%clonebugs, - %new_clones); - }; - if ($@) { - $errors++; - print {$transcript} "Failed to clone $ref: ".cleanup_eval_fail($@,$debug)."\n"; - } - } elsif (m/^package\:?\s+(\S.*\S)?\s*$/i) { - $ok++; - my @pkgs = split /\s+/, $1; - if (scalar(@pkgs) > 0) { - %limit_pkgs = map { ($_, 1) } @pkgs; - $limit{package} = [@pkgs]; - print {$transcript} "Limiting to bugs with field 'package' containing at least one of ".join(', ',map {qq('$_')} @pkgs)."\n"; - print {$transcript} "Limit currently set to"; - for my $limit_field (keys %limit) { - print {$transcript} " '$limit_field':".join(', ',map {qq('$_')} @{$limit{$limit_field}})."\n"; - } - print {$transcript} "\n"; - } else { - %limit_pkgs = (); - $limit{package} = []; - print {$transcript} "Limit cleared.\n\n"; - } - } elsif (m/^limit\:?\s+(\S.*\S)\s*$/) { - $ok++; - my ($field,@options) = split /\s+/, $1; - $field = lc($field); - if ($field =~ /^(?:clear|unset|blank)$/) { - %limit = (); - print {$transcript} "Limit cleared.\n\n"; - } - elsif (exists $Debbugs::Status::fields{$field} or $field eq 'source') { - # %limit can actually contain regexes, but because they're - # not evaluated in Safe, DO NOT allow them through without - # fixing this. - $limit{$field} = [@options]; - print {$transcript} "Limiting to bugs with field '$field' containing at least one of ".join(', ',map {qq('$_')} @options)."\n"; - print {$transcript} "Limit currently set to"; - for my $limit_field (keys %limit) { - print {$transcript} " '$limit_field':".join(', ',map {qq('$_')} @{$limit{$limit_field}})."\n"; - } - print {$transcript} "\n"; - } - else { - print {$transcript} "Limit key $field not understood. Stopping processing here.\n\n"; - $errors++; + #### "developer only" ones start here + } elsif (defined valid_control($_)) { + my ($new_errors,$terminate_control) = + control_line(line => $_, + clonebugs => \%clonebugs, + limit => \%limit, + common_control_options => \@common_control_options, + errors => \$errors, + transcript => $transcript, + debug => $debug, + ok => \$ok, + ); + if ($terminate_control) { last; } - } elsif (m/^affects?\s+\#?(-?\d+)(?:\s+((?:[=+-])?)\s*(\S.*)?)?\s*$/i) { - $ok++; - $ref = $1; - my $add_remove = $2; - my $packages = $3; - # if there isn't a package given, assume that we should unset - # affects; otherwise default to adding - if (not defined $packages or - not length $packages) { - $packages = ''; - $add_remove ||= '='; - } - elsif (not defined $add_remove or - not length $add_remove) { - $add_remove = '+'; - } - $ref = $clonebugs{$ref} if exists $clonebugs{$ref}; - eval { - affects(@common_control_options, - bug => $ref, - package => [splitpackages($3)], - ($add_remove eq '+'?(add => 1):()), - ($add_remove eq '-'?(remove => 1):()), - ); - }; - if ($@) { - $errors++; - print {$transcript} "Failed to mark $ref as affecting package(s): ".cleanup_eval_fail($@,$debug)."\n"; - } - - } elsif (m/^summary\s+\#?(-?\d+)\s*(\d+|)\s*$/i) { - $ok++; - $ref = $1; - my $summary_msg = length($2)?$2:undef; - $ref = $clonebugs{$ref} if exists $clonebugs{$ref}; - eval { - summary(@common_control_options, - bug => $ref, - summary => $summary_msg, - ); - }; - if ($@) { - $errors++; - print {$transcript} "Failed to give $ref a summary: ".cleanup_eval_fail($@,$debug)."\n"; - } - - } elsif (m/^owner\s+\#?(-?\d+)\s+((?:\S.*\S)|\!)\s*$/i) { - $ok++; - $ref = $1; - $ref = $clonebugs{$ref} if exists $clonebugs{$ref}; - my $newowner = $2; - if ($newowner eq '!') { - $newowner = $replyto; - } - eval { - owner(@common_control_options, - bug => $ref, - owner => $newowner, - ); - }; - if ($@) { - $errors++; - print {$transcript} "Failed to mark $ref as having an owner: ".cleanup_eval_fail($@,$debug)."\n"; - } - } elsif (m/^noowner\s+\#?(-?\d+)\s*$/i) { - $ok++; - $ref = $1; - $ref = $clonebugs{$ref} if exists $clonebugs{$ref}; - eval { - owner(@common_control_options, - bug => $ref, - owner => undef, - ); - }; - if ($@) { - $errors++; - print {$transcript} "Failed to mark $ref as not having an owner: ".cleanup_eval_fail($@,$debug)."\n"; - } - } elsif (m/^unarchive\s+#?(\d+)$/i) { - $ok++; - $ref = $1; - $ref = $clonebugs{$ref} if exists $clonebugs{$ref}; - eval { - bug_unarchive(@common_control_options, - bug => $ref, - recipients => \%recipients, - ); - }; - if ($@) { - $errors++; - } - } elsif (m/^archive\s+#?(\d+)$/i) { - $ok++; - $ref = $1; - $ref = $clonebugs{$ref} if exists $clonebugs{$ref}; - eval { - bug_archive(@common_control_options, - bug => $ref, - ignore_time => 1, - archive_unarchived => 0, - ); - }; - if ($@) { - $errors++; - } } else { - print {$transcript} "Unknown command or malformed arguments to command.\n\n"; $errors++; if (++$unknowns >= 5) { print {$transcript} "Too many unknown commands, stopping here.\n\n"; @@ -1087,19 +499,6 @@ my @maintccs = determine_recipients(recipients => \%recipients, address_only => 1, cc => 1, ); -my $maintccs = 'Cc: '.join(",\n ", - determine_recipients(recipients => \%recipients, - cc => 1, - ) - )."\n"; - -my $packagepr = ''; -$packagepr = "X-${gProject}-PR-Package: " . join(keys %affected_packages) . "\n" if keys %affected_packages; - -# Add Bcc's to subscribed bugs -# now handled by Debbugs::Recipients -#push @bcc, map {"bugs=$_\@$gListDomain"} keys %bug_affected; - if (!defined $header{'subject'} || $header{'subject'} eq "") { $header{'subject'} = "your mail"; } @@ -1110,7 +509,7 @@ my $error_text = $errors > 0 ? " (with $errors errors)":''; my @common_headers; push @common_headers, 'X-Loop',$gMaintainerEmail; -my $temp_transcript = ${transcript_scalar}; +my $temp_transcript = $transcript_scalar; eval{ $temp_transcript = decode("utf8",$temp_transcript,Encode::FB_CROAK); }; @@ -1143,19 +542,6 @@ my $repliedshow= join(', ',$replyto, ) ); -# -1 is the service.in log -&filelock("lock/-1"); -open(AP,">>db-h/-1.log") || die "open db-h/-1.log: $!"; -print(AP - "\2\n$repliedshow\n\5\n$reply\n\3\n". - "\6\n". - "Request received from ". - html_escape($header{'from'})."\n". - "to ".html_escape($controlrequestaddr)."\n". - "\3\n". - "\7\n",escape_log(@log),"\n\3\n") || die "writing db-h/-1.log: $!"; -close(AP) || die "open db-h/-1.log: $!"; -&unfilelock; utime(time,time,"db-h"); &sendmailmessage($reply, diff --git a/t/14_control_at_submit.t b/t/14_control_at_submit.t new file mode 100644 index 0000000..7f0659f --- /dev/null +++ b/t/14_control_at_submit.t @@ -0,0 +1,353 @@ +# -*- mode: cperl;-*- +# $Id: 05_mail.t,v 1.1 2005/08/17 21:46:17 don Exp $ + +use Test::More tests => 118; + +use warnings; +use strict; + +# Here, we're going to shoot messages through a set of things that can +# happen. + +# First, we're going to send mesages to receive. +# To do so, we'll first send a message to submit, +# then send messages to the newly created bugnumber. + +use IO::File; +use File::Temp qw(tempdir); +use Cwd qw(getcwd); +use Debbugs::MIME qw(create_mime_message); +use File::Basename qw(dirname basename); +# The test functions are placed here to make things easier +use lib qw(t/lib); +use DebbugsTest qw(:all); +use Data::Dumper; +use Encode qw(decode encode); + +# HTTP::Server:::Simple defines a SIG{CHLD} handler that breaks system; undef it here. +$SIG{CHLD} = sub {}; +my %config; +eval { + %config = create_debbugs_configuration(debug => exists $ENV{DEBUG}?$ENV{DEBUG}:0); +}; +if ($@) { + BAIL_OUT($@); +} + +my $sendmail_dir = $config{sendmail_dir}; +my $spool_dir = $config{spool_dir}; +my $config_dir = $config{config_dir}; + +END{ + if ($ENV{DEBUG}) { + diag("spool_dir: $spool_dir\n"); + diag("config_dir: $config_dir\n"); + diag("sendmail_dir: $sendmail_dir\n"); + } +} + +# We're going to use create mime message to create these messages, and +# then just send them to receive. + +send_message(to=>'submit@bugs.something', + headers => [To => 'submit@bugs.something', + From => 'foo@bugs.something', + Subject => 'Submiting a bug', + ], + body => < '1@bugs.something', + headers => [To => '1@bugs.something', + From => 'foo@bugs.something', + Subject => 'Sending a message to a bug', + ], + body => < 'control@bugs.something', + headers => [To => 'control@bugs.something', + From => 'foo@bugs.something', + Subject => 'Munging a bug', + ], + body => <1); +ok($status->{subject} eq 'new title','bug 1 retitled'); +ok($status->{severity} eq 'wishlist','bug 1 wishlisted'); + +# now we're going to go through and methododically test all of the control commands. +my @control_commands = + ( + clone => {command => 'clone', + value => '-2', + status_key => 'package', + status_value => 'foo', + bug => '3', + }, + severity_wishlist => {command => 'severity', + value => 'wishlist', + status_key => 'severity', + status_value => 'wishlist', + }, + reassign_bar_baz => {command => 'reassign', + value => 'bar,baz', + status_key => 'package', + status_value => 'bar,baz', + }, + reassign_foo => {command => 'reassign', + value => 'foo', + status_key => 'package', + status_value => 'foo', + }, + 'found_1.0' => {command => 'found', + value => '1.0', + status_key => 'found_versions', + status_value => ['1.0'], + }, + 'notfound_1.0' => {command => 'notfound', + value => '1.0', + status_key => 'found_versions', + status_value => [], + }, + 'found_1.0~5+1b2' => {command => 'found', + value => '1.0~5+1b2', + status_key => 'found_versions', + status_value => ['1.0~5+1b2'], + }, + 'notfound_1.0~5+1b2' => {command => 'notfound', + value => '1.0~5+1b2', + status_key => 'found_versions', + status_value => [], + }, + 'fixed_1.1' => {command => 'fixed', + value => '1.1', + status_key => 'fixed_versions', + status_value => ['1.1'], + }, + 'notfixed_1.1' => {command => 'notfixed', + value => '1.1', + status_key => 'fixed_versions', + status_value => [], + }, + 'found_1.0~5+1b2' => {command => 'found', + value => '1.0~5+1b2', + status_key => 'found_versions', + status_value => ['1.0~5+1b2'], + }, + 'fixed_1.2' => {command => 'fixed', + value => '1.2', + status_key => 'fixed_versions', + status_value => ['1.2'], + }, + close => {command => 'close', + value => '', + status_key => 'done', + status_value => 'foo@bugs.something', + }, + 'found_1.3' => {command => 'found', + value => '1.3', + status_key => 'done', + status_value => '', + }, + submitter_foo => {command => 'submitter', + value => 'foo@bar.com', + status_key => 'originator', + status_value => 'foo@bar.com', + }, + + forwarded_foo => {command => 'forwarded', + value => 'foo@bar.com', + status_key => 'forwarded', + status_value => 'foo@bar.com', + }, + notforwarded => {command => 'notforwarded', + value => '', + status_key => 'forwarded', + status_value => '', + }, + owner_foo => {command => 'owner', + value => 'foo@bar.com', + status_key => 'owner', + status_value => 'foo@bar.com', + }, + noowner => {command => 'noowner', + value => '', + status_key => 'owner', + status_value => '', + }, + merge => {command => 'merge', + value => '1 3', + status_key => 'mergedwith', + status_value => '3', + }, + unmerge => {command => 'unmerge', + value => '', + status_key => 'mergedwith', + status_value => '', + }, + forcemerge => {command => 'forcemerge', + value => '3', + status_key => 'mergedwith', + status_value => '3', + }, + unmerge => {command => 'unmerge', + value => '', + status_key => 'mergedwith', + status_value => '', + }, + block => {command => 'block', + value => ' with 2', + status_key => 'blockedby', + status_value => '2', + }, + unblock => {command => 'unblock', + value => ' with 2', + status_key => 'blockedby', + status_value => '', + }, + summary => {command => 'summary', + value => '5', + status_key => 'summary', + status_value => 'This is a silly bug', + }, + nosummary => {command => 'summary', + value => '', + status_key => 'summary', + status_value => '', + }, + affects => {command => 'affects', + value => 'foo', + status_key => 'affects', + status_value => 'foo', + }, + noaffects => {command => 'affects', + value => '', + status_key => 'affects', + status_value => '', + }, + close => {command => 'close', + value => '', + status_key => 'done', + status_value => 'foo@bugs.something', + }, + archive => {command => 'archive', + value => '', + status_key => 'owner', + status_value => '', + location => 'archive', + }, + unarchive => {command => 'unarchive', + value => '', + status_key => 'owner', + status_value => '', + }, + tag => {command => 'tag', + value => ' = patch', + status_key => 'keywords', + status_value => 'patch', + }, + untag => {command => 'tag', + value => ' - patch', + status_key => 'keywords', + status_value => '', + }, + plustag => {command => 'tag', + value => ' + patch', + status_key => 'keywords', + status_value => 'patch', + }, + utf8_retitle => {command => 'retitle', + value => 'Thïs is a ütff8 title [♥♡☙☎]', + status_key => 'subject', + status_value => decode("utf8",'Thïs is a ütff8 title [♥♡☙☎]'), + }, + ); + +# In order for the archive/unarchive to work, we have to munge the summary file slightly +$status = read_bug(bug => 1); +$status->{unarchived} = time; +writebug(1,$status); +while (my ($command,$control_command) = splice(@control_commands,0,2)) { + # just check to see that control doesn't explode + $control_command->{value} = " $control_command->{value}" if length $control_command->{value} + and $control_command->{value} !~ /^\s/; + send_message(to => 'submit@bugs.something', + headers => [To => 'submit@bugs.something', + From => 'foo@bugs.something', + Subject => "Munging a bug with $command", + ], + body => <{command} 1$control_command->{value} +EOF + ; + $SD_SIZE = + num_messages_sent($SD_SIZE,1, + $sendmail_dir, + 'control@bugs.something messages appear to have been sent out properly'); + # now we need to check to make sure the control message was processed without errors + ok(system('sh','-c','find '.$sendmail_dir.q( -type f | xargs grep -q "Subject: Processed: Munging a bug with $command")) == 0, + 'control@bugs.something'. "$command message was parsed without errors"); + # now we need to check to make sure that the control message actually did anything + my $status; + $status = read_bug(exists $control_command->{bug}?(bug => $control_command->{bug}):(bug=>1), + exists $control_command->{location}?(location => $control_command->{location}):(), + ); + is_deeply($status->{$control_command->{status_key}}, + $control_command->{status_value}, + "bug " . + (exists $control_command->{bug}?$control_command->{bug}:1). + " $command" + ) + or fail(Dumper($status)); +} +