require 'rubygems/version' require 'net/http' require 'json' require 'uri' # # Returns bot's username in GitHub. # # @return [String] def bot_username ENV.fetch('BOT_USERNAME', 'kite-bot') end # # Returns bot's displayed name in commits. # # @return [String] def bot_name ENV.fetch('BOT_NAME', 'Kite Bot') end # # Returns bot's displayed email in commits. # # @return [String] def bot_email ENV.fetch('BOT_EMAIL', 'kite-bot@rubykube.io') end # # Returns GitHub repository slug in form of :user|:organization/:repository. # # @return [String] def repository_slug ENV.fetch('REPOSITORY_SLUG', 'rubykube/kite') end # # Increments the newest available version which is in stage of development (master), # and publishes it on the GitHub. # # The requirements for this to work are: # 1) Repository has some tagged versions. # 2) The latest version is not released yet (e.g. there is no version-specific branch like "1-3-stable"). # # If you are on 1.1 and you want to start developing 1.2 do the following: # 1) Create branch "1-1-stable", and push up-to-date source to it. # 2) Push the same to master branch. # 3) Push new commit(s) to master, and tag master as 1.2.0. # 4) Future pushes to master will be treated as new patch version number. # def bump_from_master_branch # Get latest version of Kite. return unless (latest_version = versions.last) # Find a branch which is specific the version. # Comparision is based only on major and minor version numbers since # these type of branches are named with a convention like: "1-0-stable", "2-4-stable", and so on. linked_branch = version_specific_branches.find { |b| b[:version].segments == latest_version.segments[0...2] } # If branch exists it means that version has been already released. return if linked_branch # Increment patch version number, tag, and push. candidate_version = Gem::Version.new(latest_version.segments.dup.tap { |s| s[2] += 1 }.join('.')) tag_n_push(candidate_version.to_s, name: 'master') unless versions.include?(candidate_version) end # # Increments the version which is in stage of support (version-specific branches only), # and publishes it on the GitHub. # # The method expects branch name in form of "X-Y-stable", like "2-0-stable". # It tags the current Git commit to the next patch number version, and pushes it to Git repository. # # @param name [String] # Branch name. def bump_from_version_specific_branch(name) # This helps to ensure branch does exist. branch = version_specific_branches.find { |b| b[:name] == name } return unless branch # Find latest version for the branch (compare by major and minor). # We use find here since versions are sorted in descending order. latest_version = versions.reverse.find { |v| v.segments[0...2] == branch[:version].segments } return unless latest_version # Increment patch version number, tag, and push. candidate_version = Gem::Version.new(latest_version.segments.dup.tap { |s| s[2] += 1 }.join('.')) tag_n_push(candidate_version.to_s, branch) unless versions.include?(candidate_version) end # # Configures Git user name & email, # updates version at lib/kite/version.rb, # creates Git tag, and pushes all the changes made to repository. # # @param tag [String] def tag_n_push(tag, branch) File.open 'lib/kite/version.rb', 'w' do |f| f.write <<-RUBY module Kite VERSION = '#{tag}' end RUBY end [ %( git config --global user.email "#{bot_email}" ), %( git config --global user.name "#{bot_name}" ), %( git remote add authenticated-origin https://#{bot_username}:#{ENV.fetch('GITHUB_API_KEY')}@github.com/#{repository_slug} ), %( git checkout -b release ), %( git add lib/kite/version.rb ), %( git commit -m "[ci skip] Bump #{tag}." ), %( git push authenticated-origin release:#{branch.fetch(:name)} ), %( git tag #{tag} -a -m "Automatically generated tag from the Drone CI build #{ENV.fetch('DRONE_BUILD_NUMBER')}." ), %( git push authenticated-origin #{tag} ) ].each do |command| command.strip! unless Kernel.system(command) # Prevent GitHub API key from being published. command.gsub!(ENV['GITHUB_API_KEY'], '(secret)') raise %(Command "#{command}" exited with status #{$?.exitstatus || '(unavailable)'}.) end end end # # Loads all Kite tags, and returns them in ascending order. # # @return [Array] def versions @versions ||= github_api_authenticated_get("/repos/#{repository_slug}/tags").map do |x| Gem::Version.new(x.fetch('name')[/\d+\.\d+\.\d+/]) end.sort end # # Returns hash with all tagged commits as keys (SHA-1) and versions as values. # # @return [Hash] # Key is commit's SHA-1 hash, value is instance of Gem::Version. def tagged_commits_mapping @commits ||= github_api_authenticated_get("/repos/#{repository_slug}/tags").each_with_object({}) do |x, memo| memo[x.fetch('commit').fetch('sha')] = Gem::Version.new(x.fetch('name')[/\d+\.\d+\.\d+/]) end end # # Loads all Kite branches, selects only version-specific, and returns them. # # @return [Array] # Array of hashes each containing "name" & "version" keys. def version_specific_branches @branches ||= github_api_authenticated_get("/repos/#{repository_slug}/branches").map do |x| if x.fetch('name') =~ /\A(\d)-(\d)-\w+\z/ { name: x['name'], version: Gem::Version.new($1 + '.' + $2) } end end.compact end # # Performs call to GitHub API and returns the response. Raises in case of non-200 status. # # @param path [String] # Request path. # @return [Hash] def github_api_authenticated_get(path) http = Net::HTTP.new('api.github.com', 443) http.use_ssl = true response = http.get path, 'Authorization' => %[token #{ENV.fetch('GITHUB_API_KEY')}] if response.code.to_i == 200 JSON.load(response.body) else raise StandardError, %[HTTP #{response.code}: "#{response.body}".] end end # # Returns true if version has exactly 3 version segments (major, minor, patch), and all are integers. # # @param version [Gem::Version] # @return [true, false] def generic_semver?(version) version.segments.count == 3 && version.segments.all? { |segment| segment.match?(/\A[0-9]+\z/) } end bump_from_version_specific_branch(ENV.fetch('DRONE_BRANCH'))