-
Notifications
You must be signed in to change notification settings - Fork 914
/
changelog_pruner.rb
172 lines (135 loc) 路 5.84 KB
/
changelog_pruner.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# frozen_string_literal: true
require "dependabot/metadata_finders/base"
module Dependabot
module MetadataFinders
class Base
class ChangelogPruner
attr_reader :dependency, :changelog_text
def initialize(dependency:, changelog_text:)
@dependency = dependency
@changelog_text = changelog_text
end
def includes_new_version?
!new_version_changelog_line.nil?
end
def includes_previous_version?
!old_version_changelog_line.nil?
end
def pruned_text
changelog_lines = changelog_text.split("\n")
slice_range =
if old_version_changelog_line && new_version_changelog_line
if old_version_changelog_line < new_version_changelog_line
Range.new(old_version_changelog_line, -1)
else
Range.new(new_version_changelog_line,
old_version_changelog_line - 1)
end
elsif old_version_changelog_line
return if old_version_changelog_line.zero?
# Assumes changelog is in descending order
Range.new(0, old_version_changelog_line - 1)
elsif new_version_changelog_line
# Assumes changelog is in descending order
Range.new(new_version_changelog_line, -1)
else
return unless changelog_contains_relevant_versions?
# If the changelog contains any relevant versions, return it in
# full. We could do better here by fully parsing the changelog
Range.new(0, -1)
end
changelog_lines.slice(slice_range).join("\n").sub(/\n*\z/, "")
end
private
def old_version_changelog_line
old_version = git_source? ? previous_ref : dependency.previous_version
return nil unless old_version
changelog_line_for_version(old_version)
end
def new_version_changelog_line
return nil unless new_version
changelog_line_for_version(new_version)
end
# rubocop:disable Metrics/PerceivedComplexity
def changelog_line_for_version(version)
raise "No changelog text" unless changelog_text
return nil unless version
version = version.gsub(/^v/, "")
escaped_version = Regexp.escape(version)
changelog_lines = changelog_text.split("\n")
changelog_lines.find_index.with_index do |line, index|
next false unless line.match?(/(?<!\.)#{escaped_version}(?![.\-])/)
next false if line.match?(/#{escaped_version}\.\./)
next true if line.start_with?("#", "!", "==")
next true if line.match?(/^v?#{escaped_version}:?/)
next true if line.match?(/^[\+\*\-] (version )?#{escaped_version}/i)
next true if line.match?(/^\d{4}-\d{2}-\d{2}/)
next true if changelog_lines[index + 1]&.match?(/^[=\-\+]{3,}\s*$/)
false
end
end
# rubocop:enable Metrics/PerceivedComplexity
def changelog_contains_relevant_versions?
# Assume the changelog is relevant if we can't parse the new version
return true unless version_class.correct?(dependency.version)
# Assume the changelog is relevant if it mentions the new version
# anywhere
return true if changelog_text.include?(dependency.version)
# Otherwise check if any intermediate versions are included in headers
versions_in_changelog_headers.any? do |version|
next false unless version <= version_class.new(dependency.version)
next true unless dependency.previous_version
next true unless version_class.correct?(dependency.previous_version)
version > version_class.new(dependency.previous_version)
end
end
def versions_in_changelog_headers
changelog_lines = changelog_text.split("\n")
header_lines =
changelog_lines.select.with_index do |line, index|
next true if line.start_with?("#", "!")
next true if line.match?(/^v?\d\.\d/)
next true if changelog_lines[index + 1]&.match?(/^[=-]+\s*$/)
false
end
versions = []
header_lines.each do |line|
cleaned_line = line.gsub(/^[^0-9]*/, "").gsub(/[\s,:].*/, "")
next if cleaned_line.empty? || !version_class.correct?(cleaned_line)
versions << version_class.new(cleaned_line)
end
versions
end
def new_version
@new_version ||= git_source? ? new_ref : dependency.version
@new_version&.gsub(/^v/, "")
end
def previous_ref
previous_refs = dependency.previous_requirements.map do |r|
r.dig(:source, "ref") || r.dig(:source, :ref)
end.compact.uniq
return previous_refs.first if previous_refs.count == 1
end
def new_ref
new_refs = dependency.requirements.map do |r|
r.dig(:source, "ref") || r.dig(:source, :ref)
end.compact.uniq
return new_refs.first if new_refs.count == 1
end
# TODO: Refactor me so that Composer doesn't need to be special cased
def git_source?
# Special case Composer, which uses git as a source but handles tags
# internally
return false if dependency.package_manager == "composer"
requirements = dependency.requirements
sources = requirements.map { |r| r.fetch(:source) }.uniq.compact
return false if sources.empty?
sources.all? { |s| s[:type] == "git" || s["type"] == "git" }
end
def version_class
Utils.version_class_for_package_manager(dependency.package_manager)
end
end
end
end
end