File: git.rb

package info (click to toggle)
ruby-librarian 1.1.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 624 kB
  • sloc: ruby: 6,109; makefile: 11
file content (189 lines) | stat: -rw-r--r-- 4,972 bytes parent folder | download | duplicates (5)
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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
require 'fileutils'
require 'pathname'
require 'digest'

require 'librarian/error'
require 'librarian/source/basic_api'
require 'librarian/source/git/repository'
require 'librarian/source/local'

module Librarian
  module Source
    class Git
      include BasicApi
      include Local

      lock_name 'GIT'
      spec_options [:ref, :branch, :tag, :commit, :path]

      DEFAULTS = {
        :ref => 'master'
      }

      attr_accessor :environment
      private :environment=

      attr_accessor :uri, :ref, :branch, :tag, :commit, :sha, :path
      private :uri=, :ref=, :branch=, :tag=, :commit=, :sha=, :path=

      def initialize(environment, uri, options)
        validate_options(uri, options)

        self.environment = environment
        self.uri = uri
        self.ref = options[:ref] || options[:branch] ||
                   options[:tag] || options[:commit] ||
                   DEFAULTS[:ref]
        self.sha = options[:sha]
        self.path = options[:path]

        @repository = nil
        @repository_cache_path = nil

        ref.kind_of?(String) or raise TypeError, "ref must be a String"
      end

      def to_s
        path ? "#{uri}##{ref}(#{path})" : "#{uri}##{ref}"
      end

      def ==(other)
        other &&
        self.class  == other.class  &&
        self.uri    == other.uri    &&
        self.ref    == other.ref    &&
        self.path   == other.path   &&
        (self.sha.nil? || other.sha.nil? || self.sha == other.sha)
      end

      alias :eql? :==

      def hash
        self.to_s.hash
      end

      def to_spec_args
        options = {}
        options.merge!(:ref => ref) if ref != DEFAULTS[:ref]
        options.merge!(:path => path) if path
        [uri, options]
      end

      def to_lock_options
        options = {:remote => uri, :ref => ref, :sha => sha}
        options.merge!(:path => path) if path
        options
      end

      def pinned?
        !!sha
      end

      def unpin!
        @sha = nil
      end

      def cache!
        repository_cached? and return or repository_cached!

        unless repository.git?
          repository.path.rmtree if repository.path.exist?
          repository.path.mkpath
          repository.clone!(uri)
          raise Error, "failed to clone #{uri}" unless repository.git?
        end

        # Probably unnecessary: nobody should be writing to our cache but us.
        # Just a precaution.
        repository_clean_once!

        unless sha
          repository_update_once!
          self.sha = fetch_sha_memo
        end

        unless repository.checked_out?(sha)
          repository_update_once! unless repository.has_commit?(sha)
          repository.checkout!(sha)
          # Probably unnecessary: if git fails to checkout, it should exit
          # nonzero, and we should expect Librarian::Posix::CommandFailure.
          raise Error, "failed to checkout #{sha}" unless repository.checked_out?(sha)
        end
      end

      # For tests
      def git_ops_count
        repository.git_ops_history.size
      end

    private

      attr_accessor :repository_cached
      alias repository_cached? repository_cached

      def repository_cached!
        self.repository_cached = true
      end

      def repository_cache_path
        @repository_cache_path ||= begin
          environment.cache_path + "source/git" + cache_key
        end
      end

      def repository
        @repository ||= begin
          Repository.new(environment, repository_cache_path)
        end
      end

      def filesystem_path
        @filesystem_path ||= path ? repository.path.join(path) : repository.path
      end

      def repository_clean_once!
        remote = repository.default_remote
        runtime_cache.once ['repository-clean', uri, ref].to_s do
          repository.reset_hard!
          repository.clean!
        end
      end

      def repository_update_once!
        remote = repository.default_remote
        runtime_cache.once ['repository-update', uri, remote, ref].to_s do
          repository.fetch! remote
          repository.fetch! remote, :tags => true
        end
      end

      def fetch_sha_memo
        remote = repository.default_remote
        runtime_cache.memo ['fetch-sha', uri, remote, ref].to_s do
          repository.hash_from(remote, ref)
        end
      end

      def cache_key
        @cache_key ||= begin
          uri_part = uri
          ref_part = "##{ref}"
          key_source = [uri_part, ref_part].join
          Digest::MD5.hexdigest(key_source)[0..15]
        end
      end

      def runtime_cache
        @runtime_cache ||= environment.runtime_cache.keyspace(self.class.name)
      end

      def validate_options(uri, options)
        found = 0; [:ref, :branch, :tag, :commit].each do |opt|
          found += 1 if options.has_key?(opt)
          found > 1 and raise Error,
            "at #{uri}, use only one of ref, branch, tag, or commit"
        end
      end
    end
  end
end