summaryrefslogtreecommitdiff
path: root/lib/backup/repository.rb
blob: c8a5377bfa07ceb2dbf19ade7a681759a9e464c1 (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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# frozen_string_literal: true

require 'yaml'

module Backup
  class Repository
    attr_reader :progress

    def initialize(progress)
      @progress = progress
    end

    def dump
      prepare

      Project.find_each(batch_size: 1000) do |project|
        progress.print " * #{display_repo_path(project)} ... "

        if project.hashed_storage?(:repository)
          FileUtils.mkdir_p(File.dirname(File.join(backup_repos_path, project.disk_path)))
        else
          FileUtils.mkdir_p(File.join(backup_repos_path, project.namespace.full_path)) if project.namespace
        end

        if !empty_repo?(project)
          backup_project(project)
          progress.puts "[DONE]".color(:green)
        else
          progress.puts "[SKIPPED]".color(:cyan)
        end

        wiki = ProjectWiki.new(project)

        if !empty_repo?(wiki)
          backup_project(wiki)
          progress.puts "[DONE] Wiki".color(:green)
        else
          progress.puts "[SKIPPED] Wiki".color(:cyan)
        end
      end
    end

    def prepare_directories
      Gitlab.config.repositories.storages.each do |name, _repository_storage|
        Gitlab::GitalyClient::StorageService.new(name).delete_all_repositories
      end
    end

    def backup_project(project)
      path_to_project_bundle = path_to_bundle(project)
      Gitlab::GitalyClient::RepositoryService.new(project.repository)
        .create_bundle(path_to_project_bundle)

      backup_custom_hooks(project)
    rescue => e
      progress_warn(project, e, 'Failed to backup repo')
    end

    def backup_custom_hooks(project)
      FileUtils.mkdir_p(project_backup_path(project))

      custom_hooks_path = custom_hooks_tar(project)
      Gitlab::GitalyClient::RepositoryService.new(project.repository)
        .backup_custom_hooks(custom_hooks_path)
    end

    def restore_custom_hooks(project)
      return unless Dir.exist?(project_backup_path(project))
      return if Dir.glob("#{project_backup_path(project)}/custom_hooks*").none?

      custom_hooks_path = custom_hooks_tar(project)
      Gitlab::GitalyClient::RepositoryService.new(project.repository)
        .restore_custom_hooks(custom_hooks_path)
    end

    def restore
      prepare_directories
      gitlab_shell = Gitlab::Shell.new

      Project.find_each(batch_size: 1000) do |project|
        progress.print " * #{project.full_path} ... "
        path_to_project_bundle = path_to_bundle(project)
        project.ensure_storage_path_exists

        restore_repo_success = nil
        if File.exist?(path_to_project_bundle)
          begin
            project.repository.create_from_bundle(path_to_project_bundle)
            restore_custom_hooks(project)
            restore_repo_success = true
          rescue => e
            restore_repo_success = false
            progress.puts "Error: #{e}".color(:red)
          end
        else
          restore_repo_success = gitlab_shell.create_repository(project.repository_storage, project.disk_path)
        end

        if restore_repo_success
          progress.puts "[DONE]".color(:green)
        else
          progress.puts "[Failed] restoring #{project.full_path} repository".color(:red)
        end

        wiki = ProjectWiki.new(project)
        path_to_wiki_bundle = path_to_bundle(wiki)

        if File.exist?(path_to_wiki_bundle)
          progress.print " * #{wiki.full_path} ... "
          begin
            wiki.repository.create_from_bundle(path_to_wiki_bundle)
            restore_custom_hooks(wiki)

            progress.puts "[DONE]".color(:green)
          rescue => e
            progress.puts "[Failed] restoring #{wiki.full_path} wiki".color(:red)
            progress.puts "Error #{e}".color(:red)
          end
        end
      end
    end

    protected

    def path_to_bundle(project)
      File.join(backup_repos_path, project.disk_path + '.bundle')
    end

    def project_backup_path(project)
      File.join(backup_repos_path, project.disk_path)
    end

    def custom_hooks_tar(project)
      File.join(project_backup_path(project), "custom_hooks.tar")
    end

    def backup_repos_path
      File.join(Gitlab.config.backup.path, 'repositories')
    end

    def prepare
      FileUtils.rm_rf(backup_repos_path)
      FileUtils.mkdir_p(Gitlab.config.backup.path)
      FileUtils.mkdir(backup_repos_path, mode: 0700)
    end

    private

    def progress_warn(project, cmd, output)
      progress.puts "[WARNING] Executing #{cmd}".color(:orange)
      progress.puts "Ignoring error on #{display_repo_path(project)} - #{output}".color(:orange)
    end

    def empty_repo?(project_or_wiki)
      project_or_wiki.repository.expire_emptiness_caches
      project_or_wiki.repository.empty?
    end

    def display_repo_path(project)
      project.hashed_storage?(:repository) ? "#{project.full_path} (#{project.disk_path})" : project.full_path
    end
  end
end