git-cl: Add yapfignore support to git cl format

Adds support for .yapfignore files to "git cl format" when formatting
Python files. yapf is supposed to parse the .yapfignore file in the
current working directory, but this appears to not work when files
are explicitly passed to yapf for formatting like they are in git cl
format. Instead, parse the .yapfignore file ourselves and skip over any
matching files.

Bug: angleproject:3985
Change-Id: I5e8469470fb8ddbaa914005b012ac1f39dfdd223
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/1849807
Reviewed-by: Aaron Gable <agable@chromium.org>
Commit-Queue: Brian Sheedy <bsheedy@chromium.org>
diff --git a/git_cl.py b/git_cl.py
index 3837d4d..285e271 100755
--- a/git_cl.py
+++ b/git_cl.py
@@ -14,6 +14,7 @@
 import base64
 import collections
 import datetime
+import glob
 import httplib
 import itertools
 import json
@@ -763,6 +764,44 @@
   return ret
 
 
+def _GetYapfIgnoreFilepaths(top_dir):
+  """Returns all filepaths that match the ignored files in the .yapfignore file.
+
+  yapf is supposed to handle the ignoring of files listed in .yapfignore itself,
+  but this functionality appears to break when explicitly passing files to
+  yapf for formatting. According to
+  https://github.com/google/yapf/blob/master/README.rst#excluding-files-from-formatting-yapfignore,
+  the .yapfignore file should be in the directory that yapf is invoked from,
+  which we assume to be the top level directory in this case.
+
+  Args:
+    top_dir: The top level directory for the repository being formatted.
+
+  Returns:
+    A set of all filepaths that should be ignored by yapf.
+  """
+  yapfignore_file = os.path.join(top_dir, '.yapfignore')
+  ignore_filepaths = set()
+  if not os.path.exists(yapfignore_file):
+    return ignore_filepaths
+
+  # glob works relative to the current working directory, so we need to ensure
+  # that we're at the top level directory.
+  old_cwd = os.getcwd()
+  try:
+    os.chdir(top_dir)
+    with open(yapfignore_file) as f:
+      for line in f.readlines():
+        stripped_line = line.strip()
+        # Comments and blank lines should be ignored.
+        if stripped_line.startswith('#') or stripped_line == '':
+          continue
+        ignore_filepaths |= set(glob.glob(stripped_line))
+    return ignore_filepaths
+  finally:
+    os.chdir(old_cwd)
+
+
 def print_stats(args):
   """Prints statistics about the change to the user."""
   # --no-ext-diff is broken in some versions of Git, so try to work around
@@ -5202,7 +5241,11 @@
     if not opts.full and filtered_py_files:
       py_line_diffs = _ComputeDiffLineRanges(filtered_py_files, upstream_commit)
 
+    ignored_yapf_files = _GetYapfIgnoreFilepaths(top_dir)
+
     for f in filtered_py_files:
+      if f in ignored_yapf_files:
+        continue
       yapf_config = _FindYapfConfigFile(f, yapf_configs, top_dir)
       if yapf_config is None:
         yapf_config = chromium_default_yapf_style