Merge pull request #7876 from gilles-peskine-arm/code-style-since-2.28

Backport 2.28: code_style.py --since
diff --git a/scripts/code_style.py b/scripts/code_style.py
index c31fb29..7de93b0 100755
--- a/scripts/code_style.py
+++ b/scripts/code_style.py
@@ -22,7 +22,7 @@
 import re
 import subprocess
 import sys
-from typing import FrozenSet, List
+from typing import FrozenSet, List, Optional
 
 UNCRUSTIFY_SUPPORTED_VERSION = "0.75.1"
 CONFIG_FILE = ".uncrustify.cfg"
@@ -63,31 +63,38 @@
     checks = re.findall(CHECK_CALL_RE, content)
     return frozenset(word for s in checks for word in s.split())
 
-def get_src_files() -> List[str]:
+def get_src_files(since: Optional[str]) -> List[str]:
     """
-    Use git ls-files to get a list of the source files
+    Use git to get a list of the source files.
+
+    The optional argument since is a commit, indicating to only list files
+    that have changed since that commit. Without this argument, list all
+    files known to git.
+
+    Only C files are included, and certain files (generated, or 3rdparty)
+    are excluded.
     """
-    git_ls_files_cmd = ["git", "ls-files",
-                        "*.[hc]",
-                        "tests/suites/*.function",
-                        "scripts/data_files/*.fmt"]
+    file_patterns = ["*.[hc]",
+                     "tests/suites/*.function",
+                     "scripts/data_files/*.fmt"]
+    output = subprocess.check_output(["git", "ls-files"] + file_patterns,
+                                     universal_newlines=True)
+    src_files = output.split()
+    if since:
+        output = subprocess.check_output(["git", "diff", "--name-only",
+                                          since, "--"] +
+                                         src_files,
+                                         universal_newlines=True)
+        src_files = output.split()
 
-    result = subprocess.run(git_ls_files_cmd, stdout=subprocess.PIPE,
-                            check=False)
-
-    if result.returncode != 0:
-        print_err("git ls-files returned: " + str(result.returncode))
-        return []
-    else:
-        generated_files = list_generated_files()
-        src_files = str(result.stdout, "utf-8").split()
-        # Don't correct style for third-party files (and, for simplicity,
-        # companion files in the same subtree), or for automatically
-        # generated files (we're correcting the templates instead).
-        src_files = [filename for filename in src_files
-                     if not (filename.startswith("3rdparty/") or
-                             filename in generated_files)]
-        return src_files
+    generated_files = list_generated_files()
+    # Don't correct style for third-party files (and, for simplicity,
+    # companion files in the same subtree), or for automatically
+    # generated files (we're correcting the templates instead).
+    src_files = [filename for filename in src_files
+                 if not (filename.startswith("3rdparty/") or
+                         filename in generated_files)]
+    return src_files
 
 def get_uncrustify_version() -> str:
     """
@@ -182,6 +189,9 @@
     parser.add_argument('-f', '--fix', action='store_true',
                         help=('modify source files to fix the code style '
                               '(default: print diff, do not modify files)'))
+    parser.add_argument('-s', '--since', metavar='COMMIT',
+                        help=('only check files modified since the specified commit'
+                              ' (e.g. --since=HEAD~3 or --since=development)'))
     # --subset is almost useless: it only matters if there are no files
     # ('code_style.py' without arguments checks all files known to Git,
     # 'code_style.py --subset' does nothing). In particular,
@@ -194,7 +204,7 @@
 
     args = parser.parse_args()
 
-    covered = frozenset(get_src_files())
+    covered = frozenset(get_src_files(args.since))
     # We only check files that are known to git
     if args.subset or args.operands:
         src_files = [f for f in args.operands if f in covered]