class Dependabot::PullRequestCreator::MessageBuilder::MetadataPresenter
def break_tag
def break_tag source_provider_supports_html? ? "\n<br />" : "\n\n" end
def build_details_tag(summary:, body:)
def build_details_tag(summary:, body:) # Bitbucket does not support <details> tag (https://jira.atlassian.com/browse/BCLOUD-20231) # CodeCommit does not support the <details> tag (no url available) if source_provider_supports_html? msg = "<details>\n<summary>#{summary}</summary>\n\n" msg += body msg + "</details>\n" else "\n# #{summary}\n\n#{body}" end end
def changelog_cascade
def changelog_cascade return "" unless changelog_url && changelog_text msg = "*Sourced from " \ "[#{dependency.display_name}'s changelog]" \ "(#{changelog_url}).*\n\n" msg += quote_and_truncate(changelog_text) msg = link_issues(text: msg) msg = fix_relative_links(text: msg, base_url: changelog_url) msg = sanitize_template_tags(msg) msg = sanitize_links_and_mentions(msg) build_details_tag(summary: "Changelog", body: msg) end
def commits_cascade
def commits_cascade return "" unless commits_url && commits msg = "" commits.last(10).reverse_each do |commit| title = commit[:message].strip.split("\n").first title = title.slice(0..76) + "..." if title && title.length > 80 title = title&.gsub(/(?<=[^\w.-])([_*`~])/, '\\1') sha = commit[:sha][0, 7] msg += "- [`#{sha}`](#{commit[:html_url]}) #{title}\n" end msg = msg.gsub(/\<.*?\>/) { |tag| "\\#{tag}" } msg += if commits.count > 10 "- Additional commits viewable in " \ "[compare view](#{commits_url})\n" else "- See full diff in [compare view](#{commits_url})\n" end msg = link_issues(text: msg) msg = sanitize_links_and_mentions(msg) build_details_tag(summary: "Commits", body: msg) end
def fix_relative_links(text:, base_url:)
def fix_relative_links(text:, base_url:) text.gsub(/\[.*?\]\([^)]+\)/) do |link| next link if link.include?("://") relative_path = T.must(T.must(link.match(/\((.*?)\)/)).captures.last) base = T.must(base_url.split("://").last).gsub(%r{[^/]*$}, "") path = File.join(base, relative_path) absolute_path = base_url.sub( %r{(?<=://).*$}, Pathname.new(path).cleanpath.to_s ) link.gsub(relative_path, absolute_path) end end
def initialize(dependency:, source:, metadata_finder:,
def initialize(dependency:, source:, metadata_finder:, vulnerabilities_fixed:, github_redirection_service:) @dependency = dependency @source = source @metadata_finder = metadata_finder @vulnerabilities_fixed = vulnerabilities_fixed @github_redirection_service = github_redirection_service end
def link_issues(text:)
def link_issues(text:) IssueLinker .new(source_url: source_url) .link_issues(text: text) end
def maintainer_changes_cascade
def maintainer_changes_cascade return "" unless maintainer_changes build_details_tag( summary: "Maintainer changes", body: sanitize_links_and_mentions(maintainer_changes) + "\n" ) end
def quote_and_truncate(text, limit: 50)
def quote_and_truncate(text, limit: 50) lines = text.split("\n") lines.first(limit).tap do |limited_lines| limited_lines.map! { |line| "> #{line}\n" } limited_lines << truncated_line if lines.count > limit end.join end
def release_cascade
def release_cascade return "" unless releases_text && releases_url msg = "*Sourced from [#{dependency.display_name}'s releases]" \ "(#{releases_url}).*\n\n" msg += quote_and_truncate(releases_text) msg = link_issues(text: msg) msg = fix_relative_links( text: msg, base_url: source_url + "/blob/HEAD/" ) msg = sanitize_template_tags(msg) msg = sanitize_links_and_mentions(msg) build_details_tag(summary: "Release notes", body: msg) end
def sanitize_links_and_mentions(text, unsafe: false)
def sanitize_links_and_mentions(text, unsafe: false) LinkAndMentionSanitizer .new(github_redirection_service: github_redirection_service) .sanitize_links_and_mentions(text: text, unsafe: unsafe, format_html: source_provider_supports_html?) end
def sanitize_template_tags(text)
def sanitize_template_tags(text) text.gsub(/\<.*?\>/) do |tag| tag_contents = tag.match(/\<(.*?)\>/)&.captures&.first&.strip # Unclosed calls to template overflow out of the blockquote block, # wrecking the rest of our PRs. Other tags don't share this problem. next "\\#{tag}" if tag_contents&.start_with?("template") tag end end
def serialized_vulnerability_details(details)
def serialized_vulnerability_details(details) msg = vulnerability_source_line(details) msg += "> **#{T.must(details['title']).lines.map(&:strip).join(' ')}**\n" if details["title"] if (description = details["description"]) description.strip.lines.first(20).each { |line| msg += "> #{line}" } msg += truncated_line if description.strip.lines.count > 20 end msg += "\n" unless msg.end_with?("\n") msg += "> \n" msg += vulnerability_version_range_lines(details) msg + "\n" end
def source_provider_supports_html?
def source_provider_supports_html? !%w(bitbucket codecommit).include?(source.provider) end
def to_s
def to_s msg = "" msg += vulnerabilities_cascade msg += release_cascade msg += changelog_cascade msg += upgrade_guide_cascade msg += commits_cascade msg += maintainer_changes_cascade msg += break_tag unless msg == "" "\n" + sanitize_links_and_mentions(msg, unsafe: true) end
def truncated_line
def truncated_line # Tables can spill out of truncated details, so we close them "></tr></table> \n ... (truncated)\n" end
def upgrade_guide_cascade
def upgrade_guide_cascade return "" unless upgrade_guide_url && upgrade_guide_text msg = "*Sourced from " \ "[#{dependency.display_name}'s upgrade guide]" \ "(#{upgrade_guide_url}).*\n\n" msg += quote_and_truncate(upgrade_guide_text) msg = link_issues(text: msg) msg = fix_relative_links(text: msg, base_url: upgrade_guide_url) msg = sanitize_template_tags(msg) msg = sanitize_links_and_mentions(msg) build_details_tag(summary: "Upgrade guide", body: msg) end
def vulnerabilities_cascade
def vulnerabilities_cascade return "" unless vulnerabilities_fixed&.any? msg = "" T.must(vulnerabilities_fixed).each do |v| msg += serialized_vulnerability_details(v) end msg = sanitize_template_tags(msg) msg = sanitize_links_and_mentions(msg) build_details_tag(summary: "Vulnerabilities fixed", body: msg) end
def vulnerability_source_line(details)
def vulnerability_source_line(details) if details["source_url"] && details["source_name"] "*Sourced from [#{details['source_name']}]" \ "(#{details['source_url']}).*\n\n" elsif details["source_name"] "*Sourced from #{details['source_name']}.*\n\n" else "" end end
def vulnerability_version_range_lines(details)
def vulnerability_version_range_lines(details) msg = "" %w( patched_versions unaffected_versions affected_versions ).each do |tp| type = T.must(tp.split("_").first).capitalize next unless details[tp] versions_string = details[tp].any? ? details[tp].join("; ") : "none" versions_string = versions_string.gsub(/(?<!\\)~/, '\~') msg += "> #{type} versions: #{versions_string}\n" end msg end