findmissing: Find subsequent patches to fixes

It looks a bit silly if we are constantly submitting fixes for fixes if
know the subsequent patches before hand. Solution is to do an interative
lookup.

Note this solution is temporarily and will be replaced by
crrev.com/c/2208004 once the database supports CTEs.

BUG=None
TEST=Pass in known test SHA

Change-Id: Ia334b028378c9a8aa1aaebaf175369bc65dd73d7
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/dev-util/+/2146069
Reviewed-by: Guenter Roeck <groeck@chromium.org>
Commit-Queue: Curtis Malainey <cujomalainey@chromium.org>
Tested-by: Guenter Roeck <groeck@chromium.org>
diff --git a/contrib/findmissing/cloudsql_interface.py b/contrib/findmissing/cloudsql_interface.py
index f304692..7398a97 100755
--- a/contrib/findmissing/cloudsql_interface.py
+++ b/contrib/findmissing/cloudsql_interface.py
@@ -15,6 +15,25 @@
 
 DEFAULT_MERGED_REASON = 'Fix merged into linux chrome'
 
+
+def upstream_fixes_for_shas(db, upstream_shas):
+    """Returns list of fixer sha's for a given upstream sha.
+
+    TODO(*): remove this after build_ordered_fixes_table_map moved to SQL CTE
+    Note: above todo is blocked by migration to MySQL 5.7, once upgraded then we can switch
+    """
+    upstream_shas = ["\'" + sha + "\'" for sha in upstream_shas]
+    c = db.cursor()
+
+    # format string here since we are inserting n elements
+    q = """SELECT fixedby_upstream_sha
+            FROM upstream_fixes
+            WHERE upstream_sha IN ({})""".format(', '.join(upstream_shas))
+    c.execute(q)
+
+    return [a[0] for a in c.fetchall()]
+
+
 def get_fixes_table_primary_key(db, fixes_table, fix_change_id):
     """Retrieves the primary keys from a fixes table using changeid."""
     c = db.cursor(MySQLdb.cursors.DictCursor)
diff --git a/contrib/findmissing/missing.py b/contrib/findmissing/missing.py
index 44574ba..d92b89e 100755
--- a/contrib/findmissing/missing.py
+++ b/contrib/findmissing/missing.py
@@ -25,6 +25,54 @@
 NEW_CL_DAILY_LIMIT_PER_BRANCH = 1
 
 
+def get_subsequent_fixes(db, fixer_upstream_sha):
+    """Recursively builds a Dictionary of fixes for a fixer upstream sha.
+
+    Table will be following format given fixer_upstream_sha A:
+    {'A': ['B','C', 'H'],
+     'B': ['D', 'E'],
+     'C': ['F','G'],
+     'D': [],
+     'E': ['H'],
+     'F': [],
+     'G': [],
+     'H': []
+    }  where B Fixes A, C fixes A, D Fixes B, E Fixes B, etc.
+
+    This would end up return a list
+    ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
+    Note that H is a fix for A but is scheduled till after E since it also fixes E
+
+    TODO(*): modify this to use common table expressions (CTE) to avoid
+        building dependency table in python. Changing it to use CTE will
+        remove the use of this function.
+        Note: This change is blocked by infra: It requires mysql >= 8.0, but
+        CloudSQL currently only supports mysql version 5.7.
+    """
+    fixes = {}
+    fixes[fixer_upstream_sha] = 0
+    iteration = 0
+    new_shas = set()
+    new_shas.add(fixer_upstream_sha)
+
+    while new_shas:
+        iteration += 1
+        seen_shas = set(fixes.keys())
+        fixes_for_new_shas = cloudsql_interface.upstream_fixes_for_shas(db, list(new_shas))
+        for sha in fixes_for_new_shas:
+            # if new fixes fix previous fixes then we want to grab them last
+            current_iteration = fixes[sha] if fixes.get(sha) else 0
+            fixes[sha] = max(current_iteration, iteration)
+        new_shas = set(fixes_for_new_shas) - set(seen_shas)
+
+    # sort fixes into list
+    fixing_shas = list(fixes.items())
+    fixing_shas.sort(key=lambda x: x[1])
+    # remove order information
+    fixing_shas = [sha[0] for sha in fixing_shas]
+    return fixing_shas
+
+
 def upstream_sha_to_kernel_sha(db, chosen_table, branch, upstream_sha):
     """Retrieves chromeos/stable sha by indexing db.