github_changelog_generator 1.2.8 → 1.3.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.
@@ -38,9 +38,17 @@ module GitHubChangelogGenerator
38
38
  @generator = Generator.new(@options)
39
39
 
40
40
  @all_tags = self.get_all_tags
41
- @pull_requests = self.get_filtered_pull_requests
41
+ @issues, @pull_requests = self.fetch_issues_and_pull_requests
42
+
43
+ if @options[:pulls]
44
+ @pull_requests = self.get_filtered_pull_requests
45
+ self.fetch_merged_at_pull_requests
46
+ else
47
+ @pull_requests = []
48
+ end
49
+
42
50
  if @options[:issues]
43
- @issues = self.get_all_issues
51
+ @issues = self.get_filtered_issues
44
52
  fetch_event_for_issues(@issues)
45
53
  detect_actual_closed_dates
46
54
  else
@@ -53,11 +61,11 @@ module GitHubChangelogGenerator
53
61
  def detect_actual_closed_dates
54
62
 
55
63
  if @options[:verbose]
56
- print "Fetching close commit date for issues...\r"
64
+ print "Fetching closed dates for issues...\r"
57
65
  end
58
66
 
59
67
  threads = []
60
- @issues.each{|issue|
68
+ @issues.each { |issue|
61
69
  threads << Thread.new {
62
70
  find_closed_date_by_commit(issue)
63
71
  }
@@ -65,14 +73,14 @@ module GitHubChangelogGenerator
65
73
  threads.each { |thr| thr.join }
66
74
 
67
75
  if @options[:verbose]
68
- puts 'Fetching close commit date for issues: Done!'
76
+ puts 'Fetching closed dates for issues: Done!'
69
77
  end
70
78
  end
71
79
 
72
80
  def find_closed_date_by_commit(issue)
73
81
  unless issue['events'].nil?
74
82
  # reverse! - to find latest closed event. (event goes in date order)
75
- issue['events'].reverse!.each{|event|
83
+ issue['events'].reverse!.each { |event|
76
84
  if event[:event].eql? 'closed'
77
85
  if event[:commit_id].nil?
78
86
  issue[:actual_date] = issue[:closed_at]
@@ -96,7 +104,7 @@ module GitHubChangelogGenerator
96
104
  %x[#{exec_cmd}]
97
105
  end
98
106
 
99
- def get_all_closed_pull_requests
107
+ def fetch_merged_at_pull_requests
100
108
  if @options[:verbose]
101
109
  print "Fetching pull requests...\r"
102
110
  end
@@ -110,60 +118,99 @@ module GitHubChangelogGenerator
110
118
  print "Fetching pull requests... #{page_i}/#{count_pages * PER_PAGE_NUMBER}\r"
111
119
  pull_requests.concat(page)
112
120
  end
113
- print " \r"
121
+ print " \r"
114
122
 
115
123
  if @options[:verbose]
116
124
  puts "Received pull requests: #{pull_requests.count}"
117
125
  end
118
126
 
119
- pull_requests
127
+ @pull_requests.each { |pr|
128
+ fetched_pr = pull_requests.find { |fpr|
129
+ fpr.number == pr.number }
130
+ pr[:merged_at] = fetched_pr[:merged_at]
131
+ pull_requests.delete(fetched_pr)
132
+ }
120
133
  end
121
134
 
122
135
  def get_filtered_pull_requests
123
136
 
124
- pull_requests = self.get_all_closed_pull_requests
125
-
126
- unless @options[:pull_request_labels].nil?
127
-
128
- if @options[:verbose]
129
- puts 'Filter all pull requests by labels.'
130
- end
131
-
132
- filtered_pull_requests = pull_requests.select { |pull_request|
133
- #fetch this issue to get labels array
134
- issue = @github.issues.get @options[:user], @options[:project], pull_request.number
137
+ pull_requests = @pull_requests
138
+ filtered_pull_requests = pull_requests
135
139
 
136
- #compare is there any labels from @options[:labels] array
137
- issue_without_labels = !issue.labels.map { |label| label.name }.any?
138
140
 
139
- if @options[:verbose]
140
- puts "Filter request \##{issue.number}."
141
- end
142
-
143
- if @options[:pull_request_labels].any?
144
- select_by_label = (issue.labels.map { |label| label.name } & @options[:pull_request_labels]).any?
145
- else
146
- select_by_label = false
147
- end
141
+ unless @options[:include_labels].nil?
142
+ filtered_pull_requests = pull_requests.select { |issue|
143
+ #add all labels from @options[:incluse_labels] array
144
+ (issue.labels.map { |label| label.name } & @options[:include_labels]).any?
145
+ }
146
+ end
148
147
 
149
- select_by_label | issue_without_labels
148
+ unless @options[:exclude_labels].nil?
149
+ filtered_pull_requests = filtered_pull_requests.select { |issue|
150
+ #delete all labels from @options[:exclude_labels] array
151
+ !(issue.labels.map { |label| label.name } & @options[:exclude_labels]).any?
150
152
  }
153
+ end
151
154
 
152
- if @options[:verbose]
153
- puts "Filtered pull requests with specified labels and w/o labels: #{filtered_pull_requests.count}"
154
- end
155
- return filtered_pull_requests
155
+ if @options[:add_issues_wo_labels]
156
+ issues_wo_labels = pull_requests.select {
157
+ # add issues without any labels
158
+ |issue| !issue.labels.map { |label| label.name }.any?
159
+ }
160
+ filtered_pull_requests |= issues_wo_labels
156
161
  end
157
162
 
158
- pull_requests
163
+
164
+ if @options[:verbose]
165
+ puts "Filtered pull requests: #{filtered_pull_requests.count}"
166
+ end
167
+
168
+ filtered_pull_requests
169
+ #
170
+ # #
171
+ #
172
+ #
173
+ # unless @options[:pull_request_labels].nil?
174
+ #
175
+ # if @options[:verbose]
176
+ # puts 'Filter all pull requests by labels.'
177
+ # end
178
+ #
179
+ # filtered_pull_requests = filtered_pull_requests.select { |pull_request|
180
+ # #fetch this issue to get labels array
181
+ # issue = @github.issues.get @options[:user], @options[:project], pull_request.number
182
+ #
183
+ # #compare is there any labels from @options[:labels] array
184
+ # issue_without_labels = !issue.labels.map { |label| label.name }.any?
185
+ #
186
+ # if @options[:verbose]
187
+ # puts "Filter request \##{issue.number}."
188
+ # end
189
+ #
190
+ # if @options[:pull_request_labels].any?
191
+ # select_by_label = (issue.labels.map { |label| label.name } & @options[:pull_request_labels]).any?
192
+ # else
193
+ # select_by_label = false
194
+ # end
195
+ #
196
+ # select_by_label | issue_without_labels
197
+ # }
198
+ #
199
+ # if @options[:verbose]
200
+ # puts "Filtered pull requests with specified labels and w/o labels: #{filtered_pull_requests.count}"
201
+ # end
202
+ # return filtered_pull_requests
203
+ # end
204
+ #
205
+ # filtered_pull_requests
159
206
  end
160
207
 
161
208
  def compund_changelog
162
209
 
163
- log = "# Changelog\n\n"
210
+ log = "# Change Log\n\n"
164
211
 
165
- if @options[:last]
166
- log += self.generate_log_between_tags(self.all_tags[0], self.all_tags[1])
212
+ if @options[:unreleased_only]
213
+ log += self.generate_log_between_tags(self.all_tags[0], nil)
167
214
  elsif @options[:tag1] and @options[:tag2]
168
215
  tag1 = @options[:tag1]
169
216
  tag2 = @options[:tag2]
@@ -172,7 +219,8 @@ module GitHubChangelogGenerator
172
219
 
173
220
  if tags_strings.include?(tag1)
174
221
  if tags_strings.include?(tag2)
175
- hash = Hash[tags_strings.map.with_index.to_a]
222
+ to_a = tags_strings.map.with_index.to_a
223
+ hash = Hash[to_a]
176
224
  index1 = hash[tag1]
177
225
  index2 = hash[tag2]
178
226
  log += self.generate_log_between_tags(self.all_tags[index1], self.all_tags[index2])
@@ -188,29 +236,18 @@ module GitHubChangelogGenerator
188
236
  log += self.generate_log_for_all_tags
189
237
  end
190
238
 
191
- log += "\n\n\\* *This changelog was generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*"
239
+ log += "\n\n\\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*"
192
240
 
193
241
  output_filename = "#{@options[:output]}"
194
242
  File.open(output_filename, 'w') { |file| file.write(log) }
195
-
196
- puts "Done! Generated log placed in #{`pwd`.strip!}/#{output_filename}"
243
+ puts 'Done!'
244
+ puts "Generated log placed in #{`pwd`.strip!}/#{output_filename}"
197
245
 
198
246
  end
199
247
 
200
248
  def generate_log_for_all_tags
201
- log = ''
202
249
 
203
- if @options[:verbose]
204
- puts "Fetching tags dates.."
205
- end
206
-
207
- # Async fetching tags:
208
- threads = []
209
- @all_tags.each { |tag|
210
- # explicit set @tag_times_hash to write data safety.
211
- threads << Thread.new { self.get_time_of_tag(tag, @tag_times_hash) }
212
- }
213
- threads.each { |thr| thr.join }
250
+ fetch_tags_dates
214
251
 
215
252
  if @options[:verbose]
216
253
  puts "Sorting tags.."
@@ -222,6 +259,16 @@ module GitHubChangelogGenerator
222
259
  puts "Generating log.."
223
260
  end
224
261
 
262
+
263
+ log = ''
264
+
265
+ if @options[:unreleased]
266
+ unreleased_log = self.generate_log_between_tags(self.all_tags[0], nil)
267
+ if unreleased_log
268
+ log += unreleased_log
269
+ end
270
+ end
271
+
225
272
  (1 ... self.all_tags.size).each { |index|
226
273
  log += self.generate_log_between_tags(self.all_tags[index], self.all_tags[index-1])
227
274
  }
@@ -231,6 +278,36 @@ module GitHubChangelogGenerator
231
278
  log
232
279
  end
233
280
 
281
+ def fetch_tags_dates
282
+ if @options[:verbose]
283
+ print "Fetching tags dates..\r"
284
+ end
285
+
286
+ # Async fetching tags:
287
+ threads = []
288
+ i = 0
289
+ all = @all_tags.count
290
+ @all_tags.each { |tag|
291
+ # explicit set @tag_times_hash to write data safety.
292
+ threads << Thread.new {
293
+ self.get_time_of_tag(tag, @tag_times_hash)
294
+ if @options[:verbose]
295
+ print "Fetching tags dates: #{i+1}/#{all}\r"
296
+ i+=1
297
+ end
298
+
299
+ }
300
+ }
301
+
302
+ print " \r"
303
+
304
+ threads.each { |thr| thr.join }
305
+
306
+ if @options[:verbose]
307
+ puts 'Fetching tags: Done!'
308
+ end
309
+ end
310
+
234
311
  def is_megred(number)
235
312
  @github.pull_requests.merged? @options[:user], @options[:project], number
236
313
  end
@@ -275,39 +352,42 @@ module GitHubChangelogGenerator
275
352
 
276
353
  end
277
354
 
278
-
279
355
  def generate_log_between_tags(older_tag, newer_tag)
356
+ # older_tag nil - means it's first tag, newer_tag nil - means it unreleased section
357
+ filtered_pull_requests = delete_by_time(@pull_requests, :merged_at, older_tag, newer_tag)
358
+ filtered_issues = delete_by_time(@issues, :actual_date, older_tag, newer_tag)
280
359
 
281
- if newer_tag.nil?
282
- puts "Can't find tag -> terminate"
283
- exit 1
284
- end
285
-
286
- newer_tag_time = self.get_time_of_tag(newer_tag)
287
- newer_tag_name = newer_tag['name']
288
-
289
- filtered_pull_requests = delete_by_time(@pull_requests, :merged_at, newer_tag_time, older_tag)
290
- filtered_issues = delete_by_time(@issues, :actual_date, newer_tag_time, older_tag)
291
-
292
- older_tag_name = older_tag.nil? ? nil : older_tag['name']
360
+ newer_tag_name = newer_tag.nil? ? nil : newer_tag['name']
361
+ older_tag_name = older_tag.nil? ? nil : older_tag['name']
293
362
 
294
363
  if @options[:filter_issues_by_milestone]
295
364
  #delete excess irrelevant issues (according milestones)
296
- filtered_issues.select! { |issue|
297
- if issue.milestone.nil?
298
- true
299
- else
300
- #check, that this milestone in tag list:
301
- milestone_is_tag = @all_tags.find { |tag|
302
- tag.name == issue.milestone.title
303
- }
304
- milestone_is_tag.nil?
305
- end
365
+ filtered_issues = filter_by_milestone(filtered_issues, newer_tag_name, @issues)
366
+ filtered_pull_requests = filter_by_milestone(filtered_pull_requests, newer_tag_name, @pull_requests)
367
+ end
306
368
 
307
- }
369
+ if filtered_issues.empty? && filtered_pull_requests.empty? && newer_tag.nil?
370
+ # do not generate empty unreleased section
371
+ return nil
372
+ end
373
+
374
+ self.create_log(filtered_pull_requests, filtered_issues, newer_tag, older_tag_name)
375
+ end
376
+
377
+ def filter_by_milestone(filtered_issues, newer_tag_name, src_array)
378
+ filtered_issues.select! { |issue|
379
+ # leave issues without milestones
380
+ if issue.milestone.nil?
381
+ true
382
+ else
383
+ #check, that this milestone in tag list:
384
+ @all_tags.find { |tag| tag.name == issue.milestone.title }.nil?
385
+ end
386
+ }
387
+ unless newer_tag_name.nil?
308
388
 
309
389
  #add missed issues (according milestones)
310
- issues_to_add = @issues.select { |issue|
390
+ issues_to_add = src_array.select { |issue|
311
391
  if issue.milestone.nil?
312
392
  false
313
393
  else
@@ -326,13 +406,14 @@ module GitHubChangelogGenerator
326
406
 
327
407
  filtered_issues |= issues_to_add
328
408
  end
329
-
330
- self.create_log(filtered_pull_requests, filtered_issues, newer_tag_name, newer_tag_time, older_tag_name)
331
-
409
+ filtered_issues
332
410
  end
333
411
 
334
- def delete_by_time(array, hash_key, newer_tag_time, older_tag = nil)
412
+ def delete_by_time(array, hash_key, older_tag = nil, newer_tag = nil)
413
+
414
+ raise 'At least on of the tags should be not nil!' if (older_tag.nil? && newer_tag.nil?)
335
415
 
416
+ newer_tag_time = self.get_time_of_tag(newer_tag)
336
417
  older_tag_time = self.get_time_of_tag(older_tag)
337
418
 
338
419
  array.select { |req|
@@ -345,7 +426,12 @@ module GitHubChangelogGenerator
345
426
  tag_in_range_old = t > older_tag_time
346
427
  end
347
428
 
348
- tag_in_range_new = t <= newer_tag_time
429
+ if newer_tag_time.nil?
430
+ tag_in_range_new = true
431
+ else
432
+ tag_in_range_new = t <= newer_tag_time
433
+ end
434
+
349
435
 
350
436
  tag_in_range = (tag_in_range_old) && (tag_in_range_new)
351
437
 
@@ -358,83 +444,95 @@ module GitHubChangelogGenerator
358
444
 
359
445
  # @param [Array] pull_requests
360
446
  # @param [Array] issues
361
- # @param [String] newer_tag_name
362
- # @param [String] newer_tag_time
363
447
  # @param [String] older_tag_name
364
448
  # @return [String]
365
- def create_log(pull_requests, issues, newer_tag_name, newer_tag_time, older_tag_name = nil)
449
+ def create_log(pull_requests, issues, newer_tag, older_tag_name = nil)
366
450
 
367
- github_site = options[:github_site] || 'https://github.com'
451
+ newer_tag_time = newer_tag.nil? ? nil : self.get_time_of_tag(newer_tag)
452
+ newer_tag_name = newer_tag.nil? ? nil : newer_tag['name']
368
453
 
454
+ github_site = options[:github_site] || 'https://github.com'
369
455
  project_url = "#{github_site}/#{@options[:user]}/#{@options[:project]}"
370
456
 
371
- # Generate tag name and link
372
- log = "## [#{newer_tag_name}](#{project_url}/tree/#{newer_tag_name})\n"
373
-
374
- if @options[:compare_link] && older_tag_name
375
- # Generate compare link
376
- log += "[Full Changelog](#{project_url}/compare/#{older_tag_name}...#{newer_tag_name})\n"
457
+ if newer_tag.nil?
458
+ newer_tag_name = 'Unreleased'
459
+ newer_tag_link = 'HEAD'
460
+ newer_tag_time = Time.new
461
+ else
462
+ newer_tag_link = newer_tag_name
377
463
  end
378
464
 
379
- #Generate date string:
380
- time_string = newer_tag_time.strftime @options[:format]
381
- log += "#### #{time_string}\n"
382
-
383
- if @options[:pulls]
384
- # Generate pull requests:
385
- pull_requests.each { |pull_request|
386
- merge = @generator.get_string_for_pull_request(pull_request)
387
- log += "- #{merge}"
465
+ log = ''
388
466
 
389
- } if pull_requests
390
- end
467
+ log += generate_header(log, newer_tag_name, newer_tag_link, newer_tag_time, older_tag_name, project_url)
391
468
 
392
469
  if @options[:issues]
393
470
  # Generate issues:
394
- if issues
395
- issues.sort! { |x, y|
396
- if x.labels.any? && y.labels.any?
397
- x.labels[0].name <=> y.labels[0].name
398
- else
399
- if x.labels.any?
400
- 1
401
- else
402
- if y.labels.any?
403
- -1
404
- else
405
- 0
406
- end
407
- end
408
- end
409
- }.reverse!
410
- end
471
+ issues_a = []
472
+ enhancement_a = []
473
+ bugs_a =[]
474
+
411
475
  issues.each { |dict|
412
- is_bug = false
413
- is_enhancement = false
476
+ added = false
414
477
  dict.labels.each { |label|
415
478
  if label.name == 'bug'
416
- is_bug = true
479
+ bugs_a.push dict
480
+ added = true
481
+ next
417
482
  end
418
483
  if label.name == 'enhancement'
419
- is_enhancement = true
484
+ enhancement_a.push dict
485
+ added = true
486
+ next
420
487
  end
421
488
  }
422
-
423
- intro = 'Closed issue'
424
- if is_bug
425
- intro = 'Fixed bug'
489
+ unless added
490
+ issues_a.push dict
426
491
  end
492
+ }
427
493
 
428
- if is_enhancement
429
- intro = 'Implemented enhancement'
430
- end
494
+ log += generate_log_from_array(enhancement_a, @options[:enhancement_prefix])
495
+ log += generate_log_from_array(bugs_a, @options[:bug_prefix])
496
+ log += generate_log_from_array(issues_a, @options[:issue_prefix])
431
497
 
432
- enc_string = @generator.encapsulate_string dict[:title]
498
+ if @options[:pulls]
499
+ # Generate pull requests:
500
+ log += generate_log_from_array(pull_requests, @options[:merge_prefix])
501
+ end
433
502
 
434
- merge = "*#{intro}:* #{enc_string} [\\##{dict[:number]}](#{dict.html_url})\n\n"
435
- log += "- #{merge}"
436
- }
437
503
  end
504
+
505
+ log +="\n"
506
+ log
507
+ end
508
+
509
+ def generate_log_from_array(issues, prefix)
510
+ log = ''
511
+ if options[:simple_list].nil? && issues.any?
512
+ log += "#{prefix}\n\n"
513
+ end
514
+
515
+ issues.each { |issue|
516
+ merge_string = @generator.get_string_for_issue(issue)
517
+ log += "- #{merge_string}\n"
518
+
519
+ } if issues
520
+ log +="\n"
521
+ end
522
+
523
+ def generate_header(log, newer_tag_name, newer_tag_name2, newer_tag_time, older_tag_name, project_url)
524
+
525
+ #Generate date string:
526
+ time_string = newer_tag_time.strftime @options[:format]
527
+
528
+ # Generate tag name and link
529
+ log += "## [#{newer_tag_name}](#{project_url}/tree/#{newer_tag_name2}) (#{time_string})\n\n"
530
+
531
+ if @options[:compare_link] && older_tag_name
532
+ # Generate compare link
533
+ log += "[Full Changelog](#{project_url}/compare/#{older_tag_name}...#{newer_tag_name2})\n\n"
534
+ end
535
+
438
536
  log
439
537
  end
440
538
 
@@ -453,8 +551,44 @@ module GitHubChangelogGenerator
453
551
  @tag_times_hash[tag_name['name']] = Time.parse(time_string)
454
552
  end
455
553
 
456
- def get_all_issues
554
+ def get_filtered_issues
555
+
556
+ issues = @issues
557
+
558
+ filtered_issues = issues
559
+
560
+ unless @options[:include_labels].nil?
561
+ filtered_issues = issues.select { |issue|
562
+ #add all labels from @options[:incluse_labels] array
563
+ (issue.labels.map { |label| label.name } & @options[:include_labels]).any?
564
+ }
565
+ end
566
+
567
+ unless @options[:exclude_labels].nil?
568
+ filtered_issues = filtered_issues.select { |issue|
569
+ #delete all labels from @options[:exclude_labels] array
570
+ !(issue.labels.map { |label| label.name } & @options[:exclude_labels]).any?
571
+ }
572
+ end
573
+
574
+ if @options[:add_issues_wo_labels]
575
+ issues_wo_labels = issues.select {
576
+ # add issues without any labels
577
+ |issue| !issue.labels.map { |label| label.name }.any?
578
+ }
579
+ filtered_issues |= issues_wo_labels
580
+ end
581
+
582
+
583
+ if @options[:verbose]
584
+ puts "Filtered issues: #{filtered_issues.count}"
585
+ end
586
+
587
+ filtered_issues
588
+
589
+ end
457
590
 
591
+ def fetch_issues_and_pull_requests
458
592
  if @options[:verbose]
459
593
  print "Fetching closed issues...\r"
460
594
  end
@@ -477,31 +611,13 @@ module GitHubChangelogGenerator
477
611
  end
478
612
 
479
613
  # remove pull request from issues:
480
- issues.select! { |x|
614
+ issues_wo_pr = issues.select { |x|
481
615
  x.pull_request == nil
482
616
  }
483
-
484
- filtered_issues = issues.select { |issue|
485
- #compare is there any labels from @options[:labels] array
486
- (issue.labels.map { |label| label.name } & @options[:labels]).any?
617
+ pull_requests = issues.select { |x|
618
+ x.pull_request != nil
487
619
  }
488
-
489
-
490
- if @options[:add_issues_wo_labels]
491
- issues_wo_labels = issues.select {
492
- # add issues without any labels
493
- |issue| !issue.labels.map { |label| label.name }.any?
494
- }
495
- filtered_issues.concat(issues_wo_labels)
496
- end
497
-
498
-
499
- if @options[:verbose]
500
- puts "Filtered issues with labels #{@options[:labels]}#{@options[:add_issues_wo_labels] ? ' and w/o labels' : ''}: #{filtered_issues.count}"
501
- end
502
-
503
- filtered_issues
504
-
620
+ return issues_wo_pr, pull_requests
505
621
  end
506
622
 
507
623
  def fetch_event_for_issues(filtered_issues)