BigW Consortium Gitlab

process_commit_worker.rb 3.07 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
# Worker for processing individiual commit messages pushed to a repository.
#
# Jobs for this worker are scheduled for every commit that is being pushed. As a
# result of this the workload of this worker should be kept to a bare minimum.
# Consider using an extra worker if you need to add any extra (and potentially
# slow) processing of commits.
class ProcessCommitWorker
  include Sidekiq::Worker
  include DedicatedSidekiqQueue

  # project_id - The ID of the project this commit belongs to.
  # user_id - The ID of the user that pushed the commit.
13 14
  # commit_hash - Hash containing commit details to use for constructing a
  #               Commit object without having to use the Git repository.
15
  # default - The data was pushed to the default branch.
16
  def perform(project_id, user_id, commit_hash, default = false)
17 18 19
    project = Project.find_by(id: project_id)

    return unless project
20
    return if commit_exists_in_upstream?(project, commit_hash)
21 22 23 24 25

    user = User.find_by(id: user_id)

    return unless user

26
    commit = build_commit(project, commit_hash)
27

28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
    author = commit.author || user

    process_commit_message(project, commit, user, author, default)

    update_issue_metrics(commit, author)
  end

  def process_commit_message(project, commit, user, author, default = false)
    closed_issues = default ? commit.closes_issues(user) : []

    unless closed_issues.empty?
      close_issues(project, user, author, commit, closed_issues)
    end

    commit.create_cross_references!(author, closed_issues)
  end

  def close_issues(project, user, author, commit, issues)
    # We don't want to run permission related queries for every single issue,
    # therefor we use IssueCollection here and skip the authorization check in
    # Issues::CloseService#execute.
    IssueCollection.new(issues).updatable_by_user(user).each do |issue|
50 51
      Issues::CloseService.new(project, author)
        .close_issue(issue, commit: commit)
52 53 54 55 56 57
    end
  end

  def update_issue_metrics(commit, author)
    mentioned_issues = commit.all_references(author).issues

58 59
    return if mentioned_issues.empty?

60 61
    Issue::Metrics.where(issue_id: mentioned_issues.map(&:id), first_mentioned_in_commit_at: nil)
      .update_all(first_mentioned_in_commit_at: commit.committed_date)
62 63
  end

64 65 66 67 68 69 70 71 72 73 74
  def build_commit(project, hash)
    date_suffix = '_date'

    # When processing Sidekiq payloads various timestamps are stored as Strings.
    # Commit in turn expects Time-like instances upon input, so we have to
    # manually parse these values.
    hash.each do |key, value|
      if key.to_s.end_with?(date_suffix) && value.is_a?(String)
        hash[key] = Time.parse(value)
      end
    end
75

76
    Commit.from_hash(hash, project)
77
  end
78 79 80

  private

81
  # Avoid reprocessing commits that already exist in the upstream
82 83 84 85 86 87 88 89
  # when project is forked. This will also prevent duplicated system notes.
  def commit_exists_in_upstream?(project, commit_hash)
    return false unless project.forked?

    upstream_project = project.forked_from_project
    commit_id = commit_hash.with_indifferent_access[:id]
    upstream_project.commit(commit_id).present?
  end
90
end