aboutsummaryrefslogtreecommitdiffstats
path: root/Library/Homebrew/formula_versions.rb
blob: c10c69e674f7c31e92b4cefde5316e4b12d17ef4 (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
require "formula"

class FormulaVersions
  IGNORED_EXCEPTIONS = [
    ArgumentError, NameError, SyntaxError, TypeError,
    FormulaSpecificationError, FormulaValidationError,
    ErrorDuringExecution, LoadError, MethodDeprecatedError
  ].freeze

  MAX_VERSIONS_DEPTH = 2

  attr_reader :name, :path, :repository, :entry_name

  def initialize(formula)
    @name = formula.name
    @path = formula.path
    @repository = formula.tap.path
    @entry_name = @path.relative_path_from(repository).to_s
    @current_formula = formula
    @formula_at_revision = {}
  end

  def rev_list(branch)
    repository.cd do
      Utils.popen_read("git", "rev-list", "--abbrev-commit", "--remove-empty", branch, "--", entry_name) do |io|
        yield io.readline.chomp until io.eof?
      end
    end
  end

  def file_contents_at_revision(rev)
    repository.cd { Utils.popen_read("git", "cat-file", "blob", "#{rev}:#{entry_name}") }
  end

  def formula_at_revision(rev)
    Homebrew.raise_deprecation_exceptions = true

    yield @formula_at_revision[rev] ||= begin
      contents = file_contents_at_revision(rev)
      nostdout { Formulary.from_contents(name, path, contents) }
    end
  rescue *IGNORED_EXCEPTIONS => e
    # We rescue these so that we can skip bad versions and
    # continue walking the history
    ohai "#{e} in #{name} at revision #{rev}", e.backtrace if ARGV.debug?
  rescue FormulaUnavailableError
    return
  ensure
    Homebrew.raise_deprecation_exceptions = false
  end

  def bottle_version_map(branch)
    map = Hash.new { |h, k| h[k] = [] }

    versions_seen = 0
    rev_list(branch) do |rev|
      formula_at_revision(rev) do |f|
        bottle = f.bottle_specification
        map[f.pkg_version] << bottle.rebuild unless bottle.checksums.empty?
        versions_seen = (map.keys + [f.pkg_version]).uniq.length
      end
      return map if versions_seen > MAX_VERSIONS_DEPTH
    end
    map
  end

  def previous_version_and_checksum(branch)
    map = {}

    rev_list(branch) do |rev|
      formula_at_revision(rev) do |f|
        [:stable, :devel].each do |spec_sym|
          next unless spec = f.send(spec_sym)
          map[spec_sym] ||= { version: spec.version, checksum: spec.checksum }
        end
      end

      break if map[:stable] || map[:devel]
    end

    map[:stable] ||= {}
    map[:devel] ||= {}

    map
  end

  def version_attributes_map(attributes, branch)
    attributes_map = {}
    return attributes_map if attributes.empty?

    attributes.each do |attribute|
      attributes_map[attribute] ||= {
        stable: {},
        devel: {},
      }
    end

    stable_versions_seen = 0
    rev_list(branch) do |rev|
      formula_at_revision(rev) do |f|
        attributes.each do |attribute|
          map = attributes_map[attribute]
          set_attribute_map(map, f, attribute)

          stable_keys_length = (map[:stable].keys + [f.version]).uniq.length
          stable_versions_seen = [stable_versions_seen, stable_keys_length].max
        end
      end
      break if stable_versions_seen > MAX_VERSIONS_DEPTH
    end

    attributes_map
  end

  private

  def set_attribute_map(map, f, attribute)
    if f.stable
      map[:stable][f.stable.version] ||= []
      map[:stable][f.stable.version] << f.send(attribute)
    end
    return unless f.devel
    map[:devel][f.devel.version] ||= []
    map[:devel][f.devel.version] << f.send(attribute)
  end
end