summaryrefslogtreecommitdiff
path: root/lib/gitlab/git/checksum.rb
blob: 3ef0f0a88542b284a704079b0d4c189909a21b10 (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
module Gitlab
  module Git
    class Checksum
      include Gitlab::Git::Popen

      EMPTY_REPOSITORY_CHECKSUM = '0000000000000000000000000000000000000000'.freeze

      Failure = Class.new(StandardError)

      attr_reader :path, :relative_path, :storage, :storage_path

      def initialize(storage, relative_path)
        @storage       = storage
        @storage_path  = Gitlab.config.repositories.storages[storage].legacy_disk_path
        @relative_path = "#{relative_path}.git"
        @path          = File.join(storage_path, @relative_path)
      end

      def calculate
        unless repository_exists?
          failure!(Gitlab::Git::Repository::NoRepository, 'No repository for such path')
        end

        calculate_checksum_by_shelling_out
      end

      private

      def repository_exists?
        raw_repository.exists?
      end

      def calculate_checksum_by_shelling_out
        args = %W(--git-dir=#{path} show-ref --heads --tags)
        output, status = run_git(args)

        if status&.zero?
          refs = output.split("\n")

          result = refs.inject(nil) do |checksum, ref|
            value = Digest::SHA1.hexdigest(ref).hex

            if checksum.nil?
              value
            else
              checksum ^ value
            end
          end

          result.to_s(16)
        else
          # Empty repositories return with a non-zero status and an empty output.
          if output&.empty?
            EMPTY_REPOSITORY_CHECKSUM
          else
            failure!(Gitlab::Git::Checksum::Failure, output)
          end
        end
      end

      def failure!(klass, message)
        Gitlab::GitLogger.error("'git show-ref --heads --tags' in #{path}: #{message}")

        raise klass.new("Could not calculate the checksum for #{path}: #{message}")
      end

      def circuit_breaker
        @circuit_breaker ||= Gitlab::Git::Storage::CircuitBreaker.for_storage(storage)
      end

      def raw_repository
        Gitlab::Git::Repository.new(storage, relative_path, nil)
      end

      def run_git(args)
        circuit_breaker.perform do
          popen([Gitlab.config.git.bin_path, *args], path)
        end
      end
    end
  end
end