Skip to content

Instantly share code, notes, and snippets.

@ikoba
Last active December 19, 2022 10:14
Show Gist options
  • Select an option

  • Save ikoba/e7e5ee20679e9692b0d9b4bffcee08fa to your computer and use it in GitHub Desktop.

Select an option

Save ikoba/e7e5ee20679e9692b0d9b4bffcee08fa to your computer and use it in GitHub Desktop.
'git grep' with functionalities to output GitHub permalinks and authors
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'cgi'
require 'csv'
require 'optparse'
SUPPORTED_GIT_GREP_OPTIONS = <<~MSG
-i --ignore-case
-I
-w --word-regexp
-v --invert-match
-E --extended-regexp
-G --basic-regexp
-P --perl-regexp
-F --fixed-strings
--threads
-e
--and
--or
--not
--all-match
--
MSG
OPTIONS_SEPARATOR = '::'
sep_index = ARGV.find_index(OPTIONS_SEPARATOR) || ARGV.size
gg_argv = ARGV[..sep_index - 1].map { |arg| arg.start_with?('-') ? arg : "'#{arg}'" }
argv = ARGV[sep_index + 1..] || []
options = { author: false, header: false, separator: "\t" }
opts = OptionParser.new
opts.on('-a', '--author', 'Show the author who commit the last change.') do
options[:author] = true
end
opts.on('-h', '--header', 'Show the output table header.') do
options[:header] = true
end
opts.on('-s', '--separator SEPARATOR', 'Specify a column separator for the output table. (default: \t)') do |v|
options[:separator] = v
end
gg_opts = OptionParser.new
gg_opts.banner = <<~MSG
Usage: #{File.basename(__FILE__)} ['git grep' options and patterns] #{OPTIONS_SEPARATOR} [options]
['git grep' options and patterns]
MSG
SUPPORTED_GIT_GREP_OPTIONS.each_line(chomp: true) { |line| gg_opts.on(*line.split) }
gg_opts.separator <<~MSG
[options]
#{opts.help.each_line.to_a[1..].join}
MSG
gg_opts.parse!(gg_argv.dup)
opts.parse!(argv)
remote_url = `git config --get remote.origin.url`.chomp
if remote_url =~ /^git@github\.com:(.*)\.git$/ ||
remote_url =~ %r{^ssh://git@github.com/(.*)\.git$}
remote_url = "https://github.com/#{$1}"
end
commit_hash = `git show --format='%H' --no-patch`.chomp
url_base = "#{remote_url}/blob/#{commit_hash}"
header = %w[Path Line Matched Permalink]
header << 'Author' if options[:author]
begin
CSV($stdout, col_sep: options[:separator]) do |csv|
csv << header if options[:header]
git_grep_result = `git grep --line-number --full-name #{gg_argv.join(' ')}`
git_grep_result.each_line(chomp: true) do |line|
fields = line.split(':')
path, line = fields[..1]
matched = fields[2..].join
escaped_path = path.split('/').map { |i| CGI.escape(i) }.join('/')
url = "#{url_base}/#{escaped_path}#L#{line}"
fields = [path, line, matched, url]
if options[:author]
author = `git blame -L #{line} --line-porcelain #{path}`
.each_line(chomp: true).to_a[1]&.delete_prefix('author ')
fields << author
end
csv << fields
end
end
rescue Errno::EPIPE
# suppress error
rescue Interrupt
# suppress error
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment