]> git.donarmstrong.com Git - debbugs.git/blobdiff - Debbugs/Control.pm
output to transcript even in cases of internal requests
[debbugs.git] / Debbugs / Control.pm
index e5a1dd24b2d0ce8e05d2dbec026574dba02d3b96..0ef02599c0b40dfe690cb66c88208f83ee2b0bf5 100644 (file)
@@ -96,6 +96,7 @@ BEGIN{
                     block   => [qw(set_blocks)],
                     merge   => [qw(set_merged)],
                     tag     => [qw(set_tag)],
+                    clone   => [qw(clone_bug)],
                     archive => [qw(bug_archive bug_unarchive),
                                ],
                     log     => [qw(append_action_to_log),
@@ -108,7 +109,7 @@ BEGIN{
 
 use Debbugs::Config qw(:config);
 use Debbugs::Common qw(:lock buglog :misc get_hashname sort_versions);
-use Debbugs::Status qw(bug_archiveable :read :hook writebug splitpackages split_status_fields get_bug_status);
+use Debbugs::Status qw(bug_archiveable :read :hook writebug new_bug splitpackages split_status_fields get_bug_status);
 use Debbugs::CGI qw(html_escape);
 use Debbugs::Log qw(:misc :write);
 use Debbugs::Recipients qw(:add);
@@ -117,6 +118,7 @@ use Debbugs::Packages qw(:versions :mapping);
 use Data::Dumper qw();
 use Params::Validate qw(validate_with :types);
 use File::Path qw(mkpath);
+use File::Copy qw(copy);
 use IO::File;
 
 use Debbugs::Text qw(:templates);
@@ -457,8 +459,7 @@ sub set_blocks {
        push @changed, 'removed blocking bug(s) of '.$data->{bug_num}.': '.english_join([keys %removed_blockers]) if keys %removed_blockers;
        $action = ucfirst(join ('; ',@changed)) if @changed;
        if (not @changed) {
-           print {$transcript} "Ignoring request to alter blocking bugs of bug #$data->{bug_num} to the same blocks previously set\n"
-               unless __internal_request();
+           print {$transcript} "Ignoring request to alter blocking bugs of bug #$data->{bug_num} to the same blocks previously set\n";
            next;
        }
        $data->{blockedby} = join(' ',keys %blockers);
@@ -694,8 +695,7 @@ sub set_tag {
        push @changed, 'removed tag(s) '.english_join([keys %tag_removed]) if keys %tag_removed;
        $action = ucfirst(join ('; ',@changed)) if @changed;
        if (not @changed) {
-           print {$transcript} "Ignoring request to alter tags of bug #$data->{bug_num} to the same tags previously set\n"
-               unless __internal_request();
+           print {$transcript} "Ignoring request to alter tags of bug #$data->{bug_num} to the same tags previously set\n";
            next;
        }
        $action .= '.';
@@ -897,6 +897,24 @@ sub set_done {
                $warn_fixed = 0;
            }
        }
+       $action = "Bug reopened";
+       for my $data (@data) {
+           my $old_data = dclone($data);
+           $data->{done} = '';
+           append_action_to_log(bug => $data->{bug_num},
+                                command => 'done',
+                                new_data => $data,
+                                old_data => $old_data,
+                                get_lock => 0,
+                                __return_append_to_log_options(
+                                                               %param,
+                                                               action => $action,
+                                                              ),
+                               )
+               if not exists $param{append_log} or $param{append_log};
+           writebug($data->{bug_num},$data);
+       }
+       print {$transcript} "$action\n";
        __end_control(%info);
        if (exists $param{submitter}) {
            set_submitter(bug => $param{bug},
@@ -921,6 +939,15 @@ sub set_done {
        my %submitter_notified;
        my $requester_notified = 0;
        my $orig_report_set = 0;
+       for my $data (@data) {
+           if (exists $data->{done} and
+               defined $data->{done} and
+               length $data->{done}) {
+               print {$transcript} "Bug $data->{bug_num} is already marked as done; not doing anything.\n";
+               __end_control(%info);
+               return;
+           }
+       }
        for my $data (@data) {
            my $old_data = dclone($data);
            my $hash = get_hashname($data->{bug_num});
@@ -939,13 +966,6 @@ sub set_done {
                $orig_report_set = 1;
            }
 
-           if (exists $data->{done} and
-               defined $data->{done} and
-               length $data->{done}) {
-               print {$transcript} "Bug $data->{bug_num} is already marked as done; not doing anything.\n";
-               __end_control(%info);
-               return;
-           }
            $action = "Marked $config{bug} as done";
 
            # set done to the requester
@@ -962,6 +982,7 @@ sub set_done {
                                )
                if not exists $param{append_log} or $param{append_log};
            writebug($data->{bug_num},$data);
+           print {$transcript} "$action\n";
            # get the original report
            if ($param{notify_submitter}) {
                my $submitter_message;
@@ -1010,6 +1031,7 @@ sub set_done {
                                    );
            }
        }
+       __end_control(%info);
        if (exists $param{fixed}) {
            set_fixed(fixed => $param{fixed},
                      bug => $param{bug},
@@ -1085,8 +1107,7 @@ sub set_submitter {
              (not defined $data->{originator} or not length $data->{originator})) or
             (defined $param{submitter} and defined $data->{originator} and
              $param{submitter} eq $data->{originator})) {
-           print {$transcript} "Ignoring request to change the submitter of bug#$data->{bug_num} to the same value\n"
-               unless __internal_request();
+           print {$transcript} "Ignoring request to change the submitter of bug#$data->{bug_num} to the same value\n";
            next;
        }
        else {
@@ -1198,8 +1219,7 @@ sub set_forwarded {
        if (__all_undef_or_equal($param{forwarded},$data->{forwarded}) or
            (not defined $param{forwarded} and
             defined $data->{forwarded} and not length $data->{forwarded})) {
-           print {$transcript} "Ignoring request to change the forwarded-to-address of bug#$data->{bug_num} to the same value\n"
-               unless __internal_request();
+           print {$transcript} "Ignoring request to change the forwarded-to-address of bug#$data->{bug_num} to the same value\n";
            next;
        }
        else {
@@ -1287,8 +1307,7 @@ sub set_title {
        print {$debug} "Going to change bug title\n";
        if (defined $data->{subject} and length($data->{subject}) and
            $data->{subject} eq $param{title}) {
-           print {$transcript} "Ignoring request to change the title of bug#$data->{bug_num} to the same title\n"
-               unless __internal_request();
+           print {$transcript} "Ignoring request to change the title of bug#$data->{bug_num} to the same title\n";
            next;
        }
        else {
@@ -1392,8 +1411,7 @@ sub set_package {
        print {$debug} "Going to change assigned package\n";
        if (defined $data->{package} and length($data->{package}) and
            $data->{package} eq $new_package) {
-           print {$transcript} "Ignoring request to reassign bug #$data->{bug_num} to the same package\n"
-               unless __internal_request();
+           print {$transcript} "Ignoring request to reassign bug #$data->{bug_num} to the same package\n";
            next;
        }
        else {
@@ -1557,6 +1575,12 @@ sub set_found {
                if (not @svers) {
                    @svers = $version;
                }
+               else {
+                   if (exists $found_versions{$version}) {
+                       delete $found_versions{$version};
+                       $found_removed{$version} = 1;
+                   }
+               }
                for my $sver (@svers) {
                    if (not exists $found_versions{$sver}) {
                        $found_versions{$sver} = 1;
@@ -1625,8 +1649,7 @@ sub set_found {
            $action .= " and reopened"
        }
        if (not $reopened and not @changed) {
-           print {$transcript} "Ignoring request to alter found versions of bug #$data->{bug_num} to the same values previously set\n"
-               unless __internal_request();
+           print {$transcript} "Ignoring request to alter found versions of bug #$data->{bug_num} to the same values previously set\n";
            next;
        }
        $action .= '.';
@@ -1769,6 +1792,12 @@ sub set_fixed {
                if (not @svers) {
                    @svers = $version;
                }
+               else {
+                   if (exists $fixed_versions{$version}) {
+                       $fixed_removed{$version} = 1;
+                       delete $fixed_versions{$version};
+                   }
+               }
                for my $sver (@svers) {
                    if (not exists $fixed_versions{$sver}) {
                        $fixed_versions{$sver} = 1;
@@ -1834,8 +1863,7 @@ sub set_fixed {
            $action .= " and reopened"
        }
        if (not $reopened and not @changed) {
-           print {$transcript} "Ignoring request to alter fixed versions of bug #$data->{bug_num} to the same values previously set\n"
-               unless __internal_request();
+           print {$transcript} "Ignoring request to alter fixed versions of bug #$data->{bug_num} to the same values previously set\n";
            next;
        }
        $action .= '.';
@@ -2051,7 +2079,7 @@ sub set_merged {
            # figure out the problems
            print {$transcript} "Unable to merge bugs because:\n";
            for my $change (@{$disallowed_changes}) {
-               print {$transcript} "$change->{field} of #$change->{bug} is '$change->{orig_value}' not '$change->{value}'\n";
+               print {$transcript} "$change->{field} of #$change->{bug} is '$change->{text_orig_value}' not '$change->{text_value}'\n";
            }
            if ($attempts > 0) {
                croak "Some bugs were altered while attempting to merge";
@@ -2115,8 +2143,8 @@ sub set_merged {
        $locks += $n_locks;
        %data = %{$data};
        @data = values %data;
-       ($merge_status,$bugs_to_merge) =
-           __calculate_merge_status(\@data,\%data,$param{bug});
+       ($merge_status,$bugs_to_merge) =
+           __calculate_merge_status(\@data,\%data,$param{bug},$merge_status);
        ($disallowed_changes,$changes) = 
            __calculate_merge_changes(\@data,$merge_status,\%param);
        $attempts = max(values %bug_changed);
@@ -2167,8 +2195,9 @@ sub set_merged {
 sub __allow_relocking{
     my ($locks,$bugs) = @_;
 
-    for my $bug (@{$bugs}) {
-       my @lockfiles = grep {m{/\Q$bug\E$}} keys %{$locks->{locks}};
+    my @locks = (@{$bugs},'merge');
+    for my $lock (@locks) {
+       my @lockfiles = grep {m{/\Q$lock\E$}} keys %{$locks->{locks}};
        next unless @lockfiles;
        $locks->{relockable}{$lockfiles[0]} = 0;
     }
@@ -2249,8 +2278,8 @@ sub __lock_and_load_merged_bugs{
 
 
 sub __calculate_merge_status{
-    my ($data_a,$data_h,$master_bug,$merge) = @_;
-    my %merge_status;
+    my ($data_a,$data_h,$master_bug,$merge_status) = @_;
+    my %merge_status = %{$merge_status // {}};
     my %merged_bugs;
     my $bugs_to_merge = 0;
     for my $data (@{$data_a}) {
@@ -2268,9 +2297,9 @@ sub __calculate_merge_status{
                $merge_status{$_} = $data->{$_}
            }
        }
-       if (not $merge) {
-           next unless $data->{bug_num} == $master_bug;
-       }
+       if (defined $merge_status) {
+           next unless $data->{bug_num} == $master_bug;
+       }
        $merge_status{tag} = {} if not exists $merge_status{tag};
        for my $tag (split /\s+/, $data->{keywords}) {
            $merge_status{tag}{$tag} = 1;
@@ -2349,10 +2378,12 @@ sub __calculate_merge_changes{
                           },
             fixed_versions => {func => \&set_fixed,
                                key => 'fixed',
+                               modify_value => sub {(defined $_[0] and ref($_[0]) eq 'HASH')?[sort keys %{$_[0]}]:$_[0]},
                                allowed => 1,
                               },
             found_versions => {func => \&set_found,
                                key   => 'found',
+                               modify_value => sub {(defined $_[0] and ref($_[0]) eq 'HASH')?[sort keys %{$_[0]}]:$_[0]},
                                allowed => 1,
                               },
            );
@@ -2382,10 +2413,12 @@ sub __calculate_merge_changes{
                 function => $force_functions{$field}{func},
                 key      => $force_functions{$field}{key},
                 options  => $force_functions{$field}{options},
-                allowed  => exists $force_functions{$field}{allowed} ? 0 : $force_functions{$field}{allowed},
+                allowed  => exists $force_functions{$field}{allowed} ? $force_functions{$field}{allowed} : 0,
                };
-           if ($param->{force}) {
-               if ($field ne 'package') {
+           $change->{text_value} = ref($change->{func_value}) eq 'ARRAY'?join(' ',@{$change->{func_value}}):$change->{func_value};
+           $change->{text_orig_value} = ref($change->{orig_value}) eq 'ARRAY'?join(' ',@{$change->{orig_value}}):$change->{orig_value};
+           if ($param->{force} or $change->{allowed}) {
+               if ($field ne 'package' or $change->{allowed}) {
                    push @{$changes{$data->{bug_num}}},$change;
                    next;
                }
@@ -2539,8 +2572,7 @@ sub affects {
              }
         }
        if (not length $action) {
-           print {$transcript} "Ignoring request to set affects of bug $data->{bug_num} to the same value previously set\n"
-               unless __internal_request();
+           print {$transcript} "Ignoring request to set affects of bug $data->{bug_num} to the same value previously set\n";
            next;
        }
         my $old_data = dclone($data);
@@ -2701,8 +2733,7 @@ sub summary {
         if (((not defined $summary or not length $summary) and
              (not defined $data->{summary} or not length $data->{summary})) or
             $summary eq $data->{summary}) {
-            print {$transcript} "Ignoring request to change the summary of bug $param{bug} to the same value\n"
-                unless __internal_request();
+            print {$transcript} "Ignoring request to change the summary of bug $param{bug} to the same value\n";
             next;
         }
         if (length $summary) {
@@ -2734,6 +2765,138 @@ sub summary {
 
 
 
+=head2 clone_bug
+
+     eval {
+           clone_bug(bug          => $ref,
+                     transcript   => $transcript,
+                     ($dl > 0 ? (debug => $transcript):()),
+                     requester    => $header{from},
+                     request_addr => $controlrequestaddr,
+                     message      => \@log,
+                      affected_packages => \%affected_packages,
+                     recipients   => \%recipients,
+                    );
+       };
+       if ($@) {
+           $errors++;
+           print {$transcript} "Failed to clone bug $ref bar: $@";
+       }
+
+Clones the given bug.
+
+We currently don't support cloning merged bugs, but this could be
+handled by internally unmerging, cloning, then remerging the bugs.
+
+=cut
+
+sub clone_bug {
+    my %param = validate_with(params => \@_,
+                             spec   => {bug => {type   => SCALAR,
+                                                regex  => qr/^\d+$/,
+                                               },
+                                        new_bugs => {type => ARRAYREF,
+                                                    },
+                                        new_clones => {type => HASHREF,
+                                                       default => {},
+                                                      },
+                                        %common_options,
+                                        %append_action_options,
+                                       },
+                            );
+    my %info =
+       __begin_control(%param,
+                       command  => 'clone'
+                      );
+    my ($debug,$transcript) =
+       @info{qw(debug transcript)};
+    my @data = @{$info{data}};
+    my @bugs = @{$info{bugs}};
+
+    my $action = '';
+    for my $data (@data) {
+       if (length($data->{mergedwith})) {
+           die "Bug is marked as being merged with others. Use an existing clone.\n";
+       }
+    }
+    if (@data != 1) {
+       die "Not exactly one bug‽ This shouldn't happen.";
+    }
+    my $data = $data[0];
+    my %clones;
+    for my $newclone_id (@{$param{new_bugs}}) {
+       my $new_bug_num = new_bug(copy => $data->{bug_num});
+       $param{new_clones}{$newclone_id} = $new_bug_num;
+       $clones{$newclone_id} = $new_bug_num;
+    }
+    my @new_bugs = sort values %clones;
+    my @collapsed_ids;
+    for my $new_bug (@new_bugs) {
+       # no collapsed ids or the higher collapsed id is not one less
+       # than the next highest new bug
+       if (not @collapsed_ids or 
+           $collapsed_ids[-1][1]+1 != $new_bug) {
+           push @collapsed_ids,[$new_bug,$new_bug];
+       }
+       else {
+           $collapsed_ids[-1][1] = $new_bug;
+       }
+    }
+    my @collapsed;
+    for my $ci (@collapsed_ids) {
+       if ($ci->[0] == $ci->[1]) {
+           push @collapsed,$ci->[0];
+       }
+       else {
+           push @collapsed,$ci->[0].'-'.$ci->[1]
+       }
+    }
+    my $collapsed_str = english_join(\@collapsed);
+    $action = "Bug $data->{bug_num} cloned as bug".(@new_bugs > 1?'s':'').' '.$collapsed_str;
+    for my $new_bug (@new_bugs) {
+       append_action_to_log(bug => $new_bug,
+                            get_lock => 1,
+                            __return_append_to_log_options(
+                                                           %param,
+                                                           action => $action,
+                                                          ),
+                           )
+           if not exists $param{append_log} or $param{append_log};
+    }
+    append_action_to_log(bug => $data->{bug_num},
+                        get_lock => 0,
+                        __return_append_to_log_options(
+                                                       %param,
+                                                       action => $action,
+                                                      ),
+                       )
+       if not exists $param{append_log} or $param{append_log};
+    writebug($data->{bug_num},$data);
+    print {$transcript} "$action\n";
+    __end_control(%info);
+    # bugs that this bug is blocking are also blocked by the new clone(s)
+    for my $bug (split ' ', $data->{blocks}) {
+       for my $new_bug (@new_bugs) {
+           set_blocks(bug => $new_bug,
+                      blocks => $bug,
+                      hash_slice(%param,
+                                 keys %common_options,
+                                 keys %append_action_options),
+                     );
+       }
+    }
+    # bugs that this bug is blocked by are also blocking the new clone(s)
+    for my $bug (split ' ', $data->{blockedby}) {
+       for my $new_bug (@new_bugs) {
+           set_blocks(bug => $bug,
+                      blocks => $new_bug,
+                      hash_slice(%param,
+                                 keys %common_options,
+                                 keys %append_action_options),
+                     );
+       }
+    }
+}
 
 
 
@@ -2787,8 +2950,7 @@ sub owner {
          print {$debug} "Owner is currently '$data->{owner}' for bug $data->{bug_num}\n";
          if (not defined $param{owner} or not length $param{owner}) {
              if (not defined $data->{owner} or not length $data->{owner}) {
-                 print {$transcript} "Ignoring request to unset the owner of bug #$data->{bug_num} which was not set\n"
-                     unless __internal_request();
+                 print {$transcript} "Ignoring request to unset the owner of bug #$data->{bug_num} which was not set\n";
                  next;
              }
              $param{owner} = '';
@@ -2899,7 +3061,6 @@ sub bug_archive {
          print {$transcript} "Bug $param{bug} cannot be archived\n";
          die "Bug $param{bug} cannot be archived";
      }
-     print {$debug} "$param{bug} considering\n";
      if (not $param{archive_unarchived} and
         not exists $data[0]{unarchived}
        ) {
@@ -2955,7 +3116,7 @@ sub bug_archive {
               print {$transcript} "archived $bug to archive/$dir (from $param{bug})\n";
          }
          unlink(map {"$config{spool_dir}/db-h/$dir/$_"} @files_to_remove);
-         print {$transcript} "deleted $bug (from $param{bug})\n";
+         print {$debug} "deleted $bug (from $param{bug})\n";
      }
      bughook_archive(@bugs);
      __end_control(%info);
@@ -3413,7 +3574,8 @@ sub __begin_control {
                             );
     my $new_locks;
     my ($debug,$transcript) = __handle_debug_transcript(@_);
-    print {$debug} "$param{bug} considering\n";
+    print {$debug} "considering bug $param{bug} for ".(exists $param{command}?$param{command}:scalar caller())."\n";
+#    print {$debug} Data::Dumper->Dump([[caller(1)],\%param],[qw(caller param)])."\n";
     $lockhash = $param{locks} if exists $param{locks};
     my @data = ();
     my $old_die = $SIG{__DIE__};