repo: Support updating gerrit structure

Change-Id: I120c7ff6134d5a1dbf9686aa00b21253ba635538
diff --git a/repo/repo_settings.py b/repo/repo_settings.py
index a6401fc..38d05b2 100755
--- a/repo/repo_settings.py
+++ b/repo/repo_settings.py
@@ -15,6 +15,7 @@
 # limitations under the License.
 #
 import argparse
+import json
 import os
 import requests
 import subprocess
@@ -25,13 +26,26 @@
 PORT = "29418"
 GERRIT = "review.leafos.org"
 leaf_devices = "leaf/devices/devices.yaml"
+gerrit_structure = "leaf/gerrit-config/structure.yaml"
 
 def parse_args():
     parser = argparse.ArgumentParser()
-    parser.add_argument("-b", "--branch")
-    parser.add_argument("-d", "--device")
-    parser.add_argument("-f", "--project_file",
+    subparsers = parser.add_subparsers(dest='subcommand')
+    subparsers.required = True
+
+    # update
+    parser_update = subparsers.add_parser('update')
+    parser_update.add_argument("-b", "--branch")
+    parser_update.add_argument("-d", "--device")
+    parser_update.add_argument("-f", "--project_file",
                         default=".repo/manifests/snippets/leaf.xml")
+
+    # update_groups
+    parser_update_groups = subparsers.add_parser('update_groups')
+
+    # fetch_structure_from_gerrit
+    parser_fetch_structure = subparsers.add_parser('fetch_structure_from_gerrit')
+
     return parser.parse_args()
 
 def check_gh_token():
@@ -100,6 +114,13 @@
         check=False,
     )
 
+def set_gerrit_project_parent(project, parent, user):
+    subprocess.run(
+        ["ssh", "-n", "-p", PORT, f"{user}@{GERRIT}", "gerrit",
+            "set-project-parent", project, "--parent", parent],
+        check=False,
+    )
+
 def get_projects_from_devices(device, branch):
     projects = []
 
@@ -126,26 +147,80 @@
 
     return projects
 
+def get_projects_from_gerrit_structure():
+    with open(gerrit_structure, "r") as f:
+        return yaml.safe_load(f)
+
+def get_projects_from_gerrit(auth=None):
+    url = f"https://{GERRIT}/a/projects/?t" if auth else f"https://{GERRIT}/projects/?t"
+    resp = requests.get(url, auth=auth)
+    if resp.status_code != 200:
+        raise Exception(f"Error communicating with gerrit: {resp.text}")
+    projects = json.loads(resp.text[5:])
+    nodes = {}
+
+    for name, project in projects.items():
+        nodes[name] = []
+
+    for name, project in projects.items():
+        parent = project.get("parent")
+        if parent:
+            nodes[parent].append(name)
+    for project in nodes.keys():
+        nodes[project] = sorted(nodes[project])
+    return nodes
+
 def main():
     args = parse_args()
 
     gh_token = check_gh_token()
     gh_user = check_gh_user(gh_token)
 
-    if (args.device):
-        projects = get_projects_from_devices(args.device, args.branch)
-    else:
-        projects = get_projects_from_manifests(args.project_file, args.branch)
-    for project in projects:
-        name = project["name"]
-        if ("LeafOS-Project" in name) or ("LeafOS-Blobs" in name) or ("LeafOS-Devices" in name):
-            print(name)
-            org, repo = name.split("/")
-            branch = project["revision"]
-            create_github_repo(org, repo, gh_token)
-            set_github_repo_settings(name, branch, gh_token)
-            create_gerrit_project(name, branch, gh_user)
-            set_gerrit_project_head(name, branch, gh_user)
+    if args.subcommand == 'update':
+        if (args.device):
+            projects = get_projects_from_devices(args.device, args.branch)
+        else:
+            projects = get_projects_from_manifests(args.project_file, args.branch)
+        for project in projects:
+            name = project["name"]
+            if ("LeafOS-Project" in name) or ("LeafOS-Blobs" in name) or ("LeafOS-Devices" in name):
+                print(name)
+                org, repo = name.split("/")
+                branch = project["revision"]
+                create_github_repo(org, repo, gh_token)
+                set_github_repo_settings(name, branch, gh_token)
+                create_gerrit_project(name, branch, gh_user)
+                set_gerrit_project_head(name, branch, gh_user)
+    elif args.subcommand == 'update_groups':
+        projects = get_projects_from_gerrit_structure()
+        live_projects = get_projects_from_gerrit()
+        changes = {}
+
+        for parent, children in projects.items():
+            if parent in live_projects:
+                if (not projects[parent] or set(live_projects[parent]) == set(projects[parent])):
+                    continue
+                else:
+                    changes[parent] = list(set(projects[parent]) - set(live_projects[parent]))
+                    if not changes[parent]:
+                        del changes[parent]
+            else:
+                changes[parent] = children
+
+        if changes:
+            for parent, children in changes.items():
+                for child in children:
+                   print(f"Update parent of {child} to {parent}")
+                   set_gerrit_project_parent(child, parent, gh_user)
+    elif args.subcommand == 'fetch_structure_from_gerrit':
+        projects = get_projects_from_gerrit()
+
+        for node in sorted(projects.keys()):
+            children = sorted(projects[node])
+            if children:
+                print(f"{node}:")
+                for child in projects[node]:
+                    print(f"  - {child}")
 
 if __name__ == "__main__":
     main()