Repository: pre-commit/pre-commit

Issue:
Slow on Windows with lots of submodules
`git status` is substantially slower on Windows than on Linux. I'm working in a repo with ~240 submodules. By default, when you run `git status` in a repo with submodules, it'll recursively run `git status` in every submodule as well (sequentially). `git diff` behaves similarly to `git status`. Pre-commit appears to run `git status` once at the beginning (to check if anything needs to be stashed?) and then runs `git diff` before and after each hook (to detect any changes made by the hook?). These calls quickly add up and make pre-commit unusable in these large repos.

The bottlenecks can be seen clearly in this Process Monitor capture:

![procmon_pre-commit](https://user-images.githubusercontent.com/377567/99610629-ce40e900-29d7-11eb-881c-9b57c94cdddd.png)

Running one hook takes ~35s in this case. Two hooks take ~54s.

If I add `--ignore-submodules` to [this `git status` call](https://github.com/pre-commit/pre-commit/blob/3112e080883c4973262569d81b6d3307db08b210/pre_commit/git.py#L133) and [this `git diff` call](https://github.com/pre-commit/pre-commit/blob/3112e080883c4973262569d81b6d3307db08b210/pre_commit/commands/run.py#L253), pre-commit only takes ~1.5s for one hook and ~1.9s for two hooks.

If I understand the use of `git status` and `git diff` in pre-commit correctly, it seems like it would be completely safe to ignore submodules in the `git status` call since submodules can't be stashed to restore later. The `git diff` case might be more risky because I guess hooks could modify submodules? Perhaps an "ignore submodules" setting could be added to pre-commit?


Discussion:
I think (?) this is a duplicate of #1564 

if there's a way to ignore submodules that would definitely be an improvement, I don't think there's really going to be anything pre-commit can realistically do about those as `diff`d
Thanks for taking a look. We _are_ (trying to) use read/write hooks (clang-format being the primary one), so while #1564 could definitely help in some cases, I think this issue is somewhat different (although related to the same issue of basic git commands not scaling well on large repos).

Resolving patch:
diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py
--- a/pre_commit/commands/run.py
+++ b/pre_commit/commands/run.py
@@ -258,7 +258,9 @@ def _all_filenames(args: argparse.Namespace) -> Collection[str]:
 
 
 def _get_diff() -> bytes:
-    _, out, _ = cmd_output_b('git', 'diff', '--no-ext-diff', retcode=None)
+    _, out, _ = cmd_output_b(
+        'git', 'diff', '--no-ext-diff', '--ignore-submodules', retcode=None,
+    )
     return out
 
 
diff --git a/pre_commit/git.py b/pre_commit/git.py
--- a/pre_commit/git.py
+++ b/pre_commit/git.py
@@ -130,7 +130,9 @@ def get_staged_files(cwd: Optional[str] = None) -> List[str]:
 
 
 def intent_to_add_files() -> List[str]:
-    _, stdout, _ = cmd_output('git', 'status', '--porcelain', '-z')
+    _, stdout, _ = cmd_output(
+        'git', 'status', '--ignore-submodules', '--porcelain', '-z',
+    )
     parts = list(reversed(zsplit(stdout)))
     intent_to_add = []
     while parts:


Test patch: