get_maintainer.py: process patches individually

When given the -m/-merge-check option, get_maintainer.py parses the
Acked-by and Reviewed-by tags that may be found in a patchset or
Github PR. In presence of several patches the tags should apply to
each patch individually, not to the whole patchset as is currently
done. As a result, the script may fail to report some unapproved
changes.

Fix this issue by splitting patchsets into individual patches before
processing.

Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org>
Reviewed-by: Joakim Bech <joakim.bech@linaro.org>
diff --git a/scripts/get_maintainer.py b/scripts/get_maintainer.py
index 72cbeea..645db61 100755
--- a/scripts/get_maintainer.py
+++ b/scripts/get_maintainer.py
@@ -17,6 +17,7 @@
 DIFF_GIT_RE = re.compile(r'^diff --git a/(?P<path>.*) ')
 REVIEWED_RE = re.compile(r'^Reviewed-by: (?P<approver>.*>)')
 ACKED_RE = re.compile(r'^Acked-by: (?P<approver>.*>)')
+PATCH_START = re.compile(r'^From [0-9a-f]{40}')
 
 
 def get_args():
@@ -88,9 +89,41 @@
     return subsystems
 
 
+# If @patchset is a patchset files and contains 2 patches or more, write
+# individual patches to temporary files and return the paths.
+# Otherwise return [].
+def split_patchset(patchset):
+    psname = os.path.basename(patchset).replace('.', '_')
+    patchnum = 0
+    of = None
+    ret = []
+    f = None
+    try:
+        f = open(patchset, "r")
+    except OsError:
+        return []
+    for line in f:
+        match = re.search(PATCH_START, line)
+        if match:
+            # New patch found: create new file
+            patchnum += 1
+            prefix = "{}_{}_".format(patchnum, psname)
+            of = tempfile.NamedTemporaryFile(mode="w", prefix=prefix,
+                                             suffix=".patch",
+                                             delete=False)
+            ret.append(of.name)
+        if of:
+            of.write(line)
+    if len(ret) >= 2:
+        return ret
+    if len(ret) == 1:
+        os.remove(ret[0])
+    return []
+
+
 # If @path is a patch file, returns the paths touched by the patch as well
 # as the content of the review/ack tags
-def get_paths_from_patchset(patch):
+def get_paths_from_patch(patch):
     paths = []
     approvers = []
     try:
@@ -197,17 +230,27 @@
     args = get_args()
     all_subsystems = parse_maintainers()
     paths = []
+    arglist = []
     downloads = []
+    split_patches = []
 
     for pr in args.github_pr or []:
         downloads += [download(pr)]
 
     for arg in args.arg + downloads:
+        if os.path.exists(arg):
+            patches = split_patchset(arg)
+            if patches:
+                split_patches += patches
+                continue
+        arglist.append(arg)
+
+    for arg in arglist + split_patches:
         patch_paths = []
         approved_by = []
         if os.path.exists(arg):
-            # Try to parse as a patch or patch set
-            (patch_paths, approved_by) = get_paths_from_patchset(arg)
+            # Try to parse as a patch
+            (patch_paths, approved_by) = get_paths_from_patch(arg)
         if not patch_paths:
             # Not a patch, consider the path itself
             # as_posix() cleans the path a little bit (suppress leading ./ and
@@ -225,7 +268,7 @@
             if not approved:
                 paths += [path]
 
-    for f in downloads:
+    for f in downloads + split_patches:
         os.remove(f)
 
     if args.file: