2019-03-28 10:59:24 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
module Git
|
|
|
|
class BranchHooksService < ::Git::BaseHooksService
|
2021-02-05 10:09:28 -05:00
|
|
|
extend ::Gitlab::Utils::Override
|
|
|
|
|
2019-03-28 10:59:24 -04:00
|
|
|
def execute
|
|
|
|
execute_branch_hooks
|
|
|
|
|
|
|
|
super.tap do
|
2020-02-06 19:09:12 -05:00
|
|
|
enqueue_update_signatures
|
2019-03-28 10:59:24 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def hook_name
|
|
|
|
:push_hooks
|
|
|
|
end
|
|
|
|
|
2021-08-25 08:11:32 -04:00
|
|
|
def limited_commits
|
|
|
|
strong_memoize(:limited_commits) { threshold_commits.last(PROCESS_COMMIT_LIMIT) }
|
|
|
|
end
|
|
|
|
|
|
|
|
# Taking limit+1 commits allows us to detect when the limit is in effect
|
|
|
|
def threshold_commits
|
|
|
|
strong_memoize(:threshold_commits) do
|
2019-03-28 10:59:24 -04:00
|
|
|
if creating_default_branch?
|
2021-08-18 05:10:26 -04:00
|
|
|
# The most recent PROCESS_COMMIT_LIMIT commits in the default branch.
|
|
|
|
# They are returned newest-to-oldest, but we need to present them oldest-to-newest
|
2021-08-25 08:11:32 -04:00
|
|
|
project.repository.commits(newrev, limit: PROCESS_COMMIT_LIMIT + 1).reverse!
|
2019-03-28 10:59:24 -04:00
|
|
|
elsif creating_branch?
|
|
|
|
# Use the pushed commits that aren't reachable by the default branch
|
|
|
|
# as a heuristic. This may include more commits than are actually
|
|
|
|
# pushed, but that shouldn't matter because we check for existing
|
|
|
|
# cross-references later.
|
2021-08-25 08:11:32 -04:00
|
|
|
project.repository.commits_between(project.default_branch, newrev, limit: PROCESS_COMMIT_LIMIT + 1)
|
2019-03-28 10:59:24 -04:00
|
|
|
elsif updating_branch?
|
2021-08-25 08:11:32 -04:00
|
|
|
project.repository.commits_between(oldrev, newrev, limit: PROCESS_COMMIT_LIMIT + 1)
|
2019-03-28 10:59:24 -04:00
|
|
|
else # removing branch
|
|
|
|
[]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def commits_count
|
2021-08-25 08:11:32 -04:00
|
|
|
strong_memoize(:commits_count) do
|
|
|
|
next threshold_commits.count if
|
|
|
|
strong_memoized?(:threshold_commits) &&
|
|
|
|
threshold_commits.count <= PROCESS_COMMIT_LIMIT
|
2019-03-28 10:59:24 -04:00
|
|
|
|
2021-08-25 08:11:32 -04:00
|
|
|
if creating_default_branch?
|
|
|
|
project.repository.commit_count_for_ref(ref)
|
|
|
|
elsif creating_branch?
|
|
|
|
project.repository.count_commits_between(project.default_branch, newrev)
|
|
|
|
elsif updating_branch?
|
|
|
|
project.repository.count_commits_between(oldrev, newrev)
|
|
|
|
else # removing branch
|
|
|
|
0
|
|
|
|
end
|
|
|
|
end
|
2019-03-28 10:59:24 -04:00
|
|
|
end
|
|
|
|
|
2021-02-05 10:09:28 -05:00
|
|
|
override :invalidated_file_types
|
2019-03-28 10:59:24 -04:00
|
|
|
def invalidated_file_types
|
|
|
|
return super unless default_branch? && !creating_branch?
|
|
|
|
|
2021-02-05 10:09:28 -05:00
|
|
|
modified_file_types
|
|
|
|
end
|
|
|
|
|
|
|
|
def modified_file_types
|
2021-02-04 07:09:25 -05:00
|
|
|
paths = commit_paths.values.reduce(&:merge) || Set.new
|
2019-03-28 10:59:24 -04:00
|
|
|
|
|
|
|
Gitlab::FileDetector.types_in_paths(paths)
|
|
|
|
end
|
|
|
|
|
|
|
|
def execute_branch_hooks
|
|
|
|
project.repository.after_push_commit(branch_name)
|
|
|
|
|
|
|
|
branch_create_hooks if creating_branch?
|
|
|
|
branch_update_hooks if updating_branch?
|
|
|
|
branch_change_hooks if creating_branch? || updating_branch?
|
|
|
|
branch_remove_hooks if removing_branch?
|
|
|
|
end
|
|
|
|
|
|
|
|
def branch_create_hooks
|
2019-08-09 06:09:06 -04:00
|
|
|
project.repository.after_create_branch(expire_cache: false)
|
2019-03-28 10:59:24 -04:00
|
|
|
project.after_create_default_branch if default_branch?
|
|
|
|
end
|
|
|
|
|
|
|
|
def branch_update_hooks
|
|
|
|
# Update the bare repositories info/attributes file using the contents of
|
|
|
|
# the default branch's .gitattributes file
|
2019-10-03 11:07:07 -04:00
|
|
|
project.repository.copy_gitattributes(ref) if default_branch?
|
2019-03-28 10:59:24 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def branch_change_hooks
|
|
|
|
enqueue_process_commit_messages
|
2020-09-01 08:11:01 -04:00
|
|
|
enqueue_jira_connect_sync_messages
|
2020-10-02 14:08:56 -04:00
|
|
|
enqueue_metrics_dashboard_sync
|
2021-02-04 07:09:25 -05:00
|
|
|
track_ci_config_change_event
|
2019-03-28 10:59:24 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def branch_remove_hooks
|
2019-08-09 06:09:06 -04:00
|
|
|
project.repository.after_remove_branch(expire_cache: false)
|
2019-03-28 10:59:24 -04:00
|
|
|
end
|
|
|
|
|
2020-10-02 14:08:56 -04:00
|
|
|
def enqueue_metrics_dashboard_sync
|
|
|
|
return unless default_branch?
|
2021-02-05 10:09:28 -05:00
|
|
|
return unless modified_file_types.include?(:metrics_dashboard)
|
2020-10-02 14:08:56 -04:00
|
|
|
|
|
|
|
::Metrics::Dashboard::SyncDashboardsWorker.perform_async(project.id)
|
|
|
|
end
|
|
|
|
|
2021-02-04 07:09:25 -05:00
|
|
|
def track_ci_config_change_event
|
2021-08-12 08:11:05 -04:00
|
|
|
return unless ::ServicePing::ServicePingSettings.enabled?
|
2021-02-04 07:09:25 -05:00
|
|
|
return unless default_branch?
|
|
|
|
|
|
|
|
commits_changing_ci_config.each do |commit|
|
|
|
|
Gitlab::UsageDataCounters::HLLRedisCounter.track_event(
|
|
|
|
'o_pipeline_authoring_unique_users_committing_ciconfigfile', values: commit.author&.id
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-03-28 10:59:24 -04:00
|
|
|
# Schedules processing of commit messages
|
|
|
|
def enqueue_process_commit_messages
|
2019-08-16 14:26:31 -04:00
|
|
|
referencing_commits = limited_commits.select(&:matches_cross_reference_regex?)
|
|
|
|
|
|
|
|
upstream_commit_ids = upstream_commit_ids(referencing_commits)
|
|
|
|
|
|
|
|
referencing_commits.each do |commit|
|
|
|
|
# Avoid reprocessing commits that already exist upstream if the project
|
|
|
|
# is a fork. This will prevent duplicated/superfluous system notes on
|
|
|
|
# mentionables referenced by a commit that is pushed to the upstream,
|
|
|
|
# that is then also pushed to forks when these get synced by users.
|
|
|
|
next if upstream_commit_ids.include?(commit.id)
|
2019-03-28 10:59:24 -04:00
|
|
|
|
|
|
|
ProcessCommitWorker.perform_async(
|
|
|
|
project.id,
|
|
|
|
current_user.id,
|
|
|
|
commit.to_hash,
|
|
|
|
default_branch?
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-09-01 08:11:01 -04:00
|
|
|
def enqueue_jira_connect_sync_messages
|
|
|
|
return unless project.jira_subscription_exists?
|
|
|
|
|
|
|
|
branch_to_sync = branch_name if Atlassian::JiraIssueKeyExtractor.has_keys?(branch_name)
|
|
|
|
commits_to_sync = limited_commits.select { |commit| Atlassian::JiraIssueKeyExtractor.has_keys?(commit.safe_message) }.map(&:sha)
|
|
|
|
|
|
|
|
if branch_to_sync || commits_to_sync.any?
|
2020-11-24 16:09:39 -05:00
|
|
|
JiraConnect::SyncBranchWorker.perform_async(project.id, branch_to_sync, commits_to_sync, Atlassian::JiraConnect::Client.generate_update_sequence_id)
|
2020-09-01 08:11:01 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-02-06 19:09:12 -05:00
|
|
|
def unsigned_x509_shas(commits)
|
2021-12-13 07:12:59 -05:00
|
|
|
CommitSignatures::X509CommitSignature.unsigned_commit_shas(commits.map(&:sha))
|
2020-02-06 19:09:12 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def unsigned_gpg_shas(commits)
|
2021-12-13 07:12:59 -05:00
|
|
|
CommitSignatures::GpgSignature.unsigned_commit_shas(commits.map(&:sha))
|
2020-02-06 19:09:12 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def enqueue_update_signatures
|
2020-04-28 02:09:49 -04:00
|
|
|
unsigned = unsigned_x509_shas(limited_commits) & unsigned_gpg_shas(limited_commits)
|
2019-03-28 10:59:24 -04:00
|
|
|
return if unsigned.empty?
|
|
|
|
|
|
|
|
signable = Gitlab::Git::Commit.shas_with_signatures(project.repository, unsigned)
|
|
|
|
return if signable.empty?
|
|
|
|
|
2020-02-06 19:09:12 -05:00
|
|
|
CreateCommitSignatureWorker.perform_async(signable, project.id)
|
2019-03-28 10:59:24 -04:00
|
|
|
end
|
|
|
|
|
Look for new branches more carefully
In certain cases, GitLab can miss a PostReceive invocation the first
time a branch is pushed. When this happens, the "branch created" hooks
are not run, which means various features don't work until the branch
is deleted and pushed again.
This MR changes the `Git::BranchPushService` so it checks the cache of
existing branches in addition to the `oldrev` reported for the branch.
If the branch name isn't in the cache, chances are we haven't run the
service yet (it's what refreshes the cache), so we can go ahead and
run it, even through `oldrev` is set.
If the cache has been cleared by some other means in the meantime, then
we'll still fail to run the hooks when we should. Fixing that in the
general case is a larger problem, and we'd need to devote significant
engineering effort to it.
There's a chance that we'll run the relevant hooks *multiple times*
with this change, if there's a race between the branch being created,
and the `PostReceive` worker being run multiple times, but this can
already happen, since Sidekiq is "at-least-once" execution of jobs. So,
this should be safe.
2019-06-17 11:12:05 -04:00
|
|
|
# It's not sufficient to just check for a blank SHA as it's possible for the
|
|
|
|
# branch to be pushed, but for the `post-receive` hook to never run:
|
2019-09-18 10:02:45 -04:00
|
|
|
# https://gitlab.com/gitlab-org/gitlab-foss/issues/59257
|
2019-03-28 10:59:24 -04:00
|
|
|
def creating_branch?
|
Look for new branches more carefully
In certain cases, GitLab can miss a PostReceive invocation the first
time a branch is pushed. When this happens, the "branch created" hooks
are not run, which means various features don't work until the branch
is deleted and pushed again.
This MR changes the `Git::BranchPushService` so it checks the cache of
existing branches in addition to the `oldrev` reported for the branch.
If the branch name isn't in the cache, chances are we haven't run the
service yet (it's what refreshes the cache), so we can go ahead and
run it, even through `oldrev` is set.
If the cache has been cleared by some other means in the meantime, then
we'll still fail to run the hooks when we should. Fixing that in the
general case is a larger problem, and we'd need to devote significant
engineering effort to it.
There's a chance that we'll run the relevant hooks *multiple times*
with this change, if there's a race between the branch being created,
and the `PostReceive` worker being run multiple times, but this can
already happen, since Sidekiq is "at-least-once" execution of jobs. So,
this should be safe.
2019-06-17 11:12:05 -04:00
|
|
|
strong_memoize(:creating_branch) do
|
2019-10-03 11:07:07 -04:00
|
|
|
Gitlab::Git.blank_ref?(oldrev) ||
|
Look for new branches more carefully
In certain cases, GitLab can miss a PostReceive invocation the first
time a branch is pushed. When this happens, the "branch created" hooks
are not run, which means various features don't work until the branch
is deleted and pushed again.
This MR changes the `Git::BranchPushService` so it checks the cache of
existing branches in addition to the `oldrev` reported for the branch.
If the branch name isn't in the cache, chances are we haven't run the
service yet (it's what refreshes the cache), so we can go ahead and
run it, even through `oldrev` is set.
If the cache has been cleared by some other means in the meantime, then
we'll still fail to run the hooks when we should. Fixing that in the
general case is a larger problem, and we'd need to devote significant
engineering effort to it.
There's a chance that we'll run the relevant hooks *multiple times*
with this change, if there's a race between the branch being created,
and the `PostReceive` worker being run multiple times, but this can
already happen, since Sidekiq is "at-least-once" execution of jobs. So,
this should be safe.
2019-06-17 11:12:05 -04:00
|
|
|
!project.repository.branch_exists?(branch_name)
|
|
|
|
end
|
2019-03-28 10:59:24 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def updating_branch?
|
|
|
|
!creating_branch? && !removing_branch?
|
|
|
|
end
|
|
|
|
|
|
|
|
def removing_branch?
|
2019-10-03 11:07:07 -04:00
|
|
|
Gitlab::Git.blank_ref?(newrev)
|
2019-03-28 10:59:24 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def creating_default_branch?
|
|
|
|
creating_branch? && default_branch?
|
|
|
|
end
|
|
|
|
|
|
|
|
def default_branch?
|
|
|
|
strong_memoize(:default_branch) do
|
|
|
|
[nil, branch_name].include?(project.default_branch)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def branch_name
|
2019-10-03 11:07:07 -04:00
|
|
|
strong_memoize(:branch_name) { Gitlab::Git.ref_name(ref) }
|
2019-03-28 10:59:24 -04:00
|
|
|
end
|
2019-08-16 14:26:31 -04:00
|
|
|
|
|
|
|
def upstream_commit_ids(commits)
|
|
|
|
set = Set.new
|
|
|
|
|
|
|
|
upstream_project = project.fork_source
|
|
|
|
if upstream_project
|
|
|
|
upstream_project
|
|
|
|
.commits_by(oids: commits.map(&:id))
|
|
|
|
.each { |commit| set << commit.id }
|
|
|
|
end
|
|
|
|
|
|
|
|
set
|
|
|
|
end
|
2021-02-04 07:09:25 -05:00
|
|
|
|
|
|
|
def commits_changing_ci_config
|
|
|
|
commit_paths.select do |commit, paths|
|
|
|
|
next if commit.merge_commit?
|
|
|
|
|
|
|
|
paths.include?(project.ci_config_path_or_default)
|
|
|
|
end.keys
|
|
|
|
end
|
|
|
|
|
|
|
|
def commit_paths
|
|
|
|
strong_memoize(:commit_paths) do
|
2021-02-05 10:09:28 -05:00
|
|
|
limited_commits.to_h do |commit|
|
2021-02-04 07:09:25 -05:00
|
|
|
paths = Set.new(commit.raw_deltas.map(&:new_path))
|
|
|
|
[commit, paths]
|
2021-02-05 10:09:28 -05:00
|
|
|
end
|
2021-02-04 07:09:25 -05:00
|
|
|
end
|
|
|
|
end
|
2019-03-28 10:59:24 -04:00
|
|
|
end
|
|
|
|
end
|
2019-09-13 09:26:31 -04:00
|
|
|
|
2021-05-11 17:10:21 -04:00
|
|
|
Git::BranchHooksService.prepend_mod_with('Git::BranchHooksService')
|