class Danger::Helper

Common helper functions for our danger scripts.

Constants

CATEGORY_LABELS
RELEASE_TOOLS_BOT

Public Instance Methods

added_files() click to toggle source

@return [Array<String>] a list of filenames added in this MR.

# File lib/danger/plugins/helper.rb, line 68
def added_files
  @added_files ||= if changes_from_api
      changes_from_api.select { |file| file["new_file"] }.map { |file| file["new_path"] }
    else
      git.added_files.to_a
    end
end
all_changed_files() click to toggle source

@example

# Considering these changes:
# - A new_file.rb
# - D deleted_file.rb
# - M modified_file.rb
# - R renamed_file_before.rb -> renamed_file_after.rb
# it will return:

#=> ['new_file.rb', 'modified_file.rb', 'renamed_file_after.rb']

@return [Array<String>] a list of all files that have been added, modified or renamed.

+modified_files+ might contain paths that already have been renamed,
so we need to remove them from the list.
# File lib/danger/plugins/helper.rb, line 119
def all_changed_files
  Set.new
    .merge(added_files)
    .merge(modified_files)
    .merge(renamed_files.map { |x| x[:after] })
    .subtract(renamed_files.map { |x| x[:before] })
    .to_a
    .sort
end
categories_for_file(filename, categories) click to toggle source

@param filename [String] A file name. @param categories [{Regexp => Array<Symbol>}, {Array<Regexp> => Array<Symbol>}] A hash of the form +{ filename_regex => categories, [filename_regex, changes_regex] => categories }+.

+filename_regex+ is the regex pattern to match file names. +changes_regex+ is the regex pattern to
match changed lines in files that match +filename_regex+

@return [Array<Symbol>] the categories a file is in, e.g., [:frontend], [:backend], or +%i[frontend tooling]+

using filename regex (+filename_regex+) and specific change regex (+changes_regex+) from the given +categories+ hash.
# File lib/danger/plugins/helper.rb, line 218
def categories_for_file(filename, categories)
  _, categories = categories.find do |key, _|
    filename_regex, changes_regex = Array(key)

    found = filename_regex.match?(filename)
    found &&= changed_lines(filename).any? { |changed_line| changes_regex.match?(changed_line) } if changes_regex

    found
  end

  Array(categories || :unknown)
end
changed_files(regex) click to toggle source

@param regex [Regexp] A Regexp to match against.

@return [Array<String>] changed files matching the given regex.

# File lib/danger/plugins/helper.rb, line 381
def changed_files(regex)
  all_changed_files.grep(regex)
end
changed_lines(filename) click to toggle source

@param filename [String] A file name for which we want the diff.

@example

# Considering changing a line in lib/gitlab/usage_data.rb, it will return:

["--- a/lib/gitlab/usage_data.rb",
 "+++ b/lib/gitlab/usage_data.rb",
 "+      # Test change",
 "-      # Old change"]

@return [Array<String>] an array of changed lines in Git diff format.

# File lib/danger/plugins/helper.rb, line 140
def changed_lines(filename)
  diff = diff_for_file(filename)
  return [] unless diff

  diff.split("\n").select { |line| %r{^[+-]}.match?(line) }
end
changes(categories) click to toggle source

@param categories [{Regexp => Array<Symbol>}, {Array<Regexp> => Array<Symbol>}] A hash of the form +{ filename_regex => categories, [filename_regex, changes_regex] => categories }+.

+filename_regex+ is the regex pattern to match file names. +changes_regex+ is the regex pattern to
match changed lines in files that match +filename_regex+

@return [Gitlab::Dangerfiles::Changes] a Gitlab::Dangerfiles::Changes object that represents the changes of an MR

using filename regex (+filename_regex+) and specific change regex (+changes_regex+) from the given +categories+ hash.
# File lib/danger/plugins/helper.rb, line 187
def changes(categories)
  Gitlab::Dangerfiles::Changes.new([]).tap do |changes|
    added_files.each do |file|
      categories_for_file(file, categories).each { |category| changes << Gitlab::Dangerfiles::Change.new(file, :added, category) }
    end

    modified_files.each do |file|
      categories_for_file(file, categories).each { |category| changes << Gitlab::Dangerfiles::Change.new(file, :modified, category) }
    end

    deleted_files.each do |file|
      categories_for_file(file, categories).each { |category| changes << Gitlab::Dangerfiles::Change.new(file, :deleted, category) }
    end

    renamed_files.map { |x| x[:before] }.each do |file|
      categories_for_file(file, categories).each { |category| changes << Gitlab::Dangerfiles::Change.new(file, :renamed_before, category) }
    end

    renamed_files.map { |x| x[:after] }.each do |file|
      categories_for_file(file, categories).each { |category| changes << Gitlab::Dangerfiles::Change.new(file, :renamed_after, category) }
    end
  end
end
changes_by_category(categories) click to toggle source

@param categories [{Regexp => Array<Symbol>}, {Array<Regexp> => Array<Symbol>}] A hash of the form +{ filename_regex => categories, [filename_regex, changes_regex] => categories }+.

+filename_regex+ is the regex pattern to match file names. +changes_regex+ is the regex pattern to
match changed lines in files that match +filename_regex+

@return [{Symbol => Array<String>}] a hash of the type +{ category1: [“file1”, “file2”], category2: [“file3”, “file4”] }+

using filename regex (+filename_regex+) and specific change regex (+changes_regex+) from the given +categories+ hash.
# File lib/danger/plugins/helper.rb, line 175
def changes_by_category(categories)
  all_changed_files.each_with_object(Hash.new { |h, k| h[k] = [] }) do |file, hash|
    categories_for_file(file, categories).each { |category| hash[category] << file }
  end
end
cherry_pick_mr?() click to toggle source

@return [Boolean] whether a MR title includes “cherry-pick” or not.

# File lib/danger/plugins/helper.rb, line 308
def cherry_pick_mr?
  Gitlab::Dangerfiles::TitleLinting.has_cherry_pick_flag?(mr_title)
end
ci?() click to toggle source

@return [Boolean] whether we're in the CI context or not.

# File lib/danger/plugins/helper.rb, line 63
def ci?
  !gitlab_helper.nil?
end
config() { |c| ... } click to toggle source

Allows to set specific rule's configuration by passing a block.

@yield [c] Yield a Gitlab::Dangerfiles::Config object

@yieldparam [Gitlab::Dangerfiles::Config] The Gitlab::Dangerfiles::Config object @yieldreturn [Gitlab::Dangerfiles::Config] The Gitlab::Dangerfiles::Config object

@example

helper.config do |config|
  config.code_size_thresholds = { high: 42, medium: 12 }
end

@return [Gitlab::Dangerfiles::Config]

# File lib/danger/plugins/helper.rb, line 40
def config
  (@config ||= Gitlab::Dangerfiles::Config.new).tap do |c|
    yield c if block_given?
  end
end
deleted_files() click to toggle source

@return [Array<String>] a list of filenames deleted in this MR.

# File lib/danger/plugins/helper.rb, line 97
def deleted_files
  @deleted_files ||= if changes_from_api
      changes_from_api.select { |file| file["deleted_file"] }.map { |file| file["new_path"] }
    else
      git.deleted_files.to_a
    end
end
draft_mr?() click to toggle source

@return [Boolean] whether a MR is a Draft or not.

# File lib/danger/plugins/helper.rb, line 296
def draft_mr?
  return false unless ci?

  gitlab.mr_json["work_in_progress"]
end
group_label() click to toggle source

@return [Array<String>] the group labels (i.e. +“group::*”+) set on the MR.

# File lib/danger/plugins/helper.rb, line 386
def group_label
  mr_labels.find { |label| label.start_with?("group::") }
end
has_ci_changes?() click to toggle source

@return [Boolean] whether a MR has any CI-related changes (i.e. +“.gitlab-ci.yml”+ or +“.gitlab/ci/*”+) or not.

# File lib/danger/plugins/helper.rb, line 335
def has_ci_changes?
  changed_files(%r{\A(\.gitlab-ci\.yml|\.gitlab/ci/)}).any?
end
has_database_scoped_labels?() click to toggle source

Whether a MR has any database-scoped labels (i.e. +“database::*”+) set or not.

@return [Boolean]

# File lib/danger/plugins/helper.rb, line 330
def has_database_scoped_labels?
  mr_labels.any? { |label| label.start_with?("database::") }
end
label_for_category(category) click to toggle source

@param category [Symbol] A category.

@return [String] the GFM for a category label, making its best guess if it's not

a category we know about.
# File lib/danger/plugins/helper.rb, line 235
def label_for_category(category)
  CATEGORY_LABELS.fetch(category, "~#{category}")
end
labels_list(labels, sep: ", ") click to toggle source

@param labels [Array<String>] An array of labels. @param sep [String] A separator.

@example

labels_list(["foo", "bar baz"], sep: "; ")
 # => '~"foo"; ~"bar baz"'

@return [String] the list of labels ready for being used in a Markdown comment, separated by sep.

# File lib/danger/plugins/helper.rb, line 356
def labels_list(labels, sep: ", ")
  labels.map { |label| %Q{~"#{label}"} }.join(sep)
end
markdown_list(items) click to toggle source

@param items [Array<String>] An array of items to transform into a bullet list.

@example

markdown_list(%w[foo bar])
# => * foo
     * bar

@return [String] a bullet list for the given items. If there are more than 10 items, wrap the list in a +<details></details>+ block.

# File lib/danger/plugins/helper.rb, line 159
def markdown_list(items)
  list = items.map { |item| "* `#{item}`" }.join("\n")

  if items.size > 10
    "\n<details>\n\n#{list}\n\n</details>\n"
  else
    list
  end
end
modified_files() click to toggle source

@return [Array<String>] a list of filenames modifier in this MR.

# File lib/danger/plugins/helper.rb, line 77
def modified_files
  @modified_files ||= if changes_from_api
      changes_from_api.select { |file| !file["new_file"] && !file["deleted_file"] && !file["renamed_file"] }.map { |file| file["new_path"] }
    else
      git.modified_files.to_a
    end
end
mr_author() click to toggle source

@return [String] +`whoami`+ when not in the CI context, and the MR author username otherwise.

# File lib/danger/plugins/helper.rb, line 247
def mr_author
  return `whoami`.strip unless ci?

  gitlab_helper.mr_author
end
mr_has_labels?(*labels) click to toggle source

@param labels [Array<String>] An array of labels.

@return [Boolean] whether a MR has the given labels set or not.

# File lib/danger/plugins/helper.rb, line 342
def mr_has_labels?(*labels)
  labels = labels.flatten.uniq

  (labels & mr_labels) == labels
end
mr_iid() click to toggle source

@return [String] +“”+ when not in the CI context, and the MR IID as a string otherwise.

# File lib/danger/plugins/helper.rb, line 240
def mr_iid
  return "" unless ci?

  gitlab_helper.mr_json["iid"].to_s
end
mr_labels() click to toggle source

@return [Array<String>] [] when not in the CI context, and the MR labels otherwise.

# File lib/danger/plugins/helper.rb, line 268
def mr_labels
  return [] unless ci?

  gitlab_helper.mr_labels
end
mr_source_branch() click to toggle source

@return [String] +`git rev-parse –abbrev-ref HEAD`+ when not in the CI context, and the MR source branch otherwise.

# File lib/danger/plugins/helper.rb, line 275
def mr_source_branch
  return `git rev-parse --abbrev-ref HEAD`.strip unless ci?

  gitlab_helper.mr_json["source_branch"]
end
mr_target_branch() click to toggle source

@return [String] +“”+ when not in the CI context, and the MR target branch otherwise.

# File lib/danger/plugins/helper.rb, line 282
def mr_target_branch
  return "" unless ci?

  gitlab_helper.mr_json["target_branch"]
end
mr_title() click to toggle source

@return [String] +“”+ when not in the CI context, and the MR title otherwise.

# File lib/danger/plugins/helper.rb, line 254
def mr_title
  return "" unless ci?

  gitlab_helper.mr_json["title"]
end
mr_web_url() click to toggle source

@return [String] +“”+ when not in the CI context, and the MR URL otherwise.

# File lib/danger/plugins/helper.rb, line 261
def mr_web_url
  return "" unless ci?

  gitlab_helper.mr_json["web_url"]
end
prepare_labels_for_mr(labels) click to toggle source

@deprecated Use {#quick_action_label} instead.

# File lib/danger/plugins/helper.rb, line 361
def prepare_labels_for_mr(labels)
  quick_action_label(labels)
end
quick_action_label(labels) click to toggle source

@param labels [Array<String>] An array of labels.

@example

quick_action_label(["foo", "bar baz"])
 # => '/label ~"foo" ~"bar baz"'

@return [String] a quick action to set the given labels. Returns +“”+ if labels is empty.

# File lib/danger/plugins/helper.rb, line 372
def quick_action_label(labels)
  return "" unless labels.any?

  "/label #{labels_list(labels, sep: " ")}"
end
release_automation?() click to toggle source
# File lib/danger/plugins/helper.rb, line 147
def release_automation?
  gitlab_helper&.mr_author == RELEASE_TOOLS_BOT
end
renamed_files() click to toggle source

@return [Array<String>] a list of filenames renamed in this MR.

# File lib/danger/plugins/helper.rb, line 86
def renamed_files
  @renamed_files ||= if changes_from_api
      changes_from_api.select { |file| file["renamed_file"] }.each_with_object([]) do |file, memo|
        memo << { before: file["old_path"], after: file["new_path"] }
      end
    else
      git.renamed_files.to_a
    end
end
run_all_rspec_mr?() click to toggle source

@return [Boolean] whether a MR title includes “RUN ALL RSPEC” or not.

# File lib/danger/plugins/helper.rb, line 313
def run_all_rspec_mr?
  Gitlab::Dangerfiles::TitleLinting.has_run_all_rspec_flag?(mr_title)
end
run_as_if_foss_mr?() click to toggle source

@return [Boolean] whether a MR title includes “RUN AS-IF-FOSS” or not.

# File lib/danger/plugins/helper.rb, line 318
def run_as_if_foss_mr?
  Gitlab::Dangerfiles::TitleLinting.has_run_as_if_foss_flag?(mr_title)
end
security_mr?() click to toggle source

@return [Boolean] whether a MR is opened in the security mirror or not.

# File lib/danger/plugins/helper.rb, line 303
def security_mr?
  mr_web_url.include?("/gitlab-org/security/")
end
squash_mr?() click to toggle source

@return [Boolean] true when not in the CI context, and whether the MR is set to be squashed otherwise.

# File lib/danger/plugins/helper.rb, line 289
def squash_mr?
  return true unless ci?

  gitlab.mr_json["squash"]
end
stable_branch?() click to toggle source

@return [Boolean] whether a MR targets a stable branch or not.

# File lib/danger/plugins/helper.rb, line 323
def stable_branch?
  /\A\d+-\d+-stable-ee/i.match?(mr_target_branch)
end

Private Instance Methods

changes_from_api() click to toggle source

Fetches MR changes from the API instead of Git (default).

@return [Array<Hash>, nil]

# File lib/danger/plugins/helper.rb, line 417
def changes_from_api
  return nil unless ci?
  return nil if defined?(@force_changes_from_git)

  @changes_from_api ||= gitlab_helper.mr_changes
rescue
  # Fallback to the Git strategy in any case
  @force_changes_from_git = true
  nil
end
diff_for_file(filename) click to toggle source

@param filename [String] A filename for which we want the diff.

@return [String] the raw diff as a string for the given filename.

# File lib/danger/plugins/helper.rb, line 405
def diff_for_file(filename)
  if changes_from_api
    changes_hash = changes_from_api.find { |file| file["new_path"] == filename }
    changes_hash["diff"] if changes_hash
  else
    git.diff_for_file(filename)&.patch
  end
end
gitlab_helper() click to toggle source

@return [Danger::RequestSources::GitLab, nil] the gitlab helper, or nil when it's not available.

# File lib/danger/plugins/helper.rb, line 393
def gitlab_helper
  # Unfortunately the following does not work:
  # - respond_to?(:gitlab)
  # - respond_to?(:gitlab, true)
  gitlab
rescue NoMethodError
  nil
end