app/classes/ReleaseInsights/Bugzilla.php (99 lines of code) (raw):

<?php declare(strict_types=1); namespace ReleaseInsights; class Bugzilla { /** * Create a bugzilla link for multiple bugs * * @param array<mixed> $bug_numbers List of bug numbers * * @return string Link */ public static function getBugListLink(array $bug_numbers): string { $bug_numbers = array_unique($bug_numbers); $bug_numbers = array_filter($bug_numbers, 'is_numeric'); return URL::Bugzilla->value . 'buglist.cgi?bug_id=' . implode('%2C', $bug_numbers); } /** * Turn bug numbers in a string into Bugzilla links */ public static function linkify(string $text): ?string { return preg_replace_callback( "/bug +\d+/i", fn(array $matches) => '<a href="' . URL::Bugzilla->value . trim(str_ireplace('bug', '', (string) $matches[0])) . '">' . $matches[0] . '</a>', $text ); } /** * Extract bug numbers from a mozilla mercurial log generated by the Web interface * * @param string $query The web url to get information from * @param bool $detect_backouts Decide if we want to detect backouts separately, false by default * @param int $cache_ttl Caching time in seconds, by default set to zero * * @return array<mixed> */ public static function getBugsFromHgWeb(string $query, bool $detect_backouts = false, int $cache_ttl = 0): array { if (json_validate($query)) { // We sometimes pass directly the result of a JSON response $results = Json::toArray($query)['pushes'] ?? null; } else { $results = Json::load($query, $cache_ttl)['pushes'] ?? null; } // Handle the lack of data from HG if (empty($results)) { return [ 'bug_fixes' => [], 'backouts' => [], 'total' => [], 'no_data' => true, ]; } $changesets = array_column($results, 'changesets'); $bug_fixes = []; $backouts = []; // Extract bug number from commit message $get_bugs = function (string $str): array { if (preg_match_all("/bug \d+/", $str, $matches)) { $matches[0] = array_map( fn (string $str) => str_replace('bug', '', $str), $matches[0] ); $matches[0] = array_map('trim', $matches[0]); } return count($matches[0]) > 1 ? [$matches[0][0]] : $matches[0]; }; foreach ($changesets as $items) { foreach ($items as $subitem) { $subitem = explode("\n", (string) $subitem['desc'])[0]; $subitem = strtolower(Utils::mtrim($subitem)); if (Utils::startsWith($subitem, ['no bug', 'automatic version bump'])) { continue; } // Commits can be ignored if they contain one of these strings $ignore_list = [ 'a=test-only', 'a=release', 'a=npotb', 'a=searchfox-only', 'try-staging', 'taskcluster', 'a=tomprince', 'a=aki', 'a=testing', '[mozharness]', 'r=aki', 'r=tomprince', 'r=mtabara', 'a=jorgk', 'beetmover', '[taskgraph]', 'a=testonly', 'a=bustage', 'a=expectation-update-for-worker-image', 'a=repo-update', ]; if (Utils::inString($subitem, $ignore_list)) { continue; } if (Utils::inString($subitem, ['Backed out', 'backed out', 'back out', 'Back out']) && $detect_backouts === true) { $counter = count($bug_fixes); $bug_fixes = array_diff($bug_fixes, $get_bugs($subitem)); if ($counter === count($bug_fixes)) { $backouts[] = $subitem; } continue; } // We only include the first bug number mentionned for normal cases $bug_fixes = array_merge($bug_fixes, array_slice($get_bugs($subitem), 0, 1)); } } $bug_fixes = array_unique($bug_fixes); $backed_out_bugs = []; foreach ($backouts as $backout) { $backed_out_bugs = [...$backed_out_bugs, ...$get_bugs($backout)]; } $backed_out_bugs = array_unique($backed_out_bugs); // Substract bug_fixes that were backed out later $clean_bug_fixes = $bug_fixes; $clean_backed_out_bugs = array_diff($backed_out_bugs, $bug_fixes); $clean_bug_fixes = array_map('intval', $clean_bug_fixes); $clean_backed_out_bugs = array_map('intval', $clean_backed_out_bugs); return [ 'bug_fixes' => array_values($clean_bug_fixes), 'backouts' => array_values($clean_backed_out_bugs), 'total' => array_values([...$clean_bug_fixes, ...$clean_backed_out_bugs]), 'no_data' => false, ]; } }