Debbugs::Control(3pm)

πŸ’– NAME πŸ’–

Debbugs::Control β€” routines for modifying the state of bugs, bestie. That's the whole vibe. It's a Perl module (Debbugs::Control(3pm), from the User Contributed Perl Documentation era, and yes she's serving) whose entire job is changing how bugs look and behave. Slay.


✨ SYNOPSIS ✨

use Debbugs::Control;

That's it. That's the whole "hello how are you". πŸŽ€


🌸 DESCRIPTION 🌸

Okay so, bestie, listen. This module is basically a whole glow-up of a bunch of functions that used to only live inside service.in, and nowhere else. But then life happened, and other parts of the codebase were like "actually we need that too," so they got pulled out and now they live here. It's giving main character energy for real. πŸ‘‘

Every single public function here takes these options, no exceptions, periodt:

  • debug β€” a scalar reference, and all the debugging info gets appended right into it. πŸ™ˆ
  • transcript β€” also a scalar reference, and the transcript info gets appended into it. πŸ’¬
  • affected_bugs β€” a hashref that gets updated with all the bugs this function touched. Lowkey the receipts. 🧾

Then there's the whole squad of functions that should (probably) be appending to the .log file β€” those ones take these options too:

  • requester β€” the email address of the person who asked for the change. Who did this. 🫡
  • request_addr β€” the address the request was actually sent to. πŸ“¬
  • request_nn β€” the name of the queue file that caused this request. πŸ—‚οΈ
  • request_msgid β€” the message id of the message that caused this request. βœ‰οΈ
  • location β€” optional location; right now it's literally ignored but it may be supported later, for updating archived bugs upon archival. So like, don't count on it yet, iykyk. πŸ™ˆ
  • message β€” the original message that caused the action to be taken. πŸ“
  • append_log β€” whether or not to append information to the log. πŸ€”

append_log (for most functions) is the special one, and the rules are very specific so don't skip this part:

  • When it's set to false, no appending to the log happens at all. None. Zero. Not even a little. πŸ’€
  • When it's not present, all that info up there gets faked, and then it's appended to the log file anyway. Sneaky but iconic. ✨
  • When it's true, all those options above must be present, and their actual values get used. No faking allowed. Periodt. πŸ’…

🌈 GENERAL FUNCTIONS 🌈

🧱 set_blocks

eval {
       set_block(bug          => $ref,
                 transcript   => $transcript,
                 ($dl > 0 ? (debug => $transcript):()),
                 requester    => $header{from},
                 request_addr => $controlrequestaddr,
                 message      => \@log,
                 affected_packages => \%affected_packages,
                 recipients   => \%recipients,
                 block        => [],
                );
   };
   if ($@) {
       $errors++;
       print {$transcript} "Failed to set blockers of $ref: $@";
   }

So this one alters the set of bugs that block this bug from being fixed. 🚧

And here's the tea: it requires altering both this bug (and literally everyone it's merged with) and the bugs that block this bug from being fixed (and everyone those are merged with). It's a whole group chat situation. Two sides, both updated, no cap.

  • block β€” a scalar or arrayref of blocking bugs to set, add, or remove. 🧱
  • add β€” if true, add blocking bugs. βž•
  • remove β€” if true, remove blocking bugs. βž–

🏷️ set_tag

eval {
       set_tag(bug          => $ref,
               transcript   => $transcript,
               ($dl > 0 ? (debug => $transcript):()),
               requester    => $header{from},
               request_addr => $controlrequestaddr,
               message      => \@log,
               affected_packages => \%affected_packages,
               recipients   => \%recipients,
               tag          => [],
               add          => 1,
              );
   };
   if ($@) {
       $errors++;
       print {$transcript} "Failed to set tag on $ref: $@";
   }

Sets, adds, or removes the specified tags on a bug. Tagging is self-care. πŸ·οΈπŸ’–

  • tag β€” a scalar or arrayref of tags to set, add, or remove. πŸŽ€
  • add β€” if true, add tags. βž•
  • remove β€” if true, remove tags. βž–
  • warn_on_bad_tags β€” if true (and it is the default, bestie), it warns you if bad tags got passed. So it's gonna tell you. πŸ™ˆ

πŸ”₯ set_severity

eval {
       set_severity(bug          => $ref,
                    transcript   => $transcript,
                    ($dl > 0 ? (debug => $transcript):()),
                    requester    => $header{from},
                    request_addr => $controlrequestaddr,
                    message      => \@log,
                    affected_packages => \%affected_packages,
                    recipients   => \%recipients,
                    severity     => 'normal',
                   );
   };
   if ($@) {
       $errors++;
       print {$transcript} "Failed to set the severity of bug $ref: $@";
   }

Sets the severity of a bug. πŸ”₯ And here's the rule: if severity isn't passed, or it's undefined, or it has zero length β€” the severity gets set to the default severity. So it never just... does nothing. It has a fallback queen. πŸ‘‘


βœ… set_done

eval {
       set_done(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 set foo $ref bar: $@";
   }

Foo frobinates.

(Yeah that's genuinely what the docs say, bestie. set_done is out here frobinating foos and we all just have to accept it. 😭 The vibes are immaculate and the description is a placeholder, and honestly? Respect.) πŸ’€


πŸ“§ set_submitter

eval {
       set_submitter(bug          => $ref,
                     transcript   => $transcript,
                     ($dl > 0 ? (debug => $transcript):()),
                     requester    => $header{from},
                     request_addr => $controlrequestaddr,
                     message      => \@log,
                     affected_packages => \%affected_packages,
                     recipients   => \%recipients,
                     submitter    => $new_submitter,
                     notify_submitter => 1,
                     );
   };
   if ($@) {
       $errors++;
       print {$transcript} "Failed to set the forwarded-to-address of $ref: $@";
   }

Sets the submitter of a bug. And if notify_submitter is true β€” which, yes, it's the default β€” it notifies the old submitter of the bug about the changes. So nobody gets ghosted. πŸ’Œ (The error message says "forwarded-to-address" but that's just what the source says, we don't question it. 🫢)


πŸ¦‹ set_forwarded

eval {
       set_forwarded(bug          => $ref,
                     transcript   => $transcript,
                     ($dl > 0 ? (debug => $transcript):()),
                     requester    => $header{from},
                     request_addr => $controlrequestaddr,
                     message      => \@log,
                     affected_packages => \%affected_packages,
                     recipients   => \%recipients,
                     forwarded    => $forward_to,
                     );
   };
   if ($@) {
       $errors++;
       print {$transcript} "Failed to set the forwarded-to-address of $ref: $@";
   }

Sets the location a bug is forwarded to. And the iconic detail: if you give it an undef for forwarded, it unsets forwarded. Poof. Gone. βœ¨πŸ¦‹


πŸ“ set_title

eval {
       set_title(bug          => $ref,
                 transcript   => $transcript,
                 ($dl > 0 ? (debug => $transcript):()),
                 requester    => $header{from},
                 request_addr => $controlrequestaddr,
                 message      => \@log,
                 affected_packages => \%affected_packages,
                 recipients   => \%recipients,
                 title        => $new_title,
                 );
   };
   if ($@) {
       $errors++;
       print {$transcript} "Failed to set the title of $ref: $@";
   }

Sets the title of one specific bug. One bug, one title, very demure, very mindful. πŸ’…


πŸ“¦ set_package

eval {
       set_package(bug          => $ref,
                   transcript   => $transcript,
                   ($dl > 0 ? (debug => $transcript):()),
                   requester    => $header{from},
                   request_addr => $controlrequestaddr,
                   message      => \@log,
                   affected_packages => \%affected_packages,
                   recipients   => \%recipients,
                   package      => $new_package,
                   is_source    => 0,
                  );
   };
   if ($@) {
       $errors++;
       print {$transcript} "Failed to assign or reassign $ref to a package: $@";
   }

Indicates that a bug lives in a particular package. πŸ“¦ If is_source is true, it means the package is a source package. [Internally, this causes src: to get prepended to the package name. So that's where the src: comes from, mystery solved. πŸ”]

The default for is_source is 0. But here's the special case, and it's a good one: if the package starts with 'src:', it's assumed to be a source package and is_source gets overridden. Sorry bestie, your is_source value doesn't matter in that case. πŸ™ˆ

Oh and also β€” the package option must match the package_name_re regex. Non-negotiable. 🚨


πŸ”Ž set_found

eval {
       set_found(bug          => $ref,
                 transcript   => $transcript,
                 ($dl > 0 ? (debug => $transcript):()),
                 requester    => $header{from},
                 request_addr => $controlrequestaddr,
                 message      => \@log,
                 affected_packages => \%affected_packages,
                 recipients   => \%recipients,
                 found        => [],
                 add          => 1,
                );
   };
   if ($@) {
       $errors++;
       print {$transcript} "Failed to set found on $ref: $@";
   }

Sets, adds, or removes the specified found versions of a package. πŸ”Ž

Two very important rules here, don't skip them:

  • If the version list is empty, and the bug is currently not "done" β€” it causes the done field to be cleared. πŸ‘€
  • If any of the versions added to found are greater than any version in which the bug is fixed β€” or when the bug is found and there are no fixed versions at all β€” the done field gets cleared. 🧹

πŸ› οΈ set_fixed

eval {
       set_fixed(bug          => $ref,
                 transcript   => $transcript,
                 ($dl > 0 ? (debug => $transcript):()),
                 requester    => $header{from},
                 request_addr => $controlrequestaddr,
                 message      => \@log,
                 affected_packages => \%affected_packages,
                 recipients   => \%recipients,
                 fixed        => [],
                 add          => 1,
                 reopen       => 0,
                );
   };
   if ($@) {
       $errors++;
       print {$transcript} "Failed to set fixed on $ref: $@";
   }

Sets, adds, or removes the specified fixed versions of a package. πŸ› οΈβœ¨ That's the whole thing, and honestly? Iconic.

If the fixed versions are empty (or end up being empty after this call) or the greatest fixed version is less than the greatest found version and the reopen option is true, the bug is reopened. 🫢

And okay, fun fact: this function is also called by the reopen function, which is what causes all of the fixed versions to be cleared. So like… reopen is out here using this one as its whole personality. πŸ‘‘


🧩 set_merged

eval {
       set_merged(bug          => $ref,
                  transcript   => $transcript,
                  ($dl > 0 ? (debug => $transcript):()),
                  requester    => $header{from},
                  request_addr => $controlrequestaddr,
                  message      => \@log,
                  affected_packages => \%affected_packages,
                  recipients   => \%recipients,
                  merge_with   => 12345,
                  add          => 1,
                  force        => 1,
                  allow_reassign => 1,
                  reassign_same_source_only => 1,
                 );
   };
   if ($@) {
       $errors++;
       print {$transcript} "Failed to set merged on $ref: $@";
   }

Sets, adds, or removes the specified merged bugs of a bug. πŸ¦‹

By default, requires

bestie the manpage literally just says "By default, requires" and then stops. that's the whole sentence. iconic of it, honestly.

πŸŒ€ affects

eval {
       affects(bug          => $ref,
               transcript   => $transcript,
               ($dl > 0 ? (debug => $transcript):()),
               requester    => $header{from},
               request_addr => $controlrequestaddr,
               message      => \@log,
               affected_packages => \%affected_packages,
               recipients   => \%recipients,
               packages     => undef,
               add          => 1,
               remove       => 0,
              );
   };
   if ($@) {
       $errors++;
       print {$transcript} "Failed to mark $ref as affecting $packages: $@";
   }

This marks a bug as affecting packages which the bug is not actually in. 🫠

warning bestie: this should only be used in cases where fixing the bug instantly resolves the problem in the other packages. Like, that is the whole entire condition. don't get cute with it.

By default, the packages are set to the list of packages passed. However, if you pass add => 1 or remove => 1, the list of packages passed are added or removed from the affects list, respectively. πŸ’…


πŸ“ SUMMARY FUNCTIONS

✨ summary

eval {
       summary(bug          => $ref,
               transcript   => $transcript,
               ($dl > 0 ? (debug => $transcript):()),
               requester    => $header{from},
               request_addr => $controlrequestaddr,
               message      => \@log,
               affected_packages => \%affected_packages,
               recipients   => \%recipients,
               summary      => undef,
              );
   };
   if ($@) {
       $errors++;
       print {$transcript} "Failed to mark $ref with summary foo: $@";
   }

Handles all setting of summary fields. the way it handles every single one of them?? obsessed. 😌

  • If summary is undef, unsets the summary.
  • If summary is 0 or -1, sets the summary to the first paragraph contained in the message passed.
  • If summary is a positive integer, sets the summary to the message specified.
  • Otherwise, sets summary to the value passed.

πŸ”­ OUTLOOK FUNCTIONS

🌈 outlook

eval {
       outlook(bug          => $ref,
               transcript   => $transcript,
               ($dl > 0 ? (debug => $transcript):()),
               requester    => $header{from},
               request_addr => $controlrequestaddr,
               message      => \@log,
               affected_packages => \%affected_packages,
               recipients   => \%recipients,
               outlook      => undef,
              );
   };
   if ($@) {
       $errors++;
       print {$transcript} "Failed to mark $ref with outlook foo: $@";
   }

Handles all setting of outlook fields. same energy, different field, we love a consistent queen. πŸ’–

  • If outlook is undef, unsets the outlook.
  • If outlook is 0, sets the outlook to the first paragraph contained in the message passed.
  • If outlook is a positive integer, sets the outlook to the message specified.
  • Otherwise, sets outlook to the value passed.

🐣 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. it's giving photocopy but make it ✨official✨

We currently don't support cloning merged bugs, but this could be handled by internally unmerging, cloning, then remerging the bugs. so like, the roadmap is right there, someone just has to do it. πŸ™ˆ


πŸ‘‘ OWNER FUNCTIONS

πŸŽ€ owner

eval {
       owner(bug          => $ref,
             transcript   => $transcript,
             ($dl > 0 ? (debug => $transcript):()),
             requester    => $header{from},
             request_addr => $controlrequestaddr,
             message      => \@log,
             recipients   => \%recipients,
             owner        => undef,
            );
   };
   if ($@) {
       $errors++;
       print {$transcript} "Failed to mark $ref as having an owner: $@";
   }

Handles all setting of the owner field; given an owner of undef or of no length, indicates that a bug is not owned by anyone. no thoughts, no owner, just vibes. πŸ¦‹


πŸ“¦ ARCHIVE FUNCTIONS

πŸ—„οΈ bug_archive

my $error = '';
eval {
   bug_archive(bug => $bug_num,
               debug => \$debug,
               transcript => \$transcript,
              );
};
if ($@) {
   $errors++;
   transcript("Unable to archive $bug_num\n");
   warn $@;
}
transcript($transcript);

This routine archives a bug. πŸ“₯

  • bug β€” bug number.
  • check_archiveable β€” check whether a bug is archiveable before archiving; defaults to 1.
  • archive_unarchived β€” whether to archive bugs which have not previously been archived; defaults to 1. [Set to 0 when used from control@]
  • ignore_time β€” whether to ignore time constraints when archiving a bug; defaults to 0.

πŸ“€ bug_unarchive

my $error = '';
eval {
   bug_unarchive(bug => $bug_num,
                 debug => \$debug,
                 transcript => \$transcript,
                );
};
if ($@) {
   $errors++;
   transcript("Unable to archive bug: $bug_num");
}
transcript($transcript);

This routine unarchives a bug. the comeback arc we've all been waiting for. πŸ”₯


🏷️ valid_usertag

valid_usertag

This checks if the usertag contains valid characters or not. that's it, that's the whole job, and it does it flawlessly. ✨


πŸͺ΅ append_action_to_log

append_action_to_log

This should probably be moved to Debbugs::Log; have to think that out some more. the devs said "we'll think about it" and honestly? same, bestie, same. πŸ’€


πŸ”’ PRIVATE FUNCTIONS

bestie these are the private ones, pls don't touch unless you know what you're doing πŸ’…

🫧 __handle_affected_packages

__handle_affected_packages(affected_packages => {},
                           data => [@data],
                          )

πŸ” __handle_debug_transcript

my ($debug,$transcript) = __handle_debug_transcript(%param);

Returns a debug and transcript filehandle. two for one, slay. 🫢


πŸ› __bug_info

__bug_info($data)

Produces a small bit of bug information to kick out to the transcript. tiny snack-sized bug lore. πŸ“


πŸͺ„ __internal_request

__internal_request()
__internal_request($level)

Returns true if the caller of the function calling __internal_request belongs to __PACKAGE__.

This allows us to be magical, and don't bother to print bug info if the second caller is from this package, amongst other things. ✨ it's literally doing magic and we're just living in it

An optional level is allowed, which increments the number of levels to check by the given value. [This is basically for use by internal functions like __begin_control which are always called by "__PACKAGE__".]


🎬 __begin_control

my %info = __begin_control(%param,
                           archived=>1,
                           command=>'unarchive');
my ($debug,$transcript) = @info{qw(debug transcript)};
my @data = @{$info{data}};
my @bugs = @{$info{bugs}};

Starts the process of modifying a bug; handles all of the generic things that almost every control request needs. the setup era, the pre-show, the soundcheck. 🎀

Returns a hash containing

  • new_locks β€” number of new locks taken out by this call
  • debug β€” the debug file handle
  • transcript β€” the transcript file handle
  • data β€” an arrayref containing the data of the bugs corresponding to this request
  • bugs β€” an arrayref containing the bug numbers of the bugs corresponding to this request

🎬 __end_control

__end_control(%info);

Handles tearing down from a control request. the clean-up era, the closing credits, the 🧹. so satisfying.


βš–οΈ check_limit

check_limit(data => \@data, limit => $param{limit});

Checks to make sure that bugs match any limits; each entry of @data much satisfy the limit.

Returns true if there are no entries in data, or there are no keys in limit; returns false (0) if there are any entries which do not match. 🚦

The limit hashref elements can contain an arrayref of scalars to match; regexes are also acccepted. At least one of the entries in each element needs to match the corresponding field in all data for the limit to succeed.


πŸ’€ die

sig_die "foo"

We override die to specially handle unlocking files in the cases where we are called via eval. [If we're not called via eval, it doesn't matter.]

lowkey unhinged behaviour but highkey necessary. no cap.

and that's the whole page bestie!! πŸŽ€ we did it, we read the entire Debbugs::Control(3pm) manpage and nobody can take that from us. go forth and control some bugs, you absolute icon. πŸ‘‘βœ¨πŸ¦‹

this page came from the perl v5.40.1 docs, dated 2026-04-27, and it is indeed Debbugs::Control(3pm) β€” so if you ever need to cite it in a group chat, you now know exactly which one it was. mwah. πŸ’–

πŸ“¦ where this page comes from