changelog_jira 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +233 -0
  3. data/Rakefile +39 -0
  4. data/bin/git-generate-changelog +4 -0
  5. data/bin/github_changelog_generator +4 -0
  6. data/lib/CHANGELOG.md +58 -0
  7. data/lib/github_changelog_generator.rb +41 -0
  8. data/lib/github_changelog_generator/fetcher.rb +221 -0
  9. data/lib/github_changelog_generator/generator/generator.rb +143 -0
  10. data/lib/github_changelog_generator/generator/generator_fetcher.rb +83 -0
  11. data/lib/github_changelog_generator/generator/generator_generation.rb +190 -0
  12. data/lib/github_changelog_generator/generator/generator_processor.rb +193 -0
  13. data/lib/github_changelog_generator/generator/generator_tags.rb +184 -0
  14. data/lib/github_changelog_generator/helper.rb +37 -0
  15. data/lib/github_changelog_generator/parser.rb +285 -0
  16. data/lib/github_changelog_generator/parser_file.rb +103 -0
  17. data/lib/github_changelog_generator/reader.rb +84 -0
  18. data/lib/github_changelog_generator/task.rb +67 -0
  19. data/lib/github_changelog_generator/version.rb +3 -0
  20. data/man/git-generate-changelog.1 +252 -0
  21. data/man/git-generate-changelog.html +262 -0
  22. data/man/git-generate-changelog.md +179 -0
  23. data/spec/files/angular.js.md +9395 -0
  24. data/spec/files/bundler.md +1911 -0
  25. data/spec/files/github-changelog-generator.md +305 -0
  26. data/spec/install-gem-in-bundler.gemfile +3 -0
  27. data/spec/spec_helper.rb +55 -0
  28. data/spec/unit/fetcher_spec.rb +59 -0
  29. data/spec/unit/generator/generator_processor_spec.rb +28 -0
  30. data/spec/unit/generator/generator_tags_spec.rb +243 -0
  31. data/spec/unit/parse_file_spec.rb +73 -0
  32. data/spec/unit/parser_spec.rb +60 -0
  33. data/spec/unit/reader_spec.rb +113 -0
  34. metadata +190 -0
@@ -0,0 +1,143 @@
1
+ require_relative "../fetcher"
2
+ require_relative "generator_generation"
3
+ require_relative "generator_fetcher"
4
+ require_relative "generator_processor"
5
+ require_relative "generator_tags"
6
+
7
+ module GitHubChangelogGenerator
8
+ # Default error for ChangelogGenerator
9
+ class ChangelogGeneratorError < StandardError
10
+ end
11
+
12
+ class Generator
13
+ attr_accessor :options, :filtered_tags, :github
14
+
15
+ # A Generator responsible for all logic, related with change log generation from ready-to-parse issues
16
+ #
17
+ # Example:
18
+ # generator = GitHubChangelogGenerator::Generator.new
19
+ # content = generator.compound_changelog
20
+ def initialize(options = nil)
21
+ @options = options || {}
22
+ @tag_times_hash = {}
23
+ @fetcher = GitHubChangelogGenerator::Fetcher.new @options
24
+ end
25
+
26
+ def fetch_issues_and_pr
27
+ issues, pull_requests = @fetcher.fetch_closed_issues_and_pr
28
+
29
+ @pull_requests = @options[:pulls] ? get_filtered_pull_requests(pull_requests) : []
30
+
31
+ @issues = @options[:issues] ? get_filtered_issues(issues) : []
32
+
33
+ fetch_events_for_issues_and_pr
34
+ detect_actual_closed_dates(@issues + @pull_requests)
35
+ end
36
+
37
+ # Encapsulate characters to make markdown look as expected.
38
+ #
39
+ # @param [String] string
40
+ # @return [String] encapsulated input string
41
+ def encapsulate_string(string)
42
+ string.gsub! '\\', '\\\\'
43
+
44
+ encpas_chars = %w(< > * _ \( \) [ ] #)
45
+ encpas_chars.each do |char|
46
+ string.gsub! char, "\\#{char}"
47
+ end
48
+
49
+ string
50
+ end
51
+
52
+ # Generates log for section with header and body
53
+ #
54
+ # @param [Array] pull_requests List or PR's in new section
55
+ # @param [Array] issues List of issues in new section
56
+ # @param [String] newer_tag Name of the newer tag. Could be nil for `Unreleased` section
57
+ # @param [String] older_tag_name Older tag, used for the links. Could be nil for last tag.
58
+ # @return [String] Ready and parsed section
59
+ def create_log_for_tag(pull_requests, issues, newer_tag, older_tag_name = nil)
60
+ newer_tag_link, newer_tag_name, newer_tag_time = detect_link_tag_time(newer_tag)
61
+
62
+ github_site = options[:github_site] || "https://github.com"
63
+ project_url = "#{github_site}/#{@options[:user]}/#{@options[:project]}"
64
+
65
+ log = generate_header(newer_tag_name, newer_tag_link, newer_tag_time, older_tag_name, project_url)
66
+
67
+ if @options[:issues]
68
+ # Generate issues:
69
+ log += issues_to_log(issues, pull_requests)
70
+ end
71
+
72
+ if @options[:pulls]
73
+ # Generate pull requests:
74
+ log += generate_sub_section(pull_requests, @options[:merge_prefix])
75
+ end
76
+
77
+ log
78
+ end
79
+
80
+ # Generate ready-to-paste log from list of issues and pull requests.
81
+ #
82
+ # @param [Array] issues
83
+ # @param [Array] pull_requests
84
+ # @return [String] generated log for issues
85
+ def issues_to_log(issues, pull_requests)
86
+ log = ""
87
+ bugs_a, enhancement_a, issues_a = parse_by_sections(issues, pull_requests)
88
+
89
+ log += generate_sub_section(enhancement_a, @options[:enhancement_prefix])
90
+ log += generate_sub_section(bugs_a, @options[:bug_prefix])
91
+ log += generate_sub_section(issues_a, @options[:issue_prefix])
92
+ log
93
+ end
94
+
95
+ # This method sort issues by types
96
+ # (bugs, features, or just closed issues) by labels
97
+ #
98
+ # @param [Array] issues
99
+ # @param [Array] pull_requests
100
+ # @return [Array] tuple of filtered arrays: (Bugs, Enhancements Issues)
101
+ def parse_by_sections(issues, pull_requests)
102
+ issues_a = []
103
+ enhancement_a = []
104
+ bugs_a = []
105
+
106
+ issues.each do |dict|
107
+ added = false
108
+ dict.labels.each do |label|
109
+ if @options[:bug_labels].include? label.name
110
+ bugs_a.push dict
111
+ added = true
112
+ next
113
+ end
114
+ if @options[:enhancement_labels].include? label.name
115
+ enhancement_a.push dict
116
+ added = true
117
+ next
118
+ end
119
+ end
120
+ issues_a.push dict unless added
121
+ end
122
+
123
+ added_pull_requests = []
124
+ pull_requests.each do |dict|
125
+ dict.labels.each do |label|
126
+ if @options[:bug_labels].include? label.name
127
+ bugs_a.push dict
128
+ added_pull_requests.push dict
129
+ next
130
+ end
131
+ if @options[:enhancement_labels].include? label.name
132
+ enhancement_a.push dict
133
+ added_pull_requests.push dict
134
+ next
135
+ end
136
+ end
137
+ end
138
+ added_pull_requests.each { |p| pull_requests.delete(p) }
139
+
140
+ [bugs_a, enhancement_a, issues_a]
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,83 @@
1
+ module GitHubChangelogGenerator
2
+ class Generator
3
+ # Fetch event for issues and pull requests
4
+ # @return [Array] array of fetched issues
5
+ def fetch_events_for_issues_and_pr
6
+ if @options[:verbose]
7
+ print "Fetching events for issues and PR: 0/#{@issues.count + @pull_requests.count}\r"
8
+ end
9
+
10
+ # Async fetching events:
11
+ @fetcher.fetch_events_async(@issues + @pull_requests)
12
+ end
13
+
14
+ # Async fetching of all tags dates
15
+ def fetch_tags_dates
16
+ print "Fetching tag dates...\r" if @options[:verbose]
17
+ # Async fetching tags:
18
+ threads = []
19
+ i = 0
20
+ all = @filtered_tags.count
21
+ @filtered_tags.each do |tag|
22
+ print " \r"
23
+ threads << Thread.new do
24
+ get_time_of_tag(tag)
25
+ print "Fetching tags dates: #{i + 1}/#{all}\r" if @options[:verbose]
26
+ i += 1
27
+ end
28
+ end
29
+ threads.each(&:join)
30
+ puts "Fetching tags dates: #{i}" if @options[:verbose]
31
+ end
32
+
33
+ # Find correct closed dates, if issues was closed by commits
34
+ def detect_actual_closed_dates(issues)
35
+ print "Fetching closed dates for issues...\r" if @options[:verbose]
36
+
37
+ max_thread_number = 50
38
+ issues.each_slice(max_thread_number) do |issues_slice|
39
+ threads = []
40
+ issues_slice.each do |issue|
41
+ threads << Thread.new { find_closed_date_by_commit(issue) }
42
+ end
43
+ threads.each(&:join)
44
+ end
45
+ puts "Fetching closed dates for issues: Done!" if @options[:verbose]
46
+ end
47
+
48
+ # Fill :actual_date parameter of specified issue by closed date of the commit, if it was closed by commit.
49
+ # @param [Hash] issue
50
+ def find_closed_date_by_commit(issue)
51
+ unless issue[:events].nil?
52
+ # if it's PR -> then find "merged event", in case of usual issue -> fond closed date
53
+ compare_string = issue[:merged_at].nil? ? "closed" : "merged"
54
+ # reverse! - to find latest closed event. (event goes in date order)
55
+ issue[:events].reverse!.each do |event|
56
+ if event[:event].eql? compare_string
57
+ set_date_from_event(event, issue)
58
+ break
59
+ end
60
+ end
61
+ end
62
+ # TODO: assert issues, that remain without 'actual_date' hash for some reason.
63
+ end
64
+
65
+ # Set closed date from this issue
66
+ #
67
+ # @param [Hash] event
68
+ # @param [Hash] issue
69
+ def set_date_from_event(event, issue)
70
+ if event[:commit_id].nil?
71
+ issue[:actual_date] = issue[:closed_at]
72
+ else
73
+ begin
74
+ commit = @fetcher.fetch_commit(event)
75
+ issue[:actual_date] = commit[:author][:date]
76
+ rescue
77
+ puts "Warning: Can't fetch commit #{event[:commit_id]}. It is probably referenced from another repo.".yellow
78
+ issue[:actual_date] = issue[:closed_at]
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,190 @@
1
+ module GitHubChangelogGenerator
2
+ class Generator
3
+ # Main function to start change log generation
4
+ #
5
+ # @return [String] Generated change log file
6
+ def compound_changelog
7
+ fetch_and_filter_tags
8
+ sort_tags_by_date(@filtered_tags)
9
+ fetch_issues_and_pr
10
+
11
+ log = ""
12
+ log += @options[:frontmatter] if @options[:frontmatter]
13
+ log += "#{@options[:header]}\n\n"
14
+
15
+ log += if @options[:unreleased_only]
16
+ generate_log_between_tags(filtered_tags[0], nil)
17
+ else
18
+ generate_log_for_all_tags
19
+ end
20
+
21
+ log += File.read(@options[:base]) if File.file?(@options[:base])
22
+
23
+ log += "\n"
24
+ @log = log
25
+ end
26
+
27
+ # @return [String] temp method should be removed soon
28
+ def generate_for_2_tags(log)
29
+ tag1 = @options[:tag1]
30
+ tag2 = @options[:tag2]
31
+ tags_strings = []
32
+ filtered_tags.each { |x| tags_strings.push(x["name"]) }
33
+
34
+ if tags_strings.include?(tag1)
35
+ if tags_strings.include?(tag2)
36
+ to_a = tags_strings.map.with_index.to_a
37
+ hash = Hash[to_a]
38
+ index1 = hash[tag1]
39
+ index2 = hash[tag2]
40
+ log += generate_log_between_tags(all_tags[index1], all_tags[index2])
41
+ else
42
+ raise ChangelogGeneratorError, "Can't find tag #{tag2} -> exit".red
43
+ end
44
+ else
45
+ raise ChangelogGeneratorError, "Can't find tag #{tag1} -> exit".red
46
+ end
47
+ log
48
+ end
49
+
50
+ # @param [Array] issues List of issues on sub-section
51
+ # @param [String] prefix Nae of sub-section
52
+ # @return [String] Generate ready-to-go sub-section
53
+ def generate_sub_section(issues, prefix)
54
+ log = ""
55
+
56
+ if issues.any?
57
+ log += "#{prefix}\n\n" unless options[:simple_list]
58
+ issues.each do |issue|
59
+
60
+ if issue[:title].include? " :: "
61
+ t = issue[:title].split(" :: ")
62
+ merge_string = "[#{t[0]}](https://jira.netwerven.nl/browse/" + t[0] + ") :: " + get_string_for_issue(issue).sub("#{t[0]} :: ", "")
63
+ else
64
+ merge_string = get_string_for_issue(issue)
65
+ end
66
+ log += "- #{merge_string}\n"
67
+ end
68
+ log += "\n"
69
+ end
70
+ log
71
+ end
72
+
73
+ # It generate one header for section with specific parameters.
74
+ #
75
+ # @param [String] newer_tag_name - name of newer tag
76
+ # @param [String] newer_tag_link - used for links. Could be same as #newer_tag_name or some specific value, like HEAD
77
+ # @param [Time] newer_tag_time - time, when newer tag created
78
+ # @param [String] older_tag_link - tag name, used for links.
79
+ # @param [String] project_url - url for current project.
80
+ # @return [String] - Generate one ready-to-add section.
81
+ def generate_header(newer_tag_name, newer_tag_link, newer_tag_time, older_tag_link, project_url)
82
+ log = ""
83
+
84
+ # Generate date string:
85
+ time_string = newer_tag_time.strftime @options[:date_format]
86
+
87
+ # Generate tag name and link
88
+ release_url = if @options[:release_url]
89
+ format(@options[:release_url], newer_tag_link)
90
+ else
91
+ "#{project_url}/tree/#{newer_tag_link}"
92
+ end
93
+ log += if newer_tag_name.equal? @options[:unreleased_label]
94
+ "## [#{newer_tag_name}](#{release_url})\n\n"
95
+ else
96
+ "## [#{newer_tag_name}](#{release_url}) (#{time_string})\n"
97
+ end
98
+
99
+ if @options[:compare_link] && older_tag_link
100
+ # Generate compare link
101
+ log += "[Full Changelog](#{project_url}/compare/#{older_tag_link}...#{newer_tag_link})\n\n"
102
+ end
103
+
104
+ log
105
+ end
106
+
107
+ # Generate log only between 2 specified tags
108
+ # @param [String] older_tag all issues before this tag date will be excluded. May be nil, if it's first tag
109
+ # @param [String] newer_tag all issue after this tag will be excluded. May be nil for unreleased section
110
+ def generate_log_between_tags(older_tag, newer_tag)
111
+ filtered_issues, filtered_pull_requests = filter_issues_for_tags(newer_tag, older_tag)
112
+
113
+ older_tag_name = older_tag.nil? ? detect_since_tag : older_tag["name"]
114
+
115
+ if newer_tag.nil? && filtered_issues.empty? && filtered_pull_requests.empty?
116
+ # do not generate empty unreleased section
117
+ return ""
118
+ end
119
+
120
+ create_log_for_tag(filtered_pull_requests, filtered_issues, newer_tag, older_tag_name)
121
+ end
122
+
123
+ # Apply all filters to issues and pull requests
124
+ #
125
+ # @return [Array] filtered issues and pull requests
126
+ def filter_issues_for_tags(newer_tag, older_tag)
127
+ filtered_pull_requests = delete_by_time(@pull_requests, :actual_date, older_tag, newer_tag)
128
+ filtered_issues = delete_by_time(@issues, :actual_date, older_tag, newer_tag)
129
+
130
+ newer_tag_name = newer_tag.nil? ? nil : newer_tag["name"]
131
+
132
+ if @options[:filter_issues_by_milestone]
133
+ # delete excess irrelevant issues (according milestones). Issue #22.
134
+ filtered_issues = filter_by_milestone(filtered_issues, newer_tag_name, @issues)
135
+ filtered_pull_requests = filter_by_milestone(filtered_pull_requests, newer_tag_name, @pull_requests)
136
+ end
137
+ [filtered_issues, filtered_pull_requests]
138
+ end
139
+
140
+ # The full cycle of generation for whole project
141
+ # @return [String] The complete change log
142
+ def generate_log_for_all_tags
143
+ puts "Generating log..." if @options[:verbose]
144
+
145
+ log = generate_unreleased_section
146
+
147
+ (1...filtered_tags.size).each do |index|
148
+ log += generate_log_between_tags(filtered_tags[index], filtered_tags[index - 1])
149
+ end
150
+ if @filtered_tags.count != 0
151
+ log += generate_log_between_tags(nil, filtered_tags.last)
152
+ end
153
+
154
+ log
155
+ end
156
+
157
+ def generate_unreleased_section
158
+ log = ""
159
+ if @options[:unreleased]
160
+ unreleased_log = generate_log_between_tags(filtered_tags[0], nil)
161
+ log += unreleased_log if unreleased_log
162
+ end
163
+ log
164
+ end
165
+
166
+ # Parse issue and generate single line formatted issue line.
167
+ #
168
+ # Example output:
169
+ # - Add coveralls integration [\#223](https://github.com/skywinder/github-changelog-generator/pull/223) ([skywinder](https://github.com/skywinder))
170
+ #
171
+ # @param [Hash] issue Fetched issue from GitHub
172
+ # @return [String] Markdown-formatted single issue
173
+ def get_string_for_issue(issue)
174
+ encapsulated_title = encapsulate_string issue[:title]
175
+
176
+ title_with_number = "#{encapsulated_title} [\\##{issue[:number]}](#{issue.html_url})"
177
+
178
+ unless issue.pull_request.nil?
179
+ if @options[:author]
180
+ title_with_number += if issue.user.nil?
181
+ " ({Null user})"
182
+ else
183
+ " ([#{issue.user.login}](#{issue.user.html_url}))"
184
+ end
185
+ end
186
+ end
187
+ title_with_number
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,193 @@
1
+ module GitHubChangelogGenerator
2
+ class Generator
3
+ # delete all labels with labels from @options[:exclude_labels] array
4
+ # @param [Array] issues
5
+ # @return [Array] filtered array
6
+ def exclude_issues_by_labels(issues)
7
+ return issues if !@options[:exclude_labels] || @options[:exclude_labels].empty?
8
+
9
+ issues.reject do |issue|
10
+ labels = issue.labels.map(&:name)
11
+ (labels & @options[:exclude_labels]).any?
12
+ end
13
+ end
14
+
15
+ # @return [Array] filtered issues accourding milestone
16
+ def filter_by_milestone(filtered_issues, tag_name, all_issues)
17
+ remove_issues_in_milestones(filtered_issues)
18
+ unless tag_name.nil?
19
+ # add missed issues (according milestones)
20
+ issues_to_add = find_issues_to_add(all_issues, tag_name)
21
+
22
+ filtered_issues |= issues_to_add
23
+ end
24
+ filtered_issues
25
+ end
26
+
27
+ # Add all issues, that should be in that tag, according milestone
28
+ #
29
+ # @param [Array] all_issues
30
+ # @param [String] tag_name
31
+ # @return [Array] issues with milestone #tag_name
32
+ def find_issues_to_add(all_issues, tag_name)
33
+ all_issues.select do |issue|
34
+ if issue.milestone.nil?
35
+ false
36
+ else
37
+ # check, that this milestone in tag list:
38
+ milestone_is_tag = @filtered_tags.find do |tag|
39
+ tag.name == issue.milestone.title
40
+ end
41
+
42
+ if milestone_is_tag.nil?
43
+ false
44
+ else
45
+ issue.milestone.title == tag_name
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ # @return [Array] array with removed issues, that contain milestones with same name as a tag
52
+ def remove_issues_in_milestones(filtered_issues)
53
+ filtered_issues.select! do |issue|
54
+ # leave issues without milestones
55
+ if issue.milestone.nil?
56
+ true
57
+ else
58
+ # check, that this milestone in tag list:
59
+ @filtered_tags.find { |tag| tag.name == issue.milestone.title }.nil?
60
+ end
61
+ end
62
+ end
63
+
64
+ # Method filter issues, that belong only specified tag range
65
+ # @param [Array] issues issues to filter
66
+ # @param [Symbol] hash_key key of date value default is :actual_date
67
+ # @param [String] older_tag all issues before this tag date will be excluded. May be nil, if it's first tag
68
+ # @param [String] newer_tag all issue after this tag will be excluded. May be nil for unreleased section
69
+ # @return [Array] filtered issues
70
+ def delete_by_time(issues, hash_key = :actual_date, older_tag = nil, newer_tag = nil)
71
+ # in case if not tags specified - return unchanged array
72
+ return issues if older_tag.nil? && newer_tag.nil?
73
+
74
+ newer_tag_time = newer_tag && get_time_of_tag(newer_tag)
75
+ older_tag_time = older_tag && get_time_of_tag(older_tag)
76
+
77
+ issues.select do |issue|
78
+ if issue[hash_key]
79
+ time = Time.parse(issue[hash_key]).utc
80
+
81
+ tag_in_range_old = tag_newer_old_tag?(older_tag_time, time)
82
+
83
+ tag_in_range_new = tag_older_new_tag?(newer_tag_time, time)
84
+
85
+ tag_in_range = tag_in_range_old && tag_in_range_new
86
+
87
+ tag_in_range
88
+ else
89
+ false
90
+ end
91
+ end
92
+ end
93
+
94
+ def tag_older_new_tag?(newer_tag_time, time)
95
+ tag_in_range_new = if newer_tag_time.nil?
96
+ true
97
+ else
98
+ time <= newer_tag_time
99
+ end
100
+ tag_in_range_new
101
+ end
102
+
103
+ def tag_newer_old_tag?(older_tag_time, t)
104
+ tag_in_range_old = if older_tag_time.nil?
105
+ true
106
+ else
107
+ t > older_tag_time
108
+ end
109
+ tag_in_range_old
110
+ end
111
+
112
+ # Include issues with labels, specified in :include_labels
113
+ # @param [Array] issues to filter
114
+ # @return [Array] filtered array of issues
115
+ def include_issues_by_labels(issues)
116
+ filtered_issues = filter_by_include_labels(issues)
117
+ filtered_issues |= filter_wo_labels(issues)
118
+ filtered_issues
119
+ end
120
+
121
+ # @return [Array] issues without labels or empty array if add_issues_wo_labels is false
122
+ def filter_wo_labels(issues)
123
+ if @options[:add_issues_wo_labels]
124
+ issues_wo_labels = issues.select do |issue|
125
+ !issue.labels.map(&:name).any?
126
+ end
127
+ return issues_wo_labels
128
+ end
129
+ []
130
+ end
131
+
132
+ def filter_by_include_labels(issues)
133
+ filtered_issues = @options[:include_labels].nil? ? issues : issues.select do |issue|
134
+ labels = issue.labels.map(&:name) & @options[:include_labels]
135
+ labels.any?
136
+ end
137
+ filtered_issues
138
+ end
139
+
140
+ # General filtered function
141
+ #
142
+ # @param [Array] all_issues
143
+ # @return [Array] filtered issues
144
+ def filter_array_by_labels(all_issues)
145
+ filtered_issues = include_issues_by_labels(all_issues)
146
+ exclude_issues_by_labels(filtered_issues)
147
+ end
148
+
149
+ # Filter issues according labels
150
+ # @return [Array] Filtered issues
151
+ def get_filtered_issues(issues)
152
+ issues = filter_array_by_labels(issues)
153
+ puts "Filtered issues: #{issues.count}" if @options[:verbose]
154
+ issues
155
+ end
156
+
157
+ # This method fetches missing params for PR and filter them by specified options
158
+ # It include add all PR's with labels from @options[:include_labels] array
159
+ # And exclude all from :exclude_labels array.
160
+ # @return [Array] filtered PR's
161
+ def get_filtered_pull_requests(pull_requests)
162
+ pull_requests = filter_array_by_labels(pull_requests)
163
+ pull_requests = filter_merged_pull_requests(pull_requests)
164
+ puts "Filtered pull requests: #{pull_requests.count}" if @options[:verbose]
165
+ pull_requests
166
+ end
167
+
168
+ # This method filter only merged PR and
169
+ # fetch missing required attributes for pull requests
170
+ # :merged_at - is a date, when issue PR was merged.
171
+ # More correct to use merged date, rather than closed date.
172
+ def filter_merged_pull_requests(pull_requests)
173
+ print "Fetching merged dates...\r" if @options[:verbose]
174
+ closed_pull_requests = @fetcher.fetch_closed_pull_requests
175
+
176
+ pull_requests.each do |pr|
177
+ fetched_pr = closed_pull_requests.find do |fpr|
178
+ fpr.number == pr.number
179
+ end
180
+ if fetched_pr
181
+ pr[:merged_at] = fetched_pr[:merged_at]
182
+ closed_pull_requests.delete(fetched_pr)
183
+ end
184
+ end
185
+
186
+ pull_requests.select! do |pr|
187
+ !pr[:merged_at].nil?
188
+ end
189
+
190
+ pull_requests
191
+ end
192
+ end
193
+ end