debian-mirror-gitlab/app/models/snippet_repository.rb

137 lines
3.9 KiB
Ruby
Raw Normal View History

2020-03-13 15:44:24 +05:30
# frozen_string_literal: true
class SnippetRepository < ApplicationRecord
2021-03-08 18:12:59 +05:30
include EachBatch
2020-03-13 15:44:24 +05:30
include Shardable
2020-04-08 14:13:33 +05:30
DEFAULT_EMPTY_FILE_NAME = 'snippetfile'
EMPTY_FILE_PATTERN = /^#{DEFAULT_EMPTY_FILE_NAME}(\d+)\.txt$/.freeze
CommitError = Class.new(StandardError)
2020-05-24 23:13:21 +05:30
InvalidPathError = Class.new(CommitError)
InvalidSignatureError = Class.new(CommitError)
2020-04-08 14:13:33 +05:30
2020-03-13 15:44:24 +05:30
belongs_to :snippet, inverse_of: :snippet_repository
2021-01-03 14:25:43 +05:30
delegate :repository, :repository_storage, to: :snippet
2020-04-08 14:13:33 +05:30
2020-03-13 15:44:24 +05:30
class << self
def find_snippet(disk_path)
find_by(disk_path: disk_path)&.snippet
end
end
2020-04-08 14:13:33 +05:30
def multi_files_action(user, files = [], **options)
return if files.nil? || files.empty?
lease_key = "multi_files_action:#{snippet_id}"
lease = Gitlab::ExclusiveLease.new(lease_key, timeout: 120)
raise CommitError, 'Snippet is already being updated' unless uuid = lease.try_obtain
options[:actions] = transform_file_entries(files)
2022-10-11 01:57:18 +05:30
capture_git_error { repository.commit_files(user, **options) }
2020-04-08 14:13:33 +05:30
ensure
Gitlab::ExclusiveLease.cancel(lease_key, uuid)
end
private
def capture_git_error(&block)
yield block
rescue Gitlab::Git::Index::IndexError,
Gitlab::Git::CommitError,
Gitlab::Git::PreReceiveError,
2020-05-24 23:13:21 +05:30
Gitlab::Git::CommandError,
2022-08-27 11:52:29 +05:30
ArgumentError => e
2020-05-24 23:13:21 +05:30
2022-08-27 11:52:29 +05:30
logger.error(message: "Snippet git error. Reason: #{e.message}", snippet: snippet.id)
2020-05-24 23:13:21 +05:30
2022-08-27 11:52:29 +05:30
raise commit_error_exception(e)
2020-04-08 14:13:33 +05:30
end
def transform_file_entries(files)
next_index = get_last_empty_file_index + 1
2020-06-11 16:45:22 +05:30
files.map do |file_entry|
2020-04-22 19:07:51 +05:30
file_entry[:file_path] = file_path_for(file_entry, next_index) { next_index += 1 }
2020-04-08 14:13:33 +05:30
file_entry[:action] = infer_action(file_entry) unless file_entry[:action]
2020-06-11 16:45:22 +05:30
file_entry[:action] = file_entry[:action].to_sym
if only_rename_action?(file_entry)
file_entry[:infer_content] = true
elsif empty_update_action?(file_entry)
# There is no need to perform a repository operation
# When the update action has no content
file_entry = nil
end
file_entry
end.compact
2020-04-08 14:13:33 +05:30
end
2020-04-22 19:07:51 +05:30
def file_path_for(file_entry, next_index)
return file_entry[:file_path] if file_entry[:file_path].present?
return file_entry[:previous_path] if reuse_previous_path?(file_entry)
build_empty_file_name(next_index).tap { yield }
end
# If the user removed the file_path and the previous_path
# matches the EMPTY_FILE_PATTERN, we don't need to
# rename the file and build a new empty file name,
# we can just reuse the existing file name
def reuse_previous_path?(file_entry)
file_entry[:file_path].blank? &&
EMPTY_FILE_PATTERN.match?(file_entry[:previous_path])
end
2020-04-08 14:13:33 +05:30
def infer_action(file_entry)
return :create if file_entry[:previous_path].blank?
file_entry[:previous_path] != file_entry[:file_path] ? :move : :update
end
def get_last_empty_file_index
2020-11-24 15:15:51 +05:30
repository.ls_files(snippet.default_branch).inject(0) do |max, file|
2020-04-08 14:13:33 +05:30
idx = file[EMPTY_FILE_PATTERN, 1].to_i
[idx, max].max
end
end
def build_empty_file_name(index)
"#{DEFAULT_EMPTY_FILE_NAME}#{index}.txt"
end
2020-05-24 23:13:21 +05:30
def commit_error_exception(err)
if invalid_path_error?(err)
InvalidPathError.new('Invalid file name') # To avoid returning the message with the path included
elsif invalid_signature_error?(err)
InvalidSignatureError.new(err.message)
else
CommitError.new(err.message)
end
end
def invalid_path_error?(err)
err.is_a?(Gitlab::Git::Index::IndexError) &&
err.message.downcase.start_with?('invalid path', 'path cannot include directory traversal')
end
def invalid_signature_error?(err)
err.is_a?(ArgumentError) &&
err.message.downcase.match?(/failed to parse signature/)
end
2020-06-11 16:45:22 +05:30
def only_rename_action?(action)
action[:action] == :move && action[:content].nil?
end
def empty_update_action?(action)
action[:action] == :update && action[:content].nil?
end
2020-03-13 15:44:24 +05:30
end
2020-11-24 15:15:51 +05:30
2021-06-08 01:23:25 +05:30
SnippetRepository.prepend_mod_with('SnippetRepository')