mirror of
https://gerrit.googlesource.com/git-repo
synced 2024-12-21 07:16:21 +00:00
72ab852ca5
If `git grep` fails in any project checkout (e.g. an incomplete sync), make sure we print that error clearly rather than blowing up, and exit non-zero in the process. Bug: https://crbug.com/gerrit/11613 Change-Id: I31de1134fdcc7aaa9814cf2eb6a67d398eebf9cf Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/239237 Reviewed-by: David Pursehouse <dpursehouse@collab.net> Tested-by: Mike Frysinger <vapier@google.com>
258 lines
8.1 KiB
Python
258 lines
8.1 KiB
Python
# -*- coding:utf-8 -*-
|
|
#
|
|
# Copyright (C) 2009 The Android Open Source Project
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
from __future__ import print_function
|
|
|
|
import sys
|
|
|
|
from color import Coloring
|
|
from command import PagedCommand
|
|
from error import GitError
|
|
from git_command import git_require, GitCommand
|
|
|
|
class GrepColoring(Coloring):
|
|
def __init__(self, config):
|
|
Coloring.__init__(self, config, 'grep')
|
|
self.project = self.printer('project', attr='bold')
|
|
self.fail = self.printer('fail', fg='red')
|
|
|
|
class Grep(PagedCommand):
|
|
common = True
|
|
helpSummary = "Print lines matching a pattern"
|
|
helpUsage = """
|
|
%prog {pattern | -e pattern} [<project>...]
|
|
"""
|
|
helpDescription = """
|
|
Search for the specified patterns in all project files.
|
|
|
|
# Boolean Options
|
|
|
|
The following options can appear as often as necessary to express
|
|
the pattern to locate:
|
|
|
|
-e PATTERN
|
|
--and, --or, --not, -(, -)
|
|
|
|
Further, the -r/--revision option may be specified multiple times
|
|
in order to scan multiple trees. If the same file matches in more
|
|
than one tree, only the first result is reported, prefixed by the
|
|
revision name it was found under.
|
|
|
|
# Examples
|
|
|
|
Look for a line that has '#define' and either 'MAX_PATH or 'PATH_MAX':
|
|
|
|
repo grep -e '#define' --and -\\( -e MAX_PATH -e PATH_MAX \\)
|
|
|
|
Look for a line that has 'NODE' or 'Unexpected' in files that
|
|
contain a line that matches both expressions:
|
|
|
|
repo grep --all-match -e NODE -e Unexpected
|
|
|
|
"""
|
|
|
|
def _Options(self, p):
|
|
def carry(option,
|
|
opt_str,
|
|
value,
|
|
parser):
|
|
pt = getattr(parser.values, 'cmd_argv', None)
|
|
if pt is None:
|
|
pt = []
|
|
setattr(parser.values, 'cmd_argv', pt)
|
|
|
|
if opt_str == '-(':
|
|
pt.append('(')
|
|
elif opt_str == '-)':
|
|
pt.append(')')
|
|
else:
|
|
pt.append(opt_str)
|
|
|
|
if value is not None:
|
|
pt.append(value)
|
|
|
|
g = p.add_option_group('Sources')
|
|
g.add_option('--cached',
|
|
action='callback', callback=carry,
|
|
help='Search the index, instead of the work tree')
|
|
g.add_option('-r', '--revision',
|
|
dest='revision', action='append', metavar='TREEish',
|
|
help='Search TREEish, instead of the work tree')
|
|
|
|
g = p.add_option_group('Pattern')
|
|
g.add_option('-e',
|
|
action='callback', callback=carry,
|
|
metavar='PATTERN', type='str',
|
|
help='Pattern to search for')
|
|
g.add_option('-i', '--ignore-case',
|
|
action='callback', callback=carry,
|
|
help='Ignore case differences')
|
|
g.add_option('-a', '--text',
|
|
action='callback', callback=carry,
|
|
help="Process binary files as if they were text")
|
|
g.add_option('-I',
|
|
action='callback', callback=carry,
|
|
help="Don't match the pattern in binary files")
|
|
g.add_option('-w', '--word-regexp',
|
|
action='callback', callback=carry,
|
|
help='Match the pattern only at word boundaries')
|
|
g.add_option('-v', '--invert-match',
|
|
action='callback', callback=carry,
|
|
help='Select non-matching lines')
|
|
g.add_option('-G', '--basic-regexp',
|
|
action='callback', callback=carry,
|
|
help='Use POSIX basic regexp for patterns (default)')
|
|
g.add_option('-E', '--extended-regexp',
|
|
action='callback', callback=carry,
|
|
help='Use POSIX extended regexp for patterns')
|
|
g.add_option('-F', '--fixed-strings',
|
|
action='callback', callback=carry,
|
|
help='Use fixed strings (not regexp) for pattern')
|
|
|
|
g = p.add_option_group('Pattern Grouping')
|
|
g.add_option('--all-match',
|
|
action='callback', callback=carry,
|
|
help='Limit match to lines that have all patterns')
|
|
g.add_option('--and', '--or', '--not',
|
|
action='callback', callback=carry,
|
|
help='Boolean operators to combine patterns')
|
|
g.add_option('-(', '-)',
|
|
action='callback', callback=carry,
|
|
help='Boolean operator grouping')
|
|
|
|
g = p.add_option_group('Output')
|
|
g.add_option('-n',
|
|
action='callback', callback=carry,
|
|
help='Prefix the line number to matching lines')
|
|
g.add_option('-C',
|
|
action='callback', callback=carry,
|
|
metavar='CONTEXT', type='str',
|
|
help='Show CONTEXT lines around match')
|
|
g.add_option('-B',
|
|
action='callback', callback=carry,
|
|
metavar='CONTEXT', type='str',
|
|
help='Show CONTEXT lines before match')
|
|
g.add_option('-A',
|
|
action='callback', callback=carry,
|
|
metavar='CONTEXT', type='str',
|
|
help='Show CONTEXT lines after match')
|
|
g.add_option('-l', '--name-only', '--files-with-matches',
|
|
action='callback', callback=carry,
|
|
help='Show only file names containing matching lines')
|
|
g.add_option('-L', '--files-without-match',
|
|
action='callback', callback=carry,
|
|
help='Show only file names not containing matching lines')
|
|
|
|
|
|
def Execute(self, opt, args):
|
|
out = GrepColoring(self.manifest.manifestProject.config)
|
|
|
|
cmd_argv = ['grep']
|
|
if out.is_on and git_require((1, 6, 3)):
|
|
cmd_argv.append('--color')
|
|
cmd_argv.extend(getattr(opt, 'cmd_argv', []))
|
|
|
|
if '-e' not in cmd_argv:
|
|
if not args:
|
|
self.Usage()
|
|
cmd_argv.append('-e')
|
|
cmd_argv.append(args[0])
|
|
args = args[1:]
|
|
|
|
projects = self.GetProjects(args)
|
|
|
|
full_name = False
|
|
if len(projects) > 1:
|
|
cmd_argv.append('--full-name')
|
|
full_name = True
|
|
|
|
have_rev = False
|
|
if opt.revision:
|
|
if '--cached' in cmd_argv:
|
|
print('fatal: cannot combine --cached and --revision', file=sys.stderr)
|
|
sys.exit(1)
|
|
have_rev = True
|
|
cmd_argv.extend(opt.revision)
|
|
cmd_argv.append('--')
|
|
|
|
git_failed = False
|
|
bad_rev = False
|
|
have_match = False
|
|
|
|
for project in projects:
|
|
try:
|
|
p = GitCommand(project,
|
|
cmd_argv,
|
|
bare=False,
|
|
capture_stdout=True,
|
|
capture_stderr=True)
|
|
except GitError as e:
|
|
git_failed = True
|
|
out.project('--- project %s ---' % project.relpath)
|
|
out.nl()
|
|
out.fail('%s', str(e))
|
|
out.nl()
|
|
continue
|
|
|
|
if p.Wait() != 0:
|
|
# no results
|
|
#
|
|
if p.stderr:
|
|
if have_rev and 'fatal: ambiguous argument' in p.stderr:
|
|
bad_rev = True
|
|
else:
|
|
out.project('--- project %s ---' % project.relpath)
|
|
out.nl()
|
|
out.fail('%s', p.stderr.strip())
|
|
out.nl()
|
|
continue
|
|
have_match = True
|
|
|
|
# We cut the last element, to avoid a blank line.
|
|
#
|
|
r = p.stdout.split('\n')
|
|
r = r[0:-1]
|
|
|
|
if have_rev and full_name:
|
|
for line in r:
|
|
rev, line = line.split(':', 1)
|
|
out.write("%s", rev)
|
|
out.write(':')
|
|
out.project(project.relpath)
|
|
out.write('/')
|
|
out.write("%s", line)
|
|
out.nl()
|
|
elif full_name:
|
|
for line in r:
|
|
out.project(project.relpath)
|
|
out.write('/')
|
|
out.write("%s", line)
|
|
out.nl()
|
|
else:
|
|
for line in r:
|
|
print(line)
|
|
|
|
if git_failed:
|
|
sys.exit(1)
|
|
elif have_match:
|
|
sys.exit(0)
|
|
elif have_rev and bad_rev:
|
|
for r in opt.revision:
|
|
print("error: can't search revision %s" % r, file=sys.stderr)
|
|
sys.exit(1)
|
|
else:
|
|
sys.exit(1)
|