Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
patch.rb 3.15 KiB
require 'resource'
require 'erb'

module Patch
  def self.create(strip, src, &block)
    case strip
    when :DATA
      DATAPatch.new(:p1)
    when String
      StringPatch.new(:p1, strip)
    when Symbol
      case src
      when :DATA
        DATAPatch.new(strip)
      when String
        StringPatch.new(strip, src)
      else
        ExternalPatch.new(strip, &block)
      end
    else
      raise ArgumentError, "unexpected value #{strip.inspect} for strip"
    end
  end

  def self.normalize_legacy_patches(list)
    patches = []

    case list
    when Hash
      list
    when Array, String, :DATA
      { :p1 => list }
    else
      {}
    end.each_pair do |strip, urls|
      Array(urls).each do |url|
        case url
        when :DATA
          patch = DATAPatch.new(strip)
        else
          patch = LegacyPatch.new(strip, url)
        end
        patches << patch
      end
    end

    patches
  end
end

class EmbeddedPatch
  attr_writer :owner
  attr_reader :strip

  def initialize(strip)
    @strip = strip
  end

  def external?
    false
  end

  def contents
  end

  def apply
    data = contents.gsub("HOMEBREW_PREFIX", HOMEBREW_PREFIX)
    cmd, args = "/usr/bin/patch", %W[-g 0 -f -#{strip}]
    IO.popen("#{cmd} #{args.join(" ")}", "w") { |p| p.write(data) }
    raise ErrorDuringExecution.new(cmd, args) unless $?.success?
  end

  def inspect
    "#<#{self.class.name}: #{strip.inspect}>"
  end
end

class DATAPatch < EmbeddedPatch
  attr_accessor :path

  def initialize(strip)
    super
    @path = nil
  end

  def contents
    data = ""
    path.open("rb") do |f|
      begin
        line = f.gets
      end until line.nil? || /^__END__$/ === line
      data << line while line = f.gets
    end
    data
  end
end

class StringPatch < EmbeddedPatch
  def initialize(strip, str)
    super(strip)
    @str = str
  end

  def contents
    @str
  end
end

class ExternalPatch
  attr_reader :resource, :strip

  def initialize(strip, &block)
    @strip    = strip
    @resource = Resource.new("patch", &block)
  end

  def external?
    true
  end

  def owner= owner
    resource.owner   = owner
    resource.version = resource.checksum || ERB::Util.url_encode(resource.url)
  end

  def apply
    dir = Pathname.pwd
    resource.unpack do
      # Assumption: the only file in the staging directory is the patch
      patchfile = Pathname.pwd.children.first
      dir.cd { safe_system "/usr/bin/patch", "-g", "0", "-f", "-#{strip}", "-i", patchfile }
    end
  end

  def url
    resource.url
  end

  def fetch
    resource.fetch
  end

  def verify_download_integrity(fn)
    resource.verify_download_integrity(fn)
  end

  def cached_download
    resource.cached_download
  end

  def clear_cache
    resource.clear_cache
  end

  def inspect
    "#<#{self.class.name}: #{strip.inspect} #{url.inspect}>"
  end
end

# Legacy patches have no checksum and are not cached
class LegacyPatch < ExternalPatch
  def initialize(strip, url)
    super(strip)
    resource.url(url)
    resource.download_strategy = CurlDownloadStrategy
  end

  def fetch
    clear_cache
    super
  end

  def verify_download_integrity(fn)
    # no-op
  end

  def apply
    super
  ensure
    clear_cache
  end
end