submodule_roll: Nest .gitmodules read

Nest the reading of the .gitmodules file. This read is cached, so only
the first submodule needs step data.

Bug: b/341756093
Change-Id: I81b33ce5309f8384794e3071346b7bcf015f4486
Reviewed-on: https://pigweed-review.googlesource.com/c/infra/recipes/+/235973
Lint: Lint 🤖 <android-build-ayeaye@system.gserviceaccount.com>
Commit-Queue: Rob Mohr <mohrr@google.com>
Reviewed-by: Taylor Cramer <cramertj@google.com>
Presubmit-Verified: CQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/recipe_modules/submodule_roll/api.py b/recipe_modules/submodule_roll/api.py
index 2f7628d..f465c4b 100644
--- a/recipe_modules/submodule_roll/api.py
+++ b/recipe_modules/submodule_roll/api.py
@@ -99,8 +99,6 @@
         checkout: checkout_api.CheckoutContext,
         submodule_entry: SubmoduleEntry,
     ) -> git_roll_util_api.Roll:
-        gitmodules = self.read_gitmodules(checkout.root / '.gitmodules')
-
         submodule = Submodule(
             path=submodule_entry.path,
             name=submodule_entry.name or submodule_entry.path,
@@ -109,6 +107,8 @@
         submodule.dir = checkout.root / submodule.path
 
         with self.m.step.nest(submodule.name) as pres:
+            gitmodules = self.read_gitmodules(checkout.root / '.gitmodules')
+
             section = f'submodule "{submodule.name}"'
             if not gitmodules.has_section(section):
                 sections = gitmodules.sections()
diff --git a/recipe_modules/submodule_roll/test_api.py b/recipe_modules/submodule_roll/test_api.py
index 56f9544..e29dceb 100644
--- a/recipe_modules/submodule_roll/test_api.py
+++ b/recipe_modules/submodule_roll/test_api.py
@@ -30,7 +30,7 @@
     def entry(self, path, name=None, branch=None):
         return SubmoduleEntry(path=path, name=name or path, branch=branch or '')
 
-    def gitmodules(self, **submodules):
+    def gitmodules(self, prefix=None, **submodules):
         branches = {}
         for k, v in submodules.items():
             if k.endswith('_branch'):
@@ -49,6 +49,8 @@
             if k in branches:
                 text.append(f'\tbranch = {branches[k]}\n')
 
+        prefix = prefix.removesuffix('.') + '.' if prefix else ''
+
         return self.step_data(
-            'read .gitmodules', self.m.file.read_text(''.join(text))
+            f'{prefix}read .gitmodules', self.m.file.read_text(''.join(text))
         )
diff --git a/recipe_modules/submodule_roll/tests/full.expected/missing.json b/recipe_modules/submodule_roll/tests/full.expected/missing.json
index 75a27e8..4ea564b 100644
--- a/recipe_modules/submodule_roll/tests/full.expected/missing.json
+++ b/recipe_modules/submodule_roll/tests/full.expected/missing.json
@@ -1,5 +1,12 @@
 [
   {
+    "cmd": [],
+    "name": "b2",
+    "~followup_annotations": [
+      "@@@STEP_FAILURE@@@"
+    ]
+  },
+  {
     "cmd": [
       "vpython3",
       "-u",
@@ -11,8 +18,9 @@
       "/path/to/tmp/"
     ],
     "infra_step": true,
-    "name": "read .gitmodules",
+    "name": "b2.read .gitmodules",
     "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
       "@@@STEP_LOG_LINE@.gitmodules@[submodule \"a1\"]@@@",
       "@@@STEP_LOG_LINE@.gitmodules@\tpath = a1@@@",
       "@@@STEP_LOG_LINE@.gitmodules@\turl = sso://foo/a1@@@",
@@ -20,13 +28,6 @@
     ]
   },
   {
-    "cmd": [],
-    "name": "b2",
-    "~followup_annotations": [
-      "@@@STEP_FAILURE@@@"
-    ]
-  },
-  {
     "failure": {
       "failure": {},
       "humanReason": "no submodule \"b2\" (submodules: \"a1\")"
diff --git a/recipe_modules/submodule_roll/tests/full.expected/noop.json b/recipe_modules/submodule_roll/tests/full.expected/noop.json
index 75e319e..0151095 100644
--- a/recipe_modules/submodule_roll/tests/full.expected/noop.json
+++ b/recipe_modules/submodule_roll/tests/full.expected/noop.json
@@ -1,5 +1,9 @@
 [
   {
+    "cmd": [],
+    "name": "b2"
+  },
+  {
     "cmd": [
       "vpython3",
       "-u",
@@ -11,8 +15,9 @@
       "/path/to/tmp/"
     ],
     "infra_step": true,
-    "name": "read .gitmodules",
+    "name": "b2.read .gitmodules",
     "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
       "@@@STEP_LOG_LINE@.gitmodules@[submodule \"b2\"]@@@",
       "@@@STEP_LOG_LINE@.gitmodules@\tpath = b2@@@",
       "@@@STEP_LOG_LINE@.gitmodules@\turl = https://foo.googlesource.com/b2@@@",
@@ -21,10 +26,6 @@
     ]
   },
   {
-    "cmd": [],
-    "name": "b2"
-  },
-  {
     "cmd": [
       "git",
       "ls-remote",
diff --git a/recipe_modules/submodule_roll/tests/full.expected/success.json b/recipe_modules/submodule_roll/tests/full.expected/success.json
index aff9ba8..bf585a4 100644
--- a/recipe_modules/submodule_roll/tests/full.expected/success.json
+++ b/recipe_modules/submodule_roll/tests/full.expected/success.json
@@ -1,5 +1,9 @@
 [
   {
+    "cmd": [],
+    "name": "a1"
+  },
+  {
     "cmd": [
       "vpython3",
       "-u",
@@ -11,8 +15,9 @@
       "/path/to/tmp/"
     ],
     "infra_step": true,
-    "name": "read .gitmodules",
+    "name": "a1.read .gitmodules",
     "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
       "@@@STEP_LOG_LINE@.gitmodules@[submodule \"a1\"]@@@",
       "@@@STEP_LOG_LINE@.gitmodules@\tpath = a1@@@",
       "@@@STEP_LOG_LINE@.gitmodules@\turl = sso://foo/a1@@@",
@@ -23,10 +28,6 @@
     ]
   },
   {
-    "cmd": [],
-    "name": "a1"
-  },
-  {
     "cmd": [
       "git",
       "ls-remote",
diff --git a/recipe_modules/submodule_roll/tests/full.py b/recipe_modules/submodule_roll/tests/full.py
index e40465a..908b66f 100644
--- a/recipe_modules/submodule_roll/tests/full.py
+++ b/recipe_modules/submodule_roll/tests/full.py
@@ -60,20 +60,24 @@
     yield api.test(
         'success',
         properties(api.submodule_roll.entry('a1')),
+        api.submodule_roll.gitmodules(
+            'a1',
+            a1='sso://foo/a1',
+            b2='sso://foo/b2',
+        ),
         api.gitiles.log("a1.log a1", "A"),
-        api.submodule_roll.gitmodules(a1='sso://foo/a1', b2='sso://foo/b2'),
     )
 
     yield api.test(
         'noop',
         properties(api.submodule_roll.entry('b2')),
-        api.submodule_roll.gitmodules(b2='b2', b2_branch='branch'),
+        api.submodule_roll.gitmodules('b2', b2='b2', b2_branch='branch'),
         api.gitiles.log("b2.log b2", "A", n=0),
     )
 
     yield api.test(
         'missing',
         properties(api.submodule_roll.entry('b2')),
-        api.submodule_roll.gitmodules(a1='sso://foo/a1'),
+        api.submodule_roll.gitmodules('b2', a1='sso://foo/a1'),
         status='FAILURE',
     )
diff --git a/recipes/submodule_roller.expected/noop.json b/recipes/submodule_roller.expected/noop.json
index 04046d2..a4fcf9d 100644
--- a/recipes/submodule_roller.expected/noop.json
+++ b/recipes/submodule_roller.expected/noop.json
@@ -580,6 +580,10 @@
     ]
   },
   {
+    "cmd": [],
+    "name": "a1"
+  },
+  {
     "cmd": [
       "vpython3",
       "-u",
@@ -591,8 +595,9 @@
       "/path/to/tmp/"
     ],
     "infra_step": true,
-    "name": "read .gitmodules",
+    "name": "a1.read .gitmodules",
     "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
       "@@@STEP_LOG_LINE@.gitmodules@[submodule \"a1\"]@@@",
       "@@@STEP_LOG_LINE@.gitmodules@\tpath = a1@@@",
       "@@@STEP_LOG_LINE@.gitmodules@\turl = https://foo.googlesource.com/a1@@@",
@@ -604,10 +609,6 @@
     ]
   },
   {
-    "cmd": [],
-    "name": "a1"
-  },
-  {
     "cmd": [
       "git",
       "ls-remote",
diff --git a/recipes/submodule_roller.expected/partial_noop.json b/recipes/submodule_roller.expected/partial_noop.json
index d2e5008..cb16485 100644
--- a/recipes/submodule_roller.expected/partial_noop.json
+++ b/recipes/submodule_roller.expected/partial_noop.json
@@ -580,6 +580,10 @@
     ]
   },
   {
+    "cmd": [],
+    "name": "a1"
+  },
+  {
     "cmd": [
       "vpython3",
       "-u",
@@ -591,8 +595,9 @@
       "/path/to/tmp/"
     ],
     "infra_step": true,
-    "name": "read .gitmodules",
+    "name": "a1.read .gitmodules",
     "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
       "@@@STEP_LOG_LINE@.gitmodules@[submodule \"a1\"]@@@",
       "@@@STEP_LOG_LINE@.gitmodules@\tpath = a1@@@",
       "@@@STEP_LOG_LINE@.gitmodules@\turl = sso://foo/a1@@@",
@@ -603,10 +608,6 @@
     ]
   },
   {
-    "cmd": [],
-    "name": "a1"
-  },
-  {
     "cmd": [
       "git",
       "ls-remote",
diff --git a/recipes/submodule_roller.expected/success.json b/recipes/submodule_roller.expected/success.json
index 5cb05cf..36aab99 100644
--- a/recipes/submodule_roller.expected/success.json
+++ b/recipes/submodule_roller.expected/success.json
@@ -580,6 +580,10 @@
     ]
   },
   {
+    "cmd": [],
+    "name": "a1"
+  },
+  {
     "cmd": [
       "vpython3",
       "-u",
@@ -591,8 +595,9 @@
       "/path/to/tmp/"
     ],
     "infra_step": true,
-    "name": "read .gitmodules",
+    "name": "a1.read .gitmodules",
     "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
       "@@@STEP_LOG_LINE@.gitmodules@[submodule \"a1\"]@@@",
       "@@@STEP_LOG_LINE@.gitmodules@\tpath = a1@@@",
       "@@@STEP_LOG_LINE@.gitmodules@\turl = sso://foo/a1@@@",
@@ -603,10 +608,6 @@
     ]
   },
   {
-    "cmd": [],
-    "name": "a1"
-  },
-  {
     "cmd": [
       "git",
       "ls-remote",
diff --git a/recipes/submodule_roller.py b/recipes/submodule_roller.py
index 99aab4c..37332b4 100644
--- a/recipes/submodule_roller.py
+++ b/recipes/submodule_roller.py
@@ -107,9 +107,13 @@
             always_cc=True,
             forge_author=True,
         ),
+        api.submodule_roll.gitmodules(
+            'a1',
+            a1='sso://foo/a1',
+            b2='sso://foo/b2',
+        ),
         api.gitiles.log("a1.log a1", "A"),
         api.gitiles.log("b2.log b2", "A"),
-        api.submodule_roll.gitmodules(a1='sso://foo/a1', b2='sso://foo/b2'),
         api.auto_roller.dry_run_success(),
     )
 
@@ -120,9 +124,13 @@
             api.submodule_roll.entry('b2'),
             cc_reviewers_on_rolls=True,
         ),
+        api.submodule_roll.gitmodules(
+            'a1',
+            a1='sso://foo/a1',
+            b2='sso://foo/b2',
+        ),
         api.gitiles.log("a1.log a1", "A"),
         api.gitiles.log("b2.log b2", "A", n=0),
-        api.submodule_roll.gitmodules(a1='sso://foo/a1', b2='sso://foo/b2'),
         api.auto_roller.dry_run_success(),
     )
 
@@ -132,7 +140,12 @@
             api.submodule_roll.entry('a1'),
             api.submodule_roll.entry('b2'),
         ),
-        api.submodule_roll.gitmodules(a1='a1', b2='b2', b2_branch='branch'),
+        api.submodule_roll.gitmodules(
+            'a1',
+            a1='a1',
+            b2='b2',
+            b2_branch='branch',
+        ),
         api.gitiles.log("a1.log a1", "A", n=0),
         api.gitiles.log("b2.log b2", "A", n=0),
     )