From a8649afd110cf83a5ce5870993125912a565af59 Mon Sep 17 00:00:00 2001 From: Isaac Betesh Date: Tue, 5 May 2020 19:09:23 -0700 Subject: [PATCH 1/3] Implement RJGit::Porcelain.grep to mimic git-grep This implementation was heavily based on https://github.com/gollum/rjgit_adapter/blob/v0.6/lib/rjgit_adapter/git_layer_rjgit.rb#L179-L190 See also https://stackoverflow.com/questions/15572483/how-to-do-git-grep-e-pattern-with-jgit/16263886#16263886 for another implementation --- lib/rjgit.rb | 24 ++++++++++++++++++++++++ spec/rjgit_spec.rb | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/lib/rjgit.rb b/lib/rjgit.rb index b9f0fe0..cf33a9c 100644 --- a/lib/rjgit.rb +++ b/lib/rjgit.rb @@ -190,6 +190,30 @@ def self.describe(repository, ref, options = {}) end command.call end + + # options: + # * ref + # * path_filter + def self.grep(repository, query, options={}) + repo = RJGit.repository_type(repository) + walk = RevWalk.new(repo) + ls_tree_options = {:recursive => true, :path_filter => options[:path_filter]} + + ls_tree(repo, nil, options.fetch(:ref, 'HEAD'), ls_tree_options).each_with_object({}) do |item, result| + blob = Blob.new(repo, item[:mode], item[:path], walk.lookup_blob(ObjectId.from_string(item[:id]))) + next if blob.binary? + + rows = blob.data.split("\n") + data = case query + when Regexp then rows.grep(query) + when String then rows.select { |r| r[query] } + else raise "A #{query.class} was passed to #{self}.grep(). Only Regexps and Strings are supported!" + end + next if data.empty? + + result[blob.path] = data + end + end end module Plumbing diff --git a/spec/rjgit_spec.rb b/spec/rjgit_spec.rb index 767270b..3db90f5 100644 --- a/spec/rjgit_spec.rb +++ b/spec/rjgit_spec.rb @@ -192,6 +192,46 @@ end end + describe 'git-grep' do + before(:all) do + File.open(File.join(@temp_repo_path, @testfile), 'a') {|file| file.write("\nAppending to test file") } + Porcelain.add(@repo, @testfile) + Porcelain.commit(@repo, 'Append to testfile') + end + + it "finds files matching a String" do + expect(Porcelain.grep(@repo, ' file to add')).to eq(@testfile => ["This is a new file to add."]) + end + + it "finds files matching a Regexp" do + expect(Porcelain.grep(@repo, / file.*add/)).to eq(@testfile => ["This is a new file to add."]) + end + + it "supports the path option" do + expect(Porcelain.grep(@repo, / file.*add/, :path_filter => "chapters")).to eq({}) + expect(Porcelain.grep(@repo, / file.*add/, :path_filter => "test_file.txt")).to eq(@testfile => ["This is a new file to add."]) + end + + it 'works on nested files' do + results = Porcelain.grep(@repo, /eventually/, :path_filter => "chapters") + expect(results.keys).to contain_exactly("chapters/prematerial.txt") + expect(results.fetch("chapters/prematerial.txt")).to contain_exactly(a_string_matching(/ eventually the rubicon/)) + end + + it 'supports the ref option' do + expect(Porcelain.grep(@repo, 'Append', ref: @repo.commits[0].id)).to eq(@testfile => ["Appending to test file"]) + expect(Porcelain.grep(@repo, 'Append', ref: @repo.commits[1].id)).to eq({}) + end + + it 'ignores binary files' do + expect(Porcelain.grep(@repo, /./, :path_filter => "homer-excited.png")).to eq({}) + end + + it 'only greps for Regexps and Strings' do + expect { Porcelain.grep(@repo, 10..20) }.to raise_error("A Range was passed to RJGit::Porcelain.grep(). Only Regexps and Strings are supported!") + end + end + after(:all) do @repo = nil remove_temp_repo(@temp_repo_path) From 33a0b96f3de8740a229aad77a00204dce64a47b7 Mon Sep 17 00:00:00 2001 From: Isaac Betesh Date: Tue, 5 May 2020 21:43:44 -0700 Subject: [PATCH 2/3] Add a case_insensitive option to Porcelain.grep --- lib/rjgit.rb | 14 ++++++++++++-- spec/rjgit_spec.rb | 14 ++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/lib/rjgit.rb b/lib/rjgit.rb index cf33a9c..2eb69fd 100644 --- a/lib/rjgit.rb +++ b/lib/rjgit.rb @@ -194,11 +194,22 @@ def self.describe(repository, ref, options = {}) # options: # * ref # * path_filter + # * case_insensitive def self.grep(repository, query, options={}) + case_insensitive = options[:case_insensitive] repo = RJGit.repository_type(repository) walk = RevWalk.new(repo) ls_tree_options = {:recursive => true, :path_filter => options[:path_filter]} + raise "A #{query.class} was passed to #{self}.grep(). Only Regexps and Strings are supported!" unless String === query || Regexp === query + + if case_insensitive + query = case query + when Regexp then Regexp.new(query.source, query.options | Regexp::IGNORECASE) + when String then query.downcase + end + end + ls_tree(repo, nil, options.fetch(:ref, 'HEAD'), ls_tree_options).each_with_object({}) do |item, result| blob = Blob.new(repo, item[:mode], item[:path], walk.lookup_blob(ObjectId.from_string(item[:id]))) next if blob.binary? @@ -206,8 +217,7 @@ def self.grep(repository, query, options={}) rows = blob.data.split("\n") data = case query when Regexp then rows.grep(query) - when String then rows.select { |r| r[query] } - else raise "A #{query.class} was passed to #{self}.grep(). Only Regexps and Strings are supported!" + when String then rows.select { |r| (case_insensitive ? r.downcase : r)[query] } end next if data.empty? diff --git a/spec/rjgit_spec.rb b/spec/rjgit_spec.rb index 3db90f5..7ca28ac 100644 --- a/spec/rjgit_spec.rb +++ b/spec/rjgit_spec.rb @@ -230,6 +230,20 @@ it 'only greps for Regexps and Strings' do expect { Porcelain.grep(@repo, 10..20) }.to raise_error("A Range was passed to RJGit::Porcelain.grep(). Only Regexps and Strings are supported!") end + + it 'supports case-insensitivity for a Regexp' do + expect(Porcelain.grep(@repo, /\AThis is a/, :path_filter => "test_file.txt")).to eq(@testfile => ["This is a new file to add."]) + expect(Porcelain.grep(@repo, /\Athis Is A/, :path_filter => "test_file.txt")).to eq({}) + expect(Porcelain.grep(@repo, /\Athis Is A/, :path_filter => "test_file.txt", case_insensitive: true)).to eq(@testfile => ["This is a new file to add."]) + expect(Porcelain.grep(@repo, /\Athis Is Not A/, :path_filter => "test_file.txt", case_insensitive: true)).to eq({}) + end + + it 'supports case-insensitivity for a String' do + expect(Porcelain.grep(@repo, "This is a", :path_filter => "test_file.txt")).to eq(@testfile => ["This is a new file to add."]) + expect(Porcelain.grep(@repo, "this Is A", :path_filter => "test_file.txt")).to eq({}) + expect(Porcelain.grep(@repo, "this Is A", :path_filter => "test_file.txt", case_insensitive: true)).to eq(@testfile => ["This is a new file to add."]) + expect(Porcelain.grep(@repo, "this Is Not A", :path_filter => "test_file.txt", case_insensitive: true)).to eq({}) + end end after(:all) do From 6402ef628595d47cdfb5a8b745bb90cba0b44124 Mon Sep 17 00:00:00 2001 From: Isaac Betesh Date: Tue, 12 May 2020 12:02:51 -0700 Subject: [PATCH 3/3] Convert Strings to Regexps in Porcelain.grep to reduce the number of case statements --- lib/rjgit.rb | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/lib/rjgit.rb b/lib/rjgit.rb index 2eb69fd..794f22f 100644 --- a/lib/rjgit.rb +++ b/lib/rjgit.rb @@ -201,24 +201,20 @@ def self.grep(repository, query, options={}) walk = RevWalk.new(repo) ls_tree_options = {:recursive => true, :path_filter => options[:path_filter]} - raise "A #{query.class} was passed to #{self}.grep(). Only Regexps and Strings are supported!" unless String === query || Regexp === query - - if case_insensitive - query = case query - when Regexp then Regexp.new(query.source, query.options | Regexp::IGNORECASE) - when String then query.downcase - end + query = case query + when Regexp then query + when String then Regexp.new(Regexp.escape(query)) + else raise "A #{query.class} was passed to #{self}.grep(). Only Regexps and Strings are supported!" end + query = Regexp.new(query.source, query.options | Regexp::IGNORECASE) if case_insensitive + ls_tree(repo, nil, options.fetch(:ref, 'HEAD'), ls_tree_options).each_with_object({}) do |item, result| blob = Blob.new(repo, item[:mode], item[:path], walk.lookup_blob(ObjectId.from_string(item[:id]))) next if blob.binary? rows = blob.data.split("\n") - data = case query - when Regexp then rows.grep(query) - when String then rows.select { |r| (case_insensitive ? r.downcase : r)[query] } - end + data = rows.grep(query) next if data.empty? result[blob.path] = data