prfilter 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. #!/usr/bin/env python3
  2. import collections
  3. import json
  4. import subprocess
  5. import sys
  6. DEFAULT_REF = 'origin/master'
  7. def get_diff_ranges(ref=DEFAULT_REF):
  8. '''Get chunks affected by a merge request
  9. Args:
  10. ref (str): a reference to diff the HEAD against (default: origin/master)
  11. Returns:
  12. dict: a dict with filenames in keys and list of `(start, end)` ranges in
  13. values (start is inclusive, end is not, wrt :py:func:`range`)
  14. '''
  15. files = collections.defaultdict(list)
  16. data = subprocess.check_output(['git', 'diff', '-U0', ref, 'HEAD']).decode()
  17. path = None
  18. for line in data.split('\n'):
  19. if line.startswith('+++ '):
  20. path = (None if line == '+++ /dev/null'
  21. else line.split('/', maxsplit=1)[-1])
  22. continue
  23. if line.startswith('@@ '):
  24. # @@ -8,0 +9 @@ [class name or previous line or whatever]
  25. if path is None: # /dev/null
  26. continue
  27. _, _, plus, *_ = line.split()
  28. start, length, *_ = *(int(i) for i in plus[1:].split(',')), 1
  29. if not length:
  30. # remove-only chunk
  31. continue
  32. files[path].append((start, start + length))
  33. return files
  34. class Diff:
  35. '''A quick and dirty diff evaluator
  36. >>> diff = Diff()
  37. >>> (message['file'], message['line']) in diff
  38. True # or False
  39. The default diff is to the ``origin/master`` ref.
  40. '''
  41. # pylint: disable=too-few-public-methods
  42. def __init__(self, ref=DEFAULT_REF):
  43. self._files = get_diff_ranges(ref)
  44. def __contains__(self, pathline):
  45. path, line = pathline
  46. try:
  47. return any(start <= line < end for start, end in self._files[path])
  48. except KeyError:
  49. return False
  50. def main():
  51. diff = Diff()
  52. with sys.stdin:
  53. pylint = json.load(sys.stdin)
  54. ret = 0
  55. for message in pylint:
  56. # shellcheck
  57. if 'path' not in message:
  58. message['path'] = message['file']
  59. if 'symbol' not in message:
  60. message['symbol'] = message['code']
  61. if (message['path'], message['line']) in diff:
  62. if not ret:
  63. print('MESSAGES AFFECTING THIS PR:')
  64. print('{path} +{line}:{column}: {symbol}: {message}'.format(
  65. **message))
  66. ret += 1
  67. return min(ret, 255)
  68. if __name__ == '__main__':
  69. sys.exit(main())