80 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Ruby
		
	
	
	
			
		
		
	
	
			80 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Ruby
		
	
	
	
# frozen_string_literal: true
 | 
						|
 | 
						|
# Worker for processing individual commit messages pushed to a repository.
 | 
						|
#
 | 
						|
# Jobs for this worker are scheduled for every commit that contains mentionable
 | 
						|
# references in its message and does not exist in the upstream project. 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 ApplicationWorker
 | 
						|
 | 
						|
  data_consistency :always
 | 
						|
 | 
						|
  sidekiq_options retry: 3
 | 
						|
 | 
						|
  feature_category :source_code_management
 | 
						|
  urgency :high
 | 
						|
  weight 3
 | 
						|
  idempotent!
 | 
						|
  loggable_arguments 2, 3
 | 
						|
  deduplicate :until_executed, feature_flag: :deduplicate_process_commit_worker
 | 
						|
 | 
						|
  # project_id - The ID of the project this commit belongs to.
 | 
						|
  # user_id - The ID of the user that pushed the commit.
 | 
						|
  # commit_hash - Hash containing commit details to use for constructing a
 | 
						|
  #               Commit object without having to use the Git repository.
 | 
						|
  # default - The data was pushed to the default branch.
 | 
						|
  def perform(project_id, user_id, commit_hash, default = false)
 | 
						|
    project = Project.id_in(project_id).first
 | 
						|
 | 
						|
    return unless project
 | 
						|
 | 
						|
    user = User.id_in(user_id).first
 | 
						|
 | 
						|
    return unless user
 | 
						|
 | 
						|
    commit = Commit.build_from_sidekiq_hash(project, commit_hash)
 | 
						|
    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)
 | 
						|
    # Ignore closing references from GitLab-generated commit messages.
 | 
						|
    find_closing_issues = default && !commit.merged_merge_request?(user)
 | 
						|
    closed_issues = find_closing_issues ? issues_to_close(project, commit, user) : []
 | 
						|
 | 
						|
    close_issues(project, user, author, commit, closed_issues) if closed_issues.any?
 | 
						|
    commit.create_cross_references!(author, closed_issues)
 | 
						|
  end
 | 
						|
 | 
						|
  def close_issues(project, user, author, commit, issues)
 | 
						|
    Issues::CloseWorker.bulk_perform_async_with_contexts(
 | 
						|
      issues,
 | 
						|
      arguments_proc: -> (issue) {
 | 
						|
        [project.id, issue.id, issue.class.to_s, { closed_by: author.id, commit_hash: commit.to_hash }]
 | 
						|
      },
 | 
						|
      context_proc: -> (issue) { { project: project } }
 | 
						|
    )
 | 
						|
  end
 | 
						|
 | 
						|
  def issues_to_close(project, commit, user)
 | 
						|
    Gitlab::ClosingIssueExtractor
 | 
						|
      .new(project, user)
 | 
						|
      .closed_by_message(commit.safe_message)
 | 
						|
  end
 | 
						|
 | 
						|
  def update_issue_metrics(commit, author)
 | 
						|
    mentioned_issues = commit.all_references(author).issues
 | 
						|
 | 
						|
    return if mentioned_issues.empty?
 | 
						|
 | 
						|
    Issue::Metrics.for_issues(mentioned_issues)
 | 
						|
      .with_first_mention_not_earlier_than(commit.committed_date)
 | 
						|
      .update_all(first_mentioned_in_commit_at: commit.committed_date)
 | 
						|
  end
 | 
						|
end
 |