bazel: Support multiple rolls in one build

Sometimes one repository is included in a WORKSPACE file multiple times.
In those cases, roll both pins instead of just one. Example:

    git_repository(
        name = "pigweed",
        commit = "c13af959bab5cf8d043583371800c766956c98be",
        remote = "https://pigweed.googlesource.com/pigweed/pigweed.git",
    )

    git_repository(
        name = "pw_toolchain",
        commit = "c13af959bab5cf8d043583371800c766956c98be",
        remote = "https://pigweed.googlesource.com/pigweed/pigweed.git",
        strip_prefix = "pw_toolchain_bazel",
    )

Bug: b/323009830
Change-Id: Ifa654d585d921da046c8b92c9b41ac8ee2230a6c
Reviewed-on: https://pigweed-review.googlesource.com/c/infra/recipes/+/214133
Lint: Lint 🤖 <android-build-ayeaye@system.gserviceaccount.com>
Commit-Queue: Rob Mohr <mohrr@google.com>
Presubmit-Verified: CQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Ted Pudlik <tpudlik@google.com>
diff --git a/recipe_modules/bazel/api.py b/recipe_modules/bazel/api.py
index bbb22fe..4ff67f4 100644
--- a/recipe_modules/bazel/api.py
+++ b/recipe_modules/bazel/api.py
@@ -194,6 +194,8 @@
         )
         lines.extend([''] * num_nearby_lines)
 
+        matching_groups = []
+
         for nearby_lines in nwise(enumerate(lines), num_nearby_lines * 2 + 1):
             i, curr = nearby_lines[len(nearby_lines) // 2]
             match = re.search(
@@ -207,65 +209,69 @@
             step = self.m.step.empty(f'found remote {match_remote!r}')
             if checkout.remotes_equivalent(match_remote, project_remote):
                 step.presentation.step_summary_text = 'equivalent'
-                break
-            step.presentation.step_summary_text = 'not equivalent'
+                matching_groups.append(nearby_lines)
+            else:
+                step.presentation.step_summary_text = 'not equivalent'
 
-        else:
+        if not matching_groups:
             self.m.step.empty(
                 f'could not find remote {project_remote} in {path}',
                 status='FAILURE',
             )
 
-        nearby_lines = proximity_sort_nearby_lines(nearby_lines)
+        project_names: list[str] = []
 
-        commit_rx = re.compile(
-            r'^(?P<prefix>\s*commit\s*=\s*")'
-            r'(?P<commit>[0-9a-f]{40})'
-            r'(?P<suffix>",?\s*)$'
-        )
+        for nearby_lines in matching_groups:
+            nearby_lines = proximity_sort_nearby_lines(nearby_lines)
 
-        for i, line in nearby_lines:
-            if match := commit_rx.search(line):
-                idx = i
-                break
-        else:
-            self.m.step.empty(
-                f'could not find commit line adjacent to {curr!r} in {path}',
-                status='FAILURE',
+            commit_rx = re.compile(
+                r'^(?P<prefix>\s*commit\s*=\s*")'
+                r'(?P<commit>[0-9a-f]{40})'
+                r'(?P<suffix>",?\s*)$'
             )
 
-        old_revision = match.group('commit')
+            for i, line in nearby_lines:
+                if match := commit_rx.search(line):
+                    idx = i
+                    break
+            else:
+                self.m.step.empty(
+                    f'could not find commit line adjacent to {curr!r} in '
+                    f'{path}',
+                    status='FAILURE',
+                )
 
-        prefix = match.group("prefix")
-        suffix = match.group("suffix")
-        lines[idx] = f'{prefix}{new_revision}{suffix}'
+            old_revision = match.group('commit')
 
-        # Remove all existing metadata lines in this git_repository() entry.
-        idx2 = idx - 1
-        while lines[idx2].strip().startswith('# ROLL: '):
-            lines[idx2] = None
-            idx2 -= 1
+            prefix = match.group("prefix")
+            suffix = match.group("suffix")
+            lines[idx] = f'{prefix}{new_revision}{suffix}'
 
-        ws_prefix = re.search(r'^\s*', prefix).group(0)
-        comment_prefix = f'{ws_prefix}# ROLL: '
+            # Remove all existing metadata lines in this git_repository() entry.
+            idx2 = idx - 1
+            while lines[idx2].strip().startswith('# ROLL: '):
+                lines[idx2] = None
+                idx2 -= 1
 
-        now = self.m.time.utcnow().strftime('%Y-%m-%d')
-        comment_lines = (
-            f'{comment_prefix}Warning: this entry is automatically updated.',
-            f'{comment_prefix}Last updated {now}.',
-            f'{comment_prefix}By {self.m.buildbucket.build_url()}.',
-        )
+            ws_prefix = re.search(r'^\s*', prefix).group(0)
+            comment_prefix = f'{ws_prefix}# ROLL: '
 
-        # We no longer need idx to be consistent so it's ok to insert lines.
-        lines.insert(idx, '\n'.join(comment_lines))
+            now = self.m.time.utcnow().strftime('%Y-%m-%d')
+            comment_lines = (
+                f'{comment_prefix}Warning: this entry is automatically '
+                'updated.',
+                f'{comment_prefix}Last updated {now}.',
+                f'{comment_prefix}By {self.m.buildbucket.build_url()}.',
+            )
 
-        project_name: str | None = None
-        for _, line in nearby_lines:
-            if match := re.search(
-                r'^\s*name\s*=\s*"(?P<name>[^"]+)",?\s*$', line
-            ):
-                project_name = match.group('name')
-                break
+            lines[idx] = '\n'.join(comment_lines + (lines[idx],))
+
+            for _, line in nearby_lines:
+                if match := re.search(
+                    r'^\s*name\s*=\s*"(?P<name>[^"]+)",?\s*$', line
+                ):
+                    project_names.append(match.group('name'))
+                    break
 
         self.m.file.write_text(
             f'write new {path.name}',
@@ -279,5 +285,5 @@
 
         return UpdateCommitHashResult(
             old_revision=old_revision,
-            project_name=project_name,
+            project_name=', '.join(project_names),
         )
diff --git a/recipe_modules/bazel/test_api.py b/recipe_modules/bazel/test_api.py
index cf0f03a..c2a7569 100644
--- a/recipe_modules/bazel/test_api.py
+++ b/recipe_modules/bazel/test_api.py
@@ -11,7 +11,7 @@
 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 # License for the specific language governing permissions and limitations under
 # the License.
-"""Test API for enviroment."""
+"""Test API for bazel module."""
 
 import re
 
@@ -43,11 +43,37 @@
 )
 """
 
+MULTIPLE_ROLL_WORKSPACE_FILE = """
+git_repository(
+    name = "pigweed",
+    # ROLL: Comment line 1.
+    commit = "1111111111111111111111111111111111111111",
+    remote = "https://pigweed.googlesource.com/pigweed/pigweed.git",
+)
+
+# Filler
+# lines
+# to
+# pad
+# out
+# the
+# file.
+
+git_repository(
+    name = "pw_toolchain",
+    # ROLL: Comment line 2.
+    commit = "1111111111111111111111111111111111111111",
+    remote = "https://pigweed.googlesource.com/pigweed/pigweed",
+    strip_prefix = "pw_toolchain_bazel",
+)
+"""
+
 
 class BazelTestApi(recipe_test_api.RecipeTestApi):
     """Test API for Bazel."""
 
     TEST_WORKSPACE_FILE = TEST_WORKSPACE_FILE
+    MULTIPLE_ROLL_WORKSPACE_FILE = MULTIPLE_ROLL_WORKSPACE_FILE
 
     def options(
         self,
diff --git a/recipe_modules/bazel/tests/update_commit_hash.expected/multiple.json b/recipe_modules/bazel/tests/update_commit_hash.expected/multiple.json
new file mode 100644
index 0000000..2ffeb21
--- /dev/null
+++ b/recipe_modules/bazel/tests/update_commit_hash.expected/multiple.json
@@ -0,0 +1,598 @@
+[
+  {
+    "cmd": [],
+    "name": "checkout super"
+  },
+  {
+    "cmd": [],
+    "name": "checkout super.options",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_SUMMARY_TEXT@remote: \"https://pigweed.googlesource.com/super\"\n@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout super.options with defaults",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_SUMMARY_TEXT@remote: \"https://pigweed.googlesource.com/super\"\nmanifest_file: \"default.xml\"\nrepo_init_timeout_sec: 20\nrepo_sync_timeout_sec: 120\nnumber_of_attempts: 3\nsubmodule_timeout_sec: 600\n@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout super.not matching branch names",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout super.cache",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_SUMMARY_TEXT@miss@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0o777",
+      "[CACHE]/git"
+    ],
+    "infra_step": true,
+    "name": "checkout super.cache.ensure git cache dir",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "",
+      "[CACHE]/git/.GUARD_FILE"
+    ],
+    "infra_step": true,
+    "name": "checkout super.cache.write git cache guard file",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0o777",
+      "[CACHE]/git/pigweed.googlesource.com-super"
+    ],
+    "infra_step": true,
+    "name": "checkout super.cache.makedirs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "init"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-super",
+    "infra_step": true,
+    "name": "checkout super.cache.git init",
+    "timeout": 300.0,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "remote.origin.url",
+      "https://pigweed.googlesource.com/super"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-super",
+    "infra_step": true,
+    "name": "checkout super.cache.remote set-url",
+    "timeout": 300.0,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "fetch.uriprotocols",
+      "https"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-super",
+    "infra_step": true,
+    "name": "checkout super.cache.set fetch.uriprotocols",
+    "timeout": 300.0,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout super.cache.timeout 10s",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--prune",
+      "--tags",
+      "--jobs",
+      "4",
+      "origin",
+      "--no-recurse-submodules"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-super",
+    "infra_step": true,
+    "name": "checkout super.cache.git fetch",
+    "timeout": 1200.0,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "merge",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-super",
+    "infra_step": true,
+    "name": "checkout super.cache.git merge",
+    "timeout": 600.0,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout super.cache.timeout 10s (2)",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update",
+      "--recursive",
+      "--force",
+      "--jobs",
+      "4"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-super",
+    "infra_step": true,
+    "name": "checkout super.cache.git submodule update",
+    "timeout": 600,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "remove",
+      "[CACHE]/git/.GUARD_FILE"
+    ],
+    "infra_step": true,
+    "name": "checkout super.cache.remove git cache guard file",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copytree",
+      "--symlinks",
+      "[CACHE]/git/pigweed.googlesource.com-super",
+      "[START_DIR]/co"
+    ],
+    "infra_step": true,
+    "name": "checkout super.copy from cache",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout super.git checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout super.git checkout.timeout 10s",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0o777",
+      "[START_DIR]/co"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "deadline": {
+        "grace_period": 30.0,
+        "soft_deadline": 1337000019.0
+      }
+    },
+    "name": "checkout super.git checkout.makedirs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "init"
+    ],
+    "cwd": "[START_DIR]/co",
+    "infra_step": true,
+    "name": "checkout super.git checkout.git init",
+    "timeout": 300.0,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "remote",
+      "add",
+      "origin",
+      "https://pigweed.googlesource.com/super"
+    ],
+    "cwd": "[START_DIR]/co",
+    "infra_step": true,
+    "name": "checkout super.git checkout.git remote",
+    "timeout": 600.0,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "core.longpaths",
+      "true"
+    ],
+    "cwd": "[START_DIR]/co",
+    "infra_step": true,
+    "name": "checkout super.git checkout.set core.longpaths",
+    "timeout": 300.0,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "fetch.uriprotocols",
+      "https"
+    ],
+    "cwd": "[START_DIR]/co",
+    "infra_step": true,
+    "name": "checkout super.git checkout.set fetch.uriprotocols",
+    "timeout": 300.0,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--tags",
+      "--jobs",
+      "4",
+      "origin",
+      "main"
+    ],
+    "cwd": "[START_DIR]/co",
+    "infra_step": true,
+    "name": "checkout super.git checkout.git fetch",
+    "timeout": 1200.0,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "-f",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/co",
+    "infra_step": true,
+    "name": "checkout super.git checkout.git checkout",
+    "timeout": 600.0,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/co",
+    "infra_step": true,
+    "name": "checkout super.git checkout.git rev-parse",
+    "timeout": 300.0,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "clean",
+      "-f",
+      "-d",
+      "-x"
+    ],
+    "cwd": "[START_DIR]/co",
+    "infra_step": true,
+    "name": "checkout super.git checkout.git clean",
+    "timeout": 600.0,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout super.git log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/co",
+    "name": "checkout super.git log.[START_DIR]/co",
+    "timeout": 600.0,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0o777",
+      "[START_DIR]/snapshot"
+    ],
+    "infra_step": true,
+    "name": "checkout super.mkdir",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "status",
+      "--recursive"
+    ],
+    "cwd": "[START_DIR]/co",
+    "name": "checkout super.submodule-status",
+    "timeout": 600.0,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "submodule status filler text",
+      "[START_DIR]/snapshot/submodules.log"
+    ],
+    "infra_step": true,
+    "name": "checkout super.write submodule snapshot",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@submodules.log@submodule status filler text@@@",
+      "@@@STEP_LOG_END@submodules.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/co",
+    "name": "checkout super.log",
+    "timeout": 600.0,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "",
+      "[START_DIR]/snapshot/git.log"
+    ],
+    "infra_step": true,
+    "name": "checkout super.write git log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_END@git.log@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/co/WORKSPACE",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "read old WORKSPACE",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@WORKSPACE@@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@git_repository(@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@    name = \"pigweed\",@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@    # ROLL: Comment line 1.@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@    commit = \"1111111111111111111111111111111111111111\",@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@    remote = \"https://pigweed.googlesource.com/pigweed/pigweed.git\",@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@)@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@# Filler@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@# lines@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@# to@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@# pad@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@# out@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@# the@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@# file.@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@git_repository(@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@    name = \"pw_toolchain\",@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@    # ROLL: Comment line 2.@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@    commit = \"1111111111111111111111111111111111111111\",@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@    remote = \"https://pigweed.googlesource.com/pigweed/pigweed\",@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@    strip_prefix = \"pw_toolchain_bazel\",@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@)@@@",
+      "@@@STEP_LOG_END@WORKSPACE@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "found remote 'https://pigweed.googlesource.com/pigweed/pigweed.git'",
+    "~followup_annotations": [
+      "@@@STEP_SUMMARY_TEXT@equivalent@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "found remote 'https://pigweed.googlesource.com/pigweed/pigweed'",
+    "~followup_annotations": [
+      "@@@STEP_SUMMARY_TEXT@equivalent@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "git_repository(\n    name = \"pigweed\",\n    # ROLL: Warning: this entry is automatically updated.\n    # ROLL: Last updated 2012-05-14.\n    # ROLL: By https://cr-buildbucket.appspot.com/build/0.\n    commit = \"ffffffffffffffffffffffffffffffffffffffff\",\n    remote = \"https://pigweed.googlesource.com/pigweed/pigweed.git\",\n)\n\n# Filler\n# lines\n# to\n# pad\n# out\n# the\n# file.\n\ngit_repository(\n    name = \"pw_toolchain\",\n    # ROLL: Warning: this entry is automatically updated.\n    # ROLL: Last updated 2012-05-14.\n    # ROLL: By https://cr-buildbucket.appspot.com/build/0.\n    commit = \"ffffffffffffffffffffffffffffffffffffffff\",\n    remote = \"https://pigweed.googlesource.com/pigweed/pigweed\",\n    strip_prefix = \"pw_toolchain_bazel\",\n)\n",
+      "[START_DIR]/co/WORKSPACE"
+    ],
+    "infra_step": true,
+    "name": "write new WORKSPACE",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@WORKSPACE@git_repository(@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@    name = \"pigweed\",@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@    # ROLL: Warning: this entry is automatically updated.@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@    # ROLL: Last updated 2012-05-14.@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@    # ROLL: By https://cr-buildbucket.appspot.com/build/0.@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@    commit = \"ffffffffffffffffffffffffffffffffffffffff\",@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@    remote = \"https://pigweed.googlesource.com/pigweed/pigweed.git\",@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@)@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@# Filler@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@# lines@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@# to@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@# pad@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@# out@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@# the@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@# file.@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@git_repository(@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@    name = \"pw_toolchain\",@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@    # ROLL: Warning: this entry is automatically updated.@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@    # ROLL: Last updated 2012-05-14.@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@    # ROLL: By https://cr-buildbucket.appspot.com/build/0.@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@    commit = \"ffffffffffffffffffffffffffffffffffffffff\",@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@    remote = \"https://pigweed.googlesource.com/pigweed/pigweed\",@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@    strip_prefix = \"pw_toolchain_bazel\",@@@",
+      "@@@STEP_LOG_LINE@WORKSPACE@)@@@",
+      "@@@STEP_LOG_END@WORKSPACE@@@"
+    ]
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipe_modules/bazel/tests/update_commit_hash.expected/success.json b/recipe_modules/bazel/tests/update_commit_hash.expected/success.json
index 9e3f14a..027a9ad 100644
--- a/recipe_modules/bazel/tests/update_commit_hash.expected/success.json
+++ b/recipe_modules/bazel/tests/update_commit_hash.expected/success.json
@@ -549,6 +549,13 @@
     ]
   },
   {
+    "cmd": [],
+    "name": "found remote 'https://pigweed.googlesource.com/third/repo.git'",
+    "~followup_annotations": [
+      "@@@STEP_SUMMARY_TEXT@not equivalent@@@"
+    ]
+  },
+  {
     "cmd": [
       "vpython3",
       "-u",
diff --git a/recipe_modules/bazel/tests/update_commit_hash.py b/recipe_modules/bazel/tests/update_commit_hash.py
index 7bdcd03..c87f990 100644
--- a/recipe_modules/bazel/tests/update_commit_hash.py
+++ b/recipe_modules/bazel/tests/update_commit_hash.py
@@ -32,6 +32,7 @@
 DEPS = [
     'pigweed/bazel',
     'pigweed/checkout',
+    'recipe_engine/file',
     'recipe_engine/path',
     'recipe_engine/properties',
 ]
@@ -89,3 +90,12 @@
         api.post_process(post_process.DropExpectation),
         status='FAILURE',
     )
+
+    yield api.test(
+        'multiple',
+        properties(project_remote=_url('pigweed/pigweed')),
+        api.step_data(
+            'read old WORKSPACE',
+            api.file.read_text(api.bazel.MULTIPLE_ROLL_WORKSPACE_FILE),
+        ),
+    )
diff --git a/recipes/bazel_roller.expected/backwards.json b/recipes/bazel_roller.expected/backwards.json
index 4d0f419..002c21d 100644
--- a/recipes/bazel_roller.expected/backwards.json
+++ b/recipes/bazel_roller.expected/backwards.json
@@ -1503,6 +1503,13 @@
     ]
   },
   {
+    "cmd": [],
+    "name": "found remote 'https://pigweed.googlesource.com/third/repo.git'",
+    "~followup_annotations": [
+      "@@@STEP_SUMMARY_TEXT@not equivalent@@@"
+    ]
+  },
+  {
     "cmd": [
       "vpython3",
       "-u",
diff --git a/recipes/bazel_roller.expected/no-trigger.json b/recipes/bazel_roller.expected/no-trigger.json
index ff089fb..b8a3ae9 100644
--- a/recipes/bazel_roller.expected/no-trigger.json
+++ b/recipes/bazel_roller.expected/no-trigger.json
@@ -1503,6 +1503,13 @@
     ]
   },
   {
+    "cmd": [],
+    "name": "found remote 'https://pigweed.googlesource.com/third/repo.git'",
+    "~followup_annotations": [
+      "@@@STEP_SUMMARY_TEXT@not equivalent@@@"
+    ]
+  },
+  {
     "cmd": [
       "vpython3",
       "-u",
diff --git a/recipes/bazel_roller.expected/success.json b/recipes/bazel_roller.expected/success.json
index edcfc0a..e28c086 100644
--- a/recipes/bazel_roller.expected/success.json
+++ b/recipes/bazel_roller.expected/success.json
@@ -1740,6 +1740,13 @@
     ]
   },
   {
+    "cmd": [],
+    "name": "found remote 'https://pigweed.googlesource.com/third/repo.git'",
+    "~followup_annotations": [
+      "@@@STEP_SUMMARY_TEXT@not equivalent@@@"
+    ]
+  },
+  {
     "cmd": [
       "vpython3",
       "-u",