diff --git a/Gemfile b/Gemfile index 20094cd0f..01bcb0cae 100644 --- a/Gemfile +++ b/Gemfile @@ -3,6 +3,8 @@ source "https://rubygems.org" group :development, :test do # This is here because gemspec doesn't support require: false gem 'coveralls', :require => false + gem "netrc", :require => false + gem "octokit", :require => false end gemspec diff --git a/RELEASE.md b/RELEASE.md index 9829bcf49..bd066169c 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -21,7 +21,7 @@ To request a new release please raise an issue. services) * Ensure working on **master** * Update the version number (`lib/fog/version.rb`) -* Run `rake changelog` to update `changelog.txt` +* Run `rake changelog` to update `CHANGELOG.md` * Run `rake release` to prepare the release which does: * Prepares the release (`rake release:prepare`) * Builds the gem @@ -30,6 +30,7 @@ services) * Publishes the release (`rake release:publish`) * Pushes commit and tag to Github (Requires Credentials) * Pushes gem to Rubygems (Requires Credentials) +* Run `rake github_release` to add release to [github release feed](https://github.com/fog/fog/releases.atom) (Requires Credentials) ## Announce the release diff --git a/Rakefile b/Rakefile index a892a4cd2..d3f4bdc30 100644 --- a/Rakefile +++ b/Rakefile @@ -208,6 +208,7 @@ YARD::Rake::YardocTask.new do |t| end require "tasks/changelog_task" +require "tasks/github_release_task" Fog::Rake::ChangelogTask.new task :coveralls_push_workaround do diff --git a/lib/tasks/changelog_task.rb b/lib/tasks/changelog_task.rb index ac93ba0d6..323e7d311 100644 --- a/lib/tasks/changelog_task.rb +++ b/lib/tasks/changelog_task.rb @@ -8,103 +8,200 @@ module Fog def initialize desc "Update the changelog since the last release" task(:changelog) do - timestamp = Time.now.utc.strftime('%m/%d/%Y') - sha = `git log | head -1`.split(' ').last - changelog = ["#{Fog::VERSION} #{timestamp} #{sha}"] - changelog << ('=' * changelog[0].length) - changelog << '' - github_repo_data = Fog::JSON.decode(Excon.get('https://api.github.com/repos/fog/fog', :headers => {'User-Agent' => 'geemus'}).body) - data = github_repo_data.reject {|key, value| !['forks', 'open_issues', 'watchers'].include?(key)} - github_collaborator_data = Fog::JSON.decode(Excon.get('https://api.github.com/repos/fog/fog/collaborators', :headers => {'User-Agent' => 'geemus'}).body) - data['collaborators'] = github_collaborator_data.length - rubygems_data = Fog::JSON.decode(Excon.get('https://rubygems.org/api/v1/gems/fog.json').body) - data['downloads'] = rubygems_data['downloads'] - stats = [] - for key in data.keys.sort - stats << "'#{key}' => #{data[key]}" + @changelog = [] + @changelog << "## #{Fog::VERSION} #{timestamp}" + @changelog << "*Hash* #{sha}" + @changelog << blank_line + + @changelog << "Statistic | Value" + @changelog << "------------- | --------:" + @changelog << "Collaborators | #{collaborators}" + @changelog << "Downloads | #{downloads}" + @changelog << "Forks | #{forks}" + @changelog << "Open Issues | #{open_issues}" + @changelog << "Watchers | #{watchers}" + @changelog << blank_line + + process_commits + + @changelog << "**MVP!** #{mvp}" if mvp + @changelog << blank_line + + add_commits_to_changelog + save_changelog + end + end + + private + + def save_changelog + old_changelog = File.read('CHANGELOG.md') + File.open('CHANGELOG.md', 'w') do |file| + file.write(@changelog.join("\n")) + file.write("\n\n") + file.write(old_changelog) + end + end + + def blank_line + '' + end + + def add_commits_to_changelog + @changes.keys.sort.each do |tag| + @changelog << "#### [#{tag}]" + @changes[tag].each do |commit| + @changelog << "* #{commit}" end - changelog << "Stats! { #{stats.join(', ')} }" - changelog << '' + @changelog << blank_line + end + end - last_sha = `cat changelog.txt | head -1`.split(' ').last - shortlog = `git shortlog #{last_sha}..HEAD` - changes = {} - committers = {} - for line in shortlog.split("\n") - if line =~ /^\S/ - committer = line.split(' (', 2).first - committers[committer] = 0 - elsif line =~ /^\s*((Merge.*)|(Release.*))?$/ - # skip empty lines, Merge and Release commits - else - unless line[-1..-1] == '.' - line << '.' - end - line.lstrip! - line.gsub!(/^\[([^\]]*)\] /, '') - tag = $1 || 'misc' - changes[tag] ||= [] - changes[tag] << (line << ' thanks ' << committer) - committers[committer] += 1 - end - end - - for committer, commits in committers.to_a.sort {|x,y| y[1] <=> x[1]} - if [ - 'Aaron Suggs', - 'Ash Wilson', - 'Brian Hartsock', - 'Chris Roberts', - 'Christopher Oliver', - 'Daniel Reichert', - 'Decklin Foster', - 'Dylan Egan', - 'Erik Michaels-Ober', - 'geemus', - 'Henry Addison', - 'James Bence', - 'Kevin Menard', - 'Kevin Olbrich', - 'Kyle Rames', - 'Lincoln Stoll', - 'Luqman Amjad', - 'Michael Zeng', - 'Mike Hagedorn', - 'Mike Pountney', - 'Nat Welch', - 'Nick Osborn', - 'nightshade427', - 'Patrick Debois', - 'Paul Thornthwaite', - 'Rodrigo Estebanez', - 'Rupak Ganguly', - 'Stepan G. Fedorov', - 'Wesley Beary' - ].include?(committer) - next - end - changelog << "MVP! #{committer}" - changelog << '' - break - end - - for tag in changes.keys.sort - changelog << ('[' << tag << ']') - for commit in changes[tag] - changelog << (' ' << commit) - end - changelog << '' - end - - old_changelog = File.read('changelog.txt') - File.open('changelog.txt', 'w') do |file| - file.write(changelog.join("\n")) - file.write("\n\n") - file.write(old_changelog) + def process_commits + shortlog = `git shortlog #{last_release_sha}..HEAD` + @changes = {} + @committers = {} + @committer = nil + shortlog.split("\n").each do |line| + @current_line = line + if committer_line? + @committer = committer_match[1] + add_committer + elsif !release_merge_line? + add_period_if_necessary + @current_line.lstrip! + add_commit_line + increment_commits end end end + + def add_commit_line + @current_line.gsub!(/^\[([^\]]*)\] /, '') + tag = $1 || 'misc' + @changes[tag] ||= [] + @changes[tag] << "#{@current_line} thanks #{@committer}" + end + + def increment_commits + @committers[@committer] += 1 + end + + def add_committer + @committers[@committer] = 0 + end + + def committers_sorted_by_commits + committer_pairs = @committers.to_a.sort {|x,y| y[1] <=> x[1]} + committer_pairs.reject! {|pair| pair.last < 1 } + committer_pairs.collect {|pair| pair.first } + end + + def mvp_eligible?(committer) + [ + 'Aaron Suggs', + 'Ash Wilson', + 'Brian Hartsock', + 'Chris Roberts', + 'Christopher Oliver', + 'Daniel Reichert', + 'Decklin Foster', + 'Dylan Egan', + 'Erik Michaels-Ober', + 'geemus', + 'Henry Addison', + 'James Bence', + 'Kevin Menard', + 'Kevin Olbrich', + 'Kyle Rames', + 'Lincoln Stoll', + 'Luqman Amjad', + 'Michael Zeng', + 'Mike Hagedorn', + 'Mike Pountney', + 'Nat Welch', + 'Nick Osborn', + 'nightshade427', + 'Patrick Debois', + 'Paul Thornthwaite', + 'Rodrigo Estebanez', + 'Rupak Ganguly', + 'Stepan G. Fedorov', + 'Wesley Beary' + ].include?(committer) + end + + def mvp + return @mvp if @mvp + committers_sorted_by_commits.each do |committer| + if mvp_eligible?(committer) + @mvp = committer + return @mvp + end + end + nil + end + + def add_period_if_necessary + @current_line << "." unless @current_line[-1] == '.' + end + + def release_merge_line? + @current_line =~ /^\s*((Merge.*)|(Release.*))?$/ + end + + def committer_line? + committer_match != nil + end + + def committer_match + @current_line.match /([\w\s]+)\s+\(\d+\)/ + end + + def last_release_sha + `cat changelog.md | head -2`.split(' ').last + end + + def downloads + repsonse = Excon.get('https://rubygems.org/api/v1/gems/fog.json') + data = Fog::JSON.decode(repsonse.body) + data['downloads'] + end + + def collaborators + response = Excon.get('https://api.github.com/repos/fog/fog/collaborators', :headers => {'User-Agent' => 'geemus'}) + data = Fog::JSON.decode(response.body) + data.length + end + + def forks + repo_metadata['forks'] + end + + def open_issues + repo_metadata['open_issues'] + end + + def watchers + repo_metadata['watchers'] + end + + def repo_metadata + return @repo_metadata if @repo_metadata + response = Excon.get('https://api.github.com/repos/fog/fog', :headers => {'User-Agent' => 'geemus'}) + data = Fog::JSON.decode(response.body) + @repo_metadata = data.select {|key, value| ['forks', 'open_issues', 'watchers'].include?(key)} + end + + def sha + `git log | head -1`.split(' ').last + end + + def timestamp + @time ||= Time.now.utc.strftime('%m/%d/%Y') + end + end end end diff --git a/lib/tasks/github_release_task.rb b/lib/tasks/github_release_task.rb new file mode 100644 index 000000000..2c1640d11 --- /dev/null +++ b/lib/tasks/github_release_task.rb @@ -0,0 +1,67 @@ +require "rake" +require "rake/tasklib" +require 'octokit' +require 'netrc' + +module Fog + module Rake + class ChangelogTask < ::Rake::TaskLib + + def initialize + desc "Update the changelog since the last release" + task(:github_release) do + + File.open('CHANGELOG.md', 'r') do |file| + file.each_line do |line| + @current_line = line + if release_header? + create_release if !release_exists? && @release + @release_tag = release_match[1] + @release = line + else + @release << line + end + end + end + end + end + + private + + def create_release + github.create_release "fog/fog", "v#{@release_tag}", {:name => "v#{@release_tag}", :body => @release} + puts "creating release #{@release_tag}" + end + + def releases + return @releases if @releases + response = github.releases("fog/fog") + @releases = response.collect {|r| r.tag_name } + end + + def release_exists? + releases.find {|r| r == "v#{@release_tag}" } != nil + end + + def release_header? + release_match != nil + end + + def release_match + @current_line.match /## (\d+\.\d+\.\d+) \d+\/\d+\d+/ + end + + def github + unless @github + Octokit.auto_paginate = true + @github = Octokit::Client.new :netrc => true + unless @github.login + @github = nil + raise "Please create a ~.netrc file to authenticate with github. For more information please see https://github.com/octokit/octokit.rb/blob/master/README.md#using-a-netrc-file" + end + end + @github + end + end + end +end \ No newline at end of file