From 736b772f6b0aa7256924998cc1b010e3f8a5fa8d Mon Sep 17 00:00:00 2001 From: Lukasz Kreczko Date: Tue, 29 May 2018 15:57:11 +0100 Subject: [PATCH 01/26] added gitlab fetcher --- .../gitlab_fetcher.rb | 479 ++++++++++++++++++ 1 file changed, 479 insertions(+) create mode 100644 lib/github_changelog_generator/gitlab_fetcher.rb diff --git a/lib/github_changelog_generator/gitlab_fetcher.rb b/lib/github_changelog_generator/gitlab_fetcher.rb new file mode 100644 index 000000000..4b148094c --- /dev/null +++ b/lib/github_changelog_generator/gitlab_fetcher.rb @@ -0,0 +1,479 @@ +# frozen_string_literal: true + +require "tmpdir" +require "retriable" +require +module GitHubChangelogGenerator + # A Fetcher responsible for all requests to GitHub and all basic manipulation with related data + # (such as filtering, validating, e.t.c) + # + # Example: + # fetcher = GitHubChangelogGenerator::OctoFetcher.new(options) + class GitlabFetcher + PER_PAGE_NUMBER = 100 + MAX_THREAD_NUMBER = 25 + MAX_FORBIDDEN_RETRIES = 100 + CHANGELOG_GITHUB_TOKEN = "CHANGELOG_GITHUB_TOKEN" + GH_RATE_LIMIT_EXCEEDED_MSG = "Warning: Can't finish operation: GitHub API rate limit exceeded, changelog may be " \ + "missing some issues. You can limit the number of issues fetched using the `--max-issues NUM` argument." + NO_TOKEN_PROVIDED = "Warning: No token provided (-t option) and variable $CHANGELOG_GITHUB_TOKEN was not found. " \ + "This script can make only 50 requests to GitHub API per hour without token!" + + # @param options [Hash] Options passed in + # @option options [String] :user GitHub username + # @option options [String] :project GitHub project + # @option options [String] :since Only issues updated at or after this time are returned. This is a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ. eg. Time.parse("2016-01-01 10:00:00").iso8601 + # @option options [Boolean] :http_cache Use ActiveSupport::Cache::FileStore to cache http requests + # @option options [Boolean] :cache_file If using http_cache, this is the cache file path + # @option options [Boolean] :cache_log If using http_cache, this is the cache log file path + def initialize(options = {}) + @options = options || {} + @user = @options[:user] + @project = @options[:project] + @since = @options[:since] + @http_cache = @options[:http_cache] + @cache_file = nil + @cache_log = nil + @commits = [] + @compares = {} + # prepare_cache + # configure_octokit_ssl + @client = Gitlab::Client.new(github_options) + end + + # def prepare_cache + # return unless @http_cache + # @cache_file = @options.fetch(:cache_file) { File.join(Dir.tmpdir, "github-changelog-http-cache") } + # @cache_log = @options.fetch(:cache_log) { File.join(Dir.tmpdir, "github-changelog-logger.log") } + # init_cache + # end + + def github_options + result = {} + github_token = fetch_github_token + result[:access_token] = github_token if github_token + endpoint = @options[:github_endpoint] + result[:api_endpoint] = endpoint if endpoint + result + end + + # def configure_octokit_ssl + # ca_file = @options[:ssl_ca_file] || ENV["SSL_CA_FILE"] || File.expand_path("ssl_certs/cacert.pem", __dir__) + # Octokit.connection_options = { ssl: { ca_file: ca_file } } + # end + + # def init_cache + # Octokit.middleware = Faraday::RackBuilder.new do |builder| + # builder.use(Faraday::HttpCache, serializer: Marshal, + # store: ActiveSupport::Cache::FileStore.new(@cache_file), + # logger: Logger.new(@cache_log), + # shared_cache: false) + # builder.use Octokit::Response::RaiseError + # builder.adapter Faraday.default_adapter + # # builder.response :logger + # end + # end + + DEFAULT_REQUEST_OPTIONS = { per_page: PER_PAGE_NUMBER } + + # Fetch all tags from repo + # + # @return [Array ] array of tags + def get_all_tags + print "Fetching tags...\r" if @options[:verbose] + + check_github_response { github_fetch_tags } + end + + # # Returns the number of pages for a API call + # # + # # @return [Integer] number of pages for this API call in total + # def calculate_pages(client, method, request_options) + # # Makes the first API call so that we can call last_response + # check_github_response do + # client.send(method, user_project, DEFAULT_REQUEST_OPTIONS.merge(request_options)) + # end + # + # last_response = client.last_response + # + # if (last_pg = last_response.rels[:last]) + # querystring_as_hash(last_pg.href)["page"].to_i + # else + # 1 + # end + # end + + # Fill input array with tags + # + # @return [Array ] array of tags in repo + def github_fetch_tags + tags = [] + page_i = 0 + count_pages = calculate_pages(@client, "tags", {}) + + iterate_pages(@client, "tags") do |new_tags| + page_i += PER_PAGE_NUMBER + print_in_same_line("Fetching tags... #{page_i}/#{count_pages * PER_PAGE_NUMBER}") + tags.concat(new_tags) + end + print_empty_line + + if tags.count == 0 + Helper.log.warn "Warning: Can't find any tags in repo. \ +Make sure, that you push tags to remote repo via 'git push --tags'" + else + Helper.log.info "Found #{tags.count} tags" + end + # tags are a Sawyer::Resource. Convert to hash + tags.map { |resource| stringify_keys_deep(resource.to_hash) } + end + + def closed_pr_options + @closed_pr_options ||= { + filter: "all", labels: nil, state: "closed" + }.tap { |options| options[:since] = @since if @since } + end + + # This method fetch all closed issues and separate them to pull requests and pure issues + # (pull request is kind of issue in term of GitHub) + # + # @return [Tuple] with (issues [Array ], pull-requests [Array ]) + def fetch_closed_issues_and_pr + print "Fetching closed issues...\r" if @options[:verbose] + issues = [] + page_i = 0 + count_pages = calculate_pages(@client, "issues", closed_pr_options) + + iterate_pages(@client, "issues", closed_pr_options) do |new_issues| + page_i += PER_PAGE_NUMBER + print_in_same_line("Fetching issues... #{page_i}/#{count_pages * PER_PAGE_NUMBER}") + issues.concat(new_issues) + break if @options[:max_issues] && issues.length >= @options[:max_issues] + end + print_empty_line + Helper.log.info "Received issues: #{issues.count}" + + # separate arrays of issues and pull requests: + issues.map { |issue| stringify_keys_deep(issue.to_hash) } + .partition { |issue_or_pr| issue_or_pr["pull_request"].nil? } + end + + # Fetch all pull requests. We need them to detect :merged_at parameter + # + # @return [Array ] all pull requests + def fetch_closed_pull_requests + pull_requests = [] + options = { state: "closed" } + + page_i = 0 + count_pages = calculate_pages(@client, "pull_requests", options) + + iterate_pages(@client, "pull_requests", options) do |new_pr| + page_i += PER_PAGE_NUMBER + log_string = "Fetching merged dates... #{page_i}/#{count_pages * PER_PAGE_NUMBER}" + print_in_same_line(log_string) + pull_requests.concat(new_pr) + end + print_empty_line + + Helper.log.info "Pull Request count: #{pull_requests.count}" + pull_requests.map { |pull_request| stringify_keys_deep(pull_request.to_hash) } + end + + # Fetch event for all issues and add them to 'events' + # + # @param [Array] issues + # @return [Void] + def fetch_events_async(issues) + i = 0 + threads = [] + + issues.each_slice(MAX_THREAD_NUMBER) do |issues_slice| + issues_slice.each do |issue| + threads << Thread.new do + issue["events"] = [] + iterate_pages(@client, "issue_events", issue["number"]) do |new_event| + issue["events"].concat(new_event) + end + issue["events"] = issue["events"].map { |event| stringify_keys_deep(event.to_hash) } + print_in_same_line("Fetching events for issues and PR: #{i + 1}/#{issues.count}") + i += 1 + end + end + threads.each(&:join) + threads = [] + end + + # to clear line from prev print + print_empty_line + + Helper.log.info "Fetching events for issues and PR: #{i}" + end + + # Fetch comments for PRs and add them to "comments" + # + # @param [Array] prs The array of PRs. + # @return [Void] No return; PRs are updated in-place. + def fetch_comments_async(prs) + threads = [] + + prs.each_slice(MAX_THREAD_NUMBER) do |prs_slice| + prs_slice.each do |pr| + threads << Thread.new do + pr["comments"] = [] + iterate_pages(@client, "issue_comments", pr["number"]) do |new_comment| + pr["comments"].concat(new_comment) + end + pr["comments"] = pr["comments"].map { |comment| stringify_keys_deep(comment.to_hash) } + end + end + threads.each(&:join) + threads = [] + end + nil + end + + # Fetch tag time from repo + # + # @param [Hash] tag GitHub data item about a Tag + # + # @return [Time] time of specified tag + def fetch_date_of_tag(tag) + commit_data = fetch_commit(tag["commit"]["sha"]) + commit_data = stringify_keys_deep(commit_data.to_hash) + + commit_data["commit"]["committer"]["date"] + end + + # Fetch and cache comparison between two github refs + # + # @param [String] older The older sha/tag/branch. + # @param [String] newer The newer sha/tag/branch. + # @return [Hash] Github api response for comparison. + def fetch_compare(older, newer) + unless @compares["#{older}...#{newer}"] + compare_data = check_github_response { @client.compare(user_project, older, newer || "HEAD") } + raise StandardError, "Sha #{older} and sha #{newer} are not related; please file a github-changelog-generator issues and describe how to replicate this issue." if compare_data["status"] == "diverged" + @compares["#{older}...#{newer}"] = stringify_keys_deep(compare_data.to_hash) + end + @compares["#{older}...#{newer}"] + end + + # Fetch commit for specified event + # + # @param [String] commit_id the SHA of a commit to fetch + # @return [Hash] + def fetch_commit(commit_id) + found = commits.find do |commit| + commit["sha"] == commit_id + end + if found + stringify_keys_deep(found.to_hash) + else + # cache miss; don't add to @commits because unsure of order. + check_github_response do + commit = @client.commit(user_project, commit_id) + commit = stringify_keys_deep(commit.to_hash) + commit + end + end + end + + # Fetch all commits + # + # @return [Array] Commits in a repo. + def commits + if @commits.empty? + iterate_pages(@client, "commits") do |new_commits| + @commits.concat(new_commits) + end + end + @commits + end + + # Return the oldest commit in a repo + # + # @return [Hash] Oldest commit in the github git history. + def oldest_commit + commits.last + end + + # @return [String] Default branch of the repo + def default_branch + @default_branch ||= @client.repository(user_project)[:default_branch] + end + + # Fetch all SHAs occurring in or before a given tag and add them to + # "shas_in_tag" + # + # @param [Array] tags The array of tags. + # @return [Nil] No return; tags are updated in-place. + def fetch_tag_shas_async(tags) + i = 0 + threads = [] + print_in_same_line("Fetching SHAs for tags: #{i}/#{tags.count}\r") if @options[:verbose] + + tags.each_slice(MAX_THREAD_NUMBER) do |tags_slice| + tags_slice.each do |tag| + threads << Thread.new do + # Use oldest commit because comparing two arbitrary tags may be diverged + commits_in_tag = fetch_compare(oldest_commit["sha"], tag["name"]) + tag["shas_in_tag"] = commits_in_tag["commits"].collect { |commit| commit["sha"] } + print_in_same_line("Fetching SHAs for tags: #{i + 1}/#{tags.count}") if @options[:verbose] + i += 1 + end + end + threads.each(&:join) + threads = [] + end + + # to clear line from prev print + print_empty_line + + Helper.log.info "Fetching SHAs for tags: #{i}" + nil + end + + private + + def stringify_keys_deep(indata) + case indata + when Array + indata.map do |value| + stringify_keys_deep(value) + end + when Hash + indata.each_with_object({}) do |(key, value), output| + output[key.to_s] = stringify_keys_deep(value) + end + else + indata + end + end + + # Exception raised to warn about moved repositories. + MovedPermanentlyError = Class.new(RuntimeError) + + # Iterates through all pages until there are no more :next pages to follow + # yields the result per page + # + # @param [Octokit::Client] client + # @param [String] method (eg. 'tags') + # + # @yield [Sawyer::Resource] An OctoKit-provided response (which can be empty) + # + # @return [void] + def iterate_pages(client, method, *args) + args << DEFAULT_REQUEST_OPTIONS.merge(extract_request_args(args)) + + check_github_response { client.send(method, user_project, *args) } + last_response = client.last_response.tap do |response| + raise(MovedPermanentlyError, response.data[:url]) if response.status == 301 + end + + yield(last_response.data) + + until (next_one = last_response.rels[:next]).nil? + last_response = check_github_response { next_one.get } + yield(last_response.data) + end + end + + def extract_request_args(args) + if args.size == 1 && args.first.is_a?(Hash) + args.delete_at(0) + elsif args.size > 1 && args.last.is_a?(Hash) + args.delete_at(args.length - 1) + else + {} + end + end + + # This is wrapper with rescue block + # + # @return [Object] returns exactly the same, what you put in the block, but wrap it with begin-rescue block + def check_github_response + Retriable.retriable(retry_options) do + yield + end + rescue MovedPermanentlyError => e + fail_with_message(e, "The repository has moved, update your configuration") + rescue Gitlab::Error::Forbidden => e + fail_with_message(e, "Exceeded retry limit") + rescue Gitlab::Error::Unauthorized => e + fail_with_message(e, "Error: wrong GitHub token") + end + + # Presents the exception, and the aborts with the message. + def fail_with_message(error, message) + Helper.log.error("#{error.class}: #{error.message}") + sys_abort(message) + end + + # Exponential backoff + def retry_options + { + on: [Gitlab::Error::Forbidden], + tries: MAX_FORBIDDEN_RETRIES, + base_interval: sleep_base_interval, + multiplier: 1.0, + rand_factor: 0.0, + on_retry: retry_callback + } + end + + def sleep_base_interval + 1.0 + end + + def retry_callback + proc do |exception, try, elapsed_time, next_interval| + Helper.log.warn("RETRY - #{exception.class}: '#{exception.message}'") + Helper.log.warn("#{try} tries in #{elapsed_time} seconds and #{next_interval} seconds until the next try") + Helper.log.warn GH_RATE_LIMIT_EXCEEDED_MSG + Helper.log.warn @client.rate_limit + end + end + + def sys_abort(msg) + abort(msg) + end + + # Print specified line on the same string + # + # @param [String] log_string + def print_in_same_line(log_string) + print log_string + "\r" + end + + # Print long line with spaces on same line to clear prev message + def print_empty_line + print_in_same_line(" ") + end + + # Returns GitHub token. First try to use variable, provided by --token option, + # otherwise try to fetch it from CHANGELOG_GITHUB_TOKEN env variable. + # + # @return [String] + def fetch_github_token + env_var = @options[:token].presence || ENV["CHANGELOG_GITHUB_TOKEN"] + + Helper.log.warn NO_TOKEN_PROVIDED unless env_var + + env_var + end + + # @return [String] helper to return Github "user/project" + def user_project + "#{@options[:user]}/#{@options[:project]}" + end + + # Returns Hash of all querystring variables in given URI. + # + # @param [String] uri eg. https://api.github.com/repositories/43914960/tags?page=37&foo=1 + # @return [Hash] of all GET variables. eg. { 'page' => 37, 'foo' => 1 } + def querystring_as_hash(uri) + Hash[URI.decode_www_form(URI(uri).query || "")] + end + end +end From c2e9ed990f7304d5b376c001d51e0736b8f6acfa Mon Sep 17 00:00:00 2001 From: kreczko Date: Tue, 29 May 2018 17:36:33 +0100 Subject: [PATCH 02/26] implemented fetch_tags for GITLAB --- .../gitlab_fetcher.rb | 96 +++++++------------ 1 file changed, 35 insertions(+), 61 deletions(-) diff --git a/lib/github_changelog_generator/gitlab_fetcher.rb b/lib/github_changelog_generator/gitlab_fetcher.rb index 4b148094c..7292b5150 100644 --- a/lib/github_changelog_generator/gitlab_fetcher.rb +++ b/lib/github_changelog_generator/gitlab_fetcher.rb @@ -2,7 +2,7 @@ require "tmpdir" require "retriable" -require +require "gitlab" module GitHubChangelogGenerator # A Fetcher responsible for all requests to GitHub and all basic manipulation with related data # (such as filtering, validating, e.t.c) @@ -36,44 +36,21 @@ def initialize(options = {}) @cache_log = nil @commits = [] @compares = {} - # prepare_cache - # configure_octokit_ssl - @client = Gitlab::Client.new(github_options) - end - # def prepare_cache - # return unless @http_cache - # @cache_file = @options.fetch(:cache_file) { File.join(Dir.tmpdir, "github-changelog-http-cache") } - # @cache_log = @options.fetch(:cache_log) { File.join(Dir.tmpdir, "github-changelog-logger.log") } - # init_cache - # end + Gitlab.sudo = nil + @client = Gitlab::Client.new(gitlab_options) + @project_id = @client.project_search(@project).first.id + end - def github_options + def gitlab_options result = {} - github_token = fetch_github_token - result[:access_token] = github_token if github_token + access_token = fetch_github_token + result[:private_token] = access_token if access_token endpoint = @options[:github_endpoint] - result[:api_endpoint] = endpoint if endpoint + result[:endpoint] = endpoint if endpoint result end - # def configure_octokit_ssl - # ca_file = @options[:ssl_ca_file] || ENV["SSL_CA_FILE"] || File.expand_path("ssl_certs/cacert.pem", __dir__) - # Octokit.connection_options = { ssl: { ca_file: ca_file } } - # end - - # def init_cache - # Octokit.middleware = Faraday::RackBuilder.new do |builder| - # builder.use(Faraday::HttpCache, serializer: Marshal, - # store: ActiveSupport::Cache::FileStore.new(@cache_file), - # logger: Logger.new(@cache_log), - # shared_cache: false) - # builder.use Octokit::Response::RaiseError - # builder.adapter Faraday.default_adapter - # # builder.response :logger - # end - # end - DEFAULT_REQUEST_OPTIONS = { per_page: PER_PAGE_NUMBER } # Fetch all tags from repo @@ -82,39 +59,36 @@ def github_options def get_all_tags print "Fetching tags...\r" if @options[:verbose] - check_github_response { github_fetch_tags } + check_response { fetch_tags } end - # # Returns the number of pages for a API call - # # - # # @return [Integer] number of pages for this API call in total - # def calculate_pages(client, method, request_options) - # # Makes the first API call so that we can call last_response - # check_github_response do - # client.send(method, user_project, DEFAULT_REQUEST_OPTIONS.merge(request_options)) - # end + # Returns the number of pages for a API call # - # last_response = client.last_response - # - # if (last_pg = last_response.rels[:last]) - # querystring_as_hash(last_pg.href)["page"].to_i - # else - # 1 - # end - # end + # @return [Integer] number of pages for this API call in total + def calculate_pages(client, method, request_options) + # Makes the first API call so that we can call last_response + check_response do + client.send(method, user_project, DEFAULT_REQUEST_OPTIONS.merge(request_options)) + end + + last_response = client.last_response + + if (last_pg = last_response.rels[:last]) + querystring_as_hash(last_pg.href)["page"].to_i + else + 1 + end + end # Fill input array with tags # # @return [Array ] array of tags in repo - def github_fetch_tags + def fetch_tags tags = [] - page_i = 0 - count_pages = calculate_pages(@client, "tags", {}) + new_tags = @client.tags(@project_id, DEFAULT_REQUEST_OPTIONS) - iterate_pages(@client, "tags") do |new_tags| - page_i += PER_PAGE_NUMBER - print_in_same_line("Fetching tags... #{page_i}/#{count_pages * PER_PAGE_NUMBER}") - tags.concat(new_tags) + new_tags.auto_paginate do |new_tag| + tags.push(new_tag) end print_empty_line @@ -252,7 +226,7 @@ def fetch_date_of_tag(tag) # @return [Hash] Github api response for comparison. def fetch_compare(older, newer) unless @compares["#{older}...#{newer}"] - compare_data = check_github_response { @client.compare(user_project, older, newer || "HEAD") } + compare_data = check_response { @client.compare(user_project, older, newer || "HEAD") } raise StandardError, "Sha #{older} and sha #{newer} are not related; please file a github-changelog-generator issues and describe how to replicate this issue." if compare_data["status"] == "diverged" @compares["#{older}...#{newer}"] = stringify_keys_deep(compare_data.to_hash) end @@ -271,7 +245,7 @@ def fetch_commit(commit_id) stringify_keys_deep(found.to_hash) else # cache miss; don't add to @commits because unsure of order. - check_github_response do + check_response do commit = @client.commit(user_project, commit_id) commit = stringify_keys_deep(commit.to_hash) commit @@ -366,7 +340,7 @@ def stringify_keys_deep(indata) def iterate_pages(client, method, *args) args << DEFAULT_REQUEST_OPTIONS.merge(extract_request_args(args)) - check_github_response { client.send(method, user_project, *args) } + check_response { client.send(method, user_project, *args) } last_response = client.last_response.tap do |response| raise(MovedPermanentlyError, response.data[:url]) if response.status == 301 end @@ -374,7 +348,7 @@ def iterate_pages(client, method, *args) yield(last_response.data) until (next_one = last_response.rels[:next]).nil? - last_response = check_github_response { next_one.get } + last_response = check_response { next_one.get } yield(last_response.data) end end @@ -392,7 +366,7 @@ def extract_request_args(args) # This is wrapper with rescue block # # @return [Object] returns exactly the same, what you put in the block, but wrap it with begin-rescue block - def check_github_response + def check_response Retriable.retriable(retry_options) do yield end From 7e6d1e2c3de948a046e8d041b40a0ace3dfc2c22 Mon Sep 17 00:00:00 2001 From: kreczko Date: Wed, 30 May 2018 14:18:14 +0100 Subject: [PATCH 03/26] first successful runthrough with gitlab_fetcher --- .../gitlab_fetcher.rb | 126 ++++++------------ 1 file changed, 43 insertions(+), 83 deletions(-) diff --git a/lib/github_changelog_generator/gitlab_fetcher.rb b/lib/github_changelog_generator/gitlab_fetcher.rb index 7292b5150..08d7945ad 100644 --- a/lib/github_changelog_generator/gitlab_fetcher.rb +++ b/lib/github_changelog_generator/gitlab_fetcher.rb @@ -1,8 +1,11 @@ # frozen_string_literal: true +require 'date' require "tmpdir" require "retriable" require "gitlab" + + module GitHubChangelogGenerator # A Fetcher responsible for all requests to GitHub and all basic manipulation with related data # (such as filtering, validating, e.t.c) @@ -20,8 +23,8 @@ class GitlabFetcher "This script can make only 50 requests to GitHub API per hour without token!" # @param options [Hash] Options passed in - # @option options [String] :user GitHub username - # @option options [String] :project GitHub project + # @option options [String] :user Gitlab username + # @option options [String] :project Gitlab project # @option options [String] :since Only issues updated at or after this time are returned. This is a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ. eg. Time.parse("2016-01-01 10:00:00").iso8601 # @option options [Boolean] :http_cache Use ActiveSupport::Cache::FileStore to cache http requests # @option options [Boolean] :cache_file If using http_cache, this is the cache file path @@ -62,24 +65,6 @@ def get_all_tags check_response { fetch_tags } end - # Returns the number of pages for a API call - # - # @return [Integer] number of pages for this API call in total - def calculate_pages(client, method, request_options) - # Makes the first API call so that we can call last_response - check_response do - client.send(method, user_project, DEFAULT_REQUEST_OPTIONS.merge(request_options)) - end - - last_response = client.last_response - - if (last_pg = last_response.rels[:last]) - querystring_as_hash(last_pg.href)["page"].to_i - else - 1 - end - end - # Fill input array with tags # # @return [Array ] array of tags in repo @@ -104,7 +89,7 @@ def fetch_tags def closed_pr_options @closed_pr_options ||= { - filter: "all", labels: nil, state: "closed" + filter: "all", labels: nil, state: "merged" }.tap { |options| options[:since] = @since if @since } end @@ -114,22 +99,15 @@ def closed_pr_options # @return [Tuple] with (issues [Array ], pull-requests [Array ]) def fetch_closed_issues_and_pr print "Fetching closed issues...\r" if @options[:verbose] - issues = [] - page_i = 0 - count_pages = calculate_pages(@client, "issues", closed_pr_options) - - iterate_pages(@client, "issues", closed_pr_options) do |new_issues| - page_i += PER_PAGE_NUMBER - print_in_same_line("Fetching issues... #{page_i}/#{count_pages * PER_PAGE_NUMBER}") - issues.concat(new_issues) - break if @options[:max_issues] && issues.length >= @options[:max_issues] - end + print_empty_line + issues = @client.issues(@project_id, DEFAULT_REQUEST_OPTIONS) + p issues.first + print_empty_line Helper.log.info "Received issues: #{issues.count}" # separate arrays of issues and pull requests: - issues.map { |issue| stringify_keys_deep(issue.to_hash) } - .partition { |issue_or_pr| issue_or_pr["pull_request"].nil? } + return issues.map { |issue| stringify_keys_deep(issue.to_hash) }, fetch_closed_pull_requests end # Fetch all pull requests. We need them to detect :merged_at parameter @@ -137,17 +115,16 @@ def fetch_closed_issues_and_pr # @return [Array ] all pull requests def fetch_closed_pull_requests pull_requests = [] - options = { state: "closed" } - - page_i = 0 - count_pages = calculate_pages(@client, "pull_requests", options) - - iterate_pages(@client, "pull_requests", options) do |new_pr| - page_i += PER_PAGE_NUMBER - log_string = "Fetching merged dates... #{page_i}/#{count_pages * PER_PAGE_NUMBER}" - print_in_same_line(log_string) - pull_requests.concat(new_pr) + options = { state: "merged", scope: :all} + + @client.merge_requests(@project_id, options).auto_paginate do |new_pr| + new_pr = stringify_keys_deep(new_pr.to_hash) + # align with Github naming + new_pr["number"] = new_pr["iid"] + new_pr["merged_at"] = new_pr["updated_at"] + pull_requests.push(new_pr) end + print_empty_line Helper.log.info "Pull Request count: #{pull_requests.count}" @@ -166,8 +143,8 @@ def fetch_events_async(issues) issues_slice.each do |issue| threads << Thread.new do issue["events"] = [] - iterate_pages(@client, "issue_events", issue["number"]) do |new_event| - issue["events"].concat(new_event) + @client.project_events(@project_id).auto_paginate do |new_event| + issue["events"].push(new_event) end issue["events"] = issue["events"].map { |event| stringify_keys_deep(event.to_hash) } print_in_same_line("Fetching events for issues and PR: #{i + 1}/#{issues.count}") @@ -195,8 +172,8 @@ def fetch_comments_async(prs) prs_slice.each do |pr| threads << Thread.new do pr["comments"] = [] - iterate_pages(@client, "issue_comments", pr["number"]) do |new_comment| - pr["comments"].concat(new_comment) + @client.merge_request_notes(@project_id, pr["number"]) do |new_comment| + pr["comments"].push(new_comment) end pr["comments"] = pr["comments"].map { |comment| stringify_keys_deep(comment.to_hash) } end @@ -213,10 +190,7 @@ def fetch_comments_async(prs) # # @return [Time] time of specified tag def fetch_date_of_tag(tag) - commit_data = fetch_commit(tag["commit"]["sha"]) - commit_data = stringify_keys_deep(commit_data.to_hash) - - commit_data["commit"]["committer"]["date"] + DateTime.parse(tag["commit"]["committed_date"]) end # Fetch and cache comparison between two github refs @@ -226,8 +200,15 @@ def fetch_date_of_tag(tag) # @return [Hash] Github api response for comparison. def fetch_compare(older, newer) unless @compares["#{older}...#{newer}"] - compare_data = check_response { @client.compare(user_project, older, newer || "HEAD") } - raise StandardError, "Sha #{older} and sha #{newer} are not related; please file a github-changelog-generator issues and describe how to replicate this issue." if compare_data["status"] == "diverged" + compare_data = check_response { @client.compare(@project_id, older, newer || "HEAD") } + compare_data = stringify_keys_deep(compare_data.to_hash) + compare_data["commits"].each do |commit| + commit["sha"] = commit["id"] + end + # TODO: do not know what the equivalent for gitlab is + if compare_data["compare_same_ref"] == true then + raise StandardError, "Sha #{older} and sha #{newer} are not related; please file a github-changelog-generator issues and describe how to replicate this issue." + end @compares["#{older}...#{newer}"] = stringify_keys_deep(compare_data.to_hash) end @compares["#{older}...#{newer}"] @@ -239,15 +220,16 @@ def fetch_compare(older, newer) # @return [Hash] def fetch_commit(commit_id) found = commits.find do |commit| - commit["sha"] == commit_id + commit['sha'] == commit_id end if found stringify_keys_deep(found.to_hash) else # cache miss; don't add to @commits because unsure of order. check_response do - commit = @client.commit(user_project, commit_id) + commit = @client.commit(@project_id, commit_id) commit = stringify_keys_deep(commit.to_hash) + commit['sha'] = commit['id'] commit end end @@ -258,8 +240,10 @@ def fetch_commit(commit_id) # @return [Array] Commits in a repo. def commits if @commits.empty? - iterate_pages(@client, "commits") do |new_commits| - @commits.concat(new_commits) + @client.commits(@project_id).auto_paginate do |new_commit| + new_commit = stringify_keys_deep(new_commit.to_hash) + new_commit['sha'] = new_commit['id'] + @commits.push(new_commit) end end @commits @@ -267,14 +251,14 @@ def commits # Return the oldest commit in a repo # - # @return [Hash] Oldest commit in the github git history. + # @return [Hash] Oldest commit in the gitlab git history. def oldest_commit commits.last end # @return [String] Default branch of the repo def default_branch - @default_branch ||= @client.repository(user_project)[:default_branch] + @default_branch ||= @client.project(@project_id)[:default_branch] end # Fetch all SHAs occurring in or before a given tag and add them to @@ -328,30 +312,6 @@ def stringify_keys_deep(indata) # Exception raised to warn about moved repositories. MovedPermanentlyError = Class.new(RuntimeError) - # Iterates through all pages until there are no more :next pages to follow - # yields the result per page - # - # @param [Octokit::Client] client - # @param [String] method (eg. 'tags') - # - # @yield [Sawyer::Resource] An OctoKit-provided response (which can be empty) - # - # @return [void] - def iterate_pages(client, method, *args) - args << DEFAULT_REQUEST_OPTIONS.merge(extract_request_args(args)) - - check_response { client.send(method, user_project, *args) } - last_response = client.last_response.tap do |response| - raise(MovedPermanentlyError, response.data[:url]) if response.status == 301 - end - - yield(last_response.data) - - until (next_one = last_response.rels[:next]).nil? - last_response = check_response { next_one.get } - yield(last_response.data) - end - end def extract_request_args(args) if args.size == 1 && args.first.is_a?(Hash) From 468a7cbfa48c85fb15462a79b1ab2b3cb22822bf Mon Sep 17 00:00:00 2001 From: kreczko Date: Wed, 30 May 2018 14:19:54 +0100 Subject: [PATCH 04/26] adjusting generator_fetcher to accept pr["merge_commit_sha"] if available --- .../generator/generator_fetcher.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/github_changelog_generator/generator/generator_fetcher.rb b/lib/github_changelog_generator/generator/generator_fetcher.rb index fabe6ef3b..5d851af97 100644 --- a/lib/github_changelog_generator/generator/generator_fetcher.rb +++ b/lib/github_changelog_generator/generator/generator_fetcher.rb @@ -75,9 +75,16 @@ def associate_tagged_prs(tags, prs, total) # fetch that. See # https://developer.github.com/v3/pulls/#get-a-single-pull-request vs. # https://developer.github.com/v3/pulls/#list-pull-requests - if pr["events"] && (event = pr["events"].find { |e| e["event"] == "merged" }) + # gitlab API has this + merge_commit_sha = nil + if pr.has_key?("merge_commit_sha") then + merge_commit_sha = pr["merge_commit_sha"] + elsif pr["events"] && (event = pr["events"].find { |e| e["event"] == "merged" }) + merge_commit_sha = event["commit_id"] + end + unless merge_commit_sha.nil? # Iterate tags.reverse (oldest to newest) to find first tag of each PR. - if (oldest_tag = tags.reverse.find { |tag| tag["shas_in_tag"].include?(event["commit_id"]) }) + if (oldest_tag = tags.reverse.find { |tag| tag["shas_in_tag"].include?(merge_commit_sha) }) pr["first_occurring_tag"] = oldest_tag["name"] found = true i += 1 From df91ce22b0ff857fbb8c3662b0b1d655cb0ea337 Mon Sep 17 00:00:00 2001 From: kreczko Date: Wed, 30 May 2018 14:28:53 +0100 Subject: [PATCH 05/26] added gitlab option --- lib/github_changelog_generator/options.rb | 1 + lib/github_changelog_generator/parser.rb | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/github_changelog_generator/options.rb b/lib/github_changelog_generator/options.rb index 8370d9be3..62237a864 100644 --- a/lib/github_changelog_generator/options.rb +++ b/lib/github_changelog_generator/options.rb @@ -41,6 +41,7 @@ class Options < SimpleDelegator future_release github_endpoint github_site + gitlab header http_cache include_labels diff --git a/lib/github_changelog_generator/parser.rb b/lib/github_changelog_generator/parser.rb index 90a7f07b2..70087dd4d 100755 --- a/lib/github_changelog_generator/parser.rb +++ b/lib/github_changelog_generator/parser.rb @@ -176,6 +176,9 @@ def self.setup_parser(options) opts.on("--github-api [URL]", "The enterprise endpoint to use for your GitHub API.") do |last| options[:github_endpoint] = last end + opts.on("--[no-]gitlab", "Use Gitlab API instead of Github. Default is false.") do |last| + options[:gitlab] = last + end opts.on("--simple-list", "Create a simple list from issues and pull requests. Default is false.") do |v| options[:simple_list] = v end @@ -253,7 +256,8 @@ def self.default_options removed_prefix: "**Removed:**", security_prefix: "**Security fixes:**", http_cache: true, - require: [] + require: [], + gitlab: false ) end end From 89051212a83bc1626520db64d92752198f17ff7b Mon Sep 17 00:00:00 2001 From: kreczko Date: Wed, 30 May 2018 14:29:33 +0100 Subject: [PATCH 06/26] select correct fetcher depending on gitlab option (default github) --- lib/github_changelog_generator/generator/generator.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/github_changelog_generator/generator/generator.rb b/lib/github_changelog_generator/generator/generator.rb index dbc5af137..851d4d8ce 100644 --- a/lib/github_changelog_generator/generator/generator.rb +++ b/lib/github_changelog_generator/generator/generator.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "github_changelog_generator/octo_fetcher" +require "github_changelog_generator/gitlab_fetcher" require "github_changelog_generator/generator/generator_fetcher" require "github_changelog_generator/generator/generator_processor" require "github_changelog_generator/generator/generator_tags" @@ -34,7 +35,7 @@ class Generator def initialize(options = {}) @options = options @tag_times_hash = {} - @fetcher = GitHubChangelogGenerator::OctoFetcher.new(options) + @fetcher = options[:gitlab] ? GitHubChangelogGenerator::GitlabFetcher.new(options) : GitHubChangelogGenerator::OctoFetcher.new(options) @sections = [] end From 4f24ec07388e4fd44227799697c0c1a032b4ab19 Mon Sep 17 00:00:00 2001 From: kreczko Date: Wed, 30 May 2018 15:10:38 +0100 Subject: [PATCH 07/26] restricting gitlab fetcher to closed issues --- lib/github_changelog_generator/gitlab_fetcher.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/github_changelog_generator/gitlab_fetcher.rb b/lib/github_changelog_generator/gitlab_fetcher.rb index 08d7945ad..02f0a0e0e 100644 --- a/lib/github_changelog_generator/gitlab_fetcher.rb +++ b/lib/github_changelog_generator/gitlab_fetcher.rb @@ -99,9 +99,8 @@ def closed_pr_options # @return [Tuple] with (issues [Array ], pull-requests [Array ]) def fetch_closed_issues_and_pr print "Fetching closed issues...\r" if @options[:verbose] - print_empty_line - issues = @client.issues(@project_id, DEFAULT_REQUEST_OPTIONS) - p issues.first + options = { state: "closed", scope: :all} + issues = @client.issues(@project_id, DEFAULT_REQUEST_OPTIONS.merge(options)) print_empty_line Helper.log.info "Received issues: #{issues.count}" From 4ef20331fbfe3d808d12908af88cfd17ead6485e Mon Sep 17 00:00:00 2001 From: kreczko Date: Wed, 30 May 2018 15:23:20 +0100 Subject: [PATCH 08/26] fixed authors in merge_requests for gitlab fetcher --- .../gitlab_fetcher.rb | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/github_changelog_generator/gitlab_fetcher.rb b/lib/github_changelog_generator/gitlab_fetcher.rb index 02f0a0e0e..c2a494bd9 100644 --- a/lib/github_changelog_generator/gitlab_fetcher.rb +++ b/lib/github_changelog_generator/gitlab_fetcher.rb @@ -1,11 +1,10 @@ # frozen_string_literal: true -require 'date' +require "date" require "tmpdir" require "retriable" require "gitlab" - module GitHubChangelogGenerator # A Fetcher responsible for all requests to GitHub and all basic manipulation with related data # (such as filtering, validating, e.t.c) @@ -69,7 +68,7 @@ def get_all_tags # # @return [Array ] array of tags in repo def fetch_tags - tags = [] + tags = [] new_tags = @client.tags(@project_id, DEFAULT_REQUEST_OPTIONS) new_tags.auto_paginate do |new_tag| @@ -99,14 +98,14 @@ def closed_pr_options # @return [Tuple] with (issues [Array ], pull-requests [Array ]) def fetch_closed_issues_and_pr print "Fetching closed issues...\r" if @options[:verbose] - options = { state: "closed", scope: :all} + options = { state: "closed", scope: :all } issues = @client.issues(@project_id, DEFAULT_REQUEST_OPTIONS.merge(options)) print_empty_line Helper.log.info "Received issues: #{issues.count}" # separate arrays of issues and pull requests: - return issues.map { |issue| stringify_keys_deep(issue.to_hash) }, fetch_closed_pull_requests + [issues.map { |issue| stringify_keys_deep(issue.to_hash) }, fetch_closed_pull_requests] end # Fetch all pull requests. We need them to detect :merged_at parameter @@ -114,13 +113,15 @@ def fetch_closed_issues_and_pr # @return [Array ] all pull requests def fetch_closed_pull_requests pull_requests = [] - options = { state: "merged", scope: :all} + options = { state: "merged", scope: :all } @client.merge_requests(@project_id, options).auto_paginate do |new_pr| new_pr = stringify_keys_deep(new_pr.to_hash) # align with Github naming new_pr["number"] = new_pr["iid"] new_pr["merged_at"] = new_pr["updated_at"] + new_pr["pull_request"] = true + new_pr["user"] = { login: new_pr["author"]["username"], html_url: new_pr["author"]["web_url"] } pull_requests.push(new_pr) end @@ -205,7 +206,7 @@ def fetch_compare(older, newer) commit["sha"] = commit["id"] end # TODO: do not know what the equivalent for gitlab is - if compare_data["compare_same_ref"] == true then + if compare_data["compare_same_ref"] == true raise StandardError, "Sha #{older} and sha #{newer} are not related; please file a github-changelog-generator issues and describe how to replicate this issue." end @compares["#{older}...#{newer}"] = stringify_keys_deep(compare_data.to_hash) @@ -219,7 +220,7 @@ def fetch_compare(older, newer) # @return [Hash] def fetch_commit(commit_id) found = commits.find do |commit| - commit['sha'] == commit_id + commit["sha"] == commit_id end if found stringify_keys_deep(found.to_hash) @@ -228,7 +229,7 @@ def fetch_commit(commit_id) check_response do commit = @client.commit(@project_id, commit_id) commit = stringify_keys_deep(commit.to_hash) - commit['sha'] = commit['id'] + commit["sha"] = commit["id"] commit end end @@ -241,7 +242,7 @@ def commits if @commits.empty? @client.commits(@project_id).auto_paginate do |new_commit| new_commit = stringify_keys_deep(new_commit.to_hash) - new_commit['sha'] = new_commit['id'] + new_commit["sha"] = new_commit["id"] @commits.push(new_commit) end end @@ -311,7 +312,6 @@ def stringify_keys_deep(indata) # Exception raised to warn about moved repositories. MovedPermanentlyError = Class.new(RuntimeError) - def extract_request_args(args) if args.size == 1 && args.first.is_a?(Hash) args.delete_at(0) From 18ede113e3679da79cebb07eb77927f63072bb87 Mon Sep 17 00:00:00 2001 From: kreczko Date: Wed, 30 May 2018 16:29:34 +0100 Subject: [PATCH 09/26] fixed typo in generator_fetcher --- lib/github_changelog_generator/generator/generator_fetcher.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/github_changelog_generator/generator/generator_fetcher.rb b/lib/github_changelog_generator/generator/generator_fetcher.rb index 5d851af97..98e3b64ed 100644 --- a/lib/github_changelog_generator/generator/generator_fetcher.rb +++ b/lib/github_changelog_generator/generator/generator_fetcher.rb @@ -165,7 +165,7 @@ def associate_rebase_comment_prs(tags, prs_left, total) # @param [Hash] issue def find_closed_date_by_commit(issue) unless issue["events"].nil? - # if it's PR -> then find "merged event", in case of usual issue -> fond closed date + # if it's PR -> then find "merged event", in case of usual issue -> find closed date compare_string = issue["merged_at"].nil? ? "closed" : "merged" # reverse! - to find latest closed event. (event goes in date order) issue["events"].reverse!.each do |event| From 86b41c0a2ff953c6386877e23aa5777ba6317c35 Mon Sep 17 00:00:00 2001 From: kreczko Date: Wed, 30 May 2018 16:30:22 +0100 Subject: [PATCH 10/26] fixed issues and associated events in gitlab_fetcher --- .../gitlab_fetcher.rb | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/lib/github_changelog_generator/gitlab_fetcher.rb b/lib/github_changelog_generator/gitlab_fetcher.rb index c2a494bd9..a65d8740e 100644 --- a/lib/github_changelog_generator/gitlab_fetcher.rb +++ b/lib/github_changelog_generator/gitlab_fetcher.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "date" require "tmpdir" require "retriable" require "gitlab" @@ -97,9 +96,16 @@ def closed_pr_options # # @return [Tuple] with (issues [Array ], pull-requests [Array ]) def fetch_closed_issues_and_pr + issues = [] print "Fetching closed issues...\r" if @options[:verbose] options = { state: "closed", scope: :all } - issues = @client.issues(@project_id, DEFAULT_REQUEST_OPTIONS.merge(options)) + @client.issues(@project_id, DEFAULT_REQUEST_OPTIONS.merge(options)).auto_paginate do |issue| + issue = stringify_keys_deep(issue.to_hash) + issue["body"] = issue["description"] + issue["html_url"] = issue["web_url"] + issue["number"] = issue["iid"] + issues.push(issue) + end print_empty_line Helper.log.info "Received issues: #{issues.count}" @@ -119,6 +125,7 @@ def fetch_closed_pull_requests new_pr = stringify_keys_deep(new_pr.to_hash) # align with Github naming new_pr["number"] = new_pr["iid"] + new_pr["html_url"] = new_pr["web_url"] new_pr["merged_at"] = new_pr["updated_at"] new_pr["pull_request"] = true new_pr["user"] = { login: new_pr["author"]["username"], html_url: new_pr["author"]["web_url"] } @@ -138,15 +145,28 @@ def fetch_closed_pull_requests def fetch_events_async(issues) i = 0 threads = [] + options = { target_type: "issue" } + issue_events = [] + @client.project_events(@project_id, options).auto_paginate do |event| + event = stringify_keys_deep(event.to_hash) + # gitlab to github + event["event"] = event["action_name"] + issue_events.push(event) + end + # p issue_events issues.each_slice(MAX_THREAD_NUMBER) do |issues_slice| issues_slice.each do |issue| threads << Thread.new do issue["events"] = [] - @client.project_events(@project_id).auto_paginate do |new_event| - issue["events"].push(new_event) + issue_events.each do |new_event| + if issue["id"] == new_event["target_id"] + if new_event["action_name"].eql? "closed" + issue["closed_at"] = issue["closed_at"].nil? ? new_event["created_at"] : issue["closed_at"] + end + issue["events"].push(new_event) + end end - issue["events"] = issue["events"].map { |event| stringify_keys_deep(event.to_hash) } print_in_same_line("Fetching events for issues and PR: #{i + 1}/#{issues.count}") i += 1 end @@ -190,7 +210,7 @@ def fetch_comments_async(prs) # # @return [Time] time of specified tag def fetch_date_of_tag(tag) - DateTime.parse(tag["commit"]["committed_date"]) + Time.parse(tag["commit"]["committed_date"]) end # Fetch and cache comparison between two github refs From 2c261cb20b6117f412f4c9a0319857aa4c70d5bd Mon Sep 17 00:00:00 2001 From: kreczko Date: Wed, 30 May 2018 17:03:43 +0100 Subject: [PATCH 11/26] fixed project_id for gitlab_fetcher in the case of forks --- lib/github_changelog_generator/gitlab_fetcher.rb | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/github_changelog_generator/gitlab_fetcher.rb b/lib/github_changelog_generator/gitlab_fetcher.rb index a65d8740e..0ac70c779 100644 --- a/lib/github_changelog_generator/gitlab_fetcher.rb +++ b/lib/github_changelog_generator/gitlab_fetcher.rb @@ -40,7 +40,17 @@ def initialize(options = {}) Gitlab.sudo = nil @client = Gitlab::Client.new(gitlab_options) - @project_id = @client.project_search(@project).first.id + @project_id = find_project_id + end + + def find_project_id + project_id = nil + @client.project_search(@project).auto_paginate do |project| + if project.namespace.name.eql? @user + project_id = project.id + end + end + project_id end def gitlab_options @@ -68,9 +78,8 @@ def get_all_tags # @return [Array ] array of tags in repo def fetch_tags tags = [] - new_tags = @client.tags(@project_id, DEFAULT_REQUEST_OPTIONS) - new_tags.auto_paginate do |new_tag| + @client.tags(@project_id, DEFAULT_REQUEST_OPTIONS).auto_paginate do |new_tag| tags.push(new_tag) end print_empty_line From e696d2c3c6ec45cb3446b6b9700133b90b428fe9 Mon Sep 17 00:00:00 2001 From: kreczko Date: Wed, 30 May 2018 17:32:04 +0100 Subject: [PATCH 12/26] fixed events for merge requests in gitlab_fetcher --- lib/github_changelog_generator/gitlab_fetcher.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/github_changelog_generator/gitlab_fetcher.rb b/lib/github_changelog_generator/gitlab_fetcher.rb index 0ac70c779..1c3cfe027 100644 --- a/lib/github_changelog_generator/gitlab_fetcher.rb +++ b/lib/github_changelog_generator/gitlab_fetcher.rb @@ -154,7 +154,8 @@ def fetch_closed_pull_requests def fetch_events_async(issues) i = 0 threads = [] - options = { target_type: "issue" } + options = { } + options[:target_type] = issues.first["merged_at"].nil? ? "issue" : "merge_request" issue_events = [] @client.project_events(@project_id, options).auto_paginate do |event| event = stringify_keys_deep(event.to_hash) @@ -176,7 +177,7 @@ def fetch_events_async(issues) issue["events"].push(new_event) end end - print_in_same_line("Fetching events for issues and PR: #{i + 1}/#{issues.count}") + print_in_same_line("Fetching events for #{options[:target_type]}s: #{i + 1}/#{issues.count}") i += 1 end end @@ -202,6 +203,8 @@ def fetch_comments_async(prs) threads << Thread.new do pr["comments"] = [] @client.merge_request_notes(@project_id, pr["number"]) do |new_comment| + new_comment = stringify_keys_deep(new_comment.to_hash) + new_comment["body"] = new_comment["description"] pr["comments"].push(new_comment) end pr["comments"] = pr["comments"].map { |comment| stringify_keys_deep(comment.to_hash) } From 16fdb8dff04f648e56c839e74b92658c20bcd7a4 Mon Sep 17 00:00:00 2001 From: kreczko Date: Wed, 30 May 2018 17:32:40 +0100 Subject: [PATCH 13/26] fixed merge_commit_sha for older gitlab versions in gitlab_fetcher --- lib/github_changelog_generator/gitlab_fetcher.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/github_changelog_generator/gitlab_fetcher.rb b/lib/github_changelog_generator/gitlab_fetcher.rb index 1c3cfe027..f58a03bba 100644 --- a/lib/github_changelog_generator/gitlab_fetcher.rb +++ b/lib/github_changelog_generator/gitlab_fetcher.rb @@ -138,6 +138,8 @@ def fetch_closed_pull_requests new_pr["merged_at"] = new_pr["updated_at"] new_pr["pull_request"] = true new_pr["user"] = { login: new_pr["author"]["username"], html_url: new_pr["author"]["web_url"] } + # to make it work with older gitlab version or repos that lived across versions + new_pr["merge_commit_sha"] = new_pr["merge_commit_sha"].nil? ? new_pr["sha"]: new_pr["merge_commit_sha"] pull_requests.push(new_pr) end From d1004f7ea242ebfd4e741a490ac7e2e86762783f Mon Sep 17 00:00:00 2001 From: kreczko Date: Tue, 7 Aug 2018 17:56:32 +0100 Subject: [PATCH 14/26] separated fetching issues and pull requests as it was causing problems at times --- lib/github_changelog_generator/generator/generator_fetcher.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/github_changelog_generator/generator/generator_fetcher.rb b/lib/github_changelog_generator/generator/generator_fetcher.rb index 98e3b64ed..c82d6760f 100644 --- a/lib/github_changelog_generator/generator/generator_fetcher.rb +++ b/lib/github_changelog_generator/generator/generator_fetcher.rb @@ -12,7 +12,8 @@ def fetch_events_for_issues_and_pr end # Async fetching events: - @fetcher.fetch_events_async(@issues + @pull_requests) + @fetcher.fetch_events_async(@issues) + @fetcher.fetch_events_async(@pull_requests) end # Async fetching of all tags dates From 681c4b41ebe8b2092affe2a533d05da7e9e8824a Mon Sep 17 00:00:00 2001 From: kreczko Date: Tue, 7 Aug 2018 18:14:28 +0100 Subject: [PATCH 15/26] fix gitlab fetcher for project that have no issues --- lib/github_changelog_generator/gitlab_fetcher.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/github_changelog_generator/gitlab_fetcher.rb b/lib/github_changelog_generator/gitlab_fetcher.rb index f58a03bba..ab35059ab 100644 --- a/lib/github_changelog_generator/gitlab_fetcher.rb +++ b/lib/github_changelog_generator/gitlab_fetcher.rb @@ -157,6 +157,9 @@ def fetch_events_async(issues) i = 0 threads = [] options = { } + if issues.empty? + return + end options[:target_type] = issues.first["merged_at"].nil? ? "issue" : "merge_request" issue_events = [] @client.project_events(@project_id, options).auto_paginate do |event| From 3b5a974ce42396b1d54534310668fcabcace60da Mon Sep 17 00:00:00 2001 From: kreczko Date: Thu, 1 Nov 2018 10:04:53 +0000 Subject: [PATCH 16/26] fixed if-logic based on feedback from olleolleolle --- lib/github_changelog_generator/generator/generator_fetcher.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/github_changelog_generator/generator/generator_fetcher.rb b/lib/github_changelog_generator/generator/generator_fetcher.rb index c82d6760f..fc7a70b5e 100644 --- a/lib/github_changelog_generator/generator/generator_fetcher.rb +++ b/lib/github_changelog_generator/generator/generator_fetcher.rb @@ -78,12 +78,12 @@ def associate_tagged_prs(tags, prs, total) # https://developer.github.com/v3/pulls/#list-pull-requests # gitlab API has this merge_commit_sha = nil - if pr.has_key?("merge_commit_sha") then + if pr.has_key?("merge_commit_sha") merge_commit_sha = pr["merge_commit_sha"] elsif pr["events"] && (event = pr["events"].find { |e| e["event"] == "merged" }) merge_commit_sha = event["commit_id"] end - unless merge_commit_sha.nil? + if merge_commit_sha # Iterate tags.reverse (oldest to newest) to find first tag of each PR. if (oldest_tag = tags.reverse.find { |tag| tag["shas_in_tag"].include?(merge_commit_sha) }) pr["first_occurring_tag"] = oldest_tag["name"] From 2a1350fefa4cb49062b56edbf9df2227feb463d4 Mon Sep 17 00:00:00 2001 From: kreczko Date: Thu, 1 Nov 2018 10:50:34 +0000 Subject: [PATCH 17/26] Github --> Gitlab for GitlabFetcher --- .../generator/generator.rb | 2 +- .../gitlab_fetcher.rb | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/github_changelog_generator/generator/generator.rb b/lib/github_changelog_generator/generator/generator.rb index 851d4d8ce..9c982e4fa 100644 --- a/lib/github_changelog_generator/generator/generator.rb +++ b/lib/github_changelog_generator/generator/generator.rb @@ -35,7 +35,7 @@ class Generator def initialize(options = {}) @options = options @tag_times_hash = {} - @fetcher = options[:gitlab] ? GitHubChangelogGenerator::GitlabFetcher.new(options) : GitHubChangelogGenerator::OctoFetcher.new(options) + @fetcher = options[:gitlab] ? GitLabChangelogGenerator::GitlabFetcher.new(options) : GitHubChangelogGenerator::OctoFetcher.new(options) @sections = [] end diff --git a/lib/github_changelog_generator/gitlab_fetcher.rb b/lib/github_changelog_generator/gitlab_fetcher.rb index ab35059ab..7305c0b48 100644 --- a/lib/github_changelog_generator/gitlab_fetcher.rb +++ b/lib/github_changelog_generator/gitlab_fetcher.rb @@ -4,18 +4,18 @@ require "retriable" require "gitlab" -module GitHubChangelogGenerator +module GitLabChangelogGenerator # A Fetcher responsible for all requests to GitHub and all basic manipulation with related data # (such as filtering, validating, e.t.c) # # Example: - # fetcher = GitHubChangelogGenerator::OctoFetcher.new(options) + # fetcher = GitLabChangelogGenerator::GitlabFetcher.new(options) class GitlabFetcher PER_PAGE_NUMBER = 100 MAX_THREAD_NUMBER = 25 MAX_FORBIDDEN_RETRIES = 100 CHANGELOG_GITHUB_TOKEN = "CHANGELOG_GITHUB_TOKEN" - GH_RATE_LIMIT_EXCEEDED_MSG = "Warning: Can't finish operation: GitHub API rate limit exceeded, changelog may be " \ + RATE_LIMIT_EXCEEDED_MSG = "Warning: Can't finish operation: GitHub API rate limit exceeded, changelog may be " \ "missing some issues. You can limit the number of issues fetched using the `--max-issues NUM` argument." NO_TOKEN_PROVIDED = "Warning: No token provided (-t option) and variable $CHANGELOG_GITHUB_TOKEN was not found. " \ "This script can make only 50 requests to GitHub API per hour without token!" @@ -55,7 +55,7 @@ def find_project_id def gitlab_options result = {} - access_token = fetch_github_token + access_token = fetch_auth_token result[:private_token] = access_token if access_token endpoint = @options[:github_endpoint] result[:endpoint] = endpoint if endpoint @@ -371,7 +371,7 @@ def check_response rescue Gitlab::Error::Forbidden => e fail_with_message(e, "Exceeded retry limit") rescue Gitlab::Error::Unauthorized => e - fail_with_message(e, "Error: wrong GitHub token") + fail_with_message(e, "Error: wrong GitLab token") end # Presents the exception, and the aborts with the message. @@ -400,7 +400,7 @@ def retry_callback proc do |exception, try, elapsed_time, next_interval| Helper.log.warn("RETRY - #{exception.class}: '#{exception.message}'") Helper.log.warn("#{try} tries in #{elapsed_time} seconds and #{next_interval} seconds until the next try") - Helper.log.warn GH_RATE_LIMIT_EXCEEDED_MSG + Helper.log.warn RATE_LIMIT_EXCEEDED_MSG Helper.log.warn @client.rate_limit end end @@ -425,15 +425,15 @@ def print_empty_line # otherwise try to fetch it from CHANGELOG_GITHUB_TOKEN env variable. # # @return [String] - def fetch_github_token - env_var = @options[:token].presence || ENV["CHANGELOG_GITHUB_TOKEN"] + def fetch_auth_token + env_var = @options[:token].presence || ENV["CHANGELOG_GITLAB_TOKEN"] Helper.log.warn NO_TOKEN_PROVIDED unless env_var env_var end - # @return [String] helper to return Github "user/project" + # @return [String] "user/project" slug def user_project "#{@options[:user]}/#{@options[:project]}" end From 1de4431154df43da39dce0d034a218ddbed610d4 Mon Sep 17 00:00:00 2001 From: kreczko Date: Thu, 1 Nov 2018 11:03:29 +0000 Subject: [PATCH 18/26] added Gitlab URI example --- lib/github_changelog_generator/gitlab_fetcher.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/github_changelog_generator/gitlab_fetcher.rb b/lib/github_changelog_generator/gitlab_fetcher.rb index 7305c0b48..997ce7dd3 100644 --- a/lib/github_changelog_generator/gitlab_fetcher.rb +++ b/lib/github_changelog_generator/gitlab_fetcher.rb @@ -440,7 +440,7 @@ def user_project # Returns Hash of all querystring variables in given URI. # - # @param [String] uri eg. https://api.github.com/repositories/43914960/tags?page=37&foo=1 + # @param [String] uri eg. https://gitlab.example.com/api/v4/projects/43914960/repository/tags?page=37&foo=1 # @return [Hash] of all GET variables. eg. { 'page' => 37, 'foo' => 1 } def querystring_as_hash(uri) Hash[URI.decode_www_form(URI(uri).query || "")] From d62e1e10a9cd303918051f96f44c512f90510b24 Mon Sep 17 00:00:00 2001 From: kreczko Date: Thu, 1 Nov 2018 14:12:03 +0000 Subject: [PATCH 19/26] missing change from Github->Gitlab change: Helper --> GitHubChangelogGenerator::Helper --- .../gitlab_fetcher.rb | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/github_changelog_generator/gitlab_fetcher.rb b/lib/github_changelog_generator/gitlab_fetcher.rb index 997ce7dd3..7655a6b8f 100644 --- a/lib/github_changelog_generator/gitlab_fetcher.rb +++ b/lib/github_changelog_generator/gitlab_fetcher.rb @@ -85,10 +85,10 @@ def fetch_tags print_empty_line if tags.count == 0 - Helper.log.warn "Warning: Can't find any tags in repo. \ + GitHubChangelogGenerator::Helper.log.warn "Warning: Can't find any tags in repo. \ Make sure, that you push tags to remote repo via 'git push --tags'" else - Helper.log.info "Found #{tags.count} tags" + GitHubChangelogGenerator::Helper.log.info "Found #{tags.count} tags" end # tags are a Sawyer::Resource. Convert to hash tags.map { |resource| stringify_keys_deep(resource.to_hash) } @@ -117,7 +117,7 @@ def fetch_closed_issues_and_pr end print_empty_line - Helper.log.info "Received issues: #{issues.count}" + GitHubChangelogGenerator::Helper.log.info "Received issues: #{issues.count}" # separate arrays of issues and pull requests: [issues.map { |issue| stringify_keys_deep(issue.to_hash) }, fetch_closed_pull_requests] @@ -145,7 +145,7 @@ def fetch_closed_pull_requests print_empty_line - Helper.log.info "Pull Request count: #{pull_requests.count}" + GitHubChangelogGenerator::Helper.log.info "Pull Request count: #{pull_requests.count}" pull_requests.map { |pull_request| stringify_keys_deep(pull_request.to_hash) } end @@ -193,7 +193,7 @@ def fetch_events_async(issues) # to clear line from prev print print_empty_line - Helper.log.info "Fetching events for issues and PR: #{i}" + GitHubChangelogGenerator::Helper.log.info "Fetching events for issues and PR: #{i}" end # Fetch comments for PRs and add them to "comments" @@ -325,7 +325,7 @@ def fetch_tag_shas_async(tags) # to clear line from prev print print_empty_line - Helper.log.info "Fetching SHAs for tags: #{i}" + GitHubChangelogGenerator::Helper.log.info "Fetching SHAs for tags: #{i}" nil end @@ -376,7 +376,7 @@ def check_response # Presents the exception, and the aborts with the message. def fail_with_message(error, message) - Helper.log.error("#{error.class}: #{error.message}") + GitHubChangelogGenerator::Helper.log.error("#{error.class}: #{error.message}") sys_abort(message) end @@ -398,10 +398,10 @@ def sleep_base_interval def retry_callback proc do |exception, try, elapsed_time, next_interval| - Helper.log.warn("RETRY - #{exception.class}: '#{exception.message}'") - Helper.log.warn("#{try} tries in #{elapsed_time} seconds and #{next_interval} seconds until the next try") - Helper.log.warn RATE_LIMIT_EXCEEDED_MSG - Helper.log.warn @client.rate_limit + GitHubChangelogGenerator::Helper.log.warn("RETRY - #{exception.class}: '#{exception.message}'") + GitHubChangelogGenerator::Helper.log.warn("#{try} tries in #{elapsed_time} seconds and #{next_interval} seconds until the next try") + GitHubChangelogGenerator::Helper.log.warn RATE_LIMIT_EXCEEDED_MSG + GitHubChangelogGenerator::Helper.log.warn @client.rate_limit end end @@ -428,7 +428,7 @@ def print_empty_line def fetch_auth_token env_var = @options[:token].presence || ENV["CHANGELOG_GITLAB_TOKEN"] - Helper.log.warn NO_TOKEN_PROVIDED unless env_var + GitHubChangelogGenerator::Helper.log.warn NO_TOKEN_PROVIDED unless env_var env_var end From d6e257fa9562966b0c12a25bf0c643983d734513 Mon Sep 17 00:00:00 2001 From: kreczko Date: Thu, 1 Nov 2018 14:37:15 +0000 Subject: [PATCH 20/26] CHANGELOG_GITHUB_TOKEN --> CHANGELOG_AUTH_TOKEN for gitlab fetcher --- lib/github_changelog_generator/gitlab_fetcher.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/github_changelog_generator/gitlab_fetcher.rb b/lib/github_changelog_generator/gitlab_fetcher.rb index 7655a6b8f..3bb9fce52 100644 --- a/lib/github_changelog_generator/gitlab_fetcher.rb +++ b/lib/github_changelog_generator/gitlab_fetcher.rb @@ -14,10 +14,10 @@ class GitlabFetcher PER_PAGE_NUMBER = 100 MAX_THREAD_NUMBER = 25 MAX_FORBIDDEN_RETRIES = 100 - CHANGELOG_GITHUB_TOKEN = "CHANGELOG_GITHUB_TOKEN" + CHANGELOG_AUTH_TOKEN = "CHANGELOG_AUTH_TOKEN" RATE_LIMIT_EXCEEDED_MSG = "Warning: Can't finish operation: GitHub API rate limit exceeded, changelog may be " \ "missing some issues. You can limit the number of issues fetched using the `--max-issues NUM` argument." - NO_TOKEN_PROVIDED = "Warning: No token provided (-t option) and variable $CHANGELOG_GITHUB_TOKEN was not found. " \ + NO_TOKEN_PROVIDED = "Warning: No token provided (-t option) and variable $CHANGELOG_AUTH_TOKEN was not found. " \ "This script can make only 50 requests to GitHub API per hour without token!" # @param options [Hash] Options passed in @@ -422,11 +422,11 @@ def print_empty_line end # Returns GitHub token. First try to use variable, provided by --token option, - # otherwise try to fetch it from CHANGELOG_GITHUB_TOKEN env variable. + # otherwise try to fetch it from CHANGELOG_AUTH_TOKEN env variable. # # @return [String] def fetch_auth_token - env_var = @options[:token].presence || ENV["CHANGELOG_GITLAB_TOKEN"] + env_var = @options[:token].presence || ENV["CHANGELOG_AUTH_TOKEN"] GitHubChangelogGenerator::Helper.log.warn NO_TOKEN_PROVIDED unless env_var From 264e6baf7d0f6306fdd93542c7cbbf3c686f3509 Mon Sep 17 00:00:00 2001 From: kreczko Date: Thu, 1 Nov 2018 15:03:16 +0000 Subject: [PATCH 21/26] fixed RuboCop issues for gitlab_fetcher --- .../generator/generator_fetcher.rb | 17 +++++++++++------ .../gitlab_fetcher.rb | 14 +++++--------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/lib/github_changelog_generator/generator/generator_fetcher.rb b/lib/github_changelog_generator/generator/generator_fetcher.rb index fc7a70b5e..c299bf16a 100644 --- a/lib/github_changelog_generator/generator/generator_fetcher.rb +++ b/lib/github_changelog_generator/generator/generator_fetcher.rb @@ -77,12 +77,7 @@ def associate_tagged_prs(tags, prs, total) # https://developer.github.com/v3/pulls/#get-a-single-pull-request vs. # https://developer.github.com/v3/pulls/#list-pull-requests # gitlab API has this - merge_commit_sha = nil - if pr.has_key?("merge_commit_sha") - merge_commit_sha = pr["merge_commit_sha"] - elsif pr["events"] && (event = pr["events"].find { |e| e["event"] == "merged" }) - merge_commit_sha = event["commit_id"] - end + merge_commit_sha = try_merge_commit_sha_from_gitlab(pr) if merge_commit_sha # Iterate tags.reverse (oldest to newest) to find first tag of each PR. if (oldest_tag = tags.reverse.find { |tag| tag["shas_in_tag"].include?(merge_commit_sha) }) @@ -104,6 +99,16 @@ def associate_tagged_prs(tags, prs, total) end end + def try_merge_commit_sha_from_gitlab(merge_request) + merge_commit_sha = nil + if merge_request.key?("merge_commit_sha") + merge_commit_sha = merge_request["merge_commit_sha"] + elsif merge_request["events"] && (event = merge_request["events"].find { |e| e["event"] == "merged" }) + merge_commit_sha = event["commit_id"] + end + merge_commit_sha + end + # Associate merged PRs by the HEAD of the release branch. If no # --release-branch was specified, then the github default branch is used. # diff --git a/lib/github_changelog_generator/gitlab_fetcher.rb b/lib/github_changelog_generator/gitlab_fetcher.rb index 3bb9fce52..06a095d01 100644 --- a/lib/github_changelog_generator/gitlab_fetcher.rb +++ b/lib/github_changelog_generator/gitlab_fetcher.rb @@ -46,9 +46,7 @@ def initialize(options = {}) def find_project_id project_id = nil @client.project_search(@project).auto_paginate do |project| - if project.namespace.name.eql? @user - project_id = project.id - end + project_id = project.id if project.namespace.name.eql? @user end project_id end @@ -79,7 +77,7 @@ def get_all_tags def fetch_tags tags = [] - @client.tags(@project_id, DEFAULT_REQUEST_OPTIONS).auto_paginate do |new_tag| + @client.tags(@project_id, DEFAULT_REQUEST_OPTIONS).auto_paginate do |new_tag| tags.push(new_tag) end print_empty_line @@ -139,7 +137,7 @@ def fetch_closed_pull_requests new_pr["pull_request"] = true new_pr["user"] = { login: new_pr["author"]["username"], html_url: new_pr["author"]["web_url"] } # to make it work with older gitlab version or repos that lived across versions - new_pr["merge_commit_sha"] = new_pr["merge_commit_sha"].nil? ? new_pr["sha"]: new_pr["merge_commit_sha"] + new_pr["merge_commit_sha"] = new_pr["merge_commit_sha"].nil? ? new_pr["sha"] : new_pr["merge_commit_sha"] pull_requests.push(new_pr) end @@ -156,10 +154,8 @@ def fetch_closed_pull_requests def fetch_events_async(issues) i = 0 threads = [] - options = { } - if issues.empty? - return - end + options = {} + return if issues.empty? options[:target_type] = issues.first["merged_at"].nil? ? "issue" : "merge_request" issue_events = [] @client.project_events(@project_id, options).auto_paginate do |event| From b13d759a9600cd35e1dc7aeb68088d8763aac786 Mon Sep 17 00:00:00 2001 From: kreczko Date: Thu, 1 Nov 2018 16:38:24 +0000 Subject: [PATCH 22/26] preliminary tests for gitlab_fetcher --- spec/unit/gitlab_fetcher_spec.rb | 541 +++++++++++++++++++++++++++++++ 1 file changed, 541 insertions(+) create mode 100644 spec/unit/gitlab_fetcher_spec.rb diff --git a/spec/unit/gitlab_fetcher_spec.rb b/spec/unit/gitlab_fetcher_spec.rb new file mode 100644 index 000000000..1935206bb --- /dev/null +++ b/spec/unit/gitlab_fetcher_spec.rb @@ -0,0 +1,541 @@ +# frozen_string_literal: true + +describe GitLabChangelogGenerator::GitlabFetcher do + let(:options) do + { + user: "kreczko", + project: "changelog-testrepo", + github_endpoint: "https://gitlab.com/api/v4" + } + end + + let(:fetcher) { GitLabChangelogGenerator::GitlabFetcher.new(options) } + + describe "#check_response" do + context "when returns successfully" do + it "returns block value" do + expect(fetcher.send(:check_response) { 1 + 1 }).to eq(2) + end + end + + context "when raises GitLab::MissingCredentials" do + it "aborts" do + expect(fetcher).to receive(:sys_abort).with("Error: wrong AUTH token") + fetcher.send(:check_response) { raise(GitLab::MissingCredentials) } + end + end + + context "when raises GitLab::Forbidden" do + it "sleeps and retries and then aborts" do + retry_limit = GitLabChangelogGenerator::GitlabFetcher::MAX_FORBIDDEN_RETRIES - 1 + allow(fetcher).to receive(:sleep_base_interval).exactly(retry_limit).times.and_return(0) + + expect(fetcher).to receive(:sys_abort).with("Exceeded retry limit") + fetcher.send(:check_response) { raise(GitLab::Forbidden) } + end + end + end + + describe "#fetch_auth_token" do + token = GitLabChangelogGenerator::GitlabFetcher::CHANGELOG_AUTH_TOKEN + context "when token in ENV exist" do + before { stub_const("ENV", ENV.to_hash.merge(token => VALID_TOKEN)) } + subject { fetcher.send(:fetch_auth_token) } + it { is_expected.to eq(VALID_TOKEN) } + end + + context "when token in ENV is nil" do + before { stub_const("ENV", ENV.to_hash.merge(token => nil)) } + subject { fetcher.send(:fetch_auth_token) } + it { is_expected.to be_nil } + end + + context "when token in options and ENV is nil" do + let(:options) { { token: VALID_TOKEN } } + + before do + stub_const("ENV", ENV.to_hash.merge(token => nil)) + end + + subject { fetcher.send(:fetch_auth_token) } + it { is_expected.to eq(VALID_TOKEN) } + end + + context "when token in options and ENV specified" do + let(:options) { { token: VALID_TOKEN } } + + before do + stub_const("ENV", ENV.to_hash.merge(token => "no_matter_what")) + end + + subject { fetcher.send(:fetch_auth_token) } + it { is_expected.to eq(VALID_TOKEN) } + end + end + + describe "#get_all_tags" do + context "when fetch_tags returns tags" do + it "returns tags" do + mock_tags = ["tag"] + allow(fetcher).to receive(:fetch_tags).and_return(mock_tags) + expect(fetcher.get_all_tags).to eq(mock_tags) + end + end + end + + describe "#fetch_tags" do + context "when wrong token provided", :vcr do + let(:options) do + { + user: "skywinder", + project: "changelog_test", + token: INVALID_TOKEN, + github_endpoint: "https://gitlab.com/api/v4" + } + end + + it "should raise Unauthorized error" do + expect { fetcher.fetch_tags }.to raise_error SystemExit, "Error: wrong AUTH token" + end + end + + # TODO: swap for Gitlab API + context "when API call is valid", :vcr do + it "should return tags" do + expected_tags = [{ "name" => "v0.0.3", + "zipball_url" => + "https://api.github.com/repos/skywinder/changelog_test/zipball/v0.0.3", + "tarball_url" => + "https://api.github.com/repos/skywinder/changelog_test/tarball/v0.0.3", + "commit" => + { "sha" => "a0cba2b1a1ea9011ab07ee1ac140ba5a5eb8bd90", + "url" => + "https://api.github.com/repos/skywinder/changelog_test/commits/a0cba2b1a1ea9011ab07ee1ac140ba5a5eb8bd90" } }, + { "name" => "v0.0.2", + "zipball_url" => + "https://api.github.com/repos/skywinder/changelog_test/zipball/v0.0.2", + "tarball_url" => + "https://api.github.com/repos/skywinder/changelog_test/tarball/v0.0.2", + "commit" => + { "sha" => "9b35bb13dcd15b68e7bcbf10cde5eb937a54f710", + "url" => + "https://api.github.com/repos/skywinder/changelog_test/commits/9b35bb13dcd15b68e7bcbf10cde5eb937a54f710" } }, + { "name" => "v0.0.1", + "zipball_url" => + "https://api.github.com/repos/skywinder/changelog_test/zipball/v0.0.1", + "tarball_url" => + "https://api.github.com/repos/skywinder/changelog_test/tarball/v0.0.1", + "commit" => + { "sha" => "4c2d6d1ed58bdb24b870dcb5d9f2ceed0283d69d", + "url" => + "https://api.github.com/repos/skywinder/changelog_test/commits/4c2d6d1ed58bdb24b870dcb5d9f2ceed0283d69d" } }, + { "name" => "0.0.4", + "zipball_url" => + "https://api.github.com/repos/skywinder/changelog_test/zipball/0.0.4", + "tarball_url" => + "https://api.github.com/repos/skywinder/changelog_test/tarball/0.0.4", + "commit" => + { "sha" => "ece0c3ab7142b21064b885061c55ede00ef6ce94", + "url" => + "https://api.github.com/repos/skywinder/changelog_test/commits/ece0c3ab7142b21064b885061c55ede00ef6ce94" } }] + + expect(fetcher.fetch_tags).to eq(expected_tags) + end + + it "should return tags count" do + tags = fetcher.fetch_tags + expect(tags.size).to eq(4) + end + end + end + + describe "#fetch_closed_issues_and_pr" do + context "when API call is valid", :vcr do + it "returns issues" do + issues, pull_requests = fetcher.fetch_closed_issues_and_pr + expect(issues.size).to eq(7) + expect(pull_requests.size).to eq(14) + end + + it "returns issue with proper key/values" do + issues, _pull_requests = fetcher.fetch_closed_issues_and_pr + + expected_issue = { "url" => "https://api.github.com/repos/skywinder/changelog_test/issues/14", + "repository_url" => "https://api.github.com/repos/skywinder/changelog_test", + "labels_url" => + "https://api.github.com/repos/skywinder/changelog_test/issues/14/labels{/name}", + "comments_url" => + "https://api.github.com/repos/skywinder/changelog_test/issues/14/comments", + "events_url" => + "https://api.github.com/repos/skywinder/changelog_test/issues/14/events", + "html_url" => "https://github.com/skywinder/changelog_test/issues/14", + "id" => 95_419_412, + "number" => 14, + "title" => "Issue closed from commit from PR", + "user" => + { "login" => "skywinder", + "id" => 3_356_474, + "avatar_url" => "https://avatars.githubusercontent.com/u/3356474?v=3", + "gravatar_id" => "", + "url" => "https://api.github.com/users/skywinder", + "html_url" => "https://github.com/skywinder", + "followers_url" => "https://api.github.com/users/skywinder/followers", + "following_url" => + "https://api.github.com/users/skywinder/following{/other_user}", + "gists_url" => "https://api.github.com/users/skywinder/gists{/gist_id}", + "starred_url" => + "https://api.github.com/users/skywinder/starred{/owner}{/repo}", + "subscriptions_url" => "https://api.github.com/users/skywinder/subscriptions", + "organizations_url" => "https://api.github.com/users/skywinder/orgs", + "repos_url" => "https://api.github.com/users/skywinder/repos", + "events_url" => "https://api.github.com/users/skywinder/events{/privacy}", + "received_events_url" => + "https://api.github.com/users/skywinder/received_events", + "type" => "User", + "site_admin" => false }, + "labels" => [], + "state" => "closed", + "locked" => false, + "assignee" => nil, + "assignees" => [], + "milestone" => nil, + "comments" => 0, + "created_at" => "2015-07-16T12:06:08Z", + "updated_at" => "2015-07-16T12:21:42Z", + "closed_at" => "2015-07-16T12:21:42Z", + "body" => "" } + + # Convert times to Time + expected_issue.each_pair do |k, v| + expected_issue[k] = Time.parse(v) if v =~ /^2015-/ + end + + expect(issues.first).to eq(expected_issue) + end + + it "returns pull request with proper key/values" do + _issues, pull_requests = fetcher.fetch_closed_issues_and_pr + + expected_pr = { "url" => "https://api.github.com/repos/skywinder/changelog_test/issues/21", + "repository_url" => "https://api.github.com/repos/skywinder/changelog_test", + "labels_url" => + "https://api.github.com/repos/skywinder/changelog_test/issues/21/labels{/name}", + "comments_url" => + "https://api.github.com/repos/skywinder/changelog_test/issues/21/comments", + "events_url" => + "https://api.github.com/repos/skywinder/changelog_test/issues/21/events", + "html_url" => "https://github.com/skywinder/changelog_test/pull/21", + "id" => 124_925_759, + "number" => 21, + "title" => "Merged br (should appear in change log with #20)", + "user" => + { "login" => "skywinder", + "id" => 3_356_474, + "avatar_url" => "https://avatars.githubusercontent.com/u/3356474?v=3", + "gravatar_id" => "", + "url" => "https://api.github.com/users/skywinder", + "html_url" => "https://github.com/skywinder", + "followers_url" => "https://api.github.com/users/skywinder/followers", + "following_url" => + "https://api.github.com/users/skywinder/following{/other_user}", + "gists_url" => "https://api.github.com/users/skywinder/gists{/gist_id}", + "starred_url" => + "https://api.github.com/users/skywinder/starred{/owner}{/repo}", + "subscriptions_url" => "https://api.github.com/users/skywinder/subscriptions", + "organizations_url" => "https://api.github.com/users/skywinder/orgs", + "repos_url" => "https://api.github.com/users/skywinder/repos", + "events_url" => "https://api.github.com/users/skywinder/events{/privacy}", + "received_events_url" => + "https://api.github.com/users/skywinder/received_events", + "type" => "User", + "site_admin" => false }, + "labels" => [], + "state" => "closed", + "locked" => false, + "assignee" => nil, + "assignees" => [], + "milestone" => nil, + "comments" => 0, + "created_at" => "2016-01-05T09:24:08Z", + "updated_at" => "2016-01-05T09:26:53Z", + "closed_at" => "2016-01-05T09:24:27Z", + "pull_request" => + { "url" => "https://api.github.com/repos/skywinder/changelog_test/pulls/21", + "html_url" => "https://github.com/skywinder/changelog_test/pull/21", + "diff_url" => "https://github.com/skywinder/changelog_test/pull/21.diff", + "patch_url" => "https://github.com/skywinder/changelog_test/pull/21.patch" }, + "body" => + "to test https://github.com/skywinder/github-changelog-generator/pull/305\r\nshould appear in change log with #20" } + + # Convert times to Time + expected_pr.each_pair do |k, v| + expected_pr[k] = Time.parse(v) if v =~ /^2016-01/ + end + + expect(pull_requests.first).to eq(expected_pr) + end + + it "returns issues with labels" do + issues, _pull_requests = fetcher.fetch_closed_issues_and_pr + expected = [[], [], ["Bug"], [], ["enhancement"], ["some label"], []] + expect(issues.map { |i| i["labels"].map { |l| l["name"] } }).to eq(expected) + end + + it "returns pull_requests with labels" do + _issues, pull_requests = fetcher.fetch_closed_issues_and_pr + expected = [[], [], [], [], [], ["enhancement"], [], [], ["invalid"], [], [], [], [], ["invalid"]] + expect(pull_requests.map { |i| i["labels"].map { |l| l["name"] } }).to eq(expected) + end + end + end + + describe "#fetch_closed_pull_requests" do + context "when API call is valid", :vcr do + it "returns pull requests" do + pull_requests = fetcher.fetch_closed_pull_requests + expect(pull_requests.size).to eq(14) + end + + it "returns correct pull request keys" do + pull_requests = fetcher.fetch_closed_pull_requests + + pr = pull_requests.first + expect(pr.keys).to eq(%w[url id html_url diff_url patch_url issue_url number state locked title user body created_at updated_at closed_at merged_at merge_commit_sha assignee assignees milestone commits_url review_comments_url review_comment_url comments_url statuses_url head base _links]) + end + end + end + + describe "#fetch_events_async" do + context "when API call is valid", :vcr do + it "populates issues" do + issues = [{ "url" => "https://api.github.com/repos/skywinder/changelog_test/issues/14", + "repository_url" => "https://api.github.com/repos/skywinder/changelog_test", + "labels_url" => + "https://api.github.com/repos/skywinder/changelog_test/issues/14/labels{/name}", + "comments_url" => + "https://api.github.com/repos/skywinder/changelog_test/issues/14/comments", + "events_url" => + "https://api.github.com/repos/skywinder/changelog_test/issues/14/events", + "html_url" => "https://github.com/skywinder/changelog_test/issues/14", + "id" => 95_419_412, + "number" => 14, + "title" => "Issue closed from commit from PR", + "user" => + { "login" => "skywinder", + "id" => 3_356_474, + "avatar_url" => "https://avatars.githubusercontent.com/u/3356474?v=3", + "gravatar_id" => "", + "url" => "https://api.github.com/users/skywinder", + "html_url" => "https://github.com/skywinder", + "followers_url" => "https://api.github.com/users/skywinder/followers", + "following_url" => + "https://api.github.com/users/skywinder/following{/other_user}", + "gists_url" => "https://api.github.com/users/skywinder/gists{/gist_id}", + "starred_url" => + "https://api.github.com/users/skywinder/starred{/owner}{/repo}", + "subscriptions_url" => + "https://api.github.com/users/skywinder/subscriptions", + "organizations_url" => "https://api.github.com/users/skywinder/orgs", + "repos_url" => "https://api.github.com/users/skywinder/repos", + "events_url" => "https://api.github.com/users/skywinder/events{/privacy}", + "received_events_url" => + "https://api.github.com/users/skywinder/received_events", + "type" => "User", + "site_admin" => false }, + "labels" => [], + "state" => "closed", + "locked" => false, + "assignee" => nil, + "assignees" => [], + "milestone" => nil, + "comments" => 0, + "created_at" => "2015-07-16T12:06:08Z", + "updated_at" => "2015-07-16T12:21:42Z", + "closed_at" => "2015-07-16T12:21:42Z", + "body" => "" }] + + # Check that they are blank to begin with + expect(issues.first["events"]).to be_nil + + fetcher.fetch_events_async(issues) + issue_events = issues.first["events"] + + expected_events = [{ "id" => 357_462_189, + "url" => + "https://api.github.com/repos/skywinder/changelog_test/issues/events/357462189", + "actor" => + { "login" => "skywinder", + "id" => 3_356_474, + "avatar_url" => "https://avatars.githubusercontent.com/u/3356474?v=3", + "gravatar_id" => "", + "url" => "https://api.github.com/users/skywinder", + "html_url" => "https://github.com/skywinder", + "followers_url" => "https://api.github.com/users/skywinder/followers", + "following_url" => + "https://api.github.com/users/skywinder/following{/other_user}", + "gists_url" => "https://api.github.com/users/skywinder/gists{/gist_id}", + "starred_url" => + "https://api.github.com/users/skywinder/starred{/owner}{/repo}", + "subscriptions_url" => + "https://api.github.com/users/skywinder/subscriptions", + "organizations_url" => "https://api.github.com/users/skywinder/orgs", + "repos_url" => "https://api.github.com/users/skywinder/repos", + "events_url" => "https://api.github.com/users/skywinder/events{/privacy}", + "received_events_url" => + "https://api.github.com/users/skywinder/received_events", + "type" => "User", + "site_admin" => false }, + "event" => "referenced", + "commit_id" => "decfe840d1a1b86e0c28700de5362d3365a29555", + "commit_url" => + "https://api.github.com/repos/skywinder/changelog_test/commits/decfe840d1a1b86e0c28700de5362d3365a29555", + "created_at" => "2015-07-16T12:21:16Z" }, + { "id" => 357_462_542, + "url" => + "https://api.github.com/repos/skywinder/changelog_test/issues/events/357462542", + "actor" => + { "login" => "skywinder", + "id" => 3_356_474, + "avatar_url" => "https://avatars.githubusercontent.com/u/3356474?v=3", + "gravatar_id" => "", + "url" => "https://api.github.com/users/skywinder", + "html_url" => "https://github.com/skywinder", + "followers_url" => "https://api.github.com/users/skywinder/followers", + "following_url" => + "https://api.github.com/users/skywinder/following{/other_user}", + "gists_url" => "https://api.github.com/users/skywinder/gists{/gist_id}", + "starred_url" => + "https://api.github.com/users/skywinder/starred{/owner}{/repo}", + "subscriptions_url" => + "https://api.github.com/users/skywinder/subscriptions", + "organizations_url" => "https://api.github.com/users/skywinder/orgs", + "repos_url" => "https://api.github.com/users/skywinder/repos", + "events_url" => "https://api.github.com/users/skywinder/events{/privacy}", + "received_events_url" => + "https://api.github.com/users/skywinder/received_events", + "type" => "User", + "site_admin" => false }, + "event" => "closed", + "commit_id" => "decfe840d1a1b86e0c28700de5362d3365a29555", + "commit_url" => + "https://api.github.com/repos/skywinder/changelog_test/commits/decfe840d1a1b86e0c28700de5362d3365a29555", + "created_at" => "2015-07-16T12:21:42Z" }] + + # Convert times to Time + expected_events.map! do |event| + event.each_pair do |k, v| + event[k] = Time.parse(v) if v =~ /^201[56]-/ + end + end + + expect(issue_events).to eq(expected_events) + end + end + end + + describe "#fetch_date_of_tag" do + context "when API call is valid", :vcr do + it "returns date" do + tag = { "name" => "v0.0.3", + "zipball_url" => + "https://api.github.com/repos/skywinder/changelog_test/zipball/v0.0.3", + "tarball_url" => + "https://api.github.com/repos/skywinder/changelog_test/tarball/v0.0.3", + "commit" => + { "sha" => "a0cba2b1a1ea9011ab07ee1ac140ba5a5eb8bd90", + "url" => + "https://api.github.com/repos/skywinder/changelog_test/commits/a0cba2b1a1ea9011ab07ee1ac140ba5a5eb8bd90" } } + + dt = fetcher.fetch_date_of_tag(tag) + expect(dt).to eq(Time.parse("2015-03-04 19:01:48 UTC")) + end + end + end + + describe "#querystring_as_hash" do + it "works on the blank URL" do + expect { fetcher.send(:querystring_as_hash, "") }.not_to raise_error + end + + it "where there are no querystring params" do + expect { fetcher.send(:querystring_as_hash, "http://example.com") }.not_to raise_error + end + + it "returns a String-keyed Hash of querystring params" do + expect(fetcher.send(:querystring_as_hash, "http://example.com/o.html?str=18&wis=12")).to include("wis" => "12", "str" => "18") + end + end + + describe "#fetch_commit" do + context "when API call is valid", :vcr do + it "returns commit" do + event = { "id" => 357_462_189, + "url" => + "https://api.github.com/repos/skywinder/changelog_test/issues/events/357462189", + "actor" => + { "login" => "skywinder", + "id" => 3_356_474, + "avatar_url" => "https://avatars.githubusercontent.com/u/3356474?v=3", + "gravatar_id" => "", + "url" => "https://api.github.com/users/skywinder", + "html_url" => "https://github.com/skywinder", + "followers_url" => "https://api.github.com/users/skywinder/followers", + "following_url" => + "https://api.github.com/users/skywinder/following{/other_user}", + "gists_url" => "https://api.github.com/users/skywinder/gists{/gist_id}", + "starred_url" => + "https://api.github.com/users/skywinder/starred{/owner}{/repo}", + "subscriptions_url" => "https://api.github.com/users/skywinder/subscriptions", + "organizations_url" => "https://api.github.com/users/skywinder/orgs", + "repos_url" => "https://api.github.com/users/skywinder/repos", + "events_url" => "https://api.github.com/users/skywinder/events{/privacy}", + "received_events_url" => + "https://api.github.com/users/skywinder/received_events", + "type" => "User", + "site_admin" => false }, + "event" => "referenced", + "commit_id" => "decfe840d1a1b86e0c28700de5362d3365a29555", + "commit_url" => + "https://api.github.com/repos/skywinder/changelog_test/commits/decfe840d1a1b86e0c28700de5362d3365a29555", + "created_at" => "2015-07-16T12:21:16Z" } + commit = fetcher.fetch_commit(event) + + expectations = [ + %w[sha decfe840d1a1b86e0c28700de5362d3365a29555], + ["url", + "https://api.github.com/repos/skywinder/changelog_test/commits/decfe840d1a1b86e0c28700de5362d3365a29555"], + # OLD API: "https://api.github.com/repos/skywinder/changelog_test/git/commits/decfe840d1a1b86e0c28700de5362d3365a29555"], + ["html_url", + "https://github.com/skywinder/changelog_test/commit/decfe840d1a1b86e0c28700de5362d3365a29555"], + ["author", + { "login" => "skywinder", "id" => 3_356_474, "avatar_url" => "https://avatars2.githubusercontent.com/u/3356474?v=4", "gravatar_id" => "", "url" => "https://api.github.com/users/skywinder", "html_url" => "https://github.com/skywinder", "followers_url" => "https://api.github.com/users/skywinder/followers", "following_url" => "https://api.github.com/users/skywinder/following{/other_user}", "gists_url" => "https://api.github.com/users/skywinder/gists{/gist_id}", "starred_url" => "https://api.github.com/users/skywinder/starred{/owner}{/repo}", "subscriptions_url" => "https://api.github.com/users/skywinder/subscriptions", "organizations_url" => "https://api.github.com/users/skywinder/orgs", "repos_url" => "https://api.github.com/users/skywinder/repos", "events_url" => "https://api.github.com/users/skywinder/events{/privacy}", "received_events_url" => "https://api.github.com/users/skywinder/received_events", "type" => "User", "site_admin" => false }], + ["committer", + { "login" => "skywinder", "id" => 3_356_474, "avatar_url" => "https://avatars2.githubusercontent.com/u/3356474?v=4", "gravatar_id" => "", "url" => "https://api.github.com/users/skywinder", "html_url" => "https://github.com/skywinder", "followers_url" => "https://api.github.com/users/skywinder/followers", "following_url" => "https://api.github.com/users/skywinder/following{/other_user}", "gists_url" => "https://api.github.com/users/skywinder/gists{/gist_id}", "starred_url" => "https://api.github.com/users/skywinder/starred{/owner}{/repo}", "subscriptions_url" => "https://api.github.com/users/skywinder/subscriptions", "organizations_url" => "https://api.github.com/users/skywinder/orgs", "repos_url" => "https://api.github.com/users/skywinder/repos", "events_url" => "https://api.github.com/users/skywinder/events{/privacy}", "received_events_url" => "https://api.github.com/users/skywinder/received_events", "type" => "User", "site_admin" => false }], + ["parents", + [{ "sha" => "7ec095e5e3caceacedabf44d0b9b10da17c92e51", + "url" => + "https://api.github.com/repos/skywinder/changelog_test/commits/7ec095e5e3caceacedabf44d0b9b10da17c92e51", + # OLD API: "https://api.github.com/repos/skywinder/changelog_test/git/commits/7ec095e5e3caceacedabf44d0b9b10da17c92e51", + "html_url" => + "https://github.com/skywinder/changelog_test/commit/7ec095e5e3caceacedabf44d0b9b10da17c92e51" }]] + ] + + expectations.each do |property, val| + expect(commit[property]).to eq(val) + end + end + end + end + + describe "#commits" do + context "when API is valid", :vcr do + subject do + fetcher.commits + end + + it "returns commits" do + expect(subject.last["sha"]).to eq("4c2d6d1ed58bdb24b870dcb5d9f2ceed0283d69d") + end + end + end +end From b1f9d092cf7f9f6be35cfd3e6db64c73e8c00fde Mon Sep 17 00:00:00 2001 From: kreczko Date: Thu, 1 Nov 2018 16:46:27 +0000 Subject: [PATCH 23/26] added gitlab_changelog_generator --- .gitignore | 3 +- bin/gitlab_changelog_generator | 5 ++++ lib/gitlab_changelog_generator.rb | 48 +++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100755 bin/gitlab_changelog_generator create mode 100755 lib/gitlab_changelog_generator.rb diff --git a/.gitignore b/.gitignore index ad0adbc35..7e3fbbc46 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ -bin/ +bin/* !bin/git-generate-changelog !bin/github_changelog_generator +!bin/gitlab_changelog_generator pkg/ coverage/ .bundle diff --git a/bin/gitlab_changelog_generator b/bin/gitlab_changelog_generator new file mode 100755 index 000000000..86ff22b9d --- /dev/null +++ b/bin/gitlab_changelog_generator @@ -0,0 +1,5 @@ +#! /usr/bin/env ruby +# frozen_string_literal: true + +require_relative "../lib/gitlab_changelog_generator" +GitLabChangelogGenerator::ChangelogGenerator.new.run diff --git a/lib/gitlab_changelog_generator.rb b/lib/gitlab_changelog_generator.rb new file mode 100755 index 000000000..8197b5fe1 --- /dev/null +++ b/lib/gitlab_changelog_generator.rb @@ -0,0 +1,48 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "octokit" +require "faraday-http-cache" +require "logger" +require "active_support" +require "active_support/core_ext/object/blank" +require "json" +require "multi_json" +require "benchmark" + +require "github_changelog_generator/helper" +require "github_changelog_generator/options" +require "github_changelog_generator/parser" +require "github_changelog_generator/parser_file" +require "github_changelog_generator/generator/generator" +require "github_changelog_generator/version" +require "github_changelog_generator/reader" + +# The main module, where placed all classes (now, at least) +module GitLabChangelogGenerator + # Main class and entry point for this script. + class ChangelogGenerator + # Class, responsible for whole changelog generation cycle + # @return initialised instance of ChangelogGenerator + def initialize + @options = GitHubChangelogGenerator::Parser.parse_options + @options[:gitlab] = true + @generator = GitHubChangelogGenerator::Generator.new @options + end + + # The entry point of this script to generate changelog + # @raise (ChangelogGeneratorError) Is thrown when one of specified tags was not found in list of tags. + def run + log = @generator.compound_changelog + + if @options.write_to_file? + output_filename = @options[:output].to_s + File.open(output_filename, "wb") { |file| file.write(log) } + puts "Done!" + puts "Generated log placed in #{Dir.pwd}/#{output_filename}" + else + puts log + end + end + end +end From 0e66b159559f60fab8d1931ea90f1d0cf2766049 Mon Sep 17 00:00:00 2001 From: kreczko Date: Thu, 1 Nov 2018 16:53:31 +0000 Subject: [PATCH 24/26] Last few Github -> Gitlab conversions --- lib/github_changelog_generator/gitlab_fetcher.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/github_changelog_generator/gitlab_fetcher.rb b/lib/github_changelog_generator/gitlab_fetcher.rb index 06a095d01..e0b39a161 100644 --- a/lib/github_changelog_generator/gitlab_fetcher.rb +++ b/lib/github_changelog_generator/gitlab_fetcher.rb @@ -5,7 +5,7 @@ require "gitlab" module GitLabChangelogGenerator - # A Fetcher responsible for all requests to GitHub and all basic manipulation with related data + # A Fetcher responsible for all requests to GitLab and all basic manipulation with related data # (such as filtering, validating, e.t.c) # # Example: @@ -15,10 +15,10 @@ class GitlabFetcher MAX_THREAD_NUMBER = 25 MAX_FORBIDDEN_RETRIES = 100 CHANGELOG_AUTH_TOKEN = "CHANGELOG_AUTH_TOKEN" - RATE_LIMIT_EXCEEDED_MSG = "Warning: Can't finish operation: GitHub API rate limit exceeded, changelog may be " \ + RATE_LIMIT_EXCEEDED_MSG = "Warning: Can't finish operation: GitLab API rate limit exceeded, changelog may be " \ "missing some issues. You can limit the number of issues fetched using the `--max-issues NUM` argument." NO_TOKEN_PROVIDED = "Warning: No token provided (-t option) and variable $CHANGELOG_AUTH_TOKEN was not found. " \ - "This script can make only 50 requests to GitHub API per hour without token!" + "This script can make only 50 requests to GitLab API per hour without token!" # @param options [Hash] Options passed in # @option options [String] :user Gitlab username @@ -417,7 +417,7 @@ def print_empty_line print_in_same_line(" ") end - # Returns GitHub token. First try to use variable, provided by --token option, + # Returns AUTH token. First try to use variable, provided by --token option, # otherwise try to fetch it from CHANGELOG_AUTH_TOKEN env variable. # # @return [String] From 182ff69325f1d456bc7a33716e90540c493215e4 Mon Sep 17 00:00:00 2001 From: kreczko Date: Thu, 1 Nov 2018 17:07:58 +0000 Subject: [PATCH 25/26] resolved second batch of feedback from @olleolleolle --- .../gitlab_fetcher.rb | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/lib/github_changelog_generator/gitlab_fetcher.rb b/lib/github_changelog_generator/gitlab_fetcher.rb index e0b39a161..721c94470 100644 --- a/lib/github_changelog_generator/gitlab_fetcher.rb +++ b/lib/github_changelog_generator/gitlab_fetcher.rb @@ -21,8 +21,8 @@ class GitlabFetcher "This script can make only 50 requests to GitLab API per hour without token!" # @param options [Hash] Options passed in - # @option options [String] :user Gitlab username - # @option options [String] :project Gitlab project + # @option options [String] :user GitLab username + # @option options [String] :project GitLab project # @option options [String] :since Only issues updated at or after this time are returned. This is a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ. eg. Time.parse("2016-01-01 10:00:00").iso8601 # @option options [Boolean] :http_cache Use ActiveSupport::Cache::FileStore to cache http requests # @option options [Boolean] :cache_file If using http_cache, this is the cache file path @@ -78,17 +78,16 @@ def fetch_tags tags = [] @client.tags(@project_id, DEFAULT_REQUEST_OPTIONS).auto_paginate do |new_tag| - tags.push(new_tag) + tags << new_tag end print_empty_line - if tags.count == 0 + if tags.empty? GitHubChangelogGenerator::Helper.log.warn "Warning: Can't find any tags in repo. \ Make sure, that you push tags to remote repo via 'git push --tags'" else GitHubChangelogGenerator::Helper.log.info "Found #{tags.count} tags" end - # tags are a Sawyer::Resource. Convert to hash tags.map { |resource| stringify_keys_deep(resource.to_hash) } end @@ -98,8 +97,7 @@ def closed_pr_options }.tap { |options| options[:since] = @since if @since } end - # This method fetch all closed issues and separate them to pull requests and pure issues - # (pull request is kind of issue in term of GitHub) + # This method fetch all closed issues pull requests (GitLab uses the term "merge requests") # # @return [Tuple] with (issues [Array ], pull-requests [Array ]) def fetch_closed_issues_and_pr @@ -138,7 +136,7 @@ def fetch_closed_pull_requests new_pr["user"] = { login: new_pr["author"]["username"], html_url: new_pr["author"]["web_url"] } # to make it work with older gitlab version or repos that lived across versions new_pr["merge_commit_sha"] = new_pr["merge_commit_sha"].nil? ? new_pr["sha"] : new_pr["merge_commit_sha"] - pull_requests.push(new_pr) + pull_requests << new_pr end print_empty_line @@ -156,15 +154,15 @@ def fetch_events_async(issues) threads = [] options = {} return if issues.empty? + options[:target_type] = issues.first["merged_at"].nil? ? "issue" : "merge_request" issue_events = [] @client.project_events(@project_id, options).auto_paginate do |event| event = stringify_keys_deep(event.to_hash) # gitlab to github event["event"] = event["action_name"] - issue_events.push(event) + issue_events << event end - # p issue_events issues.each_slice(MAX_THREAD_NUMBER) do |issues_slice| issues_slice.each do |issue| @@ -175,7 +173,7 @@ def fetch_events_async(issues) if new_event["action_name"].eql? "closed" issue["closed_at"] = issue["closed_at"].nil? ? new_event["created_at"] : issue["closed_at"] end - issue["events"].push(new_event) + issue["events"] << new_event end end print_in_same_line("Fetching events for #{options[:target_type]}s: #{i + 1}/#{issues.count}") @@ -194,7 +192,7 @@ def fetch_events_async(issues) # Fetch comments for PRs and add them to "comments" # - # @param [Array] prs The array of PRs. + # @param prs [Array] PRs for which to fetch comments # @return [Void] No return; PRs are updated in-place. def fetch_comments_async(prs) threads = [] @@ -219,18 +217,18 @@ def fetch_comments_async(prs) # Fetch tag time from repo # - # @param [Hash] tag GitHub data item about a Tag + # @param [Hash] tag GitLab data item about a Tag # # @return [Time] time of specified tag def fetch_date_of_tag(tag) Time.parse(tag["commit"]["committed_date"]) end - # Fetch and cache comparison between two github refs + # Fetch and cache comparison between two GitLab refs # # @param [String] older The older sha/tag/branch. # @param [String] newer The newer sha/tag/branch. - # @return [Hash] Github api response for comparison. + # @return [Hash] GitLab api response for comparison. def fetch_compare(older, newer) unless @compares["#{older}...#{newer}"] compare_data = check_response { @client.compare(@project_id, older, newer || "HEAD") } @@ -240,7 +238,7 @@ def fetch_compare(older, newer) end # TODO: do not know what the equivalent for gitlab is if compare_data["compare_same_ref"] == true - raise StandardError, "Sha #{older} and sha #{newer} are not related; please file a github-changelog-generator issues and describe how to replicate this issue." + raise StandardError, "Sha #{older} and sha #{newer} are not related; please file a github-changelog-generator issue and describe how to replicate this issue." end @compares["#{older}...#{newer}"] = stringify_keys_deep(compare_data.to_hash) end @@ -276,7 +274,7 @@ def commits @client.commits(@project_id).auto_paginate do |new_commit| new_commit = stringify_keys_deep(new_commit.to_hash) new_commit["sha"] = new_commit["id"] - @commits.push(new_commit) + @commits << new_commit end end @commits @@ -298,7 +296,7 @@ def default_branch # "shas_in_tag" # # @param [Array] tags The array of tags. - # @return [Nil] No return; tags are updated in-place. + # @return [void] No return; tags are updated in-place. def fetch_tag_shas_async(tags) i = 0 threads = [] From 602a923b64b0624d70971959b487ae917aa040ae Mon Sep 17 00:00:00 2001 From: Chris Denneen Date: Wed, 29 Jan 2020 09:54:34 -0500 Subject: [PATCH 26/26] Add gitlab to rake task --- lib/github_changelog_generator/task.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/github_changelog_generator/task.rb b/lib/github_changelog_generator/task.rb index 57a5a19f3..d06dc6451 100644 --- a/lib/github_changelog_generator/task.rb +++ b/lib/github_changelog_generator/task.rb @@ -17,7 +17,7 @@ class RakeTask < ::Rake::TaskLib compare_link include_labels exclude_labels bug_labels enhancement_labels between_tags exclude_tags exclude_tags_regex since_tag max_issues - github_site github_endpoint simple_list + github_site github_endpoint simple_list gitlab future_release release_branch verbose release_url base configure_sections add_sections]