# frozen_string_literal: true require 'git_diff' require 'logger' module Utils def plugin # individual check classes usually set the '@plugin' variable, # otherwise this mixin was included on the plugin itself. @plugin || self end def logger return @logger if @logger @logger = Logger.new(STDERR) @logger.level = ENV['DANGER_LOG_LEVEL'] || (plugin.verbose ? Logger::INFO : Logger::ERROR) @logger end # All the diffs in the PR parsed into GitDiff objects def parsed_diffs @parsed_diffs ||= plugin.git.diff.map do |d| begin GitDiff.from_string(d.patch) rescue StandardError logger.fatal "Error parsing patch:\n#{d.patch}" raise end end end # Finds lines in the overall diff matching the given regex, and # executes a block for each matched line. # The results of the yield block are returned as an array. def find_in_diff(regex) each_file_in_diff do |file, diff| file.hunks.flat_map do |hunk| lines = hunk.lines.select { |l| l.addition? && l.content =~ regex } if block_given? lines = lines.map do |l| yield(l.content.match(regex), l, hunk, file, diff) end end lines end end end def each_file_in_diff(passed_diff = nil) diffs = passed_diff ? [passed_diff] : parsed_diffs diffs.flat_map do |diff| diff.files.flat_map do |file| yield(file, diff) end end end def each_addition_in_diff(passed_diff = nil) each_file_in_diff(passed_diff) do |file, diff| file.hunks.flat_map do |hunk| lines = hunk.lines.select(&:addition?) lines = lines.map { |l| yield(l, hunk, file, diff) } if block_given? lines end end end def run(command) logger.info "Executing command '#{command}'" result = `#{command}` logger.debug result result end # Runs a command twice - once on the merge base, and once on the current # working directory. Then, returns the git diff of the printed results. def run_and_diff(command = nil) unless command || block_given? raise ArgumentError('Must give command or block') end logger.info "Executing diff: '#{command}'" with_revision(plugin.github.base_commit) do |dir| initial = nil Dir.chdir(dir) do initial = command ? `#{command}` : yield end final = command ? `#{command}` : yield diff = diff_strings(initial, final) logger.debug diff diff end end # Executes a block after checking out the specified revision into a temp # directory. def with_revision(revision) Dir.mktmpdir do |dir| logger.debug "Checking out revision #{revision} into #{dir}" system "git worktree add #{dir} #{revision.strip}" yield(dir) end ensure system 'git worktree prune' end # Creates a git-format diff of the two strings by writing them to temp files def diff_strings(a, b) File.write('a.tmp', a) File.write('b.tmp', b) diff = `git diff --no-index a.tmp b.tmp` File.delete('a.tmp', 'b.tmp') diff end def format_links_as_markdown(line) line.gsub(/\[?(https?\:\/\/[^\s\]]+)\]?/i, '[\1](\1)') end # Adds a message to the Danger report # with the given serverity - 'message', 'warn', or 'fail def issue(message, severity: 'message', file: nil, line: nil) case severity when 'message' plugin.message(message, file: file, line: line) when 'warn' plugin.warn(message, file: file, line: line) else plugin.fail(message, file: file, line: line) end end end