diff options
Diffstat (limited to 'lib/gitlab')
27 files changed, 493 insertions, 545 deletions
diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb index 87e377de4d3..b170145f013 100644 --- a/lib/gitlab/access.rb +++ b/lib/gitlab/access.rb @@ -7,12 +7,14 @@ module Gitlab module Access AccessDeniedError = Class.new(StandardError) - NO_ACCESS = 0 - GUEST = 10 - REPORTER = 20 - DEVELOPER = 30 - MASTER = 40 - OWNER = 50 + NO_ACCESS = 0 + GUEST = 10 + REPORTER = 20 + DEVELOPER = 30 + MAINTAINER = 40 + # @deprecated + MASTER = MAINTAINER + OWNER = 50 # Branch protection settings PROTECTION_NONE = 0 @@ -32,7 +34,7 @@ module Gitlab "Guest" => GUEST, "Reporter" => REPORTER, "Developer" => DEVELOPER, - "Maintainer" => MASTER + "Maintainer" => MAINTAINER } end @@ -44,10 +46,10 @@ module Gitlab def sym_options { - guest: GUEST, - reporter: REPORTER, - developer: DEVELOPER, - master: MASTER + guest: GUEST, + reporter: REPORTER, + developer: DEVELOPER, + maintainer: MAINTAINER } end diff --git a/lib/gitlab/background_migration/delete_diff_files.rb b/lib/gitlab/background_migration/delete_diff_files.rb index 8fb2c334048..664ead1af44 100644 --- a/lib/gitlab/background_migration/delete_diff_files.rb +++ b/lib/gitlab/background_migration/delete_diff_files.rb @@ -4,41 +4,77 @@ module Gitlab module BackgroundMigration class DeleteDiffFiles - def perform(merge_request_diff_id) - merge_request_diff = MergeRequestDiff.find_by(id: merge_request_diff_id) + class MergeRequestDiff < ActiveRecord::Base + self.table_name = 'merge_request_diffs' - return unless merge_request_diff - return unless should_delete_diff_files?(merge_request_diff) + belongs_to :merge_request + has_many :merge_request_diff_files + end - MergeRequestDiff.transaction do - merge_request_diff.update_column(:state, 'without_files') - - # explain (analyze, buffers) when deleting 453 diff files: - # - # Delete on merge_request_diff_files (cost=0.57..8487.35 rows=4846 width=6) (actual time=43.265..43.265 rows=0 loops=1) - # Buffers: shared hit=2043 read=259 dirtied=254 - # -> Index Scan using index_merge_request_diff_files_on_mr_diff_id_and_order on merge_request_diff_files (cost=0.57..8487.35 rows=4846 width=6) (actu - # al time=0.466..26.317 rows=453 loops=1) - # Index Cond: (merge_request_diff_id = 463448) - # Buffers: shared hit=17 read=84 - # Planning time: 0.107 ms - # Execution time: 43.287 ms - # - MergeRequestDiffFile.where(merge_request_diff_id: merge_request_diff.id).delete_all + class MergeRequestDiffFile < ActiveRecord::Base + self.table_name = 'merge_request_diff_files' + end + + DEAD_TUPLES_THRESHOLD = 50_000 + VACUUM_WAIT_TIME = 5.minutes + + def perform(ids) + @ids = ids + + # We should reschedule until deadtuples get in a desirable + # state (e.g. < 50_000). That may take more than one reschedule. + # + if should_wait_deadtuple_vacuum? + reschedule + return end + + prune_diff_files + end + + def should_wait_deadtuple_vacuum? + return false unless Gitlab::Database.postgresql? + + diff_files_dead_tuples_count >= DEAD_TUPLES_THRESHOLD end private - def should_delete_diff_files?(merge_request_diff) - return false if merge_request_diff.state == 'without_files' + def reschedule + BackgroundMigrationWorker.perform_in(VACUUM_WAIT_TIME, self.class.name.demodulize, [@ids]) + end + + def diffs_collection + MergeRequestDiff.where(id: @ids) + end + + def diff_files_dead_tuples_count + dead_tuple = + execute_statement("SELECT n_dead_tup FROM pg_stat_all_tables "\ + "WHERE relname = 'merge_request_diff_files'")[0] - merge_request = merge_request_diff.merge_request + dead_tuple&.fetch('n_dead_tup', 0).to_i + end + + def prune_diff_files + removed = 0 + updated = 0 - return false unless merge_request.state == 'merged' - return false if merge_request_diff.id == merge_request.latest_merge_request_diff_id + MergeRequestDiff.transaction do + updated = diffs_collection.update_all(state: 'without_files') + removed = MergeRequestDiffFile.where(merge_request_diff_id: @ids).delete_all + end + + log_info("Removed #{removed} merge_request_diff_files rows, "\ + "updated #{updated} merge_request_diffs rows") + end + + def execute_statement(sql) + ActiveRecord::Base.connection.execute(sql) + end - true + def log_info(message) + Rails.logger.info("BackgroundMigration::DeleteDiffFiles - #{message}") end end end diff --git a/lib/gitlab/background_migration/schedule_diff_files_deletion.rb b/lib/gitlab/background_migration/schedule_diff_files_deletion.rb new file mode 100644 index 00000000000..609cf19187c --- /dev/null +++ b/lib/gitlab/background_migration/schedule_diff_files_deletion.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true +# rubocop:disable Style/Documentation + +module Gitlab + module BackgroundMigration + class ScheduleDiffFilesDeletion + class MergeRequestDiff < ActiveRecord::Base + self.table_name = 'merge_request_diffs' + + belongs_to :merge_request + + include EachBatch + end + + DIFF_BATCH_SIZE = 5_000 + INTERVAL = 5.minutes + MIGRATION = 'DeleteDiffFiles' + + def perform + diffs = MergeRequestDiff + .from("(#{diffs_collection.to_sql}) merge_request_diffs") + .where('merge_request_diffs.id != merge_request_diffs.latest_merge_request_diff_id') + .select(:id) + + diffs.each_batch(of: DIFF_BATCH_SIZE) do |relation, index| + ids = relation.pluck(:id) + + BackgroundMigrationWorker.perform_in(index * INTERVAL, MIGRATION, [ids]) + end + end + + private + + def diffs_collection + MergeRequestDiff + .joins(:merge_request) + .where("merge_requests.state = 'merged'") + .where('merge_requests.latest_merge_request_diff_id IS NOT NULL') + .where("merge_request_diffs.state NOT IN ('without_files', 'empty')") + .select('merge_requests.latest_merge_request_diff_id, merge_request_diffs.id') + end + end + end +end diff --git a/lib/gitlab/ee_compat_check.rb b/lib/gitlab/ee_compat_check.rb index 8c72d00c1f3..ee604e66154 100644 --- a/lib/gitlab/ee_compat_check.rb +++ b/lib/gitlab/ee_compat_check.rb @@ -138,15 +138,23 @@ module Gitlab def ee_branch_presence_check! ee_remotes.keys.each do |remote| - [ce_branch, ee_branch_prefix, ee_branch_suffix].each do |branch| - _, status = step("Fetching #{remote}/#{branch}", %W[git fetch #{remote} #{branch}]) + output, _ = step( + "Searching #{remote}", + %W[git ls-remote #{remote} *#{minimal_ee_branch_name}*]) - if status.zero? - @ee_remote_with_branch = remote - @ee_branch_found = branch - return true - end - end + branches = + output.scan(%r{(?<=refs/heads/|refs/tags/).+}).sort_by(&:size) + + next if branches.empty? + + branch = branches.first + + step("Fetching #{remote}/#{branch}", %W[git fetch #{remote} #{branch}]) + + @ee_remote_with_branch = remote + @ee_branch_found = branch + + return true end puts @@ -271,6 +279,10 @@ module Gitlab @ee_patch_full_path ||= patches_dir.join(ee_patch_name) end + def minimal_ee_branch_name + @minimal_ee_branch_name ||= ce_branch.sub(/(\Ace\-|\-ce\z)/, '') + end + def patch_name_from_branch(branch_name) branch_name.parameterize << '.patch' end diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb index 96fa94d5790..71857bd2d87 100644 --- a/lib/gitlab/git/blob.rb +++ b/lib/gitlab/git/blob.rb @@ -61,17 +61,8 @@ module Gitlab # Keep in mind that this method may allocate a lot of memory. It is up # to the caller to limit the number of blobs and blob_size_limit. # - # Gitaly migration issue: https://gitlab.com/gitlab-org/gitaly/issues/798 def batch(repository, blob_references, blob_size_limit: MAX_DATA_DISPLAY_SIZE) - Gitlab::GitalyClient.migrate(:list_blobs_by_sha_path) do |is_enabled| - if is_enabled - repository.gitaly_blob_client.get_blobs(blob_references, blob_size_limit).to_a - else - blob_references.map do |sha, path| - find(repository, sha, path, limit: blob_size_limit) - end - end - end + repository.gitaly_blob_client.get_blobs(blob_references, blob_size_limit).to_a end # Returns an array of Blob instances just with the metadata, that means @@ -84,16 +75,8 @@ module Gitlab # Returns array of Gitlab::Git::Blob # Does not guarantee blob data will be set def batch_lfs_pointers(repository, blob_ids) - repository.gitaly_migrate(:batch_lfs_pointers) do |is_enabled| - if is_enabled - repository.gitaly_blob_client.batch_lfs_pointers(blob_ids.to_a) - else - blob_ids.lazy - .select { |sha| possible_lfs_blob?(repository, sha) } - .map { |sha| rugged_raw(repository, sha, limit: LFS_POINTER_MAX_SIZE) } - .select(&:lfs_pointer?) - .force - end + repository.wrapped_gitaly_errors do + repository.gitaly_blob_client.batch_lfs_pointers(blob_ids.to_a) end end @@ -104,72 +87,6 @@ module Gitlab def size_could_be_lfs?(size) size.between?(LFS_POINTER_MIN_SIZE, LFS_POINTER_MAX_SIZE) end - - private - - # Recursive search of blob id by path - # - # Ex. - # blog/ # oid: 1a - # app/ # oid: 2a - # models/ # oid: 3a - # file.rb # oid: 4a - # - # - # Blob.find_entry_by_path(repo, '1a', 'blog', 'app', 'file.rb') # => '4a' - # - def find_entry_by_path(repository, root_id, *path_parts) - root_tree = repository.lookup(root_id) - - entry = root_tree.find do |entry| - entry[:name] == path_parts[0] - end - - return nil unless entry - - if path_parts.size > 1 - return nil unless entry[:type] == :tree - - path_parts.shift - find_entry_by_path(repository, entry[:oid], *path_parts) - else - [:blob, :commit].include?(entry[:type]) ? entry : nil - end - end - - def submodule_blob(blob_entry, path, sha) - new( - id: blob_entry[:oid], - name: blob_entry[:name], - size: 0, - data: '', - path: path, - commit_id: sha - ) - end - - def rugged_raw(repository, sha, limit:) - blob = repository.lookup(sha) - - return unless blob.is_a?(Rugged::Blob) - - new( - id: blob.oid, - size: blob.size, - data: blob.content(limit), - binary: blob.binary? - ) - end - - # Efficient lookup to determine if object size - # and type make it a possible LFS blob without loading - # blob content into memory with repository.lookup(sha) - def possible_lfs_blob?(repository, sha) - object_header = repository.rugged.read_header(sha) - - object_header[:type] == :blob && - size_could_be_lfs?(object_header[:len]) - end end def initialize(options) diff --git a/lib/gitlab/git/popen.rb b/lib/gitlab/git/popen.rb index f9f24ecc48d..7426688fc55 100644 --- a/lib/gitlab/git/popen.rb +++ b/lib/gitlab/git/popen.rb @@ -21,6 +21,10 @@ module Gitlab Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| stdout.set_encoding(Encoding::ASCII_8BIT) + # stderr and stdout pipes can block if stderr/stdout aren't drained: https://bugs.ruby-lang.org/issues/9082 + # Mimic what Ruby does with capture3: https://github.com/ruby/ruby/blob/1ec544695fa02d714180ef9c34e755027b6a2103/lib/open3.rb#L257-L273 + err_reader = Thread.new { stderr.read } + yield(stdin) if block_given? stdin.close @@ -32,7 +36,7 @@ module Gitlab cmd_output << stdout.read end - cmd_output << stderr.read + cmd_output << err_reader.value cmd_status = wait_thr.value.exitstatus end @@ -55,16 +59,20 @@ module Gitlab rerr, werr = IO.pipe pid = Process.spawn(vars, *cmd, out: wout, err: werr, chdir: path, pgroup: true) + # stderr and stdout pipes can block if stderr/stdout aren't drained: https://bugs.ruby-lang.org/issues/9082 + # Mimic what Ruby does with capture3: https://github.com/ruby/ruby/blob/1ec544695fa02d714180ef9c34e755027b6a2103/lib/open3.rb#L257-L273 + out_reader = Thread.new { rout.read } + err_reader = Thread.new { rerr.read } begin - status = process_wait_with_timeout(pid, timeout) - # close write ends so we could read them wout.close werr.close - cmd_output = rout.readlines.join - cmd_output << rerr.readlines.join # Copying the behaviour of `popen` which merges stderr into output + status = process_wait_with_timeout(pid, timeout) + + cmd_output = out_reader.value + cmd_output << err_reader.value # Copying the behaviour of `popen` which merges stderr into output [cmd_output, status.exitstatus] rescue Timeout::Error => e diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 897adbd5ec9..3c23b588f77 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -86,9 +86,6 @@ module Gitlab # Relative path of repo attr_reader :relative_path - # Rugged repo object - attr_reader :rugged - attr_reader :gitlab_projects, :storage, :gl_repository, :relative_path # This initializer method is only used on the client side (gitlab-ce). @@ -112,8 +109,9 @@ module Gitlab [storage, relative_path] == [other.storage, other.relative_path] end + # This method will be removed when Gitaly reaches v1.1. def path - @path ||= File.join( + File.join( Gitlab.config.repositories.storages[@storage].legacy_disk_path, @relative_path ) end @@ -127,8 +125,9 @@ module Gitlab raise Gitlab::Git::CommandError.new(e.message) end + # This method will be removed when Gitaly reaches v1.1. def rugged - @rugged ||= circuit_breaker.perform do + circuit_breaker.perform do Rugged::Repository.new(path, alternates: alternate_object_directories) end rescue Rugged::RepositoryError, Rugged::OSError @@ -168,24 +167,9 @@ module Gitlab # Directly find a branch with a simple name (e.g. master) # - # force_reload causes a new Rugged repository to be instantiated - # - # This is to work around a bug in libgit2 that causes in-memory refs to - # be stale/invalid when packed-refs is changed. - # See https://gitlab.com/gitlab-org/gitlab-ce/issues/15392#note_14538333 - def find_branch(name, force_reload = false) - gitaly_migrate(:find_branch) do |is_enabled| - if is_enabled - gitaly_ref_client.find_branch(name) - else - reload_rugged if force_reload - - rugged_ref = rugged.branches[name] - if rugged_ref - target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target) - Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit) - end - end + def find_branch(name) + wrapped_gitaly_errors do + gitaly_ref_client.find_branch(name) end end @@ -197,20 +181,8 @@ module Gitlab # Returns the number of valid branches def branch_count - gitaly_migrate(:branch_names) do |is_enabled| - if is_enabled - gitaly_ref_client.count_branch_names - else - rugged.branches.each(:local).count do |ref| - begin - ref.name && ref.target # ensures the branch is valid - - true - rescue Rugged::ReferenceError - false - end - end - end + wrapped_gitaly_errors do + gitaly_ref_client.count_branch_names end end @@ -233,12 +205,8 @@ module Gitlab # Returns the number of valid tags def tag_count - gitaly_migrate(:tag_names) do |is_enabled| - if is_enabled - gitaly_ref_client.count_tag_names - else - rugged.tags.count - end + wrapped_gitaly_errors do + gitaly_ref_client.count_tag_names end end @@ -261,13 +229,8 @@ module Gitlab # # Ref names must start with `refs/`. def ref_exists?(ref_name) - gitaly_migrate(:ref_exists, - status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - gitaly_ref_exists?(ref_name) - else - rugged_ref_exists?(ref_name) - end + wrapped_gitaly_errors do + gitaly_ref_exists?(ref_name) end end @@ -275,12 +238,8 @@ module Gitlab # # name - The name of the tag as a String. def tag_exists?(name) - gitaly_migrate(:ref_exists_tags, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - gitaly_ref_exists?("refs/tags/#{name}") - else - rugged_tag_exists?(name) - end + wrapped_gitaly_errors do + gitaly_ref_exists?("refs/tags/#{name}") end end @@ -288,12 +247,8 @@ module Gitlab # # name - The name of the branch as a String. def branch_exists?(name) - gitaly_migrate(:ref_exists_branches, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - gitaly_ref_exists?("refs/heads/#{name}") - else - rugged_branch_exists?(name) - end + wrapped_gitaly_errors do + gitaly_ref_exists?("refs/heads/#{name}") end end @@ -311,12 +266,8 @@ module Gitlab end def delete_all_refs_except(prefixes) - gitaly_migrate(:ref_delete_refs) do |is_enabled| - if is_enabled - gitaly_ref_client.delete_refs(except_with_prefixes: prefixes) - else - delete_refs(*all_ref_names_except(prefixes)) - end + wrapped_gitaly_errors do + gitaly_ref_client.delete_refs(except_with_prefixes: prefixes) end end @@ -329,12 +280,6 @@ module Gitlab end.map(&:name) end - def rugged_head - rugged.head - rescue Rugged::ReferenceError - nil - end - def archive_metadata(ref, storage_path, project_path, format = "tar.gz", append_sha:) ref ||= root_ref commit = Gitlab::Git::Commit.find(self, ref) @@ -564,11 +509,6 @@ module Gitlab @refs_hash end - # Lookup for rugged object by oid or ref name - def lookup(oid_or_ref_name) - rugged.rev_parse(oid_or_ref_name) - end - # Returns url for submodule # # Ex. @@ -627,7 +567,7 @@ module Gitlab def update_branch(branch_name, user:, newrev:, oldrev:) gitaly_migrate(:operation_user_update_branch) do |is_enabled| if is_enabled - gitaly_operations_client.user_update_branch(branch_name, user, newrev, oldrev) + gitaly_operation_client.user_update_branch(branch_name, user, newrev, oldrev) else OperationService.new(user, self).update_branch(branch_name, newrev, oldrev) end @@ -713,33 +653,18 @@ module Gitlab Gitlab::Git.committer_hash(email: user.email, name: user.name) end - def create_commit(params = {}) - params[:message].delete!("\r") - - Rugged::Commit.create(rugged, params) - end - # Delete the specified branch from the repository def delete_branch(branch_name) - gitaly_migrate(:delete_branch, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - gitaly_ref_client.delete_branch(branch_name) - else - rugged.branches.delete(branch_name) - end + wrapped_gitaly_errors do + gitaly_ref_client.delete_branch(branch_name) end - rescue Rugged::ReferenceError, CommandError => e + rescue CommandError => e raise DeleteBranchError, e end def delete_refs(*ref_names) - gitaly_migrate(:delete_refs, - status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - gitaly_delete_refs(*ref_names) - else - git_delete_refs(*ref_names) - end + wrapped_gitaly_errors do + gitaly_delete_refs(*ref_names) end end @@ -749,12 +674,8 @@ module Gitlab # create_branch("feature") # create_branch("other-feature", "master") def create_branch(ref, start_point = "HEAD") - gitaly_migrate(:create_branch, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - gitaly_ref_client.create_branch(ref, start_point) - else - rugged_create_branch(ref, start_point) - end + wrapped_gitaly_errors do + gitaly_ref_client.create_branch(ref, start_point) end end @@ -984,20 +905,6 @@ module Gitlab Gitlab::Git::Blob.batch(self, items, blob_size_limit: blob_size_limit) end - def commit_index(user, branch_name, index, options) - committer = user_to_committer(user) - - OperationService.new(user, self).with_branch(branch_name) do - commit_params = options.merge( - tree: index.write_tree(rugged), - author: committer, - committer: committer - ) - - create_commit(commit_params) - end - end - def fsck msg, status = gitaly_repository_client.fsck @@ -1182,7 +1089,7 @@ module Gitlab end def can_be_merged?(source_sha, target_branch) - if target_sha = find_branch(target_branch, true)&.target + if target_sha = find_branch(target_branch)&.target !gitaly_conflicts_client(source_sha, target_sha).conflicts? else false @@ -1424,23 +1331,6 @@ module Gitlab end end - # We are trying to deprecate this method because it does a lot of work - # but it seems to be used only to look up submodule URL's. - # https://gitlab.com/gitlab-org/gitaly/issues/329 - def submodules(ref) - commit = rev_parse_target(ref) - return {} unless commit - - begin - content = blob_content(commit, ".gitmodules") - rescue InvalidBlobName - return {} - end - - parser = GitmodulesParser.new(content) - fill_submodule_ids(commit, parser.parse) - end - def gitaly_submodule_url_for(ref, path) # We don't care about the contents so 1 byte is enough. Can't request 0 bytes, 0 means unlimited. commit_object = gitaly_commit_client.tree_entry(ref, path, 1) @@ -1463,68 +1353,6 @@ module Gitlab Gitlab::Git::HookEnv.all(gl_repository).values_at(*ALLOWED_OBJECT_RELATIVE_DIRECTORIES_VARIABLES).flatten.compact end - # Get the content of a blob for a given commit. If the blob is a commit - # (for submodules) then return the blob's OID. - def blob_content(commit, blob_name) - blob_entry = tree_entry(commit, blob_name) - - unless blob_entry - raise InvalidBlobName.new("Invalid blob name: #{blob_name}") - end - - case blob_entry[:type] - when :commit - blob_entry[:oid] - when :tree - raise InvalidBlobName.new("#{blob_name} is a tree, not a blob") - when :blob - rugged.lookup(blob_entry[:oid]).content - end - end - - # Fill in the 'id' field of a submodule hash from its values - # as-of +commit+. Return a Hash consisting only of entries - # from the submodule hash for which the 'id' field is filled. - def fill_submodule_ids(commit, submodule_data) - submodule_data.each do |path, data| - id = begin - blob_content(commit, path) - rescue InvalidBlobName - nil - end - data['id'] = id - end - submodule_data.select { |path, data| data['id'] } - end - - # Find the entry for +path+ in the tree for +commit+ - def tree_entry(commit, path) - pathname = Pathname.new(path) - first = true - tmp_entry = nil - - pathname.each_filename do |dir| - if first - tmp_entry = commit.tree[dir] - first = false - elsif tmp_entry.nil? - return nil - else - begin - tmp_entry = rugged.lookup(tmp_entry[:oid]) - rescue Rugged::OdbError, Rugged::InvalidError, Rugged::ReferenceError - return nil - end - - return nil unless tmp_entry.type == :tree - - tmp_entry = tmp_entry[dir] - end - end - - tmp_entry - end - # Return the Rugged patches for the diff between +from+ and +to+. def diff_patches(from, to, options = {}, *paths) options ||= {} @@ -1556,125 +1384,14 @@ module Gitlab # Returns true if the given ref name exists # # Ref names must start with `refs/`. - def rugged_ref_exists?(ref_name) - raise ArgumentError, 'invalid refname' unless ref_name.start_with?('refs/') - - rugged.references.exist?(ref_name) - rescue Rugged::ReferenceError - false - end - - # Returns true if the given ref name exists - # - # Ref names must start with `refs/`. def gitaly_ref_exists?(ref_name) gitaly_ref_client.ref_exists?(ref_name) end - # Returns true if the given tag exists - # - # name - The name of the tag as a String. - def rugged_tag_exists?(name) - !!rugged.tags[name] - end - - # Returns true if the given branch exists - # - # name - The name of the branch as a String. - def rugged_branch_exists?(name) - rugged.branches.exists?(name) - - # If the branch name is invalid (e.g. ".foo") Rugged will raise an error. - # Whatever code calls this method shouldn't have to deal with that so - # instead we just return `false` (which is true since a branch doesn't - # exist when it has an invalid name). - rescue Rugged::ReferenceError - false - end - - def rugged_create_branch(ref, start_point) - rugged_ref = rugged.branches.create(ref, start_point) - target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target) - Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit) - rescue Rugged::ReferenceError => e - raise InvalidRef.new("Branch #{ref} already exists") if e.to_s =~ %r{'refs/heads/#{ref}'} - - raise InvalidRef.new("Invalid reference #{start_point}") - end - def gitaly_copy_gitattributes(revision) gitaly_repository_client.apply_gitattributes(revision) end - def rugged_copy_gitattributes(ref) - begin - commit = lookup(ref) - rescue Rugged::ReferenceError - raise InvalidRef.new("Ref #{ref} is invalid") - end - - # Create the paths - info_dir_path = File.join(path, 'info') - info_attributes_path = File.join(info_dir_path, 'attributes') - - begin - # Retrieve the contents of the blob - gitattributes_content = blob_content(commit, '.gitattributes') - rescue InvalidBlobName - # No .gitattributes found. Should now remove any info/attributes and return - File.delete(info_attributes_path) if File.exist?(info_attributes_path) - return - end - - # Create the info directory if needed - Dir.mkdir(info_dir_path) unless File.directory?(info_dir_path) - - # Write the contents of the .gitattributes file to info/attributes - # Use binary mode to prevent Rails from converting ASCII-8BIT to UTF-8 - File.open(info_attributes_path, "wb") do |file| - file.write(gitattributes_content) - end - end - - def rugged_cherry_pick(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:) - OperationService.new(user, self).with_branch( - branch_name, - start_branch_name: start_branch_name, - start_repository: start_repository - ) do |start_commit| - - Gitlab::Git.check_namespace!(commit, start_repository) - - cherry_pick_tree_id = check_cherry_pick_content(commit, start_commit.sha) - raise CreateTreeError unless cherry_pick_tree_id - - committer = user_to_committer(user) - - create_commit(message: message, - author: { - email: commit.author_email, - name: commit.author_name, - time: commit.authored_date - }, - committer: committer, - tree: cherry_pick_tree_id, - parents: [start_commit.sha]) - end - end - - def check_cherry_pick_content(target_commit, source_sha) - args = [target_commit.sha, source_sha] - args << 1 if target_commit.merge_commit? - - cherry_pick_index = rugged.cherrypick_commit(*args) - return false if cherry_pick_index.conflicts? - - tree_id = cherry_pick_index.write_tree(rugged) - return false unless diff_exists?(source_sha, tree_id) - - tree_id - end - def local_fetch_ref(source_path, source_ref:, target_ref:) args = %W(fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref}) run_git(args) @@ -1694,20 +1411,6 @@ module Gitlab remote_update(remote_name, url: url) end - def git_delete_refs(*ref_names) - instructions = ref_names.map do |ref| - "delete #{ref}\x00\x00" - end - - message, status = run_git(%w[update-ref --stdin -z]) do |stdin| - stdin.write(instructions.join) - end - - unless status.zero? - raise GitError.new("Could not delete refs #{ref_names}: #{message}") - end - end - def gitaly_delete_refs(*ref_names) gitaly_ref_client.delete_refs(refs: ref_names) if ref_names.any? end diff --git a/lib/gitlab/git/rev_list.rb b/lib/gitlab/git/rev_list.rb index 5fdad077eea..2ba68343aa5 100644 --- a/lib/gitlab/git/rev_list.rb +++ b/lib/gitlab/git/rev_list.rb @@ -12,35 +12,12 @@ module Gitlab end # This method returns an array of new commit references - def new_refs - repository.rev_list(including: newrev, excluding: :all).split("\n") - end - - # Finds newly added objects - # Returns an array of shas + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/1233 # - # Can skip objects which do not have a path using required_path: true - # This skips commit objects and root trees, which might not be needed when - # looking for blobs - # - # When given a block it will yield objects as a lazy enumerator so - # the caller can limit work done instead of processing megabytes of data - def new_objects(options: [], require_path: nil, not_in: nil, &lazy_block) - opts = { - including: newrev, - options: options, - excluding: not_in.nil? ? :all : not_in, - require_path: require_path - } - - get_objects(opts, &lazy_block) - end - - def all_objects(options: [], require_path: nil, &lazy_block) - get_objects(including: :all, - options: options, - require_path: require_path, - &lazy_block) + def new_refs + Gitlab::GitalyClient::StorageSettings.allow_disk_access do + repository.rev_list(including: newrev, excluding: :all).split("\n") + end end private diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index db7c29be94b..35808149b90 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -2,6 +2,8 @@ # class return an instance of `GitlabAccessStatus` module Gitlab class GitAccess + include Gitlab::Utils::StrongMemoize + UnauthorizedError = Class.new(StandardError) NotFoundError = Class.new(StandardError) ProjectCreationError = Class.new(StandardError) @@ -26,7 +28,7 @@ module Gitlab PUSH_COMMANDS = %w{ git-receive-pack }.freeze ALL_COMMANDS = DOWNLOAD_COMMANDS + PUSH_COMMANDS - attr_reader :actor, :project, :protocol, :authentication_abilities, :namespace_path, :project_path, :redirected_path, :auth_result_type + attr_reader :actor, :project, :protocol, :authentication_abilities, :namespace_path, :project_path, :redirected_path, :auth_result_type, :changes def initialize(actor, project, protocol, authentication_abilities:, namespace_path: nil, project_path: nil, redirected_path: nil, auth_result_type: nil) @actor = actor @@ -40,6 +42,8 @@ module Gitlab end def check(cmd, changes) + @changes = changes + check_protocol! check_valid_actor! check_active_user! @@ -58,7 +62,7 @@ module Gitlab when *DOWNLOAD_COMMANDS check_download_access! when *PUSH_COMMANDS - check_push_access!(changes) + check_push_access! end true @@ -218,7 +222,7 @@ module Gitlab end end - def check_push_access!(changes) + def check_push_access! if project.repository_read_only? raise UnauthorizedError, ERROR_MESSAGES[:read_only] end @@ -235,17 +239,15 @@ module Gitlab return if changes.blank? # Allow access this is needed for EE. - check_change_access!(changes) + check_change_access! end - def check_change_access!(changes) + def check_change_access! # If there are worktrees with a HEAD pointing to a non-existent object, # calls to `git rev-list --all` will fail in git 2.15+. This should also # clear stale lock files. project.repository.clean_stale_repository_files - changes_list = Gitlab::ChangesList.new(changes) - # Iterate over all changes to find if user allowed all of them to be applied changes_list.each.with_index do |change, index| first_change = index == 0 @@ -321,6 +323,10 @@ module Gitlab protected + def changes_list + @changes_list ||= Gitlab::ChangesList.new(changes) + end + def user return @user if defined?(@user) diff --git a/lib/gitlab/gitaly_client/conflicts_service.rb b/lib/gitlab/gitaly_client/conflicts_service.rb index b1a01b185e6..aa7e03301f5 100644 --- a/lib/gitlab/gitaly_client/conflicts_service.rb +++ b/lib/gitlab/gitaly_client/conflicts_service.rb @@ -25,10 +25,12 @@ module Gitlab def conflicts? list_conflict_files.any? - rescue GRPC::FailedPrecondition - # The server raises this exception when it encounters ConflictSideMissing, which - # means a conflict exists but its `theirs` or `ours` data is nil due to a non-existent - # file in one of the trees. + rescue GRPC::FailedPrecondition, GRPC::Unknown + # The server raises FailedPrecondition when it encounters + # ConflictSideMissing, which means a conflict exists but its `theirs` or + # `ours` data is nil due to a non-existent file in one of the trees. + # + # GRPC::Unknown comes from Rugged::ReferenceError and Rugged::OdbError. true end diff --git a/lib/gitlab/gitaly_client/server_service.rb b/lib/gitlab/gitaly_client/server_service.rb index 2e1076d1f66..ad898278353 100644 --- a/lib/gitlab/gitaly_client/server_service.rb +++ b/lib/gitlab/gitaly_client/server_service.rb @@ -9,7 +9,7 @@ module Gitlab end def info - GitalyClient.call(@storage, :server_service, :server_info, Gitaly::ServerInfoRequest.new) + GitalyClient.call(@storage, :server_service, :server_info, Gitaly::ServerInfoRequest.new, timeout: GitalyClient.fast_timeout) end end end diff --git a/lib/gitlab/import_export/avatar_saver.rb b/lib/gitlab/import_export/avatar_saver.rb index 998c21e2586..31ef0490cb3 100644 --- a/lib/gitlab/import_export/avatar_saver.rb +++ b/lib/gitlab/import_export/avatar_saver.rb @@ -11,7 +11,12 @@ module Gitlab def save return true unless @project.avatar.exists? - copy_files(avatar_path, avatar_export_path) + Gitlab::ImportExport::UploadsManager.new( + project: @project, + shared: @shared, + relative_export_path: 'avatar', + from: avatar_path + ).save rescue => e @shared.error(e) false @@ -19,10 +24,6 @@ module Gitlab private - def avatar_export_path - File.join(@shared.export_path, 'avatar', @project.avatar_identifier) - end - def avatar_path @project.avatar.path end diff --git a/lib/gitlab/import_export/members_mapper.rb b/lib/gitlab/import_export/members_mapper.rb index 8b8e48aac76..ac827cbe1ca 100644 --- a/lib/gitlab/import_export/members_mapper.rb +++ b/lib/gitlab/import_export/members_mapper.rb @@ -47,7 +47,7 @@ module Gitlab def ensure_default_member! @project.project_members.destroy_all - ProjectMember.create!(user: @user, access_level: ProjectMember::MASTER, source_id: @project.id, importing: true) + ProjectMember.create!(user: @user, access_level: ProjectMember::MAINTAINER, source_id: @project.id, importing: true) end def add_team_member(member, existing_user = nil) diff --git a/lib/gitlab/import_export/uploads_manager.rb b/lib/gitlab/import_export/uploads_manager.rb new file mode 100644 index 00000000000..1110149712d --- /dev/null +++ b/lib/gitlab/import_export/uploads_manager.rb @@ -0,0 +1,101 @@ +module Gitlab + module ImportExport + class UploadsManager + include Gitlab::ImportExport::CommandLineUtil + + UPLOADS_BATCH_SIZE = 100 + + def initialize(project:, shared:, relative_export_path: 'uploads', from: nil) + @project = project + @shared = shared + @relative_export_path = relative_export_path + @from = from || default_uploads_path + end + + def save + copy_files(@from, uploads_export_path) if File.directory?(@from) + + if File.file?(@from) && @relative_export_path == 'avatar' + copy_files(@from, File.join(uploads_export_path, @project.avatar.filename)) + end + + copy_from_object_storage + + true + rescue => e + @shared.error(e) + false + end + + def restore + Dir["#{uploads_export_path}/**/*"].each do |upload| + next if File.directory?(upload) + + add_upload(upload) + end + + true + rescue => e + @shared.error(e) + false + end + + private + + def add_upload(upload) + uploader_context = FileUploader.extract_dynamic_path(upload).named_captures.symbolize_keys + + UploadService.new(@project, File.open(upload, 'r'), FileUploader, uploader_context).execute + end + + def copy_from_object_storage + return unless Gitlab::ImportExport.object_storage? + + each_uploader do |uploader| + next unless uploader.file + next if uploader.upload.local? # Already copied, using the old method + + download_and_copy(uploader) + end + end + + def default_uploads_path + FileUploader.absolute_base_dir(@project) + end + + def uploads_export_path + @uploads_export_path ||= File.join(@shared.export_path, @relative_export_path) + end + + def each_uploader + avatar_path = @project.avatar&.upload&.path + + if @relative_export_path == 'avatar' + yield(@project.avatar) + else + project_uploads_except_avatar(avatar_path).find_each(batch_size: UPLOADS_BATCH_SIZE) do |upload| + yield(upload.build_uploader) + end + end + end + + def project_uploads_except_avatar(avatar_path) + return @project.uploads unless avatar_path + + @project.uploads.where("path != ?", avatar_path) + end + + def download_and_copy(upload) + secret = upload.try(:secret) || '' + upload_path = File.join(uploads_export_path, secret, upload.filename) + + mkdir_p(File.join(uploads_export_path, secret)) + + File.open(upload_path, 'w') do |file| + # Download (stream) file from the uploader's location + IO.copy_stream(URI.parse(upload.file.url).open, file) + end + end + end + end +end diff --git a/lib/gitlab/import_export/uploads_restorer.rb b/lib/gitlab/import_export/uploads_restorer.rb index df19354b76e..25f85936227 100644 --- a/lib/gitlab/import_export/uploads_restorer.rb +++ b/lib/gitlab/import_export/uploads_restorer.rb @@ -2,13 +2,30 @@ module Gitlab module ImportExport class UploadsRestorer < UploadsSaver def restore - return true unless File.directory?(uploads_export_path) + if Gitlab::ImportExport.object_storage? + Gitlab::ImportExport::UploadsManager.new( + project: @project, + shared: @shared + ).restore + elsif File.directory?(uploads_export_path) + copy_files(uploads_export_path, uploads_path) - copy_files(uploads_export_path, uploads_path) + true + else + true # Proceed without uploads + end rescue => e @shared.error(e) false end + + def uploads_path + FileUploader.absolute_base_dir(@project) + end + + def uploads_export_path + @uploads_export_path ||= File.join(@shared.export_path, 'uploads') + end end end end diff --git a/lib/gitlab/import_export/uploads_saver.rb b/lib/gitlab/import_export/uploads_saver.rb index 2f08dda55fd..b3f17af5661 100644 --- a/lib/gitlab/import_export/uploads_saver.rb +++ b/lib/gitlab/import_export/uploads_saver.rb @@ -9,21 +9,14 @@ module Gitlab end def save - return true unless File.directory?(uploads_path) - - copy_files(uploads_path, uploads_export_path) + Gitlab::ImportExport::UploadsManager.new( + project: @project, + shared: @shared + ).save rescue => e @shared.error(e) false end - - def uploads_path - FileUploader.absolute_base_dir(@project) - end - - def uploads_export_path - File.join(@shared.export_path, 'uploads') - end end end end diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb index 60d5fa4d29a..af9b880ef9e 100644 --- a/lib/gitlab/import_sources.rb +++ b/lib/gitlab/import_sources.rb @@ -16,7 +16,8 @@ module Gitlab ImportSource.new('fogbugz', 'FogBugz', Gitlab::FogbugzImport::Importer), ImportSource.new('git', 'Repo by URL', nil), ImportSource.new('gitlab_project', 'GitLab export', Gitlab::ImportExport::Importer), - ImportSource.new('gitea', 'Gitea', Gitlab::LegacyGithubImport::Importer) + ImportSource.new('gitea', 'Gitea', Gitlab::LegacyGithubImport::Importer), + ImportSource.new('manifest', 'Manifest file', nil) ].freeze class << self diff --git a/lib/gitlab/kubernetes.rb b/lib/gitlab/kubernetes.rb index da43bd0af4b..15c5ece2350 100644 --- a/lib/gitlab/kubernetes.rb +++ b/lib/gitlab/kubernetes.rb @@ -1,6 +1,10 @@ module Gitlab # Helper methods to do with Kubernetes network services & resources module Kubernetes + def self.build_header_hash + Hash.new { |h, k| h[k] = [] } + end + # This is the comand that is run to start a terminal session. Kubernetes # expects `command=foo&command=bar, not `command[]=foo&command[]=bar` EXEC_COMMAND = URI.encode_www_form( @@ -37,13 +41,14 @@ module Gitlab selectors: { pod: pod_name, container: container["name"] }, url: container_exec_url(api_url, namespace, pod_name, container["name"]), subprotocols: ['channel.k8s.io'], - headers: Hash.new { |h, k| h[k] = [] }, + headers: ::Gitlab::Kubernetes.build_header_hash, created_at: created_at } end end def add_terminal_auth(terminal, token:, max_session_time:, ca_pem: nil) + terminal[:headers] ||= ::Gitlab::Kubernetes.build_header_hash terminal[:headers]['Authorization'] << "Bearer #{token}" terminal[:max_session_time] = max_session_time terminal[:ca_pem] = ca_pem if ca_pem.present? diff --git a/lib/gitlab/logger.rb b/lib/gitlab/logger.rb index a42e312b5d3..e58927a40b9 100644 --- a/lib/gitlab/logger.rb +++ b/lib/gitlab/logger.rb @@ -4,10 +4,18 @@ module Gitlab file_name_noext + '.log' end + def self.debug(message) + build.debug(message) + end + def self.error(message) build.error(message) end + def self.warn(message) + build.warn(message) + end + def self.info(message) build.info(message) end diff --git a/lib/gitlab/manifest_import/manifest.rb b/lib/gitlab/manifest_import/manifest.rb new file mode 100644 index 00000000000..4d6034fb956 --- /dev/null +++ b/lib/gitlab/manifest_import/manifest.rb @@ -0,0 +1,81 @@ +# Class to parse manifest file and build a list of repositories for import +# +# <manifest> +# <remote review="https://android-review.googlesource.com/" /> +# <project path="platform-common" name="platform" /> +# <project path="platform/art" name="platform/art" /> +# <project path="platform/device" name="platform/device" /> +# </manifest> +# +# 1. Project path must be uniq and can't be part of other project path. +# For example, you can't have projects with 'foo' and 'foo/bar' paths. +# 2. Remote must be present with review attribute so GitLab knows +# where to fetch source code +module Gitlab + module ManifestImport + class Manifest + attr_reader :parsed_xml, :errors + + def initialize(file) + @parsed_xml = Nokogiri::XML(file) { |config| config.strict } + @errors = [] + rescue Nokogiri::XML::SyntaxError + @errors = ['The uploaded file is not a valid XML file.'] + end + + def projects + raw_projects.each_with_index.map do |project, i| + { + id: i, + name: project['name'], + path: project['path'], + url: repository_url(project['name']) + } + end + end + + def valid? + return false if @errors.any? + + unless validate_remote + @errors << 'Make sure a <remote> tag is present and is valid.' + end + + unless validate_projects + @errors << 'Make sure every <project> tag has name and path attributes.' + end + + @errors.empty? + end + + private + + def validate_remote + remote.present? && URI.parse(remote).host + rescue URI::Error + false + end + + def validate_projects + raw_projects.all? do |project| + project['name'] && project['path'] + end + end + + def repository_url(name) + URI.join(remote, name).to_s + end + + def remote + return @remote if defined?(@remote) + + remote_tag = parsed_xml.css('manifest > remote').first + @remote = remote_tag['review'] if remote_tag + end + + def raw_projects + @raw_projects ||= parsed_xml.css('manifest > project') + end + end + end +end diff --git a/lib/gitlab/manifest_import/project_creator.rb b/lib/gitlab/manifest_import/project_creator.rb new file mode 100644 index 00000000000..b5967c93735 --- /dev/null +++ b/lib/gitlab/manifest_import/project_creator.rb @@ -0,0 +1,41 @@ +module Gitlab + module ManifestImport + class ProjectCreator + attr_reader :repository, :destination, :current_user + + def initialize(repository, destination, current_user) + @repository = repository + @destination = destination + @current_user = current_user + end + + def execute + group_full_path, _, project_path = repository[:path].rpartition('/') + group_full_path = File.join(destination.full_path, group_full_path) if destination + group = create_group_with_parents(group_full_path) + + params = { + import_url: repository[:url], + import_type: 'manifest', + namespace_id: group.id, + path: project_path, + name: project_path, + visibility_level: destination.visibility_level + } + + Projects::CreateService.new(current_user, params).execute + end + + private + + def create_group_with_parents(full_path) + params = { + group_path: full_path, + visibility_level: destination.visibility_level + } + + Groups::NestedCreateService.new(current_user, params).execute + end + end + end +end diff --git a/lib/gitlab/metrics/subscribers/active_record.rb b/lib/gitlab/metrics/subscribers/active_record.rb index 38f119cf06d..c205f348023 100644 --- a/lib/gitlab/metrics/subscribers/active_record.rb +++ b/lib/gitlab/metrics/subscribers/active_record.rb @@ -20,7 +20,7 @@ module Gitlab define_histogram :gitlab_sql_duration_seconds do docstring 'SQL time' base_labels Transaction::BASE_LABELS - buckets [0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0] + buckets [0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0] end def current_transaction diff --git a/lib/gitlab/popen.rb b/lib/gitlab/popen.rb index b9832a724c4..d0cb7c1a7cf 100644 --- a/lib/gitlab/popen.rb +++ b/lib/gitlab/popen.rb @@ -34,11 +34,16 @@ module Gitlab start = Time.now Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| + # stderr and stdout pipes can block if stderr/stdout aren't drained: https://bugs.ruby-lang.org/issues/9082 + # Mimic what Ruby does with capture3: https://github.com/ruby/ruby/blob/1ec544695fa02d714180ef9c34e755027b6a2103/lib/open3.rb#L257-L273 + out_reader = Thread.new { stdout.read } + err_reader = Thread.new { stderr.read } + yield(stdin) if block_given? stdin.close - cmd_stdout = stdout.read - cmd_stderr = stderr.read + cmd_stdout = out_reader.value + cmd_stderr = err_reader.value cmd_status = wait_thr.value end diff --git a/lib/gitlab/project_authorizations/with_nested_groups.rb b/lib/gitlab/project_authorizations/with_nested_groups.rb index 15b8beacf60..e3da1634fa5 100644 --- a/lib/gitlab/project_authorizations/with_nested_groups.rb +++ b/lib/gitlab/project_authorizations/with_nested_groups.rb @@ -24,7 +24,7 @@ module Gitlab user.projects.select_for_project_authorization, # The personal projects of the user. - user.personal_projects.select_as_master_for_project_authorization, + user.personal_projects.select_as_maintainer_for_project_authorization, # Projects that belong directly to any of the groups the user has # access to. diff --git a/lib/gitlab/project_authorizations/without_nested_groups.rb b/lib/gitlab/project_authorizations/without_nested_groups.rb index ad87540e6c2..7d0c00c7f36 100644 --- a/lib/gitlab/project_authorizations/without_nested_groups.rb +++ b/lib/gitlab/project_authorizations/without_nested_groups.rb @@ -15,7 +15,7 @@ module Gitlab user.projects.select_for_project_authorization, # Personal projects - user.personal_projects.select_as_master_for_project_authorization, + user.personal_projects.select_as_maintainer_for_project_authorization, # Projects of groups the user is a member of user.groups_projects.select_for_project_authorization, diff --git a/lib/gitlab/url_sanitizer.rb b/lib/gitlab/url_sanitizer.rb index 59331c827af..de8b6ec69ce 100644 --- a/lib/gitlab/url_sanitizer.rb +++ b/lib/gitlab/url_sanitizer.rb @@ -58,7 +58,7 @@ module Gitlab if raw_credentials.present? url.sub!("#{raw_credentials}@", '') - user, password = raw_credentials.split(':') + user, _, password = raw_credentials.partition(':') @credentials ||= { user: user.presence, password: password.presence } end diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index 55c899912f9..a9629a92a50 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -98,16 +98,12 @@ module Gitlab end def send_git_patch(repository, diff_refs) - params = if Gitlab::GitalyClient.feature_enabled?(:workhorse_send_git_patch, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) - { - 'GitalyServer' => gitaly_server_hash(repository), - 'RawPatchRequest' => Gitaly::RawPatchRequest.new( - gitaly_diff_or_patch_hash(repository, diff_refs) - ).to_json - } - else - workhorse_diff_or_patch_hash(repository, diff_refs) - end + params = { + 'GitalyServer' => gitaly_server_hash(repository), + 'RawPatchRequest' => Gitaly::RawPatchRequest.new( + gitaly_diff_or_patch_hash(repository, diff_refs) + ).to_json + } [ SEND_DATA_HEADER, @@ -220,14 +216,6 @@ module Gitlab } end - def workhorse_diff_or_patch_hash(repository, diff_refs) - { - 'RepoPath' => repository.path_to_repo, - 'ShaFrom' => diff_refs.base_sha, - 'ShaTo' => diff_refs.head_sha - } - end - def gitaly_diff_or_patch_hash(repository, diff_refs) { repository: repository.gitaly_repository, |