From 6be6bba0be3349c2209ce88eacc5be524da65907 Mon Sep 17 00:00:00 2001 From: kiendang Date: Sun, 17 Mar 2019 18:39:47 +0800 Subject: [PATCH 1/3] 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) From 00f74902d4e777301656a156f34265cc68421d8b Mon Sep 17 00:00:00 2001 From: kiendang Date: Mon, 18 Mar 2019 03:05:30 +0800 Subject: [PATCH 2/3] Call git log through bash for globstar support --- Library/Homebrew/utils/git.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Library/Homebrew/utils/git.rb b/Library/Homebrew/utils/git.rb index 4368962578..5b71b93471 100644 --- a/Library/Homebrew/utils/git.rb +++ b/Library/Homebrew/utils/git.rb @@ -22,7 +22,7 @@ module Git "log", "--format=%h", "--abbrev=7", "--max-count=1", "--name-only", *args, "--", file ] - out, = Open3.capture3(cmd.join(" ")) + out, = Open3.capture3("/bin/bash", "-c", cmd.join(" ")) rev, *files = out.chomp.split(/\n/).reject(&:empty?) [rev, files] end From 028b8b408dd959bb4ade72cf2365f92a68e3df6d Mon Sep 17 00:00:00 2001 From: kiendang Date: Tue, 19 Mar 2019 14:07:50 +0800 Subject: [PATCH 3/3] Remove use of bash --- Library/Homebrew/dev-cmd/extract.rb | 11 ++- Library/Homebrew/test/dev-cmd/extract_spec.rb | 67 +------------------ Library/Homebrew/utils/git.rb | 35 +++++++--- docs/Manpage.md | 3 +- manpages/brew.1 | 2 +- 5 files changed, 37 insertions(+), 81 deletions(-) diff --git a/Library/Homebrew/dev-cmd/extract.rb b/Library/Homebrew/dev-cmd/extract.rb index d6c1bae817..fb8aa976a6 100644 --- a/Library/Homebrew/dev-cmd/extract.rb +++ b/Library/Homebrew/dev-cmd/extract.rb @@ -81,7 +81,8 @@ module Homebrew Look through repository history to find the most recent version of and create a copy in `/Formula/``@``.rb`. If the tap is not - installed yet, attempt to install/clone the tap before continuing. + installed yet, attempt to install/clone the tap before continuing. To extract + a from a tap that is not homebrew/core use //. EOS flag "--version=", @@ -112,7 +113,12 @@ module Homebrew destination_tap.install unless destination_tap.installed? repo = source_tap.path - pattern = source_tap.core_tap? ? repo/"Formula/#{name}.rb" : repo/"{,**/}#{name}.rb" + 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 if args.version ohai "Searching repository history" @@ -138,6 +144,7 @@ module Homebrew end odie "Could not find #{name}! The formula or version may not have existed." if test_formula.nil? else + # Search in the root directory of as well as recursively in all of its subdirectories files = Dir[repo/"{,**/}"].map do |dir| Pathname.glob(["#{dir}/#{name}.rb"]).find(&:file?) end.compact diff --git a/Library/Homebrew/test/dev-cmd/extract_spec.rb b/Library/Homebrew/test/dev-cmd/extract_spec.rb index b905108e3c..59e7cc1905 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 from core tap, defaulting to most recent" do + it "retrieves the specified version of formula, defaulting to most recent" do path = Tap::TAP_DIRECTORY/"homebrew/homebrew-foo" (path/"Formula").mkpath target = Tap.from_path(path) @@ -29,69 +29,4 @@ 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 5b71b93471..875b07dc13 100644 --- a/Library/Homebrew/utils/git.rb +++ b/Library/Homebrew/utils/git.rb @@ -4,7 +4,11 @@ module Git module_function def last_revision_commit_of_file(repo, file, before_commit: nil) - args = [before_commit.nil? ? "--skip=1" : before_commit.split("..").first] + args = if before_commit.nil? + ["--skip=1"] + else + [before_commit.split("..").first] + end out, = Open3.capture3( HOMEBREW_SHIMS_PATH/"scm/git", "-C", repo, @@ -14,17 +18,26 @@ 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] + def last_revision_commit_of_files(repo, files, before_commit: nil) + args = if before_commit.nil? + ["--skip=1"] + else + [before_commit.split("..").first] + end - cmd = [ - HOMEBREW_SHIMS_PATH/"scm/git", "-C", repo, - "log", "--format=%h", "--abbrev=7", "--max-count=1", "--name-only", - *args, "--", file - ] - out, = Open3.capture3("/bin/bash", "-c", cmd.join(" ")) - rev, *files = out.chomp.split(/\n/).reject(&:empty?) - [rev, files] + # git log output format: + # + # + # + # ... + # return [, [file_path1, file_path2, ...]] + out, = Open3.capture3( + HOMEBREW_SHIMS_PATH/"scm/git", "-C", repo, "log", + "--pretty=format:%h", "--abbrev=7", "--max-count=1", + "--diff-filter=d", "--name-only", *args, "--", *files + ) + rev, *paths = out.chomp.split(/\n/).reject(&:empty?) + [rev, paths] end def last_revision_of_file(repo, file, before_commit: nil) diff --git a/docs/Manpage.md b/docs/Manpage.md index 9bb1e9789d..bf67269311 100644 --- a/docs/Manpage.md +++ b/docs/Manpage.md @@ -785,7 +785,8 @@ Homebrew repository for editing if no *`formula`* is provided. 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 -installed yet, attempt to install/clone the tap before continuing. +installed yet, attempt to install/clone the tap before continuing. To extract a +*`formula`* from a tap that is not homebrew/core use *`user`*/*`repo`*/*`formula`*. * `--version`: Extract the provided *`version`* of *`formula`* instead of the most recent. diff --git a/manpages/brew.1 b/manpages/brew.1 index f15edcf778..52dcf05e03 100644 --- a/manpages/brew.1 +++ b/manpages/brew.1 @@ -985,7 +985,7 @@ Generate the new formula in the provided tap, specified as \fIuser\fR\fB/\fR\fIr Open a formula in the editor set by \fBEDITOR\fR or \fBHOMEBREW_EDITOR\fR, or open the Homebrew repository for editing if no \fIformula\fR is provided\. . .SS "\fBextract\fR [\fIoptions\fR] \fIformula\fR \fItap\fR" -Look through repository history to find the most recent version of \fIformula\fR and create a copy in \fItap\fR\fB/Formula/\fR\fIformula\fR\fB@\fR\fIversion\fR\fB\.rb\fR\. If the tap is not installed yet, attempt to install/clone the tap before continuing\. +Look through repository history to find the most recent version of \fIformula\fR and create a copy in \fItap\fR\fB/Formula/\fR\fIformula\fR\fB@\fR\fIversion\fR\fB\.rb\fR\. If the tap is not installed yet, attempt to install/clone the tap before continuing\. To extract a \fIformula\fR from a tap that is not homebrew/core use \fIuser\fR/\fIrepo\fR/\fIformula\fR\. . .TP \fB\-\-version\fR