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),
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);
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);
+=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),
+ );
+ }
+ }
+}
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);
};
if ($@) {
$errors++;
- print {$transcript} $@;
print {$transcript} "Failed to forcibly merge $ref: ".cleanup_eval_fail($@,$debug)."\n";
}
} elsif (m/^clone\s+#?(\d+)\s+((-\d+\s+)*-\d+)\s*$/i) {
my $newbugsneeded = scalar(@newclonedids);
$ref = $origref;
+ if (exists $clonebugs{$ref}) {
+ $ref = $clonebugs{$ref};
+ }
$bug_affected{$ref} = 1;
- if (&setbug) {
- $affected_packages{$data->{package}} = 1;
- if (length($data->{mergedwith})) {
- print {$transcript} "$gBug is marked as being merged with others. Use an existing clone.\n\n";
- $errors++;
- &nochangebug;
- } else {
- &filelock("nextnumber.lock");
- open(N,"nextnumber") || die "nextnumber: read: $!";
- my $v=<N>; $v =~ s/\n$// || die "nextnumber bad format";
- my $firstref= $v+0; $v += $newbugsneeded;
- open(NN,">nextnumber"); print NN "$v\n"; close(NN);
- unfilelock();
-
- my $lastref = $firstref + $newbugsneeded - 1;
-
- if ($newbugsneeded == 1) {
- $action= "$gBug $origref cloned as bug $firstref.";
- } else {
- $action= "$gBug $origref cloned as bugs $firstref-$lastref.";
- }
-
- my $blocks = $data->{blocks};
- my $blockedby = $data->{blockedby};
-
- &getnextbug;
- my $ohash = get_hashname($origref);
- my $clone = $firstref;
- @bug_affected{@newclonedids} = 1 x @newclonedids;
- for my $newclonedid (@newclonedids) {
- $clonebugs{$newclonedid} = $clone;
-
- my $hash = get_hashname($clone);
- copy("db-h/$ohash/$origref.log", "db-h/$hash/$clone.log");
- copy("db-h/$ohash/$origref.status", "db-h/$hash/$clone.status");
- copy("db-h/$ohash/$origref.summary", "db-h/$hash/$clone.summary");
- copy("db-h/$ohash/$origref.report", "db-h/$hash/$clone.report");
- &bughook('new', $clone, $data);
-
- # Update blocking info of bugs blocked by or blocking the
- # cloned bug.
- foreach $ref (split ' ', $blocks) {
- &getbug;
- $data->{blockedby} = manipset($data->{blockedby}, $clone, 1);
- &savebug;
- }
- foreach $ref (split ' ', $blockedby) {
- &getbug;
- $data->{blocks} = manipset($data->{blocks}, $clone, 1);
- &savebug;
- }
-
- $clone++;
- }
- }
+ 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++;