diff --git a/build.xml b/build.xml index 4ba366c..ffd852c 100644 --- a/build.xml +++ b/build.xml @@ -25,6 +25,9 @@ ]]> + + + @@ -450,6 +453,9 @@ --updateSetName '${updateSetName}' --targetVersion '${targetVersion}' --targetVersionCode '${targetVersionCode}' + --issuesRepository '${issuesRepository}' + --resolvedIssuesMilestone '${resolvedIssuesMilestone}' + --resolvedIssuesLink '${resolvedIssuesLink}' --distVersionDataFile '${distVersionDataFile}'" /> diff --git a/php/version-data.php b/php/version-data.php index 878f622..3bd54b7 100644 --- a/php/version-data.php +++ b/php/version-data.php @@ -11,9 +11,20 @@ 'updateSetName:', 'targetVersion:', 'targetVersionCode:', + 'issuesRepository:', + 'resolvedIssuesMilestone:', + 'resolvedIssuesLink:', 'distVersionDataFile:', ]); +define('MY_USERAGENT', 'mybb/mybb-build'); +define('DEFAULT_CURLOPTS', [ + CURLOPT_USERAGENT => MY_USERAGENT, + CURLOPT_TIMEOUT => 10, + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_SSL_VERIFYPEER => 1, +]); + function arrayToYml($array, $level = 1) { $output = null; @@ -60,6 +71,144 @@ function directoryStructureSort($a, $b) { } } +function fetchJson($ch, $path) { + $curlopt = [ + CURLOPT_URL => $path, + ] + DEFAULT_CURLOPTS; + + curl_setopt_array($ch, $curlopt); + + $response = curl_exec($ch); + + $data = json_decode($response, true); + + if ($data === null) { + return null; + } + + return $data; +} + +function fetchJsonPaged($ch, $path, $sep, $callback, $perPage=100) { + $results = []; + $page = 1; + + $curlopt = [ + CURLOPT_HEADER => 1, + ] + DEFAULT_CURLOPTS; + + while ($page !== null) { + $curlopt[CURLOPT_URL] = $path . $sep . 'per_page=' . $perPage . '&page=' . $page; + + curl_setopt_array($ch, $curlopt); + + $response = curl_exec($ch); + + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + + $data = json_decode($body, true); + + if ($data === null) { + break; + } + + foreach ($data as $item) { + $result = $callback($item); + if ($result === null) { + continue; + } + + $results[] = $result; + } + + preg_match('/^Link:(.*?)&page=([0-9]+)>; rel="next"/im', $header, $matches); + + if ($matches) { + $page = $matches[2]; + } else { + $page = null; + } + } + + return $results; +} + +function getGithubIssueAges($ch, $repo, $resolvedIssuesMilestone) { + $githubApiPath = 'https://api.github.com/repos/' . $repo . '/'; + + $milestones = fetchJsonPaged($ch, $githubApiPath . 'milestones?state=all', '&', function($item) use ($resolvedIssuesMilestone) { + if ($item['title'] === $resolvedIssuesMilestone) { + return $item; + } + + return null; + }); + + if (empty($milestones[0])) { + return null; + } + + $path = $githubApiPath . 'issues?milestone=' . $milestones[0]['number'] . '&state=closed'; + + $issueAges = fetchJsonPaged($ch, $path, '&', function($item) { + if (empty($item['closed_at']) || !empty($item['pull_request'])) { + return null; + } + + $created = DateTime::createFromFormat(DateTime::ISO8601, $item['created_at']); + $closed = DateTime::createFromFormat(DateTime::ISO8601, $item['closed_at']); + + if ($created === false || $closed === false) { + return null; + } + + $interval = $created->diff($closed); + return (int) $interval->format('%a'); + }); + + return $issueAges; +} + +function getIssuesData($ch, $issuesRepository, $resolvedIssuesMilestone) { + $parsed = parse_url($issuesRepository); + + if ($parsed === false) { + return null; + } + + $repo = trim(preg_replace('/\.git$/', '', $parsed['path']), '/'); + + switch ($parsed['host']) { + case 'github.com': + $issueAges = getGithubIssueAges($ch, $repo, $resolvedIssuesMilestone); + break; + default: + return null; + } + + sort($issueAges); + + $numIssues = count($issueAges); + + if ($numIssues == 0) { + return null; + } + + if ($numIssues % 2 === 0) { + $median = ($issueAges[($numIssues/2)-1] + $issueAges[$numIssues/2]) / 2; + } else { + $median = $issueAges[intdiv($numIssues, 2)]; + } + + return [ + 'medianAge' => $median, + 'meanAge' => round(array_sum($issueAges) / $numIssues, 1), + 'numIssues' => $numIssues, + ]; +} + // changed files YML $file = $args['distChangedFilesFile']; @@ -175,6 +324,30 @@ function directoryStructureSort($a, $b) { } } +// resolved issue YML +$resolvedIssuesYml = ''; + +$ch = curl_init(); + +$issuesData = getIssuesData($ch, $args['issuesRepository'], $args['resolvedIssuesMilestone']); + +if ($issuesData !== null) { + $resolvedIssuesYml .= <<