git hyper-blame: Added automatically ignoring revs from a file.

Added --ignore-file argument, so you can specify ignored commits in a
file rather than as raw command-line arguments. Also, automatically
searches for a file called .git-blame-ignore-revs, which is
automatically used as an ignore list by default.

Also, specifying an unknown revision (either on the command line or in a
file) now generates a warning, not an error.

Notes on some decisions:
- The file is called .git-blame-ignore-revs (not mentioning hyper-blame)
  because we may use the same list in tools other than hyper-blame in
  the future.
- We look at the *currently checked out* version of
  .git-blame-ignore-revs (not the version at the specified revision) for
  consistency with .git-ignore. Because we only expect revisions to be
  added (not deleted), it should be fine to use an ignore list from a
  newer version than the revision being blamed.
- We considered using git notes for the ignore list so that you could
  add a revision to the ignore list without needing a follow-up CL.
  However, there are some problems with this approach. git notes is not
  automatically synced with git clone/pull. Also the Chromium infra
  tools (Reitveld, CQ) are not set up to allow modification of git
  notes, nor are changes to git notes subject to OWNERS checks. Using a
  regular file ensures all users synced to a particular revision are
  using the same ignore list.

BUG=574290

Review URL: https://codereview.chromium.org/1697423004

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@298897 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/git_hyper_blame.py b/git_hyper_blame.py
index 5a7daa0..b9965a7 100755
--- a/git_hyper_blame.py
+++ b/git_hyper_blame.py
@@ -22,6 +22,9 @@
 logging.getLogger().setLevel(logging.INFO)
 
 
+DEFAULT_IGNORE_FILE_NAME = '.git-blame-ignore-revs'
+
+
 class Commit(object):
   """Info about a commit."""
   def __init__(self, commithash):
@@ -323,12 +326,25 @@
 
   return 0
 
+
+def parse_ignore_file(ignore_file):
+  for line in ignore_file:
+    line = line.split('#', 1)[0].strip()
+    if line:
+      yield line
+
+
 def main(args, stdout=sys.stdout, stderr=sys.stderr):
   parser = argparse.ArgumentParser(
       prog='git hyper-blame',
       description='git blame with support for ignoring certain commits.')
   parser.add_argument('-i', metavar='REVISION', action='append', dest='ignored',
                       default=[], help='a revision to ignore')
+  parser.add_argument('--ignore-file', metavar='FILE',
+                      type=argparse.FileType('r'), dest='ignore_file',
+                      help='a file containing a list of revisions to ignore')
+  parser.add_argument('--no-default-ignores', dest='no_default_ignores',
+                      help='Do not ignore commits from .git-blame-ignore-revs.')
   parser.add_argument('revision', nargs='?', default='HEAD', metavar='REVISION',
                       help='revision to look at')
   parser.add_argument('filename', metavar='FILE', help='filename to blame')
@@ -349,14 +365,21 @@
   filename = os.path.normpath(filename)
   filename = os.path.normcase(filename)
 
+  ignored_list = list(args.ignored)
+  if not args.no_default_ignores and os.path.exists(DEFAULT_IGNORE_FILE_NAME):
+    with open(DEFAULT_IGNORE_FILE_NAME) as ignore_file:
+      ignored_list.extend(parse_ignore_file(ignore_file))
+
+  if args.ignore_file:
+    ignored_list.extend(parse_ignore_file(args.ignore_file))
+
   ignored = set()
-  for c in args.ignored:
+  for c in ignored_list:
     try:
       ignored.add(git_common.hash_one(c))
     except subprocess2.CalledProcessError as e:
-      # Custom error message (the message from git-rev-parse is inappropriate).
-      stderr.write('fatal: unknown revision \'%s\'.\n' % c)
-      return e.returncode
+      # Custom warning string (the message from git-rev-parse is inappropriate).
+      stderr.write('warning: unknown revision \'%s\'.\n' % c)
 
   return hyper_blame(ignored, filename, args.revision, out=stdout, err=stderr)