repository_mirroring.rb 3.16 KB
Newer Older
1 2 3
module Gitlab
  module Git
    module RepositoryMirroring
Douwe Maan's avatar
Douwe Maan committed
4 5 6
      REFMAPS = {
        # With `:all_refs`, the repository is equivalent to the result of `git clone --mirror`
        all_refs: '+refs/*:refs/*',
7 8 9
        heads: '+refs/heads/*:refs/heads/*',
        tags: '+refs/tags/*:refs/tags/*'
      }.freeze
10 11 12

      RemoteError = Class.new(StandardError)

Douwe Maan's avatar
Douwe Maan committed
13 14
      def set_remote_as_mirror(remote_name, refmap: :all_refs)
        set_remote_refmap(remote_name, refmap)
15 16 17 18 19 20 21 22 23 24 25 26 27 28

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

      def remote_tags(remote)
        # 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).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}. Output: #{output}"
29
            break []
30 31 32 33 34 35 36 37
          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)
38 39 40 41 42
          Gitlab::Git::Tag.new(self, {
            name: name,
            target: target,
            target_commit: target_commit
          })
43 44 45 46 47 48 49
        end.compact
      end

      def remote_branches(remote_name)
        branches = []

        rugged.references.each("refs/remotes/#{remote_name}/*").map do |ref|
50
          name = ref.name.sub(%r{\Arefs/remotes/#{remote_name}/}, '')
51 52 53 54 55 56 57 58 59 60 61 62 63 64

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

        branches
      end

      private

65 66 67 68 69 70 71 72 73 74 75 76 77 78
      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

79 80
      def list_remote_tags(remote)
        tag_list, exit_code, error = nil
81
        cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{path} ls-remote --tags #{remote})
82 83 84 85 86 87 88 89 90

        Open3.popen3(*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?

91
        tag_list.split("\n")
92 93 94 95
      end
    end
  end
end