From 0a983e998e8c5b2ea03b50bb9e110afd5f8c0a95 Mon Sep 17 00:00:00 2001 From: Neil Lathwood Date: Fri, 15 Jun 2018 13:55:31 +0100 Subject: [PATCH] Add Changelog generator (#8791) * feature: Added initial changelog generator * More updates * Update and rename release.php to Release.php * Update Kernel.php --- LibreNMS/Util/GitHub.php | 241 +++++++++++++++++++++++++++++++ app/Console/Commands/Release.php | 67 +++++++++ app/Console/Kernel.php | 2 +- 3 files changed, 309 insertions(+), 1 deletion(-) create mode 100644 LibreNMS/Util/GitHub.php create mode 100644 app/Console/Commands/Release.php diff --git a/LibreNMS/Util/GitHub.php b/LibreNMS/Util/GitHub.php new file mode 100644 index 0000000000..b863f8fb69 --- /dev/null +++ b/LibreNMS/Util/GitHub.php @@ -0,0 +1,241 @@ +. + * + * @package LibreNMS + * @link http://librenms.org + * @copyright 2018 Neil Lathwood + * @author Neil Lathwood + */ + +namespace LibreNMS\Util; + +use Requests; +use DateTime; +use SebastianBergmann\CodeCoverage\Report\PHP; + +class GitHub +{ + protected $tag; + protected $from; + protected $token; + protected $file; + protected $pr; + protected $stop = false; + protected $pull_requests = []; + protected $changelog = []; + protected $markdown; + protected $labels = ['webui', 'api', 'documentation', 'security', 'feature', 'enhancement', 'device', 'bug', 'alerting']; + protected $github = 'https://api.github.com/repos/librenms/librenms'; + + public function __construct($tag, $from, $file, $token = null, $pr = null) + { + $this->tag = $tag; + $this->from = $from; + $this->file = $file; + $this->pr = $pr; + if (is_null($token) === false || getenv('GH_TOKEN')) { + $this->token = $token ?: getenv('GH_TOKEN'); + } + } + + /** + * + * Return the GitHub Authorization header for the API call + * + * @return array + */ + public function getHeaders() + { + if (is_null($this->token) === false) { + return ['Authorization' => "token {$this->token}"]; + } + return []; + } + + /** + * + * Get the release information for a specific tag + * + * @param $tag + * @return mixed + */ + public function getRelease($tag) + { + $release = Requests::get($this->github . "/releases/tags/$tag", self::getHeaders()); + return json_decode($release->body, true); + } + + /** + * + * Get a single pull request information + * + * @return mixed + */ + public function getPullRequest() + { + $pull_request = Requests::get($this->github . "/pulls/{$this->pr}", self::getHeaders()); + $this->pr = json_decode($pull_request->body, true); + } + + /** + * + * Get all closed pull requests up to a certain date + * + * @param $date + * @param int $page + * @return bool + */ + public function getPullRequests($date, $page = 1) + { + $prs = Requests::get($this->github . "/pulls?state=closed&page=$page", self::getHeaders()); + $prs = json_decode($prs->body, true); + foreach ($prs as $k => $pr) { + if ($pr['merged_at']) { + $merged = new DateTime($pr['merged_at']); + $end_date = new DateTime($date); + if (isset($this->pr['merged_at']) && $merged > new DateTime($this->pr['merged_at'])) { + // If the date of this PR is newer than the final PR then skip over it + continue; + } elseif ($merged < $end_date) { + // If the date of this PR is older than the last release we're done + return true; + } else { + // If not, assign this PR to the array + $this->pull_requests[] = $pr; + } + } + } + $this->getPullRequests($date, $page+1); + } + + /** + * + * Build the data for the change log. + * + */ + public function buildChangeLog() + { + $output = []; + $users = []; + foreach ($this->pull_requests as $k => $pr) { + if (isset($users[$pr['user']['login']]) === false) { + $users[$pr['user']['login']] = 0; + } + if ($pr['merged_at']) { + foreach ($pr['labels'] as $k => $label) { + $name = preg_replace('/ :[\S]+:/', '', strtolower($label['name'])); + if (in_array($name, $this->labels)) { + $title = ucfirst(trim(preg_replace('/^[\S]+: /', '', $pr['title']))); + $output[$name][] = "$title ([#{$pr['number']}]({$pr['html_url']})) - [{$pr['user']['login']}]({$pr['user']['html_url']})" . PHP_EOL; + } + } + $users[$pr['user']['login']] += 1; + } + } + $this->changelog = ['changelog' => $output, 'users' => $users]; + } + + /** + * + * Format the change log into Markdown. + * + */ + public function formatChangeLog() + { + $tmp_markdown = "##$this->tag" . PHP_EOL; + $tmp_markdown .= '*(' . date('Y-m-d') . ')*' . PHP_EOL . PHP_EOL; + if (!empty($this->changelog['users'])) { + $tmp_markdown .= "A big thank you to the following " . count($this->changelog['users']) . " contributors this last month:" . PHP_EOL; + asort($this->changelog['users']); + foreach (array_reverse($this->changelog['users']) as $user => $count) { + $tmp_markdown .= " - $user ($count)" . PHP_EOL; + } + } + + $tmp_markdown .= PHP_EOL; + + foreach ($this->changelog['changelog'] as $section => $items) { + $tmp_markdown .= "#### " . ucfirst($section) . PHP_EOL; + $tmp_markdown .= '* ' . implode('* ', $items) . PHP_EOL; + } + + $this->markdown = $tmp_markdown; + } + + /** + * + * Update the specified file with the new Change log info. + * + */ + public function writeChangeLog() + { + if (file_exists($this->file)) { + $existing = file_get_contents($this->file); + $content = $this->getMarkdown() . PHP_EOL . $existing; + if (is_writable($this->file)) { + file_put_contents($this->file, $content); + } + } else { + echo "Couldn't write to file {$this->file}" . PHP_EOL; + exit; + } + } + + /** + * + * Return the generated markdown. + * + * @return mixed + */ + public function getMarkdown() + { + return $this->markdown; + } + + public function createRelease() + { + //FIXME Come back to this + return false; + $sha = isset($this->pr['merge_commit_sha']) ? $this->pr['merge_commit_sha'] : 'master'; + $release = Requests::post($this->github . "/releases", self::getHeaders(), [ + 'tag_name' => $this->tag, + 'target_commitish' => $sha, + 'body' => $this->getMarkdown(), + 'draft' => true, + ]); + } + + /** + * + * Function to control the creation of creating a change log. + * + */ + public function createChangelog() + { + $previous_release = $this->getRelease($this->from); + if (is_null($this->pr) !== true) { + $this->getPullRequest(); + } + $this->getPullRequests($previous_release['published_at']); + $this->buildChangeLog(); + $this->formatChangeLog(); + $this->writeChangeLog(); + } +} diff --git a/app/Console/Commands/Release.php b/app/Console/Commands/Release.php new file mode 100644 index 0000000000..6355b1d947 --- /dev/null +++ b/app/Console/Commands/Release.php @@ -0,0 +1,67 @@ +argument('tag'); + $from = $this->argument('from'); + $file = $this->option('file') ?: 'doc/General/Changelog.md'; + $pr = $this->option('pr'); + $token = getenv('GH_TOKEN') ?: $this->secret('Enter a GitHub Token?'); + + $this->info("Creating release $tag....."); + $gh = new GitHub($tag, $from, $file, $token, $pr); + $gh->createChangelog(); + $this->info("Changelog generated for $tag"); + + if ($this->confirm('Do you want to view the generated Changelog?')) { + echo $gh->getMarkdown(); + } + + if ($this->confirm("Do you want to create the release $tag on GitHub?")) { + //$gh->createRelease(); + $this->error('Unsupported right now'); + } + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 622e774b33..6da222af72 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -13,7 +13,7 @@ class Kernel extends ConsoleKernel * @var array */ protected $commands = [ - // + Commands\Release::class, ]; /**