| 
									
										
										
										
											2019-04-19 15:38:03 +09:00
										 |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-17 18:25:08 +09:00
										 |  |  | require "cli/parser" | 
					
						
							| 
									
										
										
										
											2018-07-29 20:51:57 -04:00
										 |  |  | require "utils/git" | 
					
						
							|  |  |  | require "formulary" | 
					
						
							|  |  |  | require "tap" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-25 14:52:30 -04:00
										 |  |  | def with_monkey_patch | 
					
						
							|  |  |  |   BottleSpecification.class_eval do | 
					
						
							| 
									
										
										
										
											2019-02-19 13:11:32 +00:00
										 |  |  |     alias_method :old_method_missing, :method_missing if method_defined?(:method_missing) | 
					
						
							| 
									
										
										
										
											2018-08-25 14:52:30 -04:00
										 |  |  |     define_method(:method_missing) { |*| } | 
					
						
							|  |  |  |   end | 
					
						
							| 
									
										
										
										
											2018-08-24 11:38:14 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-25 14:52:30 -04:00
										 |  |  |   Module.class_eval do | 
					
						
							| 
									
										
										
										
											2019-02-19 13:11:32 +00:00
										 |  |  |     alias_method :old_method_missing, :method_missing if method_defined?(:method_missing) | 
					
						
							| 
									
										
										
										
											2018-08-25 14:52:30 -04:00
										 |  |  |     define_method(:method_missing) { |*| } | 
					
						
							| 
									
										
										
										
											2018-08-24 11:38:14 -04:00
										 |  |  |   end | 
					
						
							| 
									
										
										
										
											2018-08-02 00:21:09 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-25 14:52:30 -04:00
										 |  |  |   Resource.class_eval do | 
					
						
							| 
									
										
										
										
											2019-02-19 13:11:32 +00:00
										 |  |  |     alias_method :old_method_missing, :method_missing if method_defined?(:method_missing) | 
					
						
							| 
									
										
										
										
											2018-08-25 14:52:30 -04:00
										 |  |  |     define_method(:method_missing) { |*| } | 
					
						
							|  |  |  |   end | 
					
						
							| 
									
										
										
										
											2018-08-24 11:38:14 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-25 19:52:22 -04:00
										 |  |  |   DependencyCollector.class_eval do | 
					
						
							| 
									
										
										
										
											2019-02-19 13:11:32 +00:00
										 |  |  |     alias_method :old_parse_symbol_spec, :parse_symbol_spec if method_defined?(:parse_symbol_spec) | 
					
						
							| 
									
										
										
										
											2018-08-25 19:52:22 -04:00
										 |  |  |     define_method(:parse_symbol_spec) { |*| } | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-26 02:22:09 -04:00
										 |  |  |   if defined?(DependencyCollector::Compat) | 
					
						
							|  |  |  |     DependencyCollector::Compat.class_eval do | 
					
						
							| 
									
										
										
										
											2019-02-19 13:11:32 +00:00
										 |  |  |       alias_method :old_parse_string_spec, :parse_string_spec if method_defined?(:parse_string_spec) | 
					
						
							| 
									
										
										
										
											2018-08-26 02:22:09 -04:00
										 |  |  |       define_method(:parse_string_spec) { |*| } | 
					
						
							| 
									
										
										
										
											2018-08-25 19:52:22 -04:00
										 |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-25 14:52:30 -04:00
										 |  |  |   yield | 
					
						
							|  |  |  | ensure | 
					
						
							|  |  |  |   BottleSpecification.class_eval do | 
					
						
							|  |  |  |     if method_defined?(:old_method_missing) | 
					
						
							|  |  |  |       alias_method :method_missing, :old_method_missing | 
					
						
							|  |  |  |       undef :old_method_missing | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2018-08-24 11:38:14 -04:00
										 |  |  |   end | 
					
						
							| 
									
										
										
										
											2018-08-02 00:21:09 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-25 14:52:30 -04:00
										 |  |  |   Module.class_eval do | 
					
						
							|  |  |  |     if method_defined?(:old_method_missing) | 
					
						
							|  |  |  |       alias_method :method_missing, :old_method_missing | 
					
						
							|  |  |  |       undef :old_method_missing | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							| 
									
										
										
										
											2018-08-25 13:33:25 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-25 14:52:30 -04:00
										 |  |  |   Resource.class_eval do | 
					
						
							|  |  |  |     if method_defined?(:old_method_missing) | 
					
						
							|  |  |  |       alias_method :method_missing, :old_method_missing | 
					
						
							|  |  |  |       undef :old_method_missing | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2018-08-25 13:33:25 -04:00
										 |  |  |   end | 
					
						
							| 
									
										
										
										
											2018-08-02 00:21:09 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-25 19:52:22 -04:00
										 |  |  |   DependencyCollector.class_eval do | 
					
						
							|  |  |  |     if method_defined?(:old_parse_symbol_spec) | 
					
						
							|  |  |  |       alias_method :parse_symbol_spec, :old_parse_symbol_spec | 
					
						
							|  |  |  |       undef :old_parse_symbol_spec | 
					
						
							| 
									
										
										
										
											2018-08-02 00:21:09 -04:00
										 |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							| 
									
										
										
										
											2018-08-16 12:52:54 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-26 02:22:09 -04:00
										 |  |  |   if defined?(DependencyCollector::Compat) | 
					
						
							|  |  |  |     DependencyCollector::Compat.class_eval do | 
					
						
							|  |  |  |       if method_defined?(:old_parse_string_spec) | 
					
						
							|  |  |  |         alias_method :parse_string_spec, :old_parse_string_spec | 
					
						
							|  |  |  |         undef :old_parse_string_spec | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2018-08-25 19:52:22 -04:00
										 |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							| 
									
										
										
										
											2018-08-02 00:21:09 -04:00
										 |  |  | end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-29 20:51:57 -04:00
										 |  |  | module Homebrew | 
					
						
							|  |  |  |   module_function | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-03 20:16:05 +05:30
										 |  |  |   def extract_args | 
					
						
							|  |  |  |     Homebrew::CLI::Parser.new do | 
					
						
							|  |  |  |       usage_banner <<~EOS | 
					
						
							| 
									
										
										
										
											2018-10-15 15:06:33 -04:00
										 |  |  |         `extract` [<options>] <formula> <tap> | 
					
						
							| 
									
										
										
										
											2018-10-03 20:16:05 +05:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-08 22:49:03 -04:00
										 |  |  |         Look through repository history to find the most recent version of <formula> and | 
					
						
							|  |  |  |         create a copy in <tap>`/Formula/`<formula>`@`<version>`.rb`. If the tap is not | 
					
						
							| 
									
										
										
										
											2019-03-19 14:07:50 +08:00
										 |  |  |         installed yet, attempt to install/clone the tap before continuing. To extract | 
					
						
							| 
									
										
										
										
											2019-08-20 00:04:14 -04:00
										 |  |  |         a formula from a tap that is not `homebrew/core` use its fully-qualified form of | 
					
						
							|  |  |  |         <user>`/`<repo>`/`<formula>. | 
					
						
							| 
									
										
										
										
											2018-10-03 20:16:05 +05:30
										 |  |  |       EOS | 
					
						
							| 
									
										
										
										
											2019-08-06 13:23:19 -04:00
										 |  |  |       flag   "--version=", | 
					
						
							| 
									
										
										
										
											2019-08-20 00:04:14 -04:00
										 |  |  |              description: "Extract the specified <version> of <formula> instead of the most recent." | 
					
						
							| 
									
										
										
										
											2018-07-30 18:41:45 -04:00
										 |  |  |       switch :force | 
					
						
							| 
									
										
										
										
											2018-10-08 22:49:03 -04:00
										 |  |  |       switch :debug | 
					
						
							| 
									
										
										
										
											2020-03-04 17:28:24 +00:00
										 |  |  |       named 2
 | 
					
						
							| 
									
										
										
										
											2018-07-29 21:02:36 -04:00
										 |  |  |     end | 
					
						
							| 
									
										
										
										
											2018-10-03 20:16:05 +05:30
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def extract | 
					
						
							|  |  |  |     extract_args.parse | 
					
						
							| 
									
										
										
										
											2018-07-29 20:51:57 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-04 17:28:24 +00:00
										 |  |  |     if args.named.first !~ HOMEBREW_TAP_FORMULA_REGEX | 
					
						
							|  |  |  |       name = args.named.first.downcase | 
					
						
							| 
									
										
										
										
											2019-03-17 18:39:47 +08:00
										 |  |  |       source_tap = CoreTap.instance | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       name = Regexp.last_match(3).downcase | 
					
						
							|  |  |  |       source_tap = Tap.fetch(Regexp.last_match(1), Regexp.last_match(2)) | 
					
						
							|  |  |  |       raise TapFormulaUnavailableError.new(source_tap, name) unless source_tap.installed? | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-04 17:28:24 +00:00
										 |  |  |     destination_tap = Tap.fetch(args.named.second) | 
					
						
							| 
									
										
										
										
											2020-04-05 15:44:50 +01:00
										 |  |  |     unless Homebrew::EnvConfig.developer? | 
					
						
							| 
									
										
										
										
											2020-02-27 10:36:10 +00:00
										 |  |  |       odie "Cannot extract formula to homebrew/core!" if destination_tap.core_tap? | 
					
						
							|  |  |  |       odie "Cannot extract formula to the same tap!" if destination_tap == source_tap | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2018-07-30 18:41:45 -04:00
										 |  |  |     destination_tap.install unless destination_tap.installed? | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-17 18:39:47 +08:00
										 |  |  |     repo = source_tap.path | 
					
						
							| 
									
										
										
										
											2019-03-19 14:07:50 +08:00
										 |  |  |     pattern = if source_tap.core_tap? | 
					
						
							|  |  |  |       [repo/"Formula/#{name}.rb"] | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       # A formula can technically live in the root directory of a tap or in any of its subdirectories | 
					
						
							|  |  |  |       [repo/"#{name}.rb", repo/"**/#{name}.rb"] | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2018-07-29 20:51:57 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-10 14:59:09 -04:00
										 |  |  |     if args.version | 
					
						
							| 
									
										
										
										
											2018-08-25 13:40:57 -04:00
										 |  |  |       ohai "Searching repository history" | 
					
						
							| 
									
										
										
										
											2018-08-02 00:21:09 -04:00
										 |  |  |       version = args.version | 
					
						
							| 
									
										
										
										
											2019-12-01 22:26:03 +08:00
										 |  |  |       version_segments = Gem::Version.new(version).segments if Gem::Version.correct?(version) | 
					
						
							|  |  |  |       rev = nil | 
					
						
							| 
									
										
										
										
											2018-08-02 00:21:09 -04:00
										 |  |  |       test_formula = nil | 
					
						
							| 
									
										
										
										
											2019-03-17 18:39:47 +08:00
										 |  |  |       result = "" | 
					
						
							| 
									
										
										
										
											2018-08-02 00:21:09 -04:00
										 |  |  |       loop do | 
					
						
							| 
									
										
										
										
											2019-12-01 22:26:03 +08:00
										 |  |  |         rev = rev.nil? ? "HEAD" : "#{rev}~1" | 
					
						
							|  |  |  |         rev, (path,) = Git.last_revision_commit_of_files(repo, pattern, before_commit: rev) | 
					
						
							| 
									
										
										
										
											2020-04-14 17:50:54 +05:30
										 |  |  |         if rev.nil? && source_tap.shallow? | 
					
						
							|  |  |  |           odie <<~EOS | 
					
						
							|  |  |  |             Could not find #{name} but #{source_tap} is a shallow clone! | 
					
						
							|  |  |  |             Try again after running: | 
					
						
							|  |  |  |               git -C "#{source_tap.path}" fetch --unshallow | 
					
						
							|  |  |  |           EOS | 
					
						
							|  |  |  |         elsif rev.nil? | 
					
						
							|  |  |  |           odie "Could not find #{name}! The formula or version may not have existed." | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2018-09-17 02:45:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-17 18:39:47 +08:00
										 |  |  |         file = repo/path | 
					
						
							|  |  |  |         result = Git.last_revision_of_file(repo, file, before_commit: rev) | 
					
						
							|  |  |  |         if result.empty? | 
					
						
							| 
									
										
										
										
											2019-12-01 22:26:03 +08:00
										 |  |  |           odebug "Skipping revision #{rev} - file is empty at this revision" | 
					
						
							| 
									
										
										
										
											2019-03-17 18:39:47 +08:00
										 |  |  |           next | 
					
						
							| 
									
										
										
										
											2018-08-02 00:21:09 -04:00
										 |  |  |         end | 
					
						
							| 
									
										
										
										
											2019-03-17 18:39:47 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-02 00:21:09 -04:00
										 |  |  |         test_formula = formula_at_revision(repo, name, file, rev) | 
					
						
							|  |  |  |         break if test_formula.nil? || test_formula.version == version | 
					
						
							| 
									
										
										
										
											2018-09-17 02:45:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-01 22:26:03 +08:00
										 |  |  |         if version_segments && Gem::Version.correct?(test_formula.version) | 
					
						
							|  |  |  |           test_formula_version_segments = Gem::Version.new(test_formula.version).segments | 
					
						
							|  |  |  |           if version_segments.length < test_formula_version_segments.length | 
					
						
							| 
									
										
										
										
											2020-06-18 11:19:04 -04:00
										 |  |  |             odebug "Apply semantic versioning with #{test_formula_version_segments}" | 
					
						
							| 
									
										
										
										
											2019-12-01 22:26:03 +08:00
										 |  |  |             break if version_segments == test_formula_version_segments.first(version_segments.length) | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         odebug "Trying #{test_formula.version} from revision #{rev} against desired #{version}" | 
					
						
							| 
									
										
										
										
											2018-08-02 00:21:09 -04:00
										 |  |  |       end | 
					
						
							|  |  |  |       odie "Could not find #{name}! The formula or version may not have existed." if test_formula.nil? | 
					
						
							| 
									
										
										
										
											2018-08-10 14:59:09 -04:00
										 |  |  |     else | 
					
						
							| 
									
										
										
										
											2019-03-19 14:07:50 +08:00
										 |  |  |       # Search in the root directory of <repo> as well as recursively in all of its subdirectories | 
					
						
							| 
									
										
										
										
											2019-03-17 18:39:47 +08:00
										 |  |  |       files = Dir[repo/"{,**/}"].map do |dir| | 
					
						
							|  |  |  |         Pathname.glob(["#{dir}/#{name}.rb"]).find(&:file?) | 
					
						
							|  |  |  |       end.compact | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if files.empty? | 
					
						
							|  |  |  |         ohai "Searching repository history" | 
					
						
							|  |  |  |         rev, (path,) = Git.last_revision_commit_of_files(repo, pattern) | 
					
						
							|  |  |  |         odie "Could not find #{name}! The formula or version may not have existed." if rev.nil? | 
					
						
							|  |  |  |         file = repo/path | 
					
						
							|  |  |  |         version = formula_at_revision(repo, name, file, rev).version | 
					
						
							|  |  |  |         result = Git.last_revision_of_file(repo, file) | 
					
						
							|  |  |  |       else | 
					
						
							|  |  |  |         file = files.first.realpath | 
					
						
							|  |  |  |         rev = "HEAD" | 
					
						
							|  |  |  |         version = Formulary.factory(file).version | 
					
						
							|  |  |  |         result = File.read(file) | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2018-08-02 00:21:09 -04:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-20 09:20:45 -04:00
										 |  |  |     # The class name has to be renamed to match the new filename, | 
					
						
							|  |  |  |     # e.g. Foo version 1.2.3 becomes FooAT123 and resides in Foo@1.2.3.rb. | 
					
						
							| 
									
										
										
										
											2019-03-15 11:27:39 +08:00
										 |  |  |     class_name = Formulary.class_s(name) | 
					
						
							| 
									
										
										
										
											2020-02-21 17:26:24 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # Remove any existing version suffixes, as a new one will be added later | 
					
						
							|  |  |  |     name.sub!(/\b@(.*)\z\b/i, "") | 
					
						
							| 
									
										
										
										
											2019-03-15 11:27:39 +08:00
										 |  |  |     versioned_name = Formulary.class_s("#{name}@#{version}") | 
					
						
							| 
									
										
										
										
											2018-08-02 00:21:09 -04:00
										 |  |  |     result.gsub!("class #{class_name} < Formula", "class #{versioned_name} < Formula") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-10 14:59:09 -04:00
										 |  |  |     path = destination_tap.path/"Formula/#{name}@#{version}.rb" | 
					
						
							| 
									
										
										
										
											2018-07-30 18:41:45 -04:00
										 |  |  |     if path.exist? | 
					
						
							| 
									
										
										
										
											2020-03-04 17:28:24 +00:00
										 |  |  |       unless args.force? | 
					
						
							| 
									
										
										
										
											2018-07-30 18:41:45 -04:00
										 |  |  |         odie <<~EOS | 
					
						
							|  |  |  |           Destination formula already exists: #{path} | 
					
						
							|  |  |  |           To overwrite it and continue anyways, run: | 
					
						
							| 
									
										
										
										
											2018-10-08 22:49:03 -04:00
										 |  |  |             brew extract --force --version=#{version} #{name} #{destination_tap.name} | 
					
						
							| 
									
										
										
										
											2018-07-30 18:41:45 -04:00
										 |  |  |         EOS | 
					
						
							| 
									
										
										
										
											2018-07-29 20:51:57 -04:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2019-12-01 22:26:03 +08:00
										 |  |  |       odebug "Overwriting existing formula at #{path}" | 
					
						
							| 
									
										
										
										
											2018-07-30 18:41:45 -04:00
										 |  |  |       path.delete | 
					
						
							| 
									
										
										
										
											2018-07-29 20:51:57 -04:00
										 |  |  |     end | 
					
						
							| 
									
										
										
										
											2018-08-25 13:35:08 -04:00
										 |  |  |     ohai "Writing formula for #{name} from revision #{rev} to #{path}" | 
					
						
							| 
									
										
										
										
											2018-07-30 18:41:45 -04:00
										 |  |  |     path.write result | 
					
						
							| 
									
										
										
										
											2018-07-29 20:51:57 -04:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # @private | 
					
						
							| 
									
										
										
										
											2018-08-02 00:21:09 -04:00
										 |  |  |   def formula_at_revision(repo, name, file, rev) | 
					
						
							| 
									
										
										
										
											2018-08-10 14:59:09 -04:00
										 |  |  |     return if rev.empty? | 
					
						
							| 
									
										
										
										
											2018-09-17 02:45:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-02 00:21:09 -04:00
										 |  |  |     contents = Git.last_revision_of_file(repo, file, before_commit: rev) | 
					
						
							| 
									
										
										
										
											2018-08-16 12:52:54 -04:00
										 |  |  |     contents.gsub!("@url=", "url ") | 
					
						
							|  |  |  |     contents.gsub!("require 'brewkit'", "require 'formula'") | 
					
						
							| 
									
										
										
										
											2018-08-25 14:52:30 -04:00
										 |  |  |     with_monkey_patch { Formulary.from_contents(name, file, contents) } | 
					
						
							| 
									
										
										
										
											2018-07-29 20:51:57 -04:00
										 |  |  |   end | 
					
						
							|  |  |  | end |