lib/gitlab_git/repository.rb in gitlab_git-7.0.0.rc11 vs lib/gitlab_git/repository.rb in gitlab_git-7.0.0.rc12
- old
+ new
@@ -6,10 +6,12 @@
module Gitlab
module Git
class Repository
include Gitlab::Git::Popen
+ SEARCH_CONTEXT_LINES = 3
+
class NoRepository < StandardError; end
# Default branch in the repository
attr_accessor :root_ref
@@ -918,18 +920,66 @@
end
# Return an array of BlobSnippets for lines in +file_contents+ that match
# +query+
def build_greps(file_contents, query, ref, filename)
+ # The file_contents string is potentially huge so we make sure to loop
+ # through it one line at a time. This gives Ruby the chance to GC lines
+ # we are not interested in.
+ #
+ # We need to do a little extra work because we are not looking for just
+ # the lines that matches the query, but also for the context
+ # (surrounding lines). We will use Enumerable#each_cons to efficiently
+ # loop through the lines while keeping surrounding lines on hand.
+ #
+ # First, we turn "foo\nbar\nbaz" into
+ # [
+ # [nil, -3], [nil, -2], [nil, -1],
+ # ['foo', 0], ['bar', 1], ['baz', 3],
+ # [nil, 4], [nil, 5], [nil, 6]
+ # ]
+ lines_with_index = Enumerator.new do |yielder|
+ # Yield fake 'before' lines for the first line of file_contents
+ (-SEARCH_CONTEXT_LINES..-1).each do |i|
+ yielder.yield [nil, i]
+ end
+
+ # Yield the actual file contents
+ count = 0
+ file_contents.each_line.each_with_index do |line, i|
+ line.chomp!
+ yielder.yield [line, i]
+ count += 1
+ end
+
+ # Yield fake 'after' lines for the last line of file_contents
+ (count+1..count+SEARCH_CONTEXT_LINES).each do |i|
+ yielder.yield [nil, i]
+ end
+ end
+
greps = []
- file_contents.split("\n").each_with_index do |line, i|
- next unless line.match(/#{Regexp.escape(query)}/i)
+ # Loop through consecutive blocks of lines with indexes
+ lines_with_index.each_cons(2 * SEARCH_CONTEXT_LINES + 1) do |line_block|
+ # Get the 'middle' line and index from the block
+ line, i = line_block[SEARCH_CONTEXT_LINES]
+ next unless line && line.match(/#{Regexp.escape(query)}/i)
+
+ # Yay, 'line' contains a match!
+ # Get an array with just the context lines (no indexes)
+ match_with_context = line_block.map(&:first)
+ # Remove 'nil' lines in case we are close to the first or last line
+ match_with_context.compact!
+
+ # Get the line number (1-indexed) of the first context line
+ first_context_line_number = line_block[0][1] + 1
+
greps << Gitlab::Git::BlobSnippet.new(
ref,
- file_contents.split("\n")[i - 3..i + 3],
- i - 2,
+ match_with_context,
+ first_context_line_number,
filename
)
end
greps