clone => [qw(clone_bug)],
archive => [qw(bug_archive bug_unarchive),
],
+ limit => [qw(check_limit)],
log => [qw(append_action_to_log),
],
);
$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,
)) {
}
}
}
- if (not __check_limit(data => \@data,
+ if (not check_limit(data => \@data,
exists $param{limit}?(limit => $param{limit}):(),
transcript => $transcript,
)) {
}
-=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
=cut
-sub __check_limit{
+sub check_limit{
my %param = validate_with(params => \@_,
spec => {data => {type => ARRAYREF|SCALAR,
},
my $hole_var = {'&bugurl' =>
sub{"$_[0]: ".
'http://'.$config{cgi_domain}.'/'.
- Debbugs::CGI::bug_url($_[0]);
+ Debbugs::CGI::bug_links(bug => $_[0],
+ links_only => 1,
+ );
}
};
--- /dev/null
+# 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 <don@donarmstrong.com>.
+
+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<append_log> (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__
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;
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);
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=<M>;
-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= <<END;
-Received: via spool by $baddress\@$gEmailDomain id=$nn
- (code $codeletter ref $tryref); $tdate
-END
+my $fwd= "Received: via spool by $baddress\@$gEmailDomain id=$nn\n".
+ " (code $codeletter".(defined($tryref)?" ref $tryref":'')."); $tdate\n";
# header and decoded body respectively
my (@headerlines, @bodylines);
our $brokenness = '';
-my $parser = new MIME::Parser;
-mkdir "$gSpoolDir/mime.tmp", 0777;
-$parser->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;
$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'};
#psuedoheaders
my %pheader;
+my @control_bits;
# extract pseudo-headers
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]
$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'};
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}) {
messageid => $header{'message-id'},
},
)),'');
- &appendlog;
- &finish;
+ appendlog($ref,$msg);
+ finish();
}
} else {
&filelock('lock/-1');
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'};
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';
messageid => $header{'message-id'},
},
)),'');
- &appendlog;
- &finish;
+ appendlog($ref,$msg);
+ finish();
}
&checkmaintainers;
writebug($ref, $data);
my $hash = get_hashname($ref);
- open(O,"db-h/$hash/$ref.report") || die "read original report: $!";
- my $orig_report= join('',<O>); 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(
),
[join("\n",@msg),$orig_report]),'',undef,1);
}
- &appendlog;
+ appendlog($ref,$msg);
}
- &finish;
+ finish();
}
if ($ref<0) { # new bug report
messageid => $header{'message-id'},
},
)),'');
- &appendlog;
- &finish;
+ appendlog($ref,$msg);
+ finish();
}
$data->{found_versions} = [];
baddress => $baddress,
},
),[join("\n", @msg)]), '',undef,1);
- &appendlog;
- &finish;
+ appendlog($ref,$msg);
+ finish();
}
if (defined $config{default_package}) {
&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*//;
],$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' => "<handler.s.$nn.transcript\@$gEmailDomain>",
+ '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 {
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' =>
# 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,
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;
}
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);
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,
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);
my %bcc = ();
-my @bcc;
-sub addbcc {
- push @bcc, $_[0] unless grep { $_ eq $_[0] } @bcc;
-}
-
our $data;
our $message;
our $extramessage;
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 = ();
(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";
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";
}
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);
};
)
);
-# -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".
- "<strong>Request received</strong> from <code>".
- html_escape($header{'from'})."</code>\n".
- "to <code>".html_escape($controlrequestaddr)."</code>\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,
--- /dev/null
+# -*- 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 => <<EOF) or fail('Unable to send message');
+Package: foo
+Severity: normal
+
+This is a silly bug
+EOF
+
+# now we check to see that we have a bug, and nextnumber has been incremented
+ok(-e "$spool_dir/db-h/01/1.log",'log file created');
+ok(-e "$spool_dir/db-h/01/1.summary",'sumary file created');
+ok(-e "$spool_dir/db-h/01/1.status",'status file created');
+ok(-e "$spool_dir/db-h/01/1.report",'report file created');
+
+# next, we check to see that (at least) the proper messages have been
+# sent out. 1) ack to submitter 2) mail to maintainer
+
+# This keeps track of the previous size of the sendmail directory
+my $SD_SIZE = 0;
+$SD_SIZE =
+ num_messages_sent($SD_SIZE,2,
+ $sendmail_dir,
+ 'submit messages appear to have been sent out properly',
+ );
+
+
+# now send a message to the bug
+
+send_message(to => '1@bugs.something',
+ headers => [To => '1@bugs.something',
+ From => 'foo@bugs.something',
+ Subject => 'Sending a message to a bug',
+ ],
+ body => <<EOF) or fail('sending message to 1@bugs.someting failed');
+Package: foo
+Severity: normal
+
+This is a silly bug
+EOF
+
+$SD_SIZE =
+ num_messages_sent($SD_SIZE,2,
+ $sendmail_dir,
+ '1@bugs.something messages appear to have been sent out properly');
+
+# just check to see that control doesn't explode
+send_message(to => 'control@bugs.something',
+ headers => [To => 'control@bugs.something',
+ From => 'foo@bugs.something',
+ Subject => 'Munging a bug',
+ ],
+ body => <<EOF) or fail 'message to control@bugs.something failed';
+severity 1 wishlist
+retitle 1 new title
+thanks
+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")) == 0,
+ 'control@bugs.something message was parsed without errors');
+# now we need to check to make sure that the control message actually did anything
+# This is an eval because $ENV{DEBBUGS_CONFIG_FILE} isn't set at BEGIN{} time
+eval "use Debbugs::Status qw(read_bug writebug);";
+my $status = read_bug(bug=>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 => <<EOF) or fail 'message to control@bugs.something failed';
+Package: foo
+Control: $control_command->{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));
+}
+