blob: 2a854211a2bf6bc2f157d2e92c51d53af483f650 [file] [log] [blame]
#!/usr/bin/env python3
import os
import subprocess
from pathlib import Path
import optparse
import sys
import re
def run(command, cwd=None):
try:
return subprocess.Popen(
command, shell=True, cwd=cwd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
except OSError as err:
raise OSError("Error in command '{0}': {1}".format(command, err))
def parse_location(line):
# take substring between @@
# take second part of it
location = line.split(b'@@')[1].strip().split(b' ')[1]
tokens = location.split(b',')
if len(tokens) == 1:
return (int(tokens[0][1:]), 1)
elif len(tokens) == 2:
return (int(tokens[0][1:]), int(tokens[1]))
def changed_files(directory, scope):
result = {}
proc = run('git diff --no-prefix --unified={0}'.format(scope), cwd=str(directory))
file_path = None
for line in iter(proc.stdout.readline, b''):
if line.startswith(b'diff --git '):
# this would be problematic if directory has space in the name
file_name = line.split(b' ')[3].strip()
file_path = str(directory.joinpath(str(file_name, 'utf-8')))
result[file_path] = set()
continue
if line.startswith(b'@@'):
start_pos, number_of_lines = parse_location(line)
for line_number in range(start_pos, start_pos + number_of_lines):
result[file_path].add(line_number)
return result
def print_changed(file_name, line_number):
print('{0}:{1}'.format(str(file_name), str(line_number)))
def changes(dirs, scope):
result = {}
for directory in dirs:
result.update(changed_files(directory, scope))
return result
def repositories(root):
for directory in Path(root).rglob('.git'):
if not directory.is_dir():
continue
yield directory.parent
def setup_argparse():
parser = optparse.OptionParser(description="Filter output to remove unrelated warning")
parser.add_option(
"-r",
"--regexp",
dest="regexp",
default='(?P<file_name>[^:]+):(?P<line>\d+).*',
help="Regexp used to extract file_name and line number",
)
parser.add_option(
"-s",
"--scope",
dest="scope",
default=0,
help="Number of lines surrounding the change we consider relevant",
)
parser.add_option(
"-p",
"--print-only",
action="store_true",
dest="print_only",
default=False,
help="Print changed lines only",
)
return parser.parse_args()
def filter_stdin(regexp, changes):
any_matches = False
for line in iter(sys.stdin.readline, ''):
matches = re.match(regexp, line)
if matches:
file_name = matches.group('file_name')
line_number = int(matches.group('line'))
if file_name in changes and line_number in changes[file_name]:
print(line, end='')
any_matches = True
return any_matches
def validate_regexp(regexp):
index = regexp.groupindex
if 'file_name' in index and 'line' in index:
return True
else:
raise TypeError("Regexp must define following groups:\n - file_name\n - line")
def main():
opts, args = setup_argparse()
if opts.print_only:
for file_name, changed_lines in changes(repositories('.'), opts.scope).items():
for line_number in changed_lines:
print_changed(file_name, line_number)
return 0
else:
regexp = re.compile(opts.regexp)
validate_regexp(regexp)
if filter_stdin(regexp, changes(repositories('.'), opts.scope)):
return 1
else:
return 0
if __name__ == "__main__":
try:
sys.exit(main())
except KeyboardInterrupt:
pass