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

repository_mirroring.rb « git « gitlab « lib « ruby - gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: a1d75251ecc2a89d39c3942f730e8c8fac1690a0 (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
require_relative 'push_results'

module Gitlab
  module Git
    module RepositoryMirroring
      GITLAB_PROJECTS_TIMEOUT = Gitlab.config.gitlab_shell.git_timeout

      RemoteError = Class.new(StandardError)

      REFMAPS = {
        # With `:all_refs`, the repository is equivalent to the result of `git clone --mirror`
        all_refs: '+refs/*:refs/*',
        heads: '+refs/heads/*:refs/heads/*',
        tags: '+refs/tags/*:refs/tags/*'
      }.freeze

      def remote_branches(remote_name)
        branches = []

        rugged.references.each("refs/remotes/#{remote_name}/*").map do |ref|
          name = ref.name.sub(%r{\Arefs/remotes/#{remote_name}/}, '')

          begin
            target_commit = Gitlab::Git::Commit.find(self, ref.target.oid)
            branches << Gitlab::Git::Branch.new(self, name, ref.target, target_commit)
          rescue Rugged::ReferenceError
            # Omit invalid branch
          end
        end

        branches
      end

      def push_remote_branches(remote_name, branch_names, forced: true, env: {})
        success = @gitlab_projects.push_branches(
          remote_name,
          GITLAB_PROJECTS_TIMEOUT,
          forced,
          branch_names,
          env: env
        )

        begin
          success || gitlab_projects_error
        rescue CommandError => ex
          results = PushResults.new(ex.message)

          accepted_branches = results.accepted_branches
          rejected_branches = results.rejected_branches

          @gitlab_projects.logger.info(
            "Failed to push to remote #{remote_name}. " \
            "Accepted: #{accepted_branches.join(', ')} / " \
            "Rejected: #{rejected_branches.join(', ')}"
          )

          raise ex unless accepted_branches.any? &&
            @gitlab_projects.features.enabled?(:push_mirror_retry)

          @gitlab_projects.logger.info("Retrying failed push to #{remote_name} with limited branches: #{accepted_branches}")

          # Try one more push without branches that failed
          success = @gitlab_projects.push_branches(
            remote_name,
            GITLAB_PROJECTS_TIMEOUT,
            forced,
            accepted_branches,
            env: env
          )

          success || gitlab_projects_error
        end
      end

      def delete_remote_branches(remote_name, branch_names, env: {})
        success = @gitlab_projects.delete_remote_branches(remote_name, branch_names, env: env)

        success || gitlab_projects_error
      end

      def set_remote_as_mirror(remote_name, refmap: :all_refs)
        set_remote_refmap(remote_name, refmap)

        rugged.config["remote.#{remote_name}.mirror"] = true
        rugged.config["remote.#{remote_name}.prune"] = true
      end

      def remote_tags(remote, env: {})
        # Each line has this format: "dc872e9fa6963f8f03da6c8f6f264d0845d6b092\trefs/tags/v1.10.0\n"
        # We want to convert it to: [{ 'v1.10.0' => 'dc872e9fa6963f8f03da6c8f6f264d0845d6b092' }, ...]
        list_remote_tags(remote, env: env).map do |line|
          target, path = line.strip.split("\t")

          # When the remote repo does not have tags.
          if target.nil? || path.nil?
            Rails.logger.info "Empty or invalid list of tags for remote: #{remote}"
            break []
          end

          name = path.split('/', 3).last
          # We're only interested in tag references
          # See: http://stackoverflow.com/questions/15472107/when-listing-git-ls-remote-why-theres-after-the-tag-name
          next if name =~ /\^\{\}\Z/

          target_commit = Gitlab::Git::Commit.find(self, target)
          Gitlab::Git::Tag.new(self,
                               name: name,
                               target: target,
                               target_commit: target_commit)
        end.compact
      end

      private

      def set_remote_refmap(remote_name, refmap)
        Array(refmap).each_with_index do |refspec, i|
          refspec = REFMAPS[refspec] || refspec

          # We need multiple `fetch` entries, but Rugged only allows replacing a config, not adding to it.
          # To make sure we start from scratch, we set the first using rugged, and use `git` for any others
          if i == 0
            rugged.config["remote.#{remote_name}.fetch"] = refspec
          else
            run_git(%W[config --add remote.#{remote_name}.fetch #{refspec}])
          end
        end
      end

      def list_remote_tags(remote, env:)
        tag_list, exit_code, error = nil
        cmd = %W[#{Gitlab.config.git.bin_path} --git-dir=#{path} ls-remote --tags #{remote}]

        Open3.popen3(env, *cmd) do |_stdin, stdout, stderr, wait_thr|
          tag_list  = stdout.read
          error     = stderr.read
          exit_code = wait_thr.value.exitstatus
        end

        raise RemoteError, error unless exit_code.zero?

        tag_list.split("\n")
      end
    end
  end
end