From 6be6bba0be3349c2209ce88eacc5be524da65907 Mon Sep 17 00:00:00 2001 From: kiendang Date: Sun, 17 Mar 2019 18:39:47 +0800 Subject: [PATCH] brew extract from all taps --- Library/Homebrew/dev-cmd/extract.rb | 57 ++++++++++------ Library/Homebrew/test/dev-cmd/extract_spec.rb | 67 ++++++++++++++++++- Library/Homebrew/utils/git.rb | 13 ++++ 3 files changed, 116 insertions(+), 21 deletions(-) diff --git a/Library/Homebrew/dev-cmd/extract.rb b/Library/Homebrew/dev-cmd/extract.rb index dca97bc7ec..d6c1bae817 100644 --- a/Library/Homebrew/dev-cmd/extract.rb +++ b/Library/Homebrew/dev-cmd/extract.rb @@ -97,47 +97,64 @@ module Homebrew # Expect exactly two named arguments: formula and tap raise UsageError if ARGV.named.length != 2 + if ARGV.named.first !~ HOMEBREW_TAP_FORMULA_REGEX + name = ARGV.named.first.downcase + 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 + destination_tap = Tap.fetch(ARGV.named.second) 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 destination_tap.install unless destination_tap.installed? - name = ARGV.named.first.downcase - repo = CoreTap.instance.path - # Formulae can technically live in "/.rb" or - # "/Formula/.rb", but explicitly use the latter for now - # since that is how the core tap is structured. - file = repo/"Formula/#{name}.rb" + repo = source_tap.path + pattern = source_tap.core_tap? ? repo/"Formula/#{name}.rb" : repo/"{,**/}#{name}.rb" if args.version ohai "Searching repository history" version = args.version rev = "HEAD" test_formula = nil + result = "" loop do - loop do - rev = Git.last_revision_commit_of_file(repo, file, before_commit: "#{rev}~1") - break if rev.empty? - break unless Git.last_revision_of_file(repo, file, before_commit: rev).empty? + rev, (path,) = Git.last_revision_commit_of_files(repo, pattern, before_commit: "#{rev}~1") + odie "Could not find #{name}! The formula or version may not have existed." if rev.nil? + file = repo/path + result = Git.last_revision_of_file(repo, file, before_commit: rev) + if result.empty? ohai "Skipping revision #{rev} - file is empty at this revision" if ARGV.debug? + next end + test_formula = formula_at_revision(repo, name, file, rev) break if test_formula.nil? || test_formula.version == version ohai "Trying #{test_formula.version} from revision #{rev} against desired #{version}" if ARGV.debug? end odie "Could not find #{name}! The formula or version may not have existed." if test_formula.nil? - result = Git.last_revision_of_file(repo, file, before_commit: rev) - elsif File.exist?(file) - rev = "HEAD" - version = Formulary.factory(file).version - result = File.read(file) else - ohai "Searching repository history" - rev = Git.last_revision_commit_of_file(repo, file) - version = formula_at_revision(repo, name, file, rev).version - odie "Could not find #{name}! The formula or version may not have existed." if rev.empty? - result = Git.last_revision_of_file(repo, file) + 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 end # The class name has to be renamed to match the new filename, diff --git a/Library/Homebrew/test/dev-cmd/extract_spec.rb b/Library/Homebrew/test/dev-cmd/extract_spec.rb index 59e7cc1905..b905108e3c 100644 --- a/Library/Homebrew/test/dev-cmd/extract_spec.rb +++ b/Library/Homebrew/test/dev-cmd/extract_spec.rb @@ -1,5 +1,5 @@ describe "brew extract", :integration_test do - it "retrieves the specified version of formula, defaulting to most recent" do + it "retrieves the specified version of formula from core tap, defaulting to most recent" do path = Tap::TAP_DIRECTORY/"homebrew/homebrew-foo" (path/"Formula").mkpath target = Tap.from_path(path) @@ -29,4 +29,69 @@ describe "brew extract", :integration_test do expect(Formulary.factory(path/"Formula/testball@0.1.rb").version).to be == "0.1" end + + it "retrieves the specified version of formula from a tap other than core, defaulting to most recent" do + destination = Tap::TAP_DIRECTORY/"homebrew/homebrew-foo" + (destination/"Formula").mkpath + destination_tap = Tap.from_path(destination) + + source = Tap::TAP_DIRECTORY/"homebrew/homebrew-bar" + source.mkpath + source_tap = Tap.from_path(source) + + tarball = if OS.linux? + TEST_FIXTURE_DIR/"tarballs/testball-0.1-linux.tbz" + else + TEST_FIXTURE_DIR/"tarballs/testball-0.1.tbz" + end + + content = <<~RUBY + desc "Some test" + homepage "https://brew.sh/testball" + url "file://#{tarball}" + sha256 "#{tarball.sha256}" + + option "with-foo", "Build with foo" + + def install + (prefix/"foo"/"test").write("test") if build.with? "foo" + prefix.install Dir["*"] + (buildpath/"test.c").write \ + "#include \\nint main(){return printf(\\"test\\");}" + bin.mkpath + system ENV.cc, "test.c", "-o", bin/"test" + end + RUBY + + formula_file = source_tap.path/"testball.rb" + formula_file.write <<~RUBY + class Testball < Formula + #{content} + end + RUBY + + source_tap.path.cd do + system "git", "init" + system "git", "add", "--all" + system "git", "commit", "-m", "testball 0.1" + contents = File.read(formula_file) + contents.gsub!("testball-0.1", "testball-0.2") + File.write(formula_file, contents) + system "git", "add", "--all" + system "git", "commit", "-m", "testball 0.2" + end + expect { brew "extract", "homebrew/bar/testball", destination_tap.name } + .to be_a_success + + expect(destination/"Formula/testball@0.2.rb").to exist + + expect(Formulary.factory(destination/"Formula/testball@0.2.rb").version).to be == "0.2" + + expect { brew "extract", "homebrew/bar/testball", destination_tap.name, "--version=0.1" } + .to be_a_success + + expect(destination/"Formula/testball@0.1.rb").to exist + + expect(Formulary.factory(destination/"Formula/testball@0.1.rb").version).to be == "0.1" + end end diff --git a/Library/Homebrew/utils/git.rb b/Library/Homebrew/utils/git.rb index e0bb176fed..4368962578 100644 --- a/Library/Homebrew/utils/git.rb +++ b/Library/Homebrew/utils/git.rb @@ -14,6 +14,19 @@ module Git out.chomp end + def last_revision_commit_of_files(repo, file, before_commit: nil) + args = [before_commit.nil? ? "--skip=1" : before_commit.split("..").first] + + cmd = [ + HOMEBREW_SHIMS_PATH/"scm/git", "-C", repo, + "log", "--format=%h", "--abbrev=7", "--max-count=1", "--name-only", + *args, "--", file + ] + out, = Open3.capture3(cmd.join(" ")) + rev, *files = out.chomp.split(/\n/).reject(&:empty?) + [rev, files] + end + def last_revision_of_file(repo, file, before_commit: nil) relative_file = Pathname(file).relative_path_from(repo)