Welcome to mirror list, hosted at ThFree Co, Russian Federation.

update_repository_storage_service.rb « projects « services « app - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 2e5de9411d1af126bb9d83ab8dae0965d9873dd4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# frozen_string_literal: true

module Projects
  class UpdateRepositoryStorageService < BaseService
    include Gitlab::ShellAdapter

    Error = Class.new(StandardError)
    SameFilesystemError = Class.new(Error)

    def initialize(project)
      @project = project
    end

    def execute(new_repository_storage_key)
      raise SameFilesystemError if same_filesystem?(project.repository.storage, new_repository_storage_key)

      mirror_repositories(new_repository_storage_key)

      mark_old_paths_for_archive

      project.update(repository_storage: new_repository_storage_key, repository_read_only: false)
      project.leave_pool_repository
      project.track_project_repository

      enqueue_housekeeping

      success

    rescue Error, ArgumentError, Gitlab::Git::BaseError => e
      project.update(repository_read_only: false)

      Gitlab::ErrorTracking.track_exception(e, project_path: project.full_path)

      error(s_("UpdateRepositoryStorage|Error moving repository storage for %{project_full_path} - %{message}") % { project_full_path: project.full_path, message: e.message })
    end

    private

    def same_filesystem?(old_storage, new_storage)
      Gitlab::GitalyClient.filesystem_id(old_storage) == Gitlab::GitalyClient.filesystem_id(new_storage)
    end

    def mirror_repositories(new_repository_storage_key)
      mirror_repository(new_repository_storage_key)

      if project.wiki.repository_exists?
        mirror_repository(new_repository_storage_key, type: Gitlab::GlRepository::WIKI)
      end
    end

    def mirror_repository(new_storage_key, type: Gitlab::GlRepository::PROJECT)
      unless wait_for_pushes(type)
        raise Error, s_('UpdateRepositoryStorage|Timeout waiting for %{type} repository pushes') % { type: type.name }
      end

      repository = type.repository_for(project)
      full_path = repository.full_path
      raw_repository = repository.raw
      checksum = repository.checksum

      # Initialize a git repository on the target path
      new_repository = Gitlab::Git::Repository.new(
        new_storage_key,
        raw_repository.relative_path,
        raw_repository.gl_repository,
        full_path
      )

      new_repository.create_repository

      new_repository.replicate(raw_repository)
      new_checksum = new_repository.checksum

      if checksum != new_checksum
        raise Error, s_('UpdateRepositoryStorage|Failed to verify %{type} repository checksum from %{old} to %{new}') % { type: type.name, old: checksum, new: new_checksum }
      end
    end

    def mark_old_paths_for_archive
      old_repository_storage = project.repository_storage
      new_project_path = moved_path(project.disk_path)

      # Notice that the block passed to `run_after_commit` will run with `project`
      # as its context
      project.run_after_commit do
        GitlabShellWorker.perform_async(:mv_repository,
                                        old_repository_storage,
                                        disk_path,
                                        new_project_path)

        if wiki.repository_exists?
          GitlabShellWorker.perform_async(:mv_repository,
                                          old_repository_storage,
                                          wiki.disk_path,
                                          "#{new_project_path}.wiki")
        end
      end
    end

    def moved_path(path)
      "#{path}+#{project.id}+moved+#{Time.now.to_i}"
    end

    # The underlying FetchInternalRemote call uses a `git fetch` to move data
    # to the new repository, which leaves it in a less-well-packed state,
    # lacking bitmaps and commit graphs. Housekeeping will boost performance
    # significantly.
    def enqueue_housekeeping
      return unless Gitlab::CurrentSettings.housekeeping_enabled?
      return unless Feature.enabled?(:repack_after_shard_migration, project)

      Projects::HousekeepingService.new(project, :gc).execute
    rescue Projects::HousekeepingService::LeaseTaken
      # No action required
    end

    def wait_for_pushes(type)
      reference_counter = project.reference_counter(type: type)

      # Try for 30 seconds, polling every 10
      3.times do
        return true if reference_counter.value == 0

        sleep 10
      end

      false
    end
  end
end

Projects::UpdateRepositoryStorageService.prepend_if_ee('EE::Projects::UpdateRepositoryStorageService')