OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright 2016 The Chromium Authors. All rights reserved. | 2 # Copyright 2016 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Wrapper around git blame that ignores certain commits. | 6 """Wrapper around git blame that ignores certain commits. |
7 """ | 7 """ |
8 | 8 |
9 from __future__ import print_function | 9 from __future__ import print_function |
10 | 10 |
11 import argparse | 11 import argparse |
12 import collections | 12 import collections |
13 import logging | 13 import logging |
14 import os | 14 import os |
15 import subprocess2 | 15 import subprocess2 |
16 import sys | 16 import sys |
17 | 17 |
18 import git_common | 18 import git_common |
19 import git_dates | 19 import git_dates |
20 | 20 |
21 | 21 |
22 logging.getLogger().setLevel(logging.INFO) | 22 logging.getLogger().setLevel(logging.INFO) |
23 | 23 |
24 | 24 |
| 25 DEFAULT_IGNORE_FILE_NAME = '.git-blame-ignore-revs' |
| 26 |
| 27 |
25 class Commit(object): | 28 class Commit(object): |
26 """Info about a commit.""" | 29 """Info about a commit.""" |
27 def __init__(self, commithash): | 30 def __init__(self, commithash): |
28 self.commithash = commithash | 31 self.commithash = commithash |
29 self.author = None | 32 self.author = None |
30 self.author_mail = None | 33 self.author_mail = None |
31 self.author_time = None | 34 self.author_time = None |
32 self.author_tz = None | 35 self.author_tz = None |
33 self.committer = None | 36 self.committer = None |
34 self.committer_mail = None | 37 self.committer_mail = None |
(...skipping 281 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
316 # filename display for the entire blame output. | 319 # filename display for the entire blame output. |
317 if line.commit.filename != filename: | 320 if line.commit.filename != filename: |
318 show_filenames = True | 321 show_filenames = True |
319 | 322 |
320 new_parsed.append(line) | 323 new_parsed.append(line) |
321 | 324 |
322 pretty_print(new_parsed, show_filenames=show_filenames, out=out) | 325 pretty_print(new_parsed, show_filenames=show_filenames, out=out) |
323 | 326 |
324 return 0 | 327 return 0 |
325 | 328 |
| 329 |
| 330 def parse_ignore_file(ignore_file): |
| 331 for line in ignore_file: |
| 332 line = line.split('#', 1)[0].strip() |
| 333 if line: |
| 334 yield line |
| 335 |
| 336 |
326 def main(args, stdout=sys.stdout, stderr=sys.stderr): | 337 def main(args, stdout=sys.stdout, stderr=sys.stderr): |
327 parser = argparse.ArgumentParser( | 338 parser = argparse.ArgumentParser( |
328 prog='git hyper-blame', | 339 prog='git hyper-blame', |
329 description='git blame with support for ignoring certain commits.') | 340 description='git blame with support for ignoring certain commits.') |
330 parser.add_argument('-i', metavar='REVISION', action='append', dest='ignored', | 341 parser.add_argument('-i', metavar='REVISION', action='append', dest='ignored', |
331 default=[], help='a revision to ignore') | 342 default=[], help='a revision to ignore') |
| 343 parser.add_argument('--ignore-file', metavar='FILE', |
| 344 type=argparse.FileType('r'), dest='ignore_file', |
| 345 help='a file containing a list of revisions to ignore') |
| 346 parser.add_argument('--no-default-ignores', dest='no_default_ignores', |
| 347 help='Do not ignore commits from .git-blame-ignore-revs.') |
332 parser.add_argument('revision', nargs='?', default='HEAD', metavar='REVISION', | 348 parser.add_argument('revision', nargs='?', default='HEAD', metavar='REVISION', |
333 help='revision to look at') | 349 help='revision to look at') |
334 parser.add_argument('filename', metavar='FILE', help='filename to blame') | 350 parser.add_argument('filename', metavar='FILE', help='filename to blame') |
335 | 351 |
336 args = parser.parse_args(args) | 352 args = parser.parse_args(args) |
337 try: | 353 try: |
338 repo_root = git_common.repo_root() | 354 repo_root = git_common.repo_root() |
339 except subprocess2.CalledProcessError as e: | 355 except subprocess2.CalledProcessError as e: |
340 stderr.write(e.stderr) | 356 stderr.write(e.stderr) |
341 return e.returncode | 357 return e.returncode |
342 | 358 |
343 # Make filename relative to the repository root, and cd to the root dir (so | 359 # Make filename relative to the repository root, and cd to the root dir (so |
344 # all filenames throughout this script are relative to the root). | 360 # all filenames throughout this script are relative to the root). |
345 filename = os.path.relpath(args.filename, repo_root) | 361 filename = os.path.relpath(args.filename, repo_root) |
346 os.chdir(repo_root) | 362 os.chdir(repo_root) |
347 | 363 |
348 # Normalize filename so we can compare it to other filenames git gives us. | 364 # Normalize filename so we can compare it to other filenames git gives us. |
349 filename = os.path.normpath(filename) | 365 filename = os.path.normpath(filename) |
350 filename = os.path.normcase(filename) | 366 filename = os.path.normcase(filename) |
351 | 367 |
| 368 ignored_list = list(args.ignored) |
| 369 if not args.no_default_ignores and os.path.exists(DEFAULT_IGNORE_FILE_NAME): |
| 370 with open(DEFAULT_IGNORE_FILE_NAME) as ignore_file: |
| 371 ignored_list.extend(parse_ignore_file(ignore_file)) |
| 372 |
| 373 if args.ignore_file: |
| 374 ignored_list.extend(parse_ignore_file(args.ignore_file)) |
| 375 |
352 ignored = set() | 376 ignored = set() |
353 for c in args.ignored: | 377 for c in ignored_list: |
354 try: | 378 try: |
355 ignored.add(git_common.hash_one(c)) | 379 ignored.add(git_common.hash_one(c)) |
356 except subprocess2.CalledProcessError as e: | 380 except subprocess2.CalledProcessError as e: |
357 # Custom error message (the message from git-rev-parse is inappropriate). | 381 # Custom warning string (the message from git-rev-parse is inappropriate). |
358 stderr.write('fatal: unknown revision \'%s\'.\n' % c) | 382 stderr.write('warning: unknown revision \'%s\'.\n' % c) |
359 return e.returncode | |
360 | 383 |
361 return hyper_blame(ignored, filename, args.revision, out=stdout, err=stderr) | 384 return hyper_blame(ignored, filename, args.revision, out=stdout, err=stderr) |
362 | 385 |
363 | 386 |
364 if __name__ == '__main__': # pragma: no cover | 387 if __name__ == '__main__': # pragma: no cover |
365 with git_common.less() as less_input: | 388 with git_common.less() as less_input: |
366 sys.exit(main(sys.argv[1:], stdout=less_input)) | 389 sys.exit(main(sys.argv[1:], stdout=less_input)) |
OLD | NEW |